diff --git a/RefreshLib/.gitignore b/RefreshLib/.gitignore
new file mode 100644
index 0000000..e2713a2
--- /dev/null
+++ b/RefreshLib/.gitignore
@@ -0,0 +1,6 @@
+/node_modules
+/oh_modules
+/.preview
+/build
+/.cxx
+/.test
\ No newline at end of file
diff --git a/RefreshLib/BuildProfile.ets b/RefreshLib/BuildProfile.ets
new file mode 100644
index 0000000..3a501e5
--- /dev/null
+++ b/RefreshLib/BuildProfile.ets
@@ -0,0 +1,17 @@
+/**
+ * Use these variables when you tailor your ArkTS code. They must be of the const type.
+ */
+export const HAR_VERSION = '1.0.0';
+export const BUILD_MODE_NAME = 'debug';
+export const DEBUG = true;
+export const TARGET_NAME = 'default';
+
+/**
+ * BuildProfile Class is used only for compatibility purposes.
+ */
+export default class BuildProfile {
+ static readonly HAR_VERSION = HAR_VERSION;
+ static readonly BUILD_MODE_NAME = BUILD_MODE_NAME;
+ static readonly DEBUG = DEBUG;
+ static readonly TARGET_NAME = TARGET_NAME;
+}
\ No newline at end of file
diff --git a/RefreshLib/CHANGELOG.md b/RefreshLib/CHANGELOG.md
new file mode 100644
index 0000000..7cba6e5
--- /dev/null
+++ b/RefreshLib/CHANGELOG.md
@@ -0,0 +1,5 @@
+# 版本更新记录
+
+## [v1.0.0] 2024-09-24
+
+# 首次上传
\ No newline at end of file
diff --git a/RefreshLib/Index.ets b/RefreshLib/Index.ets
new file mode 100644
index 0000000..83c3253
--- /dev/null
+++ b/RefreshLib/Index.ets
@@ -0,0 +1,15 @@
+export { PullToRefreshLayout } from './src/main/ets/PullToRefreshLayout'
+
+export { RefreshLayout } from './src/main/ets/RefreshLayout'
+
+export { RefreshLayoutConfig } from './src/main/ets/RefreshLayoutConfig'
+
+export { PullDown } from './src/main/ets/PullDown'
+
+export { PullStatus } from './src/main/ets/PullStatus'
+
+export { RefreshLayoutHelper } from './src/main/ets/RefreshLayoutHelper'
+
+export { RefreshController } from './src/main/ets/RefreshController'
+
+export { PullToRefreshConfig } from './src/main/ets/PullToRefreshConfig'
diff --git a/RefreshLib/README.md b/RefreshLib/README.md
new file mode 100644
index 0000000..7e28ca9
--- /dev/null
+++ b/RefreshLib/README.md
@@ -0,0 +1,22 @@
+PullToRefresh
+
+简介
+PullToRefresh 实现垂直列表下拉刷新,上拉加载,横向列表左拉刷新,右拉加载
+
+特点
+1.无入侵性,不需要传数据源
+
+2.不限制组件,支持任意布局(List,Grid,Web,Scroll,Text,Row,Column等布局)
+
+3.支持header和footer定制(支持Lottie动画)
+
+4.支持垂直列表和横向列表的刷新和加载
+
+5.支持下拉(或者上拉)打开其他页面
+
+提供了RefreshLayout和PullToRefreshLayout
+1.RefreshLayout支持各种定制化
+2.PullToRefreshLayout是在RefreshLayout基础上定制的,实现常用刷新和加载功能
+3.如果没有个性化需求,可以直接使用PullToRefreshLayout
+
+详细使用Dome:https://gitee.com/myspace01/refresh-lib
\ No newline at end of file
diff --git a/RefreshLib/build-profile.json5 b/RefreshLib/build-profile.json5
new file mode 100644
index 0000000..e6773f9
--- /dev/null
+++ b/RefreshLib/build-profile.json5
@@ -0,0 +1,31 @@
+{
+ "apiType": "stageMode",
+ "buildOption": {
+ },
+ "buildOptionSet": [
+ {
+ "name": "release",
+ "arkOptions": {
+ "obfuscation": {
+ "ruleOptions": {
+ "enable": false,
+ "files": [
+ "./obfuscation-rules.txt"
+ ]
+ },
+ "consumerFiles": [
+ "./consumer-rules.txt"
+ ]
+ }
+ },
+ },
+ ],
+ "targets": [
+ {
+ "name": "default"
+ },
+ {
+ "name": "ohosTest"
+ }
+ ]
+}
diff --git a/RefreshLib/consumer-rules.txt b/RefreshLib/consumer-rules.txt
new file mode 100644
index 0000000..e69de29
diff --git a/RefreshLib/hvigorfile.ts b/RefreshLib/hvigorfile.ts
new file mode 100644
index 0000000..4218707
--- /dev/null
+++ b/RefreshLib/hvigorfile.ts
@@ -0,0 +1,6 @@
+import { harTasks } from '@ohos/hvigor-ohos-plugin';
+
+export default {
+ system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
+ plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
+}
diff --git a/RefreshLib/obfuscation-rules.txt b/RefreshLib/obfuscation-rules.txt
new file mode 100644
index 0000000..272efb6
--- /dev/null
+++ b/RefreshLib/obfuscation-rules.txt
@@ -0,0 +1,23 @@
+# Define project specific obfuscation rules here.
+# You can include the obfuscation configuration files in the current module's build-profile.json5.
+#
+# For more details, see
+# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5
+
+# Obfuscation options:
+# -disable-obfuscation: disable all obfuscations
+# -enable-property-obfuscation: obfuscate the property names
+# -enable-toplevel-obfuscation: obfuscate the names in the global scope
+# -compact: remove unnecessary blank spaces and all line feeds
+# -remove-log: remove all console.* statements
+# -print-namecache: print the name cache that contains the mapping from the old names to new names
+# -apply-namecache: reuse the given cache file
+
+# Keep options:
+# -keep-property-name: specifies property names that you want to keep
+# -keep-global-name: specifies names that you want to keep in the global scope
+
+-enable-property-obfuscation
+-enable-toplevel-obfuscation
+-enable-filename-obfuscation
+-enable-export-obfuscation
\ No newline at end of file
diff --git a/RefreshLib/oh-package.json5 b/RefreshLib/oh-package.json5
new file mode 100644
index 0000000..6e36506
--- /dev/null
+++ b/RefreshLib/oh-package.json5
@@ -0,0 +1,9 @@
+{
+ "name": "refreshlib",
+ "version": "1.0.0",
+ "description": "Please describe the basic information.",
+ "main": "Index.ets",
+ "author": "os_space",
+ "license": "Apache-2.0",
+ "dependencies": {}
+}
diff --git a/RefreshLib/src/main/ets/PullDown.ets b/RefreshLib/src/main/ets/PullDown.ets
new file mode 100644
index 0000000..995dc2a
--- /dev/null
+++ b/RefreshLib/src/main/ets/PullDown.ets
@@ -0,0 +1,20 @@
+import { PullStatus } from './PullStatus'
+
+export interface PullDown {
+ /*是否是下拉*/
+ isPullDown: boolean
+ /*是否是上拉*/
+ isPullUp: boolean
+ /*是否触摸*/
+ isTouch: boolean
+ /*下拉距离*/
+ distance: number
+ /*上拉距离*/
+ distanceLoad: number
+ /*headerView高度*/
+ headerViewSize: number
+ /*footerView高度*/
+ footerViewSize: number
+ /*状态*/
+ status: PullStatus
+}
\ No newline at end of file
diff --git a/RefreshLib/src/main/ets/PullStatus.ets b/RefreshLib/src/main/ets/PullStatus.ets
new file mode 100644
index 0000000..d93f4be
--- /dev/null
+++ b/RefreshLib/src/main/ets/PullStatus.ets
@@ -0,0 +1,34 @@
+export enum PullStatus {
+ /*0默认状态(无下拉距离)*/
+ DEF,
+ /***********************************下拉状态*******************************************/
+ /*1下拉状态*/
+ PullDown,
+ /*2下拉至可以刷新的状态(准备刷新)*/
+ PreRefresh,
+ /*3刷新中*/
+ Refresh,
+ /*4下拉超过刷新高度时准备打开其他页面*/
+ PreOpenPage,
+ /*5下拉超过刷新高度时松手打开其他页面*/
+ OpenPage,
+ /*6刷新成功*/
+ RefreshSuccess,
+ /*7刷新失败*/
+ RefreshError,
+ /***********************************上拉状态*******************************************/
+ /*8上拉状态*/
+ PullUp,
+ /*9上拉至可以加载的状态(准备加载)*/
+ PreLoad,
+ /*10加载中*/
+ Load,
+ /*11上拉超过加载高度时准备打开其他页面*/
+ PreLoadOpenPage,
+ /*12上拉超过加载高度时松手打开其他页面*/
+ LoadOpenPage,
+ /*13加载成功*/
+ LoadSuccess,
+ /*14加载失败*/
+ LoadError
+}
\ No newline at end of file
diff --git a/RefreshLib/src/main/ets/PullToRefreshConfig.ets b/RefreshLib/src/main/ets/PullToRefreshConfig.ets
new file mode 100644
index 0000000..8fb5b9e
--- /dev/null
+++ b/RefreshLib/src/main/ets/PullToRefreshConfig.ets
@@ -0,0 +1,95 @@
+export class PullToRefreshConfig {
+ /*刷新loading宽度*/
+ public refreshLoadingWidth: Length = 25
+ /*刷新loading高度*/
+ public refreshLoadingHeight: Length = 25
+ /*刷新loading颜色*/
+ public refreshLoadingColor: ResourceColor = "#333333"
+ /*下拉刷新箭头*/
+ public arrowImage: Resource = $r("app.media.icon_refresh_arrow")
+ /*下拉刷新箭头颜色*/
+ public arrowImageColor: ResourceColor = "#333333"
+ /*下拉刷新箭头宽度*/
+ public arrowImageWidth: Length = 20
+ /*下拉刷新箭头高度*/
+ public arrowImageHeight: Length = 20
+
+
+ /*上拉刷新箭头*/
+ public arrowLoadImage: Resource = $r("app.media.icon_refresh_arrow")
+ /*上拉刷新箭头颜色*/
+ public arrowLoadImageColor: ResourceColor = "#333333"
+ /*上拉刷新箭头宽度*/
+ public arrowLoadImageWidth: Length = 20
+ /*上拉刷新箭头高度*/
+ public arrowLoadImageHeight: Length = 20
+
+ /*暂无更多提示*/
+ public noMoreTips: string | Resource = $r("app.string.zr_load_no_more")
+ /*暂无更多提示大小*/
+ public noMoreTipsSize: number | string | Resource = 13
+ /*暂无更多提示颜色*/
+ public noMoreTipsColor: ResourceColor = "#666666"
+ /*暂无更多提示——横向列表*/
+ public noMoreTipsHorizontal: string | Resource = $r("app.string.h_zr_load_no_more")
+ /*是否显示刷新时间*/
+ public showRefreshTime: boolean = false
+ /*刷新时间tips大小*/
+ public refreshTimeTipsSize: number | string | Resource = 11
+ /*刷新时间tips颜色*/
+ public refreshTimeTipsColor: ResourceColor = "#999999"
+ /*下拉刷新提示*/
+ public pullDownTips: string | Resource = $r("app.string.zr_pull_down_to_refresh")
+ /*下拉刷新提示——横向列表*/
+ public pullDownTipsHorizontal: string | Resource = $r("app.string.h_zr_pull_down_to_refresh")
+ /*下拉刷新提示大小*/
+ public pullDownTipsSize: number | string | Resource = 13
+ /*下拉刷新提示颜色*/
+ public pullDownTipsColor: ResourceColor = "#666666"
+ /*释放立即刷新tips*/
+ public releaseRefreshTips: string | Resource = $r("app.string.zr_release_to_refresh")
+ /*释放立即刷新tips——横向列表*/
+ public releaseRefreshTipsHorizontal: string | Resource = $r("app.string.h_zr_release_to_refresh")
+ /*正在刷新tips*/
+ public refreshTips: string | Resource = $r("app.string.zr_refreshing")
+ /*正在刷新tips——横向列表*/
+ public refreshTipsHorizontal: string | Resource = $r("app.string.h_zr_refreshing")
+ /*刷新成功tips*/
+ public refreshSuccessTips: string | Resource = $r("app.string.zr_refresh_success")
+ /*刷新成功tips——横向列表*/
+ public refreshSuccessTipsHorizontal: string | Resource = $r("app.string.h_zr_refresh_success")
+ /*刷新失败tips*/
+ public refreshErrorTips: string | Resource = $r("app.string.zr_refresh_error")
+ /*刷新失败tips——横向列表*/
+ public refreshErrorTipsHorizontal: string | Resource = $r("app.string.h_zr_refresh_error")
+ /*下拉打开其他页面tips*/
+ public pullOpenPageTips: string | Resource = $r("app.string.zr_release_to_open_age")
+ /*下拉打开其他页面tips——横向列表*/
+ public pullOpenPageTipsHorizontal: string | Resource = $r("app.string.h_zr_release_to_open_age")
+ /*上拉加载提示*/
+ public pullUpTips: string | Resource = $r("app.string.zr_pull_up_to_load")
+ /*上拉加载提示——横向列表*/
+ public pullUpTipsHorizontal: string | Resource = $r("app.string.h_zr_pull_up_to_load")
+ /*上拉加载提示大小*/
+ public pullUpTipsSize: number | string | Resource = 13
+ /*上拉加载提示颜色*/
+ public pullUpTipsColor: ResourceColor = "#333333"
+ /*释放立即加载tips*/
+ public releaseLoadTips: string | Resource = $r("app.string.zr_release_to_load")
+ /*释放立即加载tips——横向列表*/
+ public releaseLoadTipsHorizontal: string | Resource = $r("app.string.h_zr_release_to_load")
+ /*正在加载tips*/
+ public loadTips: string | Resource = $r("app.string.zr_loading")
+ /*正在加载tips——横向列表*/
+ public loadTipsHorizontal: string | Resource = $r("app.string.h_zr_loading")
+ /*加载成功tips*/
+ public loadSuccessTips: string | Resource = $r("app.string.zr_load_success")
+ /*加载成功tips——横向列表*/
+ public loadSuccessTipsHorizontal: string | Resource = $r("app.string.h_zr_load_success")
+ /*加载失败tips*/
+ public loadErrorTips: string | Resource = $r("app.string.zr_load_error")
+ /*加载失败tips——横向列表*/
+ public loadErrorTipsHorizontal: string | Resource = $r("app.string.h_zr_load_error")
+ /*下拉打开其他页面tips*/
+ public loadOpenPageTips: string | Resource = $r("app.string.zr_release_to_open_age")
+}
\ No newline at end of file
diff --git a/RefreshLib/src/main/ets/PullToRefreshLayout.ets b/RefreshLib/src/main/ets/PullToRefreshLayout.ets
new file mode 100644
index 0000000..5455d38
--- /dev/null
+++ b/RefreshLib/src/main/ets/PullToRefreshLayout.ets
@@ -0,0 +1,806 @@
+import { PullDown } from './PullDown';
+import { PullStatus } from './PullStatus';
+import { RefreshController } from './RefreshController';
+import { RefreshLayout } from './RefreshLayout';
+import { RefreshLayoutConfig } from './RefreshLayoutConfig';
+import { _ContentView } from './RefreshLayoutHelper';
+import web from '@ohos.web.webview';
+import preferences from '@ohos.data.preferences';
+import { PullToRefreshConfig } from './PullToRefreshConfig';
+
+@ComponentV2
+export struct PullToRefreshLayout {
+ /*下拉箭头旋转角度*/
+ @Local arrowRotate: number = 0;
+ /*上拉箭头旋转角度*/
+ @Local arrowRotateLoad: number = 180;
+ @Local status: PullStatus = PullStatus.DEF
+ /********************************************************************************************************/
+ @Param public pullConfig: PullToRefreshConfig = new PullToRefreshConfig()
+ /*记录组件刷新时间*/
+ @Param public viewKey: string = ""
+ @Param public scroller: Scroller | undefined = undefined
+ @Param public webviewController: web.WebviewController | undefined = undefined
+ @Param public controller: RefreshController = new RefreshController()
+ /*是否可以下拉*/
+ @Param public onCanPullRefresh: () => boolean = () => true
+ /*是否可以下拉*/
+ @Param public onCanPullLoad: () => boolean = () => false
+ /*刷新通知*/
+ @Param public onRefresh: () => void = () => {
+ }
+ /*刷新通知*/
+ @Param public onLoad: () => void = () => {
+ }
+ /*打开页面通知*/
+ @Param public onOpenPage: () => void = () => {
+ }
+ /*打开页面通知*/
+ @Param public onLoadOpenPage: () => void = () => {
+ }
+ /*下拉状态监听*/
+ @Param public onPullListener: (pullDown: PullDown) => void = () => {
+ }
+ //内容视图
+ @BuilderParam contentView: () => void = _ContentView
+ //loading视图
+ @BuilderParam headerLoadIngView?: () => void
+ //loading视图
+ @BuilderParam footerLoadIngView?: () => void
+ /*下拉刷新配置*/
+ @Param public config: RefreshLayoutConfig = new RefreshLayoutConfig()
+ //正在加载视图
+ @BuilderParam viewLoading?: () => void
+ //空视图
+ @BuilderParam viewEmpty?: () => void
+ //加载错误视图
+ @BuilderParam viewError?: () => void
+ //无网络视图
+ @BuilderParam viewNoNetwork?: () => void
+
+ /********************************************************************************************************/
+
+ aboutToAppear(): void {
+ this.resetArrowRotate()
+ this.resetArrowRotateLoad()
+ }
+
+ /*************垂直列表和水平列表feader视图箭头角度**************/
+ private isPullRotate(): boolean {
+ if (this.isVerticalLayout()) {
+ return this.arrowRotate == 0
+ } else {
+ return this.arrowRotate == -90
+ }
+ }
+
+ private isPreRefreshRotate(): boolean {
+ if (this.isVerticalLayout()) {
+ return this.arrowRotate == 180
+ } else {
+ return this.arrowRotate == 90
+ }
+ }
+
+ private setPreRefreshRotate() {
+ if (this.isVerticalLayout()) {
+ this.arrowRotate = 180
+ } else {
+ this.arrowRotate = 90
+ }
+ }
+
+ private resetArrowRotate() {
+ if (this.isVerticalLayout()) {
+ this.arrowRotate = 0
+ } else {
+ this.arrowRotate = -90
+ }
+ }
+
+ /*************垂直列表和水平列表footer视图箭头角度**************/
+ private isPullLoadRotate(): boolean {
+ if (this.isVerticalLayout()) {
+ return this.arrowRotateLoad == 180
+ } else {
+ return this.arrowRotateLoad == 90
+ }
+ }
+
+ private isPreLoadRotate(): boolean {
+ if (this.isVerticalLayout()) {
+ return this.arrowRotateLoad == 0
+ } else {
+ return this.arrowRotateLoad == -90
+ }
+ }
+
+ private setPreLoadRotate() {
+ if (this.isVerticalLayout()) {
+ this.arrowRotateLoad = 180
+ } else {
+ this.arrowRotateLoad = 90
+ }
+ }
+
+ private resetArrowRotateLoad() {
+ if (this.isVerticalLayout()) {
+ this.arrowRotateLoad = 0
+ } else {
+ this.arrowRotateLoad = -90
+ }
+ }
+
+ build() {
+ RefreshLayout({
+ scroller: this.scroller,
+ webviewController: this.webviewController,
+ controller: this.controller,
+ config: this.config,
+ headerView: () => {
+ if (this.isVerticalLayout()) {
+ this.defHeaderView()
+ } else {
+ this.defHeaderViewHorizontal()
+ }
+ },
+ onCanPullRefresh: () => {
+ return this.onCanPullRefresh()
+ },
+ onCanPullLoad: () => {
+ return this.onCanPullLoad()
+ },
+ onRefresh: () => {
+ this.onRefresh()
+ },
+ onLoad: () => {
+ this.onLoad()
+ },
+ onOpenPage: () => {
+ this.onOpenPage()
+ },
+ onLoadOpenPage: () => {
+ this.onLoadOpenPage()
+ },
+ contentView: () => {
+ this.contentView()
+ },
+ loadView: () => {
+ if (this.isVerticalLayout()) {
+ this.defFooterView()
+ } else {
+ this.defFooterViewHorizontal()
+ }
+ },
+ noMoreView: () => {
+ if (this.isVerticalLayout()) {
+ this.defFooterNoMoreView()
+ } else {
+ this.defFooterNoMoreViewHorizontal()
+ }
+ },
+ onPullListener: (pullDown: PullDown) => {
+ this.status = pullDown.status
+ this.updateRefreshTime(this.status)
+ this.onPullListener?.(pullDown)
+ if (this.config.pullHeaderHeightRefresh <= pullDown.headerViewSize) {
+ this.config.pullHeaderHeightRefresh = pullDown.headerViewSize * 1.5
+ }
+ if (this.config.pullHeaderHeightOpenPage <= this.config.pullHeaderHeightRefresh) {
+ this.config.pullHeaderHeightOpenPage = pullDown.headerViewSize * 2.6
+ }
+
+ if (this.config.pullFooterHeightLoad <= pullDown.footerViewSize) {
+ this.config.pullFooterHeightLoad = pullDown.footerViewSize * 1.1
+ }
+ if (this.config.pullFooterHeightOpenPage <= this.config.pullFooterHeightLoad) {
+ this.config.pullFooterHeightOpenPage = pullDown.footerViewSize * 2.6
+ }
+ if (this.status == PullStatus.PullDown) {
+ if (this.isPullRotate()) {
+ return
+ }
+ /*进入下拉状态*/
+ animateTo({ duration: 10, curve: Curve.Linear }, () => {
+ this.resetArrowRotate()
+ })
+ } else if (this.status == PullStatus.PreRefresh) {
+ if (this.isPreRefreshRotate()) {
+ return
+ }
+ /*进入释放刷新状态*/
+ animateTo({ duration: 150, curve: Curve.Linear }, () => {
+ this.setPreRefreshRotate()
+ })
+ } else if (this.status == PullStatus.PullUp) {
+ if (this.isPullLoadRotate()) {
+ return
+ }
+ /*进入上拉状态*/
+ animateTo({ duration: 10, curve: Curve.Linear }, () => {
+ this.setPreLoadRotate()
+ })
+ } else if (this.status == PullStatus.PreLoad) {
+ if (this.isPreLoadRotate()) {
+ return
+ }
+ /*进入释放加载状态*/
+ animateTo({ duration: 150, curve: Curve.Linear }, () => {
+
+ this.resetArrowRotateLoad()
+ })
+ }
+
+ },
+ /*正在加载视图*/
+ viewLoading: this.viewLoading,
+ /*空数据视图*/
+ viewEmpty: this.viewEmpty,
+ /*加载失败视图*/
+ viewError: this.viewError,
+ /*无网络视图*/
+ viewNoNetwork: this.viewNoNetwork
+ }).clip(true)
+
+ }
+
+ private isVerticalLayout(): boolean {
+ return (this.config.isVertical || this.config.horizontalMode == 1 || this.config.horizontalMode == 2)
+ }
+
+ @Local private zrLastUpdate: string = getContext().resourceManager.getStringByNameSync(("zr_last_update"))
+
+ @Builder
+ defHeaderViewHorizontal() {
+ RelativeContainer() {
+ Stack() {
+ Image(this.pullConfig.arrowImage)
+ .width(this.pullConfig.arrowImageWidth)
+ .height(this.pullConfig.arrowImageHeight)
+ .visibility(this.getPullArrowVisibility())
+ .fillColor(this.pullConfig.arrowImageColor)
+ .margin({ top: 5 })
+ .rotate({
+ x: 0,
+ y: 0,
+ z: 1,
+ angle: this.arrowRotate
+ })
+ if (this.headerLoadIngView) {
+ Stack() {
+ this.headerLoadIngView()
+ }
+ .visibility(this.getRefreshVisibility())
+ } else {
+ LoadingProgress()
+ .color(this.pullConfig.refreshLoadingColor)
+ .width(this.pullConfig.refreshLoadingWidth)
+ .height(this.pullConfig.refreshLoadingHeight)
+ .visibility(this.getRefreshVisibility())
+ }
+ }.alignRules({
+ top: { anchor: "title", align: VerticalAlign.Bottom },
+ middle: { anchor: "__container__", align: HorizontalAlign.Center }
+ }).id("arrow")
+
+ Row() {
+ Text(this.getTips())
+ .fontSize(this.pullConfig.pullDownTipsSize)
+ .fontColor(this.pullConfig.pullDownTipsColor)
+ .textAlign(TextAlign.Center)
+ Text(this.zrLastUpdate)
+ .fontSize(this.pullConfig.refreshTimeTipsSize)
+ .fontColor(this.pullConfig.refreshTimeTipsColor)
+ .margin({ left: 2 })
+ .visibility(this.pullConfig.showRefreshTime ? this.getTimeTipsVisibility() : Visibility.None)
+ .textAlign(TextAlign.Center)
+ }.id("title").alignRules({
+ middle: { anchor: "__container__", align: HorizontalAlign.Center },
+ center: { anchor: "__container__", align: VerticalAlign.Center }
+ }).alignItems(VerticalAlign.Center)
+
+ }.width(50).height("100%")
+ }
+
+ @Builder
+ defHeaderView() {
+ RelativeContainer() {
+ Stack() {
+ Image(this.pullConfig.arrowImage)
+ .width(this.pullConfig.arrowImageWidth)
+ .height(this.pullConfig.arrowImageHeight)
+ .visibility(this.getPullArrowVisibility())
+ .fillColor(this.pullConfig.arrowImageColor)
+ .margin({ right: 10 })
+ .rotate({
+ x: 0,
+ y: 0,
+ z: 1,
+ angle: this.arrowRotate
+ })
+ if (this.headerLoadIngView) {
+ Stack() {
+ this.headerLoadIngView()
+ }
+ .visibility(this.getRefreshVisibility())
+ } else {
+ LoadingProgress()
+ .color(this.pullConfig.refreshLoadingColor)
+ .width(this.pullConfig.refreshLoadingWidth)
+ .height(this.pullConfig.refreshLoadingHeight)
+ .visibility(this.getRefreshVisibility())
+ }
+ }.alignRules({
+ right: { anchor: "title", align: HorizontalAlign.Start },
+ center: { anchor: "__container__", align: VerticalAlign.Center }
+ }).id("arrow")
+
+ Column() {
+ Text(this.getTips()).fontSize(this.pullConfig.pullDownTipsSize)
+ .fontColor(this.pullConfig.pullDownTipsColor)
+ Text(this.zrLastUpdate).fontSize(this.pullConfig.refreshTimeTipsSize)
+ .fontColor(this.pullConfig.refreshTimeTipsColor).margin({ top: 2 })
+ .visibility(this.pullConfig.showRefreshTime ? this.getTimeTipsVisibility() : Visibility.None)
+ }.id("title").alignRules({
+ middle: { anchor: "__container__", align: HorizontalAlign.Center },
+ center: { anchor: "__container__", align: VerticalAlign.Center }
+ }).constraintSize({ minWidth: 80 }).alignItems(HorizontalAlign.Center)
+
+ }.height(50).width("100%")
+
+ }
+
+ @Builder
+ defFooterViewHorizontal() {
+ RelativeContainer() {
+ Stack() {
+ Image(this.pullConfig.arrowLoadImage)
+ .width(this.pullConfig.arrowLoadImageWidth)
+ .height(this.pullConfig.arrowLoadImageHeight)
+ .visibility(this.getPullUpArrowVisibility())
+ .fillColor(this.pullConfig.arrowLoadImageColor)
+ .margin({ top: 5 })
+ .rotate({
+ x: 0,
+ y: 0,
+ z: 1,
+ angle: this.arrowRotateLoad
+ })
+ if (this.footerLoadIngView) {
+ Stack() {
+ this.footerLoadIngView()
+ }
+ .visibility(this.getLoadVisibility())
+ } else {
+ LoadingProgress()
+ .color(this.pullConfig.refreshLoadingColor)
+ .width(this.pullConfig.refreshLoadingWidth)
+ .height(this.pullConfig.refreshLoadingHeight).visibility(this.getLoadVisibility())
+ }
+ }.alignRules({
+ top: { anchor: "title", align: VerticalAlign.Bottom },
+ middle: { anchor: "__container__", align: HorizontalAlign.Center }
+ }).id("arrow")
+
+ Row() {
+ Text(this.getLoadTips())
+ .fontSize(this.pullConfig.pullUpTipsSize)
+ .fontColor(this.pullConfig.pullUpTipsColor)
+ .textAlign(TextAlign.Center)
+ }.id("title").alignRules({
+ middle: { anchor: "__container__", align: HorizontalAlign.Center },
+ center: { anchor: "__container__", align: VerticalAlign.Center }
+ }).alignItems(VerticalAlign.Center)
+
+ }.width(50).height("100%")
+ }
+
+ @Builder
+ defFooterNoMoreViewHorizontal() {
+ Text(this.pullConfig.noMoreTipsHorizontal)
+ .textAlign(TextAlign.Center)
+ .height("100%")
+ .width(50)
+ .fontSize(this.pullConfig.noMoreTipsSize)
+ .fontColor(this.pullConfig.noMoreTipsColor)
+ }
+
+ @Builder
+ defFooterView() {
+ RelativeContainer() {
+ Stack() {
+ Image(this.pullConfig.arrowLoadImage)
+ .width(this.pullConfig.arrowLoadImageWidth)
+ .height(this.pullConfig.arrowLoadImageHeight)
+ .visibility(this.getPullUpArrowVisibility())
+ .fillColor(this.pullConfig.arrowLoadImageColor)
+ .margin({ right: 10 })
+ .rotate({
+ x: 0,
+ y: 0,
+ z: 1,
+ angle: this.arrowRotateLoad
+ })
+ if (this.footerLoadIngView) {
+ Stack() {
+ this.footerLoadIngView()
+ }
+ .visibility(this.getLoadVisibility())
+ } else {
+
+ LoadingProgress()
+ .color(this.pullConfig.refreshLoadingColor)
+ .width(this.pullConfig.refreshLoadingWidth)
+ .height(this.pullConfig.refreshLoadingHeight)
+ .visibility(this.getLoadVisibility())
+ }
+ }.alignRules({
+ right: { anchor: "title", align: HorizontalAlign.Start },
+ center: { anchor: "__container__", align: VerticalAlign.Center }
+ }).id("arrow")
+
+ Column() {
+ Text(this.getLoadTips()).fontSize(this.pullConfig.pullUpTipsSize).fontColor(this.pullConfig.pullUpTipsColor)
+ }.id("title").alignRules({
+ middle: { anchor: "__container__", align: HorizontalAlign.Center },
+ center: { anchor: "__container__", align: VerticalAlign.Center }
+ }).constraintSize({ minWidth: 80 }).alignItems(HorizontalAlign.Center)
+
+ }.height(50).width("100%")
+
+ }
+
+ @Builder
+ defFooterNoMoreView() {
+ Text(this.pullConfig.noMoreTips)
+ .textAlign(TextAlign.Center)
+ .height(50)
+ .width("100%")
+ .fontSize(this.pullConfig.noMoreTipsSize)
+ .fontColor(this.pullConfig.noMoreTipsColor)
+ }
+
+ private getTips(): Resource | string {
+ // todo
+ if (!this.config.isVertical && this.config.horizontalMode != 1 && this.config.horizontalMode != 2) {
+ switch (this.status) {
+ case PullStatus.DEF:
+ case PullStatus.PullDown:
+ return this.pullConfig.pullDownTipsHorizontal
+ case PullStatus.PreRefresh:
+ return this.pullConfig.releaseRefreshTipsHorizontal
+ case PullStatus.Refresh:
+ return this.pullConfig.refreshTipsHorizontal
+ case PullStatus.PreOpenPage:
+ case PullStatus.OpenPage:
+ return this.pullConfig.pullOpenPageTipsHorizontal
+ case PullStatus.RefreshSuccess:
+ if (this.controller.getConfig().refreshShowSuccess) {
+ return this.pullConfig.refreshSuccessTipsHorizontal
+ } else {
+ /*如果不显示刷新成功的视图*/
+ return this.pullConfig.refreshTipsHorizontal
+ }
+ case PullStatus.RefreshError:
+ if (this.controller.getConfig().refreshShowError) {
+ return this.pullConfig.refreshErrorTipsHorizontal
+ } else {
+ /*如果不显示刷新失败的视图*/
+ return this.pullConfig.refreshTipsHorizontal
+ }
+ default:
+ return this.pullConfig.pullDownTipsHorizontal
+ }
+ }
+ switch (this.status) {
+ case PullStatus.DEF:
+ case PullStatus.PullDown:
+ return this.pullConfig.pullDownTips
+ case PullStatus.PreRefresh:
+ return this.pullConfig.releaseRefreshTips
+ case PullStatus.Refresh:
+ return this.pullConfig.refreshTips
+ case PullStatus.PreOpenPage:
+ case PullStatus.OpenPage:
+ return this.pullConfig.pullOpenPageTips
+ case PullStatus.RefreshSuccess:
+ if (this.controller.getConfig().refreshShowSuccess) {
+ return this.pullConfig.refreshSuccessTips
+ } else {
+ /*如果不显示刷新成功的视图*/
+ return this.pullConfig.refreshTips
+ }
+ case PullStatus.RefreshError:
+ if (this.controller.getConfig().refreshShowError) {
+ return this.pullConfig.refreshErrorTips
+ } else {
+ /*如果不显示刷新失败的视图*/
+ return this.pullConfig.refreshTips
+ }
+ default:
+ return this.pullConfig.pullDownTips
+ }
+ }
+
+ private getLoadTips(): Resource | string {
+ if (!this.config.isVertical && this.config.horizontalMode != 1 && this.config.horizontalMode != 2) {
+ switch (this.status) {
+ case PullStatus.DEF:
+ case PullStatus.PullUp:
+ return this.pullConfig.pullUpTipsHorizontal
+ case PullStatus.PreLoad:
+ return this.pullConfig.releaseLoadTipsHorizontal
+ case PullStatus.Load:
+ return this.pullConfig.loadTipsHorizontal
+ case PullStatus.PreLoadOpenPage:
+ case PullStatus.LoadOpenPage:
+ return this.pullConfig.pullOpenPageTipsHorizontal
+ case PullStatus.LoadSuccess:
+ if (this.controller.getConfig().loadShowSuccess) {
+ return this.pullConfig.loadSuccessTipsHorizontal
+ } else {
+ /*如果不显示刷新成功的视图*/
+ return this.pullConfig.loadTipsHorizontal
+ }
+ case PullStatus.LoadError:
+ if (this.controller.getConfig().loadShowError) {
+ return this.pullConfig.loadErrorTipsHorizontal
+ } else {
+ /*如果不显示刷新失败的视图*/
+ return this.pullConfig.loadTipsHorizontal
+ }
+ default:
+ return this.pullConfig.pullUpTipsHorizontal
+ }
+ }
+ switch (this.status) {
+ case PullStatus.DEF:
+ case PullStatus.PullUp:
+ return this.pullConfig.pullUpTips
+ case PullStatus.PreLoad:
+ return this.pullConfig.releaseLoadTips
+ case PullStatus.Load:
+ return this.pullConfig.loadTips
+ case PullStatus.PreLoadOpenPage:
+ case PullStatus.LoadOpenPage:
+ return this.pullConfig.loadOpenPageTips
+ case PullStatus.LoadSuccess:
+ if (this.controller.getConfig().loadShowSuccess) {
+ return this.pullConfig.loadSuccessTips
+ } else {
+ /*如果不显示刷新成功的视图*/
+ return this.pullConfig.loadTips
+ }
+ case PullStatus.LoadError:
+ if (this.controller.getConfig().loadShowError) {
+ return this.pullConfig.loadErrorTips
+ } else {
+ /*如果不显示刷新失败的视图*/
+ return this.pullConfig.loadTips
+ }
+ default:
+ return this.pullConfig.pullUpTips
+ }
+ }
+
+ /*下拉箭头显示逻辑*/
+ private getPullArrowVisibility(): Visibility {
+ switch (this.status) {
+ case PullStatus.DEF:
+ case PullStatus.PullDown:
+ case PullStatus.PreRefresh:
+ return Visibility.Visible
+ }
+ return Visibility.Hidden
+ }
+
+ private getRefreshVisibility(): Visibility {
+ switch (this.status) {
+ case PullStatus.Refresh:
+ return Visibility.Visible
+ case PullStatus.RefreshSuccess:
+ if (this.controller.getConfig().refreshShowSuccess) {
+ return Visibility.Hidden
+ } else {
+ /*如果不显示刷新成功的视图*/
+ return Visibility.Visible
+ }
+ case PullStatus.RefreshError:
+ if (this.controller.getConfig().refreshShowError) {
+ return Visibility.Hidden
+ } else {
+ /*如果不显示刷新失败的视图*/
+ return Visibility.Visible
+ }
+ }
+ return Visibility.Hidden
+ }
+
+ private getPullUpArrowVisibility(): Visibility {
+ switch (this.status) {
+ case PullStatus.DEF:
+ case PullStatus.PullUp:
+ case PullStatus.PreLoad:
+ return Visibility.Visible
+ }
+ return Visibility.Hidden
+ }
+
+ private getLoadVisibility(): Visibility {
+ switch (this.status) {
+ case PullStatus.Load:
+ return Visibility.Visible
+ case PullStatus.LoadSuccess:
+ if (this.controller.getConfig().loadShowSuccess) {
+ return Visibility.Hidden
+ } else {
+ /*如果不显示刷新成功的视图*/
+ return Visibility.Visible
+ }
+ case PullStatus.LoadError:
+ if (this.controller.getConfig().loadShowError) {
+ return Visibility.Hidden
+ } else {
+ /*如果不显示刷新失败的视图*/
+ return Visibility.Visible
+ }
+ }
+ return Visibility.Hidden
+ }
+
+ private getTimeTipsVisibility(): Visibility {
+ switch (this.status) {
+ case PullStatus.Refresh:
+ case PullStatus.PreOpenPage:
+ case PullStatus.OpenPage:
+ case PullStatus.RefreshSuccess:
+ case PullStatus.RefreshError:
+ return Visibility.None
+ }
+ if (this.getLastRefreshTime() <= 0) {
+ return Visibility.None
+ }
+ return Visibility.Visible
+ }
+
+ private sp: preferences.Preferences | undefined = undefined
+ private lastRefreshTime: number | undefined = undefined
+
+ private getLastRefreshTime(): number {
+ if (!this.sp) {
+ this.sp = preferences.getPreferencesSync(getContext(), {
+ name: "PullToRefreshLayoutTime"
+ })
+ }
+ if (this.lastRefreshTime) {
+ return this.lastRefreshTime
+ }
+ let preTime: number = this.sp.getSync("lastRefreshTimeKey" + this.viewKey, 0) as number
+ this.lastRefreshTime = preTime
+ return preTime;
+ }
+
+ private saveRefreshTime() {
+ if (!this.sp) {
+ this.sp = preferences.getPreferencesSync(getContext(), {
+ name: "PullToRefreshLayoutTime"
+ })
+ }
+ this.lastRefreshTime = new Date().getTime()
+ this.sp.put("lastRefreshTimeKey" + this.viewKey, this.lastRefreshTime)
+ this.sp.flush()
+ }
+
+ private lineBreak(): string {
+ if (this.isVerticalLayout()) {
+ return ""
+ } else {
+ return "\n"
+ }
+ }
+
+ private getLastFormatRefreshTime(): string {
+ const time = this.getLastRefreshTime();
+ if (time <= 0) {
+ return ""
+ }
+ const currentTime = new Date().getTime()
+ const timeIntervalSecond = Math.floor(((currentTime - time) / 1000))
+ let timeFormat: string = ""
+ if (!this.zr_last_update) {
+ if (this.isVerticalLayout()) {
+ this.zr_last_update = getContext().resourceManager.getStringByNameSync(("zr_last_update"))
+ } else {
+ this.zr_last_update = "\n" + getContext().resourceManager.getStringByNameSync(("h_zr_last_update"))
+ }
+ }
+ timeFormat = timeFormat + this.zr_last_update
+ if (timeIntervalSecond < 60) {
+ timeFormat = timeFormat + this.lineBreak() + timeIntervalSecond;
+ if (!this.zr_seconds_ago) {
+ if (this.isVerticalLayout()) {
+ this.zr_seconds_ago = getContext().resourceManager.getStringByNameSync(("zr_seconds_ago"))
+ } else {
+ this.zr_seconds_ago = "\n" + getContext().resourceManager.getStringByNameSync(("h_zr_seconds_ago"))
+ }
+ }
+ timeFormat = timeFormat + this.zr_seconds_ago
+ } else if (timeIntervalSecond < 3600) {
+ /*小于一小时*/
+ timeFormat = timeFormat + this.lineBreak() + Math.floor((timeIntervalSecond / 60));
+
+ if (!this.zr_minutes_ago) {
+ if (this.isVerticalLayout()) {
+ this.zr_minutes_ago = getContext().resourceManager.getStringByNameSync(("zr_minutes_ago"))
+ } else {
+ this.zr_minutes_ago = "\n" + getContext().resourceManager.getStringByNameSync(("h_zr_minutes_ago"))
+ }
+ }
+ timeFormat = timeFormat + this.zr_minutes_ago
+ } else if (timeIntervalSecond < 86400) {
+ /*小于一天*/
+ timeFormat = timeFormat + this.lineBreak() + Math.floor((timeIntervalSecond / 3600));
+ if (!this.zr_hours_ago) {
+ if (this.isVerticalLayout()) {
+ this.zr_hours_ago = getContext().resourceManager.getStringByNameSync(("zr_hours_ago"))
+ } else {
+ this.zr_hours_ago = "\n" + getContext().resourceManager.getStringByNameSync(("h_zr_hours_ago"))
+ }
+ }
+ timeFormat = timeFormat + this.zr_hours_ago
+ } else {
+ /*大于一天*/
+ timeFormat = timeFormat + this.lineBreak() + Math.floor((timeIntervalSecond / 86400));
+ if (!this.zr_days_ago) {
+
+ if (this.isVerticalLayout()) {
+ this.zr_days_ago = getContext().resourceManager.getStringByNameSync(("zr_days_ago"))
+ } else {
+ this.zr_days_ago = "\n" + getContext().resourceManager.getStringByNameSync(("h_zr_days_ago"))
+ }
+ }
+ timeFormat = timeFormat + this.zr_days_ago
+ }
+ return timeFormat
+ }
+
+ private zr_last_update: string | undefined = undefined
+ private zr_seconds_ago: string | undefined = undefined
+ private zr_minutes_ago: string | undefined = undefined
+ private zr_hours_ago: string | undefined = undefined
+ private zr_days_ago: string | undefined = undefined
+ private isUpdateTime = false;
+
+ private updateRefreshTime(status: PullStatus) {
+ if (status == PullStatus.RefreshSuccess) {
+ this.saveRefreshTime()
+ }
+ if (status == PullStatus.PullDown || status == PullStatus.PreRefresh) {
+ if (this.isUpdateTime) {
+ return;
+ }
+ this.isUpdateTime = true
+ setTimeout(() => {
+ this.zrLastUpdate = this.getLastFormatRefreshTime()
+ this.startUpdateTime();
+ })
+ } else {
+ this.stopUpdateTime();
+ }
+ }
+
+ private startUpdateTime() {
+ if (!this.isUpdateTime) {
+ return
+ }
+ setTimeout(() => {
+ this.zrLastUpdate = this.getLastFormatRefreshTime()
+ // console.log(this.zrLastUpdate + "=======startUpdateTime=====" + (1000 - new Date().getTime() % 1000));
+ this.startUpdateTime();
+ }, 1000 - new Date().getTime() % 1000)
+ }
+
+ private stopUpdateTime() {
+ this.isUpdateTime = false
+ }
+}
\ No newline at end of file
diff --git a/RefreshLib/src/main/ets/RefreshController.ets b/RefreshLib/src/main/ets/RefreshController.ets
new file mode 100644
index 0000000..9696ba2
--- /dev/null
+++ b/RefreshLib/src/main/ets/RefreshController.ets
@@ -0,0 +1,80 @@
+import { PullStatus } from './PullStatus'
+import { RefreshLayoutConfig } from './RefreshLayoutConfig'
+export enum ViewState{
+ success=0,
+ loading,
+ empty,
+ error,
+ noNetwork
+}
+export class RefreshController {
+ /*刷新成功*/
+ refreshSuccess: (ignoreViewTips?:boolean) => void = (ignoreViewTips?:boolean) => {
+ }
+ /*刷新失败*/
+ refreshError: () => void = () => {
+ }
+ /*刷新完成,true:成功,false:失败*/
+ refreshComplete: (isSuccess: boolean,ignoreViewTips?:boolean) => void = (isSuccess: boolean,ignoreViewTips?:boolean) => {
+ }
+ /*取消刷新*/
+ refreshCancel: () => void = () => {
+ }
+ /*加载成功*/
+ loadSuccess: (hasMore?: boolean) => void = (hasMore?: boolean) => {
+ }
+ /*加载失败*/
+ loadError: () => void = () => {
+ }
+ /*加载完成,true:成功,false:失败*/
+ loadComplete: (isSuccess: boolean, hasMore?: boolean) => void = (isSuccess: boolean, hasMore?: boolean) => {
+ }
+ /*取消加载*/
+ loadCancel: () => void = () => {
+ }
+ /*显示加载中*/
+ viewLoading: () => void = () => {
+ this.viewState=ViewState.loading
+ }
+ /*显示空布局*/
+ viewEmpty: () => void = () => {
+ this.viewState=ViewState.empty
+ }
+ /*显示加载失败布局*/
+ viewError: () => void = () => {
+ this.viewState=ViewState.error
+ }
+ /*显示无网络布局*/
+ viewNoNetwork: () => void = () => {
+ this.viewState=ViewState.noNetwork
+ }
+ /**/
+ /*获取当前状态*/
+ getStatus: () => PullStatus = () => PullStatus.DEF
+ /*设置是否还有更多数据*/
+ hasMore: (hasMore: boolean) => void = (hasMore: boolean) => {
+ }
+ /*手动触发下拉刷新*/
+ refresh: () => void = () => {
+ }
+ /*手动触发上拉*/
+ load: () => void = () => {
+ }
+ /*下拉刷新开关是否打开*/
+ refreshIsEnable: () => boolean = () => true
+ /*下拉刷新开关是否打开*/
+ loadIsEnable: () => boolean = () => false
+ /*设置配置*/
+ /*由于v2装饰器@Param限制原因,setConfig方法在v2版本上不提供*/
+ /*@Param装饰的变量在子组件中无法进行修改。但当装饰的变量类型为对象时,在子组件中修改对象中属性是允许的。*/
+ // setConfig: (config: RefreshLayoutConfig) => void = () => {}
+ /*获取配置*/
+ getConfig: () => RefreshLayoutConfig = () => new RefreshLayoutConfig()
+ /*webview专用*/
+ onWebviewScroll: (xOffset: number, yOffset: number) => void = (xOffset: number, yOffset: number) => {
+ }
+ private viewState:ViewState=ViewState.success
+ public getViewState():ViewState{
+ return this.viewState
+ }
+}
\ No newline at end of file
diff --git a/RefreshLib/src/main/ets/RefreshLayout.ets b/RefreshLib/src/main/ets/RefreshLayout.ets
new file mode 100644
index 0000000..914ba97
--- /dev/null
+++ b/RefreshLib/src/main/ets/RefreshLayout.ets
@@ -0,0 +1,1610 @@
+import { RefreshLayoutConfig } from './RefreshLayoutConfig'
+import { _ContentView, _headerView, _loadMoreView, _noMoreView, RefreshLayoutHelper } from './RefreshLayoutHelper'
+import { AnimatorResult } from '@kit.ArkUI'
+import animator from '@ohos.animator'
+import { PullStatus } from './PullStatus'
+import { RefreshController, ViewState } from './RefreshController'
+import { PullDown } from './PullDown'
+import web from '@ohos.web.webview'
+
+@ComponentV2
+export struct RefreshLayout {
+ /*下拉刷新逻辑处理*/
+ private helper: RefreshLayoutHelper = new RefreshLayoutHelper()
+ /*****************************************对外提供属性******************************************************/
+ @Param public scroller: Scroller | undefined = undefined
+ @Param public webviewController: web.WebviewController | undefined = undefined
+ @Param public controller: RefreshController = new RefreshController()
+ //头布局视图
+ @BuilderParam headerView: () => void = _headerView
+ //内容视图
+ @BuilderParam contentView: () => void = _ContentView
+ //加载更多视图
+ @BuilderParam loadView: () => void = _loadMoreView
+ //暂无更多视图
+ @BuilderParam noMoreView: () => void = _noMoreView
+ //正在加载视图
+ @BuilderParam viewLoading?: () => void
+ //空视图
+ @BuilderParam viewEmpty?: () => void
+ //加载错误视图
+ @BuilderParam viewError?: () => void
+ //无网络视图
+ @BuilderParam viewNoNetwork?: () => void
+ /*是否可以下拉刷新*/
+ @Param public onCanPullRefresh: () => boolean = () => true
+ /*刷新通知*/
+ @Param public onRefresh: () => void = () => {
+ }
+ /*加载通知*/
+ @Param public onLoad: () => void = () => {
+ }
+ /*是否可以上拉加载*/
+ @Param public onCanPullLoad: () => boolean = () => false //todo
+ /*下拉状态监听*/
+ @Param public onPullListener: (pullDown: PullDown) => void = () => {
+ }
+ /*打开页面通知*/
+ @Param public onOpenPage: () => void = () => {
+ }
+ /*打开页面通知*/
+ @Param public onLoadOpenPage: () => void = () => {
+ }
+ /*下拉刷新配置*/
+ @Param public config: RefreshLayoutConfig = new RefreshLayoutConfig()
+ /***********************************************************************************************/
+ /*下拉或右拉总偏移量*/
+ @Local currentOffset: number = 0
+ /*当前状态*/
+ @Local status: PullStatus = PullStatus.DEF
+ /*上拉或左拉总偏移量*/
+ @Local currentOffsetLoad: number = 0
+ /*是否有更多数据*/
+ @Local hasMore: boolean = true
+ /*防止重复挂载,这里通过多个变量动态控制挂载,显示,隐藏*/
+ @Local viewSuccessState: number = 1
+ @Local viewLoadingState: number = 0
+ @Local viewEmptyState: number = 0
+ @Local viewErrorState: number = 0
+ @Local viewNoNetworkState: number = 0
+
+ /***********************************************************************************************/
+ //region onMeasureSize
+ private log(str: string, tag: string = "RefreshLayout") {
+ console.debug(tag + "==" + str)
+ }
+
+ private setViewLoadSuccessState() {
+ this.viewSuccessState = 1
+ /*不等于0才修改值*/
+ if (this.viewLoadingState) {
+ this.viewLoadingState = 2
+ }
+ if (this.viewEmptyState) {
+ this.viewEmptyState = 2
+ }
+ if (this.viewErrorState) {
+ this.viewErrorState = 2
+ }
+ if (this.viewNoNetworkState) {
+ this.viewNoNetworkState = 2
+ }
+ }
+
+ private setViewLoadingState() {
+ this.viewLoadingState = 1
+ if (this.viewEmptyState) {
+ this.viewEmptyState = 2
+ }
+ if (this.viewErrorState) {
+ this.viewErrorState = 2
+ }
+ if (this.viewNoNetworkState) {
+ this.viewNoNetworkState = 2
+ }
+ this.viewSuccessState = 0
+ }
+
+ private setViewLoadEmptyState() {
+ if (this.viewLoadingState) {
+ this.viewLoadingState = 2
+ }
+ this.viewEmptyState = 1
+ if (this.viewErrorState) {
+ this.viewErrorState = 2
+ }
+ if (this.viewNoNetworkState) {
+ this.viewNoNetworkState = 2
+ }
+ this.viewSuccessState = 0
+ }
+
+ private setViewLoadErrorState() {
+ if (this.viewLoadingState) {
+ this.viewLoadingState = 2
+ }
+ if (this.viewEmptyState) {
+ this.viewEmptyState = 2
+ }
+ this.viewErrorState = 1
+ if (this.viewNoNetworkState) {
+ this.viewNoNetworkState = 2
+ }
+ this.viewSuccessState = 0
+ }
+
+ private setViewLoadNoNetworkState() {
+ if (this.viewLoadingState) {
+ this.viewLoadingState = 2
+ }
+ if (this.viewEmptyState) {
+ this.viewEmptyState = 2
+ }
+ if (this.viewErrorState) {
+ this.viewErrorState = 2
+ }
+ this.viewNoNetworkState = 1
+ this.viewSuccessState = 0
+ }
+
+ private sizeResult: SizeResult = { width: 0, height: 0 }
+
+ onMeasureSize(selfLayoutInfo: GeometryInfo, children: Measurable[], constraint: ConstraintSizeOptions): SizeResult {
+ // this.log("======onAppear=1")
+ const count = children.length
+ if (typeof (constraint.maxWidth) == "number") {
+ this.sizeResult.width = constraint.maxWidth as number;
+ }
+ if (typeof (constraint.maxHeight) == "number") {
+ this.sizeResult.height = constraint.maxHeight as number;
+ }
+ for (let i = 0; i < count; i++) {
+ const result = children[i].measure(constraint)
+ if (i == 0) {
+ if (this.config.isVertical || this.config.horizontalMode == 1 || this.config.horizontalMode == 2) {
+ /*保存header高度*/
+ this.helper.headerSize = result.height
+ } else {
+ /*保存header宽度*/
+ this.helper.headerSize = result.width
+ }
+ } else if (i == 1 && (this.sizeResult.width <= 0 || this.sizeResult.height <= 0)) {
+ this.sizeResult.width = result.width;
+ this.sizeResult.height = result.height;
+ } else if (i == 2) {
+ if (this.config.isVertical || this.config.horizontalMode == 1 || this.config.horizontalMode == 2) {
+ /*保存footer高度*/
+ this.helper.footerSize = result.height
+ } else {
+ /*保存footer宽度*/
+ this.helper.footerSize = result.width
+ }
+ }
+ }
+ return this.sizeResult
+ }
+
+ onPlaceChildren(selfLayoutInfo: GeometryInfo, children: Layoutable[], constraint: ConstraintSizeOptions): void {
+ let count = children.length
+ if (count <= 0) {
+ return
+ }
+ for (let i = 0; i < count; i++) {
+ const child = children[i]
+ if (i == 0) {
+ /*header*/
+ // this.helper.initOffsetY = -child.measureResult.height
+ if (this.config.isVertical) {
+ child.layout({ y: -child.measureResult.height })
+ } else {
+ if (this.config.horizontalMode == 1 || this.config.horizontalMode == 2) {
+ child.layout({
+ x: -child.measureResult.width / 2 - child.measureResult.height / 2,
+ y: this.sizeResult.height / 2 - child.measureResult.height / 2
+ })
+ } else {
+ child.layout({ x: -child.measureResult.width })
+ }
+ }
+ } else if (i == 1) {
+ /*content*/
+ child.layout({})
+ } else if (i == 2) {
+ /*footer*/
+ if (this.config.isVertical) {
+ child.layout({ y: this.sizeResult.height })
+ } else {
+ if (this.config.horizontalMode == 1 || this.config.horizontalMode == 2) {
+ child.layout({
+ x: this.sizeResult.width - child.measureResult.width / 2 + child.measureResult.height / 2,
+ y: this.sizeResult.height / 2 - child.measureResult.height / 2
+ })
+ } else {
+ child.layout({ x: this.sizeResult.width })
+ }
+ }
+ }
+ }
+ }
+
+ build() {
+ this.headerAndContent()
+ }
+
+ // endregion
+ /***********************************************************************************************/
+
+
+ /*****************************************下拉刷新******************************************************/
+ /*释放刷新回弹至header动画*/
+ private anim: AnimatorResult | undefined = animator.create(this.helper.animOptions);
+ /*回弹动画暂停*/
+ private animPause = false
+ private setDefStatusId = 0;
+ /*自动刷新动画*/
+ private autoRefreshAnim: AnimatorResult | undefined = undefined
+ /*记录动画执行进度,在回弹至header高度过程中记录已经执行了多少进度,方便在回弹header高度之前收到刷新或者加载成功的结果后,再执行剩余时间的动画*/
+ private animProgress: number = 1
+ /******************************************上拉加载*****************************************************/
+ /*释放加载回弹至footer动画*/
+ private animLoad: AnimatorResult | undefined = undefined
+ /*回弹动画暂停*/
+ private animLoadPause = false
+ private setDefLoadStatusId = 0;
+ /*自动刷新动画*/
+ private autoLoadAnim: AnimatorResult | undefined = undefined
+ /*记录动画执行进度,在回弹至header高度过程中记录已经执行了多少进度,方便在回弹header高度之前收到刷新或者加载成功的结果后,再执行剩余时间的动画*/
+ private animProgressLoad: number = 1
+
+ aboutToAppear(): void {
+ /*刷新成功*/
+ this.controller.refreshSuccess = (ignoreViewTips?: boolean) => {
+ this.refreshSuccess(ignoreViewTips)
+ }
+ /*刷新失败*/
+ this.controller.refreshError = () => {
+ this.refreshError()
+ }
+ /*通过参数通知刷新结果*/
+ this.controller.refreshComplete = (isSuccess: boolean, ignoreViewTips?: boolean) => {
+ this.refreshComplete(isSuccess, ignoreViewTips)
+ }
+ /*刷新取消*/
+ this.controller.refreshCancel = () => {
+ this.refreshCancel()
+ }
+
+ /*刷新成功*/
+ this.controller.loadSuccess = (hasMore?: boolean) => {
+ this.loadSuccess(hasMore)
+ }
+ /*刷新失败*/
+ this.controller.loadError = () => {
+ this.loadError()
+ }
+ /*通过参数通知加载结果*/
+ this.controller.loadComplete = (isSuccess: boolean, hasMore?: boolean) => {
+ this.loadComplete(isSuccess, hasMore)
+ }
+ /*加载取消*/
+ this.controller.loadCancel = () => {
+ this.loadCancel()
+ }
+ /*设置当前是否还有更多*/
+ this.controller.hasMore = (hasMore: boolean) => {
+ this.hasMore = hasMore
+ }
+ /*获取当前状态*/
+ this.controller.getStatus = () => {
+ return this.status
+ }
+ /*自动刷新*/
+ this.controller.refresh = () => {
+ this.autoRefresh()
+ }
+ /*自动加载*/
+ this.controller.load = () => {
+ this.autoLoad()
+ }
+ /*是否开启刷新*/
+ this.controller.refreshIsEnable = () => {
+ return this.config.pullRefreshEnable
+ }
+ /*是否开启加载*/
+ this.controller.loadIsEnable = () => {
+ return this.config.pullLoadEnable
+ }
+ /*设置配置*/
+ /*由于v2装饰器@Param限制原因,setConfig方法在v2版本上不提供*/
+ /*@Param装饰的变量在子组件中无法进行修改。但当装饰的变量类型为对象时,在子组件中修改对象中属性是允许的。*/
+ /* this.controller.setConfig = (config: RefreshLayoutConfig) => {
+ this.config = config
+ }*/
+ this.controller.getConfig = () => this.config
+ /*webView专用*/
+ this.controller.onWebviewScroll = (xOffset, yOffset) => {
+ this.helper.webViewXOffset = xOffset;
+ this.helper.webViewYOffset = yOffset
+ }
+ const viewState = this.controller.getViewState()
+ if (viewState == ViewState.loading) {
+ this.setViewLoadingState()
+ } else if (viewState == ViewState.empty) {
+ this.setViewLoadEmptyState()
+ } else if (viewState == ViewState.error) {
+ this.setViewLoadErrorState()
+ } else if (viewState == ViewState.noNetwork) {
+ this.setViewLoadNoNetworkState()
+ }
+ this.controller.viewLoading = () => {
+ if (this.viewLoading) {
+ this.setViewLoadingState()
+ }
+ }
+
+ this.controller.viewEmpty = () => {
+ if (this.viewEmpty) {
+ this.setViewLoadEmptyState()
+ }
+ }
+
+ this.controller.viewError = () => {
+ if (this.viewError) {
+ this.setViewLoadErrorState()
+ }
+ }
+
+ this.controller.viewNoNetwork = () => {
+ if (this.viewNoNetwork) {
+ this.setViewLoadNoNetworkState()
+ }
+ }
+ this.setAnimListener()
+ }
+
+ aboutToDisappear(): void {
+ //按照官方示例,置空处理防止内存泄漏
+ this.anim = undefined
+ this.autoRefreshAnim = undefined
+
+ this.animLoad = undefined
+ }
+
+ private setAnimListener() {
+ if (!this.anim) {
+ return
+ }
+ this.anim.onFrame = (progress: number) => {
+ if (this.animPause) {
+ return
+ }
+ this.animProgress = progress
+ this.currentOffset = this.helper.totalOffset * (1 - progress)
+ }
+ this.anim.onFinish = () => {
+ this.animProgress = 1
+ /*如果是回弹至header高度结束,开始判断是否是刷新完成或失败状态,继续回弹动画*/
+ if (this.status == PullStatus.RefreshSuccess || this.status == PullStatus.RefreshError) {
+ if (this.currentOffset > 0) {
+ //开始隐藏header动画
+ this.hiddenHeaderAnim()
+ } else if (this.currentOffset <= 0) {
+ /*如果动画结束,需要改变状态*/
+ this.setDefStatus()
+ }
+ /*状态监听*/
+ this.pullDownListener({
+ isPullDown: false,
+ isPullUp: false,
+ isTouch: this.helper.isPressDown,
+ distance: this.currentOffset,
+ distanceLoad: this.currentOffsetLoad,
+ headerViewSize: this.helper.headerSize,
+ footerViewSize: this.helper.footerSize,
+ status: this.status
+ }, 0)
+ return
+ }
+ if (this.helper.notReleaseRefresh && this.currentOffset <= 0) {
+ /*非释放刷新,在刷新完成后,需要改变状态,否则非释放刷新时不松手,刷新完成后再下拉没反应*/
+ this.helper.notReleaseRefresh = false
+ }
+ this.helper.totalOffset = this.currentOffset;
+ this.changeStatus(this.currentOffset)
+ /*状态监听*/
+ this.pullDownListener({
+ isPullDown: false,
+ isPullUp: false,
+ isTouch: this.helper.isPressDown,
+ distance: this.currentOffset,
+ distanceLoad: this.currentOffsetLoad,
+ headerViewSize: this.helper.headerSize,
+ footerViewSize: this.helper.footerSize,
+ status: this.status
+ }, 1)
+ }
+ this.anim.onCancel = () => {
+ this.helper.totalOffset = this.currentOffset;
+ }
+ }
+
+ //用于上拉加载更多
+ private initLoadAnim() {
+ if (this.animLoad) {
+ return
+ }
+ this.animLoad = animator.create(this.helper.animLoadOptions);
+ this.animLoad.onFrame = (progress: number) => {
+ if (this.animLoadPause) {
+ return
+ }
+ this.animProgressLoad = progress
+ this.currentOffsetLoad = this.helper.totalOffsetLoad * (1 - progress)
+ }
+ this.animLoad.onFinish = () => {
+ this.animProgressLoad = 1
+ /*如果是回弹至header高度结束,开始判断是否是刷新完成或失败状态,继续回弹动画*/
+ if (this.status == PullStatus.LoadSuccess || this.status == PullStatus.LoadError) {
+ if (this.currentOffsetLoad < 0) {
+ //开始隐藏footer动画
+ this.hiddenFooterAnim()
+ } else if (this.currentOffsetLoad <= 0) {
+ /*如果动画结束,需要改变状态*/
+ this.setDefStatusLoad()
+ }
+ /*状态监听*/
+ this.pullDownListener({
+ isPullDown: false,
+ isPullUp: false,
+ isTouch: this.helper.isPressDown,
+ distance: this.currentOffset,
+ distanceLoad: this.currentOffsetLoad,
+ headerViewSize: this.helper.headerSize,
+ footerViewSize: this.helper.footerSize,
+ status: this.status
+ }, 2)
+ return
+ }
+ if (this.helper.notReleaseLoad && this.currentOffsetLoad >= 0) {
+ /*非释放加载,在加载完成后,需要改变状态,否则非释放加载时不松手,加载完成后再上拉没反应*/
+ this.helper.notReleaseLoad = false
+ }
+ this.helper.totalOffsetLoad = this.currentOffsetLoad;
+ this.changeStatusLoad(this.currentOffsetLoad)
+ /*状态监听*/
+ this.pullDownListener({
+ isPullDown: false,
+ isPullUp: false,
+ isTouch: this.helper.isPressDown,
+ distance: this.currentOffset,
+ distanceLoad: this.currentOffsetLoad,
+ headerViewSize: this.helper.headerSize,
+ footerViewSize: this.helper.footerSize,
+ status: this.status
+ }, 3)
+ }
+ this.animLoad.onCancel = () => {
+ this.helper.totalOffsetLoad = this.currentOffsetLoad;
+ }
+ }
+
+ private getHeaderOffsetY() {
+ return this.currentOffset;
+ }
+
+ private getFooterOffsetY() {
+ return this.currentOffsetLoad;
+ }
+
+ /*下拉高度触发刷新*/
+ public getPullRefreshHeaderHeight(): number {
+ if (this.config.pullHeaderHeightRefresh <= 0) {
+ this.config.pullHeaderHeightRefresh = this.helper.headerSize * 1.5
+ }
+ return this.config.pullHeaderHeightRefresh
+ }
+
+ /*下拉高度触发打开页面*/
+ public getPullOpenPageHeight(): number {
+ if (this.config.pullHeaderHeightOpenPage <= this.getPullRefreshHeaderHeight()) {
+ this.config.pullHeaderHeightOpenPage = this.helper.headerSize * 2.6
+ }
+ return this.config.pullHeaderHeightOpenPage
+ }
+
+ /*上拉高度触发加载*/
+ public getPullLoadFooterHeight(): number {
+ if (this.config.pullFooterHeightLoad <= 0) {
+ this.config.pullFooterHeightLoad = this.helper.footerSize * 1.1
+ }
+ return this.config.pullFooterHeightLoad
+ }
+
+ /*上拉高度触发打开页面*/
+ public getPullLoadOpenPageHeight(): number {
+ if (this.config.pullFooterHeightOpenPage <= this.getPullLoadFooterHeight()) {
+ this.config.pullFooterHeightOpenPage = this.helper.footerSize * 2.6
+ }
+ return this.config.pullFooterHeightOpenPage
+ }
+
+ private getAngle() {
+ if (this.config.isVertical) {
+ return 0
+ }
+ /*横向模式0:正常横向布局,1:header和footer逆时针旋转90度,2:header和footer顺时针旋转90度*/
+ if (this.config.horizontalMode == 1) {
+ return -90
+ } else if (this.config.horizontalMode == 2) {
+ return 90
+ }
+ return 0
+ }
+
+ /*是否是垂直列表或者布局旋转后的横向header或footer*/
+ private isVerticalLayout(): boolean {
+ return (this.config.isVertical || this.config.horizontalMode == 1 || this.config.horizontalMode == 2)
+ }
+
+ /***********************************************************************************************/
+ @Builder
+ private headerAndContent() {
+ Stack() {
+ this.headerView()
+ }
+ .onAppear(() => {
+ // this.totalOffsetY = this.helper.initOffsetY
+ this.getPullRefreshHeaderHeight()
+ this.getPullOpenPageHeight()
+ })
+ .offset({
+ y: this.config.isVertical ? this.getHeaderOffsetY() + this.getFooterOffsetY() : 0,
+ x: this.config.isVertical ? 0 : this.getHeaderOffsetY() + this.getFooterOffsetY()
+ })
+ .rotate({
+ angle: this.getAngle(),
+ z: 1,
+ x: 0,
+ y: 0
+ })
+ .width(this.isVerticalLayout() ? "100%" : "auto")
+ .height(this.isVerticalLayout() ? "auto" : "100%")
+
+ // .borderWidth(2)
+ // .borderColor(Color.Black)
+
+ Stack() {
+ /*正在加载中视图*/
+ if (this.viewLoading && this.viewLoadingState) {
+ Stack() {
+ this.viewLoading()
+ }.width("100%").height("100%").visibility(this.viewLoadingState == 1 ? Visibility.Visible : Visibility.Hidden)
+ }
+ /*空数据视图*/
+ if (this.viewEmpty && this.viewEmptyState) {
+ Stack() {
+ this.viewEmpty()
+ }.width("100%").height("100%").visibility(this.viewEmptyState == 1 ? Visibility.Visible : Visibility.Hidden)
+ }
+ /*数据加载失败视图*/
+ if (this.viewError && this.viewErrorState) {
+ Stack() {
+ this.viewError()
+ }.width("100%").height("100%").visibility(this.viewErrorState == 1 ? Visibility.Visible : Visibility.Hidden)
+ }
+ /*无网络视图*/
+ if (this.viewNoNetwork && this.viewNoNetworkState) {
+ Stack() {
+ this.viewNoNetwork()
+ }.width("100%").height("100%").visibility(this.viewNoNetworkState == 1 ? Visibility.Visible : Visibility.Hidden)
+ }
+ Stack() {
+ this.contentView()
+ }.width("100%").height("100%").visibility(this.viewSuccessState == 1 ? Visibility.Visible : Visibility.Hidden)
+ }
+ .offset({
+ y: this.config.isVertical ? this.getHeaderOffsetY() + this.getFooterOffsetY() : 0,
+ x: this.config.isVertical ? 0 : this.getHeaderOffsetY() + this.getFooterOffsetY()
+ })
+ // .borderWidth(2)
+ // .borderColor(Color.Red)
+ .width("100%")
+ .height("100%")
+ .onTouch((event: TouchEvent) => {
+ if (this.status == PullStatus.OpenPage) {
+ /*下拉打开其他页面回弹结束前不响应事件*/
+ return;
+ }
+ if (this.status == PullStatus.LoadOpenPage) {
+ /*上拉打开其他页面回弹结束前不响应事件*/
+ return;
+ }
+ if (event.type == TouchType.Down) {
+
+ //todo 临时方案,后续等官方增加事件拦截后再替换
+ if (this.currentOffset > 0 || this.currentOffsetLoad < 0) {
+ //因为通过Scroller.scrollTo不让列表滑动,这里提前记录当前滑动距离,后面判断最大值(回弹期间快速松手,回弹完成之前再按下,需要记录grid列表当前滑动距离,防止上拉再下拉时列表跟随滚动)
+ if (this.scroller) {
+ /*其他列表记录当前滑动距离*/
+ if (this.config.isVertical) {
+ /*垂直列表*/
+ this.helper.scrollerOffset = this.scroller?.currentOffset()?.yOffset ?? 0
+ } else {
+ /*水平列表*/
+ this.helper.scrollerOffset = this.scroller?.currentOffset()?.xOffset ?? 0
+ }
+ } else if (this.webviewController) {
+ /*webview记录当前滑动距离*/
+ this.helper.scrollerOffset =
+ this.config.isVertical ? this.helper.webViewYOffset : this.helper.webViewXOffset
+ }
+ }
+
+
+ this.helper.isPressDown = true
+ /*暂停下拉刷新相关动画*/
+ this.pauseAnim()
+ /*暂停上拉加载相关动画*/
+ this.pauseAnimLoad()
+ if (this.status == PullStatus.RefreshSuccess || this.status == PullStatus.RefreshError) {
+ /*刷新中,或者刷新完成,此时触摸暂停动画,但是状态还是需要继续改变*/
+ this.setDefAfterRefreshEnd(this.config.refreshResultDurationTime)
+ } else if (this.status == PullStatus.LoadSuccess || this.status == PullStatus.LoadError) {
+ /*加载中,或者加载完成,此时触摸暂停动画,但是状态还是需要继续改变*/
+ this.setDefAfterLoadEnd(this.config.loadResultDurationTime)
+ }
+
+ /*状态监听*/
+ this.pullDownListener({
+ isPullDown: false,
+ isPullUp: false,
+ isTouch: true,
+ distance: this.currentOffset,
+ distanceLoad: this.currentOffsetLoad,
+ headerViewSize: this.helper.headerSize,
+ footerViewSize: this.helper.footerSize,
+ status: this.status
+ }, 4)
+ } else if (event.type == TouchType.Up || event.type == TouchType.Cancel) {
+ this.helper.scrollerOffset = 0
+ this.helper.notReleaseRefresh = false
+ this.helper.notReleaseLoad = false
+ /*刷新完成状态,开始倒计时重置状态时,正好触发down事件,如果在倒计时结束之前this.currentOffsetY>0触发了up或者cancel事件,则需要取消,否则在回弹期间会突然改变状态*/
+ /*下拉刷新相关*/
+ if (this.setDefStatusId != 0 && this.currentOffsetLoad > 0) {
+ clearTimeout(this.setDefStatusId)
+ this.setDefStatusId = 0
+ }
+ /*同上*/
+ /*上拉加载相关*/
+ if (this.setDefLoadStatusId != 0 && this.currentOffsetLoad < 0) {
+ clearTimeout(this.setDefLoadStatusId)
+ this.setDefLoadStatusId = 0
+ }
+
+ this.helper.isPressDown = false
+ this.animLoadPause = false
+ this.animPause = false
+
+ if (this.currentOffset > 0 || (this.currentOffset == 0 && !this.isLoadStatus())) {
+ /*是否关闭下拉刷新*/
+ if (this.config.pullRefreshEnable) {
+ /*如果处于加载相关的状态,则不需要执行该方法*/
+ this.releaseActionByRefresh()
+ }
+ } else if (this.currentOffsetLoad <= 0) {
+ /*是否关闭上拉加载*/
+ if (this.config.pullLoadEnable) {
+ this.releaseActionByLoad()
+ }
+ }
+
+
+ /*状态监听*/
+ this.pullDownListener({
+ isPullDown: false,
+ isPullUp: false,
+ isTouch: false,
+ distance: this.currentOffset,
+ distanceLoad: this.currentOffsetLoad,
+ headerViewSize: this.helper.headerSize,
+ footerViewSize: this.helper.footerSize,
+ status: this.status
+ }, 5)
+ }
+ })
+ .parallelGesture(PanGesture(new PanGestureOptions(this.config.isVertical ? {
+ direction: PanDirection.Down | PanDirection.Up
+ } : { direction: PanDirection.Horizontal }))
+ .onActionStart((event: GestureEvent) => {
+ if (this.status == PullStatus.OpenPage) {
+ /*打开其他页面回弹结束前不响应事件*/
+ return;
+ }
+ if (this.status == PullStatus.LoadOpenPage) {
+ /*打开其他页面回弹结束前不响应事件*/
+ return;
+ }
+ this.helper.preOffset = this.getOffset(event)
+ })
+ .onActionUpdate((event: GestureEvent) => {
+ if (this.getOffset(event) == this.helper.preOffset) {
+ /*如果没有偏移量*/
+ return
+ }
+ const pullDown = this.getOffset(event) > this.helper.preOffset
+ /*下拉刷新相关动作*/
+ //因为下拉之后可以上拉,所以增加this.currentOffsetY>0判断
+ if ((pullDown || this.currentOffset > 0) && (this.currentOffsetLoad >= 0)) { //如果加载视图屏幕不可见,才允许下拉
+ /*关闭下拉刷新*/
+ if (!this.config.pullRefreshEnable) {
+ this.helper.preOffset = this.getOffset(event)
+ return
+ }
+ /*下拉后可以往上拉,所以需要event.offsetY > this.helper.preOffsetY判断,onCanPullRefresh需要配合下拉动作,这里如果是上拉动作,就不需要判断onCanPullRefresh*/
+ if (pullDown && !this.onCanPullRefresh?.() && this.viewSuccessState == 1) {
+ this.helper.preOffset = this.getOffset(event)
+ /*不可以下拉*/
+ return
+ }
+ //todo 临时方案,后续等官方增加事件拦截后再替换
+ if (this.currentOffset > 0 && !pullDown) {
+ /*如果下拉再上拉,不让列表滑动*/
+ if (this.scroller) {
+ /*其他列表*/
+ if (this.config.isVertical) {
+ this.scroller.scrollTo({ yOffset: 0, xOffset: this.scroller.currentOffset()?.xOffset ?? 0 })
+ } else {
+ this.scroller.scrollTo({ yOffset: this.scroller.currentOffset()?.yOffset ?? 0, xOffset: 0 })
+ }
+ } else if (this.webviewController) {
+ /*webview*/
+ if (this.config.isVertical) {
+ this.webviewController.scrollTo(this.helper.webViewXOffset, 0)
+ } else {
+ this.webviewController.scrollTo(0, this.helper.webViewYOffset)
+ }
+ }
+ }
+ /*如果正在加载中,加载成功或失败,不允许下拉*/
+ if (this.status == PullStatus.Load || this.status == PullStatus.LoadSuccess ||
+ this.status == PullStatus.LoadError) {
+ return
+ }
+ this.actionUpdateForRefresh(event)
+
+ return
+ }
+ /*上拉加载相关动作*/
+ if (!pullDown || this.currentOffsetLoad < 0) {
+ /*关闭上拉加载*/
+ if (!this.config.pullLoadEnable) {
+ this.helper.preOffset = this.getOffset(event)
+ return
+ }
+ //todo 临时方案,后续等官方增加事件拦截后再替换
+ if (this.onCanPullLoad?.()) {
+ //因为通过Scroller.scrollTo不让列表滑动,这里提前记录当前滑动距离,后面判断最大值
+ if (this.scroller) {
+ /*其他列表*/
+ if (this.config.isVertical) {
+ /*垂直列表*/
+ this.helper.scrollerOffset = this.scroller?.currentOffset()?.yOffset ?? 0
+ } else {
+ /*水平列表*/
+ this.helper.scrollerOffset = this.scroller?.currentOffset()?.xOffset ?? 0
+ }
+ } else if (this.webviewController) {
+ /*webview*/
+ this.helper.scrollerOffset =
+ this.config.isVertical ? this.helper.webViewYOffset : this.helper.webViewXOffset
+ }
+ }
+ /*上拉后可以往下拉,所以需要event.offsetY < this.helper.preOffsetYLoad判断,onCanPullLoad需要配合上拉动作,这里如果是下拉动作,就不需要判断onCanPullLoad*/
+ if (!pullDown && !this.onCanPullLoad?.()) {
+ this.helper.preOffset = this.getOffset(event)
+ /*不可以上拉*/
+ return
+ }
+ //todo 临时方案,后续等官方增加事件拦截后再替换
+ if (this.currentOffsetLoad < 0 && pullDown) {
+ /*如果下拉再上拉,不让列表滑动*/
+ if (this.scroller) {
+ /*其他列表*/
+ let offset = this.scroller.currentOffset()
+ if (offset) {
+ /*判断垂直还是水平列表*/
+ if (this.config.isVertical) {
+ this.scroller.scrollTo({
+ yOffset: Math.max(this.helper.scrollerOffset,
+ this.config.isVertical ? offset.yOffset : offset.xOffset),
+ xOffset: offset.xOffset
+ })
+ } else {
+ this.scroller.scrollTo({
+ yOffset: offset.yOffset,
+ xOffset: Math.max(this.helper.scrollerOffset,
+ this.config.isVertical ? offset.yOffset : offset.xOffset)
+ })
+ }
+ }
+ } else if (this.webviewController) {
+ /*webview*/
+ if (this.config.isVertical) {
+ this.webviewController.scrollTo(this.helper.webViewXOffset, this.helper.scrollerOffset)
+ } else {
+ this.webviewController.scrollTo(this.helper.scrollerOffset, this.helper.webViewYOffset)
+ }
+ }
+ }
+ /*如果正在刷新中,加载成功或失败,不允许下拉*/
+ if (this.status == PullStatus.Refresh || this.status == PullStatus.RefreshSuccess ||
+ this.status == PullStatus.RefreshError) {
+ return
+ }
+ this.actionUpdateForLoad(event)
+ return
+ }
+ })
+ .onActionEnd((event: GestureEvent) => {
+ // this.releaseAction()
+ })
+ .onActionCancel(() => {
+ // this.releaseAction()
+ }))
+
+ if (this.config.pullLoadEnable) {
+ Stack() {
+ if (this.hasMore || this.status == PullStatus.PreLoadOpenPage || this.status == PullStatus.LoadOpenPage) {
+ this.loadView()
+ } else {
+ this.noMoreView()
+ }
+ }
+ .onAppear(() => {
+ this.getPullLoadFooterHeight()
+ this.getPullLoadOpenPageHeight()
+ })
+ .offset({
+ y: this.config.isVertical ? this.getFooterOffsetY() : 0,
+ x: this.config.isVertical ? 0 : this.getFooterOffsetY()
+ })
+ .rotate({
+ angle: this.getAngle(),
+ z: 1,
+ x: 0,
+ y: 0
+ })
+ .width(this.isVerticalLayout() ? "100%" : "auto")
+ .height(this.isVerticalLayout() ? "auto" : "100%")
+
+ // .borderWidth(2)
+ // .borderColor(Color.Black)
+ }
+ }
+
+ private getOffset(event: GestureEvent) {
+ if (this.config.isVertical) {
+ return event.offsetY
+ } else {
+ return event.offsetX
+ }
+ }
+
+ /*下拉刷新*/
+ private actionUpdateForRefresh(event: GestureEvent) {
+ /*如果触发了非释放刷新,需要在up事件中改变状态*/
+ if (this.helper.notReleaseRefresh) {
+ /*非释放刷新过程中,继续下拉,需要保存当前偏移量,(否则保存的是刚触发刷新时的偏移量),等刷新完成后,防止出现headerView闪动情况*/
+ this.helper.preOffset = this.getOffset(event)
+ return
+ }
+ if (this.status == PullStatus.OpenPage) {
+ /*打开其他页面回弹结束前不响应事件*/
+ return;
+ }
+ if (this.config.pullRefreshResistance < 0) {
+ this.config.pullRefreshResistance = 0.5
+ } else if (this.config.pullRefreshResistance > 1) {
+ this.config.pullRefreshResistance = 1
+ }
+ this.currentOffset =
+ this.currentOffset + (this.getOffset(event) - this.helper.preOffset) * this.config.pullRefreshResistance
+ if (this.currentOffset < 0) {
+ this.currentOffset = 0;
+ } else if (this.currentOffset > this.config.pullMaxDistance) {
+ this.currentOffset = this.config.pullMaxDistance
+ }
+ this.changeStatus(this.currentOffset)
+ /*状态监听*/
+ this.pullDownListener({
+ isPullDown: this.currentOffset > this.helper.totalOffset,
+ isPullUp: this.currentOffsetLoad < this.helper.totalOffsetLoad,
+ isTouch: true,
+ distance: this.currentOffset,
+ distanceLoad: this.currentOffsetLoad,
+ headerViewSize: this.helper.headerSize,
+ footerViewSize: this.helper.footerSize,
+ status: this.status
+ }, 6)
+ this.helper.totalOffset = this.currentOffset
+ this.helper.preOffset = this.getOffset(event)
+ /*是否是下拉不用up事件触发刷新*/
+ if (!this.config.releaseRefresh) {
+ if (this.currentOffset >= this.getPullRefreshHeaderHeight() && this.status != PullStatus.Refresh &&
+ this.status != PullStatus.RefreshSuccess && this.status != PullStatus.RefreshError) {
+ this.helper.notReleaseRefresh = true
+ //自动触发刷新,改变动画暂停状态
+ this.animPause = false
+ this.releaseActionByRefresh()
+ return
+ }
+ }
+ }
+
+ /*加载更多*/
+ private actionUpdateForLoad(event: GestureEvent) {
+ /*如果触发了非释放加载,需要在up事件中改变状态*/
+ if (this.helper.notReleaseLoad) {
+ /*非释放加载过程中,继续上拉,需要保存当前偏移量,(否则保存的是刚触发加载时的偏移量),等加载完成后,防止出现footerView闪动情况*/
+ this.helper.preOffset = this.getOffset(event)
+ return
+ }
+ if (this.status == PullStatus.LoadOpenPage) {
+ /*打开其他页面回弹结束前不响应事件*/
+ return;
+ }
+ if (this.config.pullLoadResistance < 0) {
+ this.config.pullLoadResistance = 0.5
+ } else if (this.config.pullLoadResistance > 1) {
+ this.config.pullLoadResistance = 1
+ }
+ this.currentOffsetLoad =
+ this.currentOffsetLoad + (this.getOffset(event) - this.helper.preOffset) * this.config.pullLoadResistance
+ if (this.currentOffsetLoad > 0) {
+ this.currentOffsetLoad = 0;
+ } else if (-this.currentOffsetLoad > this.config.pullLoadMaxDistance) {
+ this.currentOffsetLoad = -this.config.pullLoadMaxDistance
+ }
+ this.changeStatusLoad(this.currentOffsetLoad)
+ /*状态监听*/
+ this.pullDownListener({
+ isPullDown: this.currentOffset > this.helper.totalOffset,
+ isPullUp: this.currentOffsetLoad < this.helper.totalOffsetLoad,
+ isTouch: true,
+ distance: this.currentOffset,
+ distanceLoad: this.currentOffsetLoad,
+ headerViewSize: this.helper.headerSize,
+ footerViewSize: this.helper.footerSize,
+ status: this.status
+ }, 7)
+ this.helper.totalOffsetLoad = this.currentOffsetLoad
+ this.helper.preOffset = this.getOffset(event)
+
+ if (this.hasMore) {
+ /*如果有更多数据*/
+ /*是否是下拉不用up事件触发刷新*/
+ if (!this.config.releaseLoad) {
+ if (-this.currentOffsetLoad >= this.getPullLoadFooterHeight() && this.status != PullStatus.Load &&
+ this.status != PullStatus.LoadSuccess && this.status != PullStatus.LoadError) {
+ this.helper.notReleaseLoad = true
+ //自动触发刷新,改变动画暂停状态
+ this.animLoadPause = false
+ this.releaseActionByLoad()
+ return
+ }
+ }
+ }
+ }
+
+ private pullDownListener(pullDown: PullDown, flag: number = 0) {
+ /*状态监听*/
+ this.onPullListener?.(pullDown)
+ // this.log(flag + "===pullDownListener===" + pullDown.isPullDown)
+ }
+
+ public autoRefresh() {
+ if (this.status == PullStatus.Refresh || this.status == PullStatus.RefreshSuccess ||
+ this.status == PullStatus.RefreshError || this.status == PullStatus.OpenPage) {
+ return
+ }
+ if (this.status == PullStatus.Load) {
+ return
+ }
+ if (!this.autoRefreshAnim) {
+ this.autoRefreshAnim = animator.create({
+ duration: 200,
+ easing: "fast-out-linear-in",
+ delay: 0,
+ fill: "forwards",
+ direction: "normal",
+ iterations: 1,
+ begin: 0,
+ end: 1
+ })
+ this.autoRefreshAnim.onFrame = (progress: number) => {
+ /*如果触摸视图,也暂停处理*/
+ if (this.animPause) {
+ this.autoRefreshAnim?.cancel()
+ return
+ }
+ this.currentOffset = this.helper.headerSize * progress
+ this.helper.totalOffset = this.currentOffset
+ }
+ this.autoRefreshAnim.onFinish = () => {
+ this.releaseActionByRefresh()
+ }
+ }
+ this.status = PullStatus.Refresh
+ this.onRefresh?.()
+ this.autoRefreshAnim.play()
+ /*状态监听*/
+ this.pullDownListener({
+ isPullDown: true,
+ isPullUp: false,
+ isTouch: this.helper.isPressDown,
+ distance: this.helper.headerSize,
+ distanceLoad: this.currentOffsetLoad,
+ headerViewSize: this.helper.headerSize,
+ footerViewSize: this.helper.footerSize,
+ status: this.status
+ }, 8)
+ }
+
+ public autoLoad() {
+ if (this.status == PullStatus.Load || this.status == PullStatus.LoadSuccess ||
+ this.status == PullStatus.LoadError || this.status == PullStatus.LoadOpenPage) {
+ return
+ }
+ if (this.status == PullStatus.Refresh) {
+ return
+ }
+ if (!this.autoLoadAnim) {
+ this.autoLoadAnim = animator.create({
+ duration: 200,
+ easing: "fast-out-linear-in",
+ delay: 0,
+ fill: "forwards",
+ direction: "normal",
+ iterations: 1,
+ begin: 0,
+ end: 1
+ })
+ this.autoLoadAnim.onFrame = (progress: number) => {
+ /*如果触摸视图,也暂停处理*/
+ if (this.animLoadPause) {
+ this.autoLoadAnim?.cancel()
+ return
+ }
+ this.currentOffsetLoad = -this.helper.footerSize * progress
+ this.helper.totalOffsetLoad = this.currentOffsetLoad
+ }
+ this.autoLoadAnim.onFinish = () => {
+ this.releaseActionByLoad()
+ }
+ }
+ this.status = PullStatus.Load
+ this.onLoad?.()
+ this.autoLoadAnim.play()
+ /*状态监听*/
+ this.pullDownListener({
+ isPullDown: false,
+ isPullUp: true,
+ isTouch: this.helper.isPressDown,
+ distance: this.currentOffset,
+ distanceLoad: -this.helper.footerSize,
+ headerViewSize: this.helper.headerSize,
+ footerViewSize: this.helper.footerSize,
+ status: this.status
+ }, 82)
+ }
+
+ /*刷新成功*/
+ public refreshSuccess(ignoreViewTips?: boolean) {
+ /*如果上一个状态不是刷新中,直接返回*/
+ if (this.status != PullStatus.Refresh && this.viewSuccessState == 1) {
+ /*如果显示其他异常页面,则刷新成功不需要校验刷新中的状态*/
+ return
+ }
+ /*刷新成功后,可能存在暂无更多,这里不改成true*/
+ // this.hasMore=true
+ if (!ignoreViewTips) {
+ this.status = PullStatus.RefreshSuccess
+ }
+ this.setViewLoadSuccessState()
+ this.refreshEnd();
+ }
+
+ /*刷新失败*/
+ public refreshError() {
+ /*如果上一个状态不是刷新中,直接返回*/
+ if (this.status != PullStatus.Refresh) {
+ return
+ }
+ this.status = PullStatus.RefreshError
+ this.refreshEnd();
+ }
+
+ /*完成刷新*/
+ public refreshComplete(refreshSuccess: boolean, ignoreViewTips?: boolean) {
+ if (refreshSuccess) {
+ this.refreshSuccess(ignoreViewTips);
+ } else {
+ this.refreshError();
+ }
+ }
+
+ /*刷新取消*/
+ public refreshCancel() {
+ /*如果上一个状态不是刷新中,直接返回*/
+ if (this.status != PullStatus.Refresh) {
+ return
+ }
+ this.refreshEnd();
+ }
+
+ private refreshEnd() {
+ /*状态监听*/
+ this.pullDownListener({
+ isPullDown: false || (this.helper.isPressDown && this.helper.preOffset < this.currentOffset),
+ isPullUp: false || (this.helper.isPressDown && this.helper.preOffset > this.currentOffsetLoad),
+ isTouch: this.helper.isPressDown,
+ distance: this.currentOffset,
+ distanceLoad: this.currentOffsetLoad,
+ headerViewSize: this.helper.headerSize,
+ footerViewSize: this.helper.footerSize,
+ status: this.status
+ }, 9)
+
+ /*如果不需要释放刷新情况下,不需要考虑是否是触摸状态*/
+ if (!this.helper.notReleaseRefresh) {
+ /*如果是touch状态,则返回不做回弹操作*/
+ if (this.helper.isPressDown || this.currentOffset <= 0) {
+ //超过显示刷新结果的时间之后改变状态
+ this.setDefAfterRefreshEnd(this.config.refreshResultDurationTime)
+ return
+ }
+ }
+ /*设置刷新结果之后准备回弹*/
+ if (this.currentOffset > this.helper.headerSize) {
+ /*如果回弹至header高度前就刷新成功了,则先回弹至header高度,再回弹至0高度*/
+ // 这里会直接回弹至header高度后再回弹至0高度,所以不需要暂停动画再回弹
+ this.pauseAnim()
+ this.releaseActionByRefresh();
+ // (动画执行完可能存在误差(精度问题),导致return不会执行hiddenFooterAnim,最终还是不注释)
+ return
+ }
+ /*如果偏移量距离小于等于header高度,直接回弹至0高度*/
+ this.hiddenHeaderAnim()
+ }
+
+ /*加载成功*/
+ public loadSuccess(hasMore: boolean = true) {
+ /*如果上一个状态不是加载中,直接返回*/
+ if (this.status != PullStatus.Load) {
+ return
+ }
+ this.hasMore = hasMore
+ this.status = PullStatus.LoadSuccess
+ this.loadEnd();
+ }
+
+ /*加载失败*/
+ public loadError() {
+ /*如果上一个状态不是加载中,直接返回*/
+ if (this.status != PullStatus.Load) {
+ return
+ }
+ this.status = PullStatus.LoadError
+ this.loadEnd();
+ }
+
+ /*加载完成*/
+ public loadComplete(loadSuccess: boolean, hasMore: boolean = true) {
+ if (loadSuccess) {
+ this.loadSuccess(hasMore);
+ } else {
+ this.loadError();
+ }
+ }
+
+ /*加载取消*/
+ public loadCancel() {
+ /*如果上一个状态不是加载中,直接返回*/
+ if (this.status != PullStatus.Load) {
+ return
+ }
+ this.loadEnd();
+ }
+
+ private loadEnd() {
+ /*状态监听*/
+ this.pullDownListener({
+ isPullDown: false || (this.helper.isPressDown && this.helper.preOffset < this.currentOffset),
+ isPullUp: false || (this.helper.isPressDown && this.helper.preOffset > this.currentOffsetLoad),
+ isTouch: this.helper.isPressDown,
+ distance: this.currentOffset,
+ distanceLoad: this.currentOffsetLoad,
+ headerViewSize: this.helper.headerSize,
+ footerViewSize: this.helper.footerSize,
+ status: this.status
+ }, 9)
+
+ /*如果不需要释放加载情况下,不需要考虑是否是触摸状态*/
+ if (!this.helper.notReleaseLoad) {
+ /*如果是touch状态,则返回不做回弹操作*/
+ if (this.helper.isPressDown || this.currentOffsetLoad >= 0) {
+ //超过显示刷新结果的时间之后改变状态
+ this.setDefAfterLoadEnd(this.config.loadResultDurationTime)
+ return
+ }
+ }
+
+ /*设置刷新结果之后准备回弹*/
+ if (-this.currentOffsetLoad - this.helper.footerSize > 0.5) {
+ /*如果回弹至header高度前就刷新成功了,则先回弹至footer高度,再回弹至0高度*/
+ // 这里会直接回弹至footer高度后再回弹至0高度,所以不需要暂停动画再回弹
+ this.pauseAnimLoad()
+ this.releaseActionByLoad();
+ // (动画执行完可能存在误差(精度问题),导致return不会执行hiddenFooterAnim,最终还是不注释)
+ return
+ }
+ /*如果偏移量距离小于等于footer高度,直接回弹至0高度*/
+ this.hiddenFooterAnim()
+ }
+
+ /*设置下拉刷新默认状态*/
+ private setDefStatus() {
+ // this.arrowRotate = 0
+ this.status = PullStatus.DEF
+ this.changeStatus(this.currentOffset)
+ }
+
+ /*设置上拉加载默认状态*/
+ private setDefStatusLoad() {
+ // this.arrowRotate = 0
+ this.status = PullStatus.DEF
+ this.changeStatusLoad(this.currentOffsetLoad)
+ }
+
+ /*刷新完成之后改变状态,1:刷新时触摸,2:刷新完成时再触摸*/
+ private setDefAfterRefreshEnd(durationTime: number) {
+ if (durationTime <= 0) {
+ durationTime = 200
+ }
+ if (this.setDefStatusId != 0) {
+ // this.log("=====setDefStatusId==防止重复调用===" + this.setDefStatusId)
+ //防止重复调用
+ return
+ }
+ this.setDefStatusId = setTimeout(() => {
+ this.setDefStatusId = 0
+ this.setDefStatus()
+ /*刷新中,手动把header视图上拉至0*/
+ this.pullDownListener({
+ isPullDown: false || (this.helper.isPressDown && this.helper.preOffset < this.currentOffset),
+ isPullUp: false || (this.helper.isPressDown && this.helper.preOffset > this.currentOffsetLoad),
+ isTouch: this.helper.isPressDown,
+ distance: this.currentOffset,
+ distanceLoad: this.currentOffsetLoad,
+ headerViewSize: this.helper.headerSize,
+ footerViewSize: this.helper.footerSize,
+ status: this.status
+ }, 10)
+ }, durationTime)
+ // this.log("=====setDefStatusId=====" + this.setDefStatusId)
+ }
+
+ /*加载完成之后改变状态,1:加载时触摸,2:加载完成时再触摸*/
+ private setDefAfterLoadEnd(durationTime: number) {
+ if (durationTime <= 0) {
+ durationTime = 200
+ }
+ if (this.setDefLoadStatusId != 0) {
+ // this.log("=====setDefLoadStatusId==防止重复调用===" + this.setDefLoadStatusId)
+ //防止重复调用
+ return
+ }
+ this.setDefLoadStatusId = setTimeout(() => {
+ this.setDefLoadStatusId = 0
+ this.setDefStatusLoad()
+ /*加载中,手动把footer视图下拉至0*/
+ this.pullDownListener({
+ isPullDown: false || (this.helper.isPressDown && this.helper.preOffset < this.currentOffset),
+ isPullUp: false || (this.helper.isPressDown && this.helper.preOffset > this.currentOffsetLoad),
+ isTouch: this.helper.isPressDown,
+ distance: this.currentOffset,
+ distanceLoad: this.currentOffsetLoad,
+ headerViewSize: this.helper.headerSize,
+ footerViewSize: this.helper.footerSize,
+ status: this.status
+ }, 10)
+ }, durationTime)
+ // this.log("=====setDefLoadStatusId=====" + this.setDefLoadStatusId)
+ }
+
+ /*隐藏header回弹动画*/
+ private hiddenHeaderAnim() {
+ /*如果不需要显示刷新成功或者失败视图*/
+ let timeDuration = 0
+ if ((this.config.refreshShowSuccess && this.status == PullStatus.RefreshSuccess) ||
+ (this.config.refreshShowError && this.status == PullStatus.RefreshError)) {
+ timeDuration = this.config.refreshResultDurationTime
+ }
+ setTimeout(() => {
+ /*如果不需要释放刷新情况下,不需要考虑是否是触摸状态*/
+ if (!this.helper.notReleaseRefresh) {
+ //显示刷新完成的结果之后,准备回弹至0时,正好触摸
+ if (this.helper.isPressDown || this.currentOffset <= 0) {
+ this.setDefStatus()
+ return
+ }
+ }
+
+ //不管动画执行完毕还是动画被触摸时暂停,都需要重置状态(隐藏header之后)
+ this.setDefAfterRefreshEnd(this.config.durationCloseHeader)
+
+
+ this.pauseAnim()
+ this.anim?.reset({
+ duration: this.config.durationCloseHeader * (this.currentOffset / this.helper.headerSize),
+ easing: "fast-out-linear-in",
+ delay: 0,
+ fill: "forwards",
+ direction: "normal",
+ iterations: 1,
+ begin: 0,
+ end: 1
+ })
+ this.animPause = false
+ this.anim?.play()
+ }, timeDuration)
+ }
+
+ /*隐藏footer回弹动画*/
+ private hiddenFooterAnim() {
+ this.initLoadAnim()
+ /*如果不需要显示加载成功或者失败视图*/
+ let timeDuration = 0
+ if ((this.config.loadShowSuccess && this.status == PullStatus.LoadSuccess) ||
+ (this.config.loadShowError && this.status == PullStatus.LoadError)) {
+ timeDuration = this.config.loadResultDurationTime
+ }
+ setTimeout(() => {
+ /*如果不需要释放加载情况下,不需要考虑是否是触摸状态*/
+ if (!this.helper.notReleaseLoad) {
+ //显示刷新完成的结果之后,准备回弹至0时,正好触摸
+ if (this.helper.isPressDown || this.currentOffsetLoad >= 0) {
+ this.setDefStatusLoad()
+ return
+ }
+ }
+
+ //不管动画执行完毕还是动画被触摸时暂停,都需要重置状态(隐藏header之后)
+ this.setDefAfterLoadEnd(this.config.durationCloseFooter)
+
+
+ this.pauseAnimLoad()
+ this.animLoad?.reset({
+ duration: this.config.durationCloseFooter * (-this.currentOffsetLoad / this.helper.footerSize),
+ easing: "fast-out-linear-in",
+ delay: 0,
+ fill: "forwards",
+ direction: "normal",
+ iterations: 1,
+ begin: 0,
+ end: 1
+ })
+ this.animLoadPause = false
+ this.animLoad?.play()
+ }, timeDuration)
+ }
+
+ private changeStatus(currentOffsetY: number) {
+ /*如果是刷新中,刷新完成,刷新失败,下拉不改变状态*/
+ if (this.status == PullStatus.Refresh || this.status == PullStatus.RefreshSuccess ||
+ this.status == PullStatus.RefreshError) {
+ return
+ }
+ /*根据下拉距离改变状态*/
+ if (this.config.pullRefreshOpenPageEnable && currentOffsetY >= this.getPullOpenPageHeight()) {
+ /*满足打开其他页面条件*/
+ this.status = PullStatus.PreOpenPage
+ } else if (currentOffsetY >= this.getPullRefreshHeaderHeight()) {
+ if (this.status == PullStatus.PullDown) {
+ /*进入释放刷新状态*/
+ /*animateTo({ duration: 150, curve: Curve.Linear }, () => {
+ this.arrowRotate = 180
+ })*/
+ }
+ /*满足释放刷新条件*/
+ this.status = PullStatus.PreRefresh
+ } else if (currentOffsetY > 0) {
+ if (this.status == PullStatus.PreRefresh) {
+ /*进入下拉状态*/
+ /*animateTo({ duration: 10, curve: Curve.Linear }, () => {
+ this.arrowRotate = 0
+ })*/
+ }
+ /*满足下拉条件*/
+ this.status = PullStatus.PullDown
+ } else if (currentOffsetY <= 0) {
+ /*默认状态*/
+ this.status = PullStatus.DEF
+ }
+ }
+
+ private changeStatusLoad(currentOffsetYLoad: number) {
+ /*如果是加载中,加载完成,加载失败,上拉不改变状态*/
+ if (this.status == PullStatus.Load || this.status == PullStatus.LoadSuccess ||
+ this.status == PullStatus.LoadError) {
+ return
+ }
+ /*根据下拉距离改变状态*/
+ if (this.config.pullLoadOpenPageEnable && -currentOffsetYLoad >= this.getPullLoadOpenPageHeight()) {
+ /*满足打开其他页面条件*/
+ this.status = PullStatus.PreLoadOpenPage
+ } else if (-currentOffsetYLoad >= this.getPullLoadFooterHeight()) {
+ /*如果没有更多数据*/
+ if (!this.hasMore) {
+ this.status = PullStatus.PullUp
+ return
+ }
+ if (this.status == PullStatus.PullUp) {
+ /*进入释放加载状态*/
+ /*animateTo({ duration: 150, curve: Curve.Linear }, () => {
+ this.arrowRotate = 180
+ })*/
+ }
+ /*满足释放加载条件*/
+ this.status = PullStatus.PreLoad
+ } else if (-currentOffsetYLoad > 0) {
+ if (this.status == PullStatus.PreLoad) {
+ /*进入上拉状态*/
+ /*animateTo({ duration: 10, curve: Curve.Linear }, () => {
+ this.arrowRotate = 0
+ })*/
+ }
+ /*满足上拉条件*/
+ this.status = PullStatus.PullUp
+ } else if (currentOffsetYLoad <= 0) {
+ /*默认状态*/
+ this.status = PullStatus.DEF
+ }
+ }
+
+ /*下拉刷新*/
+ private pauseAnim() {
+ //回弹过程中暂停动画
+ this.animPause = true
+ this.anim?.cancel()
+ }
+
+ /*上拉加载*/
+ private pauseAnimLoad() {
+ //回弹过程中暂停动画
+ this.animLoadPause = true
+ this.initLoadAnim()
+ this.animLoad?.cancel()
+ }
+
+ private releaseActionByRefresh() {
+ this.helper.totalOffset = this.currentOffset
+ if (this.currentOffset <= 0) {
+ // this.arrowRotate = 0
+ this.changeStatus(this.currentOffset)
+ return
+ }
+
+ if (this.status == PullStatus.PreRefresh) {
+ /*释放刷新*/
+ this.status = PullStatus.Refresh
+ // this.startRefresh()
+ /*触发刷新操作*/
+ this.onRefresh?.()
+ } else if (this.status == PullStatus.PreOpenPage) {
+ /*打开其他页面*/
+ this.status = PullStatus.OpenPage
+ this.onOpenPage?.()
+ }
+
+ let endFraction: number = 1;
+ /*如果刷新需要保持header视图*/
+ if (this.config.refreshKeepHeader) {
+ if (this.status == PullStatus.Refresh || this.status == PullStatus.RefreshSuccess ||
+ this.status == PullStatus.RefreshError) {
+ /*如果此时下拉距离大于header高度,则回弹至header高度,否则回弹到0*/
+ if (this.currentOffset >= this.helper.headerSize) {
+ endFraction = (this.currentOffset - this.helper.headerSize) / this.currentOffset
+ }
+ }
+ }
+
+ let durationTime = this.config.durationToHeader
+ /*如果回弹至header高度过程中刷新完成了,再次回弹时不用执行相同时间*/
+ if (this.animProgress != 1) {
+ durationTime = durationTime * (1 - this.animProgress)
+ }
+ if (this.status == PullStatus.OpenPage) {
+ durationTime = this.config.durationCloseForOpenPage
+ }
+
+ this.animPause = false
+ this.anim?.reset({
+ duration: durationTime,
+ easing: "fast-out-linear-in",
+ delay: 0,
+ fill: "forwards",
+ direction: "normal",
+ iterations: 1,
+ begin: 0,
+ end: endFraction
+ })
+ this.anim?.play()
+ }
+
+ /*是否处于加载相关的状态*/
+ private isLoadStatus(): boolean {
+ if (this.status == PullStatus.PullUp || this.status == PullStatus.PreLoad || this.status == PullStatus.Load
+ || this.status == PullStatus.PreLoadOpenPage || this.status == PullStatus.LoadOpenPage ||
+ this.status == PullStatus.LoadSuccess || this.status == PullStatus.LoadError) {
+ return true
+ }
+ return false
+ }
+
+ private releaseActionByLoad() {
+ this.helper.totalOffsetLoad = this.currentOffsetLoad
+ if (this.currentOffsetLoad >= 0) {
+ // this.arrowRotate = 0
+ this.changeStatusLoad(this.currentOffsetLoad)
+ return
+ }
+
+ if (this.status == PullStatus.PreLoad) {
+ /*释放加载*/
+ this.status = PullStatus.Load
+ /*触发加载操作*/
+ this.onLoad?.()
+ } else if (this.status == PullStatus.PreLoadOpenPage) {
+ /*打开其他页面*/
+ this.status = PullStatus.LoadOpenPage
+ this.onLoadOpenPage?.()
+ }
+
+ let endFraction: number = 1;
+ /*如果刷新需要保持header视图*/
+ if (this.config.loadKeepFooter) {
+ if (this.status == PullStatus.Load || this.status == PullStatus.LoadSuccess ||
+ this.status == PullStatus.LoadError) {
+ /*如果此时下拉距离大于header高度,则回弹至header高度,否则回弹到0*/
+ if (-this.currentOffsetLoad >= this.helper.footerSize) {
+ endFraction = (-this.currentOffsetLoad - this.helper.footerSize) / -this.currentOffsetLoad
+ }
+ }
+ }
+
+ let durationTime = this.config.durationToFooter
+ /*如果回弹至footer高度过程中刷新完成了,再次回弹时不用执行相同时间*/
+ if (this.animProgressLoad != 1) {
+ durationTime = durationTime * (1 - this.animProgressLoad)
+ }
+ if (this.status == PullStatus.LoadOpenPage) {
+ durationTime = this.config.durationCloseLoadForOpenPage
+ }
+ this.animLoadPause = false
+ this.initLoadAnim()
+ this.animLoad?.reset({
+ duration: durationTime,
+ easing: "fast-out-linear-in",
+ delay: 0,
+ fill: "forwards",
+ direction: "normal",
+ iterations: 1,
+ begin: 0,
+ end: endFraction
+ })
+ this.animLoad?.play()
+ }
+}
+
diff --git a/RefreshLib/src/main/ets/RefreshLayoutConfig.ets b/RefreshLib/src/main/ets/RefreshLayoutConfig.ets
new file mode 100644
index 0000000..b5201f5
--- /dev/null
+++ b/RefreshLib/src/main/ets/RefreshLayoutConfig.ets
@@ -0,0 +1,68 @@
+export class RefreshLayoutConfig {
+ /*是否是垂直列表*/
+ public isVertical: boolean = true
+ /*横向模式0:正常横向布局,1:header和footer逆时针旋转90度,2:header和footer顺时针旋转90度*/
+ public horizontalMode: number = 0
+ /*是否打开刷新*/
+ public pullRefreshEnable: boolean = true
+ /*是否打开加载更多*/
+ public pullLoadEnable: boolean = true
+ /*******************************刷新配置*******************************************/
+ /*true:释放刷新,false下拉到一定距离刷新*/
+ public releaseRefresh: boolean = true
+ /*下拉最大距离*/
+ public pullMaxDistance: number = 500
+ /*下拉阻力*/
+ public pullRefreshResistance: number = 0.5
+ /*下拉距离超过多少时达到刷新条件*/
+ public pullHeaderHeightRefresh: number = 0
+ /*下拉打开其他页面开关*/
+ public pullRefreshOpenPageEnable: boolean = false
+ /*下拉距离超过多少时达到打开其他页面条件*/
+ public pullHeaderHeightOpenPage: number = 0
+ /*释放刷新时,回弹至headerView高度的时间*/
+ public durationToHeader: number = 250
+ /*头布局刷新结束时回弹的时间*/
+ public durationCloseHeader: number = 200
+ /*需要打开其他页面时,头布局的回弹时间*/
+ public durationCloseForOpenPage: number = 180
+ /*刷新时是否显示头view*/
+ public refreshKeepHeader: boolean = true
+ /*是否显示刷新成功状态的view*/
+ public refreshShowSuccess: boolean = true;
+ /*是否显示刷新失败状态的view*/
+ public refreshShowError: boolean = true;
+ /*刷新结果view显示持续时间*/
+ public refreshResultDurationTime: number = 600
+
+
+
+ /*******************************加载更多配置*******************************************/
+
+ /*true:释放加载,false下拉到一定距离加载*/
+ public releaseLoad: boolean = true
+ /*上拉最大距离*/
+ public pullLoadMaxDistance: number = 500
+ /*上拉阻力*/
+ public pullLoadResistance: number = 0.5
+ /*上拉距离超过多少时达到刷新条件*/
+ public pullFooterHeightLoad: number = 0
+ /*上拉打开其他页面开关*/
+ public pullLoadOpenPageEnable: boolean = false
+ /*上拉距离超过多少时达到打开其他页面条件*/
+ public pullFooterHeightOpenPage: number = 0
+ /*释放刷新时,回弹至footerView高度的时间*/
+ public durationToFooter: number = 250
+ /*footer布局刷新结束时回弹的时间*/
+ public durationCloseFooter: number = 200
+ /*需要打开其他页面时,头布局的回弹时间*/
+ public durationCloseLoadForOpenPage: number = 180
+ /*刷新时是否显示footerView*/
+ public loadKeepFooter: boolean = true
+ /*是否显示刷新成功状态的view*/
+ public loadShowSuccess: boolean = true;
+ /*是否显示刷新失败状态的view*/
+ public loadShowError: boolean = true;
+ /*刷新结果view显示持续时间*/
+ public loadResultDurationTime: number = 600
+}
\ No newline at end of file
diff --git a/RefreshLib/src/main/ets/RefreshLayoutHelper.ets b/RefreshLib/src/main/ets/RefreshLayoutHelper.ets
new file mode 100644
index 0000000..a98d5e3
--- /dev/null
+++ b/RefreshLib/src/main/ets/RefreshLayoutHelper.ets
@@ -0,0 +1,67 @@
+import { AnimatorOptions } from '@kit.ArkUI';
+
+@Builder
+export function _headerView() {
+ Text("headerView:()=>{your @Builder View}")
+}
+
+@Builder
+export function _ContentView() {
+ Text("contentView:()=>{your @Builder View}")
+}
+
+@Builder
+export function _loadMoreView() {
+ Text("loadView:()=>{your @Builder View}")
+}
+
+@Builder
+export function _noMoreView() {
+ Text("noMoreView:()=>{your @Builder View}")
+}
+
+export class RefreshLayoutHelper {
+ private log(str: string, tag: string = "RefreshLayout") {
+ console.debug(tag + "==" + str)
+ }
+
+ /*记录列表滑动到底部的偏移量*/
+ public scrollerOffset: number = 0;
+ /*下拉刷新*/
+ public preOffset: number = 0;
+ public totalOffset: number = 0;
+ /*上拉加载*/
+ // public preOffsetLoad: number = 0;
+ public totalOffsetLoad: number = 0;
+ public headerSize: number = 0;
+ public footerSize: number = 0;
+ public isPressDown: boolean = false
+ public options: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Down | PanDirection.Up })
+ /*不是释放刷新*/
+ public notReleaseRefresh: boolean = false
+ /*不是释放加载*/
+ public notReleaseLoad: boolean = false
+ public animOptions: AnimatorOptions = {
+ duration: 250,
+ easing: "fast-out-linear-in",
+ delay: 0,
+ fill: "forwards",
+ direction: "normal",
+ iterations: 1,
+ begin: 0,
+ end: 1
+ }
+ public animLoadOptions: AnimatorOptions = {
+ duration: 250,
+ easing: "fast-out-linear-in",
+ delay: 0,
+ fill: "forwards",
+ direction: "normal",
+ iterations: 1,
+ begin: 0,
+ end: 1
+ }
+ /*webview滑动偏移量*/
+ public webViewXOffset: number = 0
+ public webViewYOffset: number = 0
+}
\ No newline at end of file
diff --git a/RefreshLib/src/main/module.json5 b/RefreshLib/src/main/module.json5
new file mode 100644
index 0000000..17b498c
--- /dev/null
+++ b/RefreshLib/src/main/module.json5
@@ -0,0 +1,11 @@
+{
+ "module": {
+ "name": "refreshlib",
+ "type": "har",
+ "deviceTypes": [
+ "default",
+ "tablet",
+ "2in1"
+ ]
+ }
+}
diff --git a/RefreshLib/src/main/resources/base/element/string.json b/RefreshLib/src/main/resources/base/element/string.json
new file mode 100644
index 0000000..eb9ae82
--- /dev/null
+++ b/RefreshLib/src/main/resources/base/element/string.json
@@ -0,0 +1,72 @@
+{
+ "string": [
+ {
+ "name": "zr_pull_down_to_refresh",
+ "value": "下拉刷新"
+ },
+ {
+ "name": "zr_release_to_refresh",
+ "value": "释放立即刷新"
+ },
+ {
+ "name": "zr_release_to_open_age",
+ "value": "释放打开其他页面"
+ },
+ {
+ "name": "zr_refreshing",
+ "value": "正在刷新..."
+ },
+ {
+ "name": "zr_refresh_success",
+ "value": "刷新完成"
+ },
+ {
+ "name": "zr_refresh_error",
+ "value": "刷新失败"
+ },
+ {
+ "name": "zr_last_update",
+ "value": "上次更新:"
+ },
+ {
+ "name": "zr_seconds_ago",
+ "value": " 秒之前"
+ },
+ {
+ "name": "zr_minutes_ago",
+ "value": " 分钟之前"
+ },
+ {
+ "name": "zr_hours_ago",
+ "value": " 小时之前"
+ },
+ {
+ "name": "zr_days_ago",
+ "value": " 天之前"
+ },
+ {
+ "name": "zr_pull_up_to_load",
+ "value": "上拉加载"
+ },
+ {
+ "name": "zr_release_to_load",
+ "value": "释放立即加载"
+ },
+ {
+ "name": "zr_loading",
+ "value": "正在加载..."
+ },
+ {
+ "name": "zr_load_success",
+ "value": "加载完成"
+ },
+ {
+ "name": "zr_load_error",
+ "value": "加载失败"
+ },
+ {
+ "name": "zr_load_no_more",
+ "value": "暂无更多"
+ }
+ ]
+}
diff --git a/RefreshLib/src/main/resources/base/element/string_h.json b/RefreshLib/src/main/resources/base/element/string_h.json
new file mode 100644
index 0000000..58f1dd5
--- /dev/null
+++ b/RefreshLib/src/main/resources/base/element/string_h.json
@@ -0,0 +1,72 @@
+{
+ "string": [
+ {
+ "name": "h_zr_pull_down_to_refresh",
+ "value": "右\n拉\n刷\n新"
+ },
+ {
+ "name": "h_zr_release_to_refresh",
+ "value": "释\n放\n立\n即\n刷\n新"
+ },
+ {
+ "name": "h_zr_release_to_open_age",
+ "value": "释\n放\n打\n开\n其\n他\n页\n面"
+ },
+ {
+ "name": "h_zr_refreshing",
+ "value": "正\n在\n刷\n新\n..."
+ },
+ {
+ "name": "h_zr_refresh_success",
+ "value": "刷\n新\n完\n成"
+ },
+ {
+ "name": "h_zr_refresh_error",
+ "value": "刷\n新\n失\n败"
+ },
+ {
+ "name": "h_zr_last_update",
+ "value": "上\n次\n更\n新"
+ },
+ {
+ "name": "h_zr_seconds_ago",
+ "value": "秒\n之\n前"
+ },
+ {
+ "name": "h_zr_minutes_ago",
+ "value": "分\n钟\n之\n前"
+ },
+ {
+ "name": "h_zr_hours_ago",
+ "value": "小\n时\n之\n前"
+ },
+ {
+ "name": "h_zr_days_ago",
+ "value": "天\n之\n前"
+ },
+ {
+ "name": "h_zr_pull_up_to_load",
+ "value": "左\n拉\n加\n载"
+ },
+ {
+ "name": "h_zr_release_to_load",
+ "value": "释\n放\n立\n即\n加\n载"
+ },
+ {
+ "name": "h_zr_loading",
+ "value": "正\n在\n加\n载\n..."
+ },
+ {
+ "name": "h_zr_load_success",
+ "value": "加\n载\n完\n成"
+ },
+ {
+ "name": "h_zr_load_error",
+ "value": "加\n载\n失\n败"
+ },
+ {
+ "name": "h_zr_load_no_more",
+ "value": "暂\n无\n更\n多"
+ }
+ ]
+}
diff --git a/RefreshLib/src/main/resources/base/media/icon_refresh_arrow.svg b/RefreshLib/src/main/resources/base/media/icon_refresh_arrow.svg
new file mode 100644
index 0000000..372b591
--- /dev/null
+++ b/RefreshLib/src/main/resources/base/media/icon_refresh_arrow.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/RefreshLib/src/main/resources/en_US/element/string.json b/RefreshLib/src/main/resources/en_US/element/string.json
new file mode 100644
index 0000000..f51a9c8
--- /dev/null
+++ b/RefreshLib/src/main/resources/en_US/element/string.json
@@ -0,0 +1,8 @@
+{
+ "string": [
+ {
+ "name": "page_show",
+ "value": "page from package"
+ }
+ ]
+}
diff --git a/RefreshLib/src/main/resources/zh_CN/element/string.json b/RefreshLib/src/main/resources/zh_CN/element/string.json
new file mode 100644
index 0000000..f51a9c8
--- /dev/null
+++ b/RefreshLib/src/main/resources/zh_CN/element/string.json
@@ -0,0 +1,8 @@
+{
+ "string": [
+ {
+ "name": "page_show",
+ "value": "page from package"
+ }
+ ]
+}
diff --git a/RefreshLib/src/ohosTest/ets/test/Ability.test.ets b/RefreshLib/src/ohosTest/ets/test/Ability.test.ets
new file mode 100644
index 0000000..85c78f6
--- /dev/null
+++ b/RefreshLib/src/ohosTest/ets/test/Ability.test.ets
@@ -0,0 +1,35 @@
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
+
+export default function abilityTest() {
+ describe('ActsAbilityTest', () => {
+ // 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.
+ hilog.info(0x0000, 'testTag', '%{public}s', 'it begin');
+ let a = 'abc';
+ let b = 'b';
+ // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
+ expect(a).assertContain(b);
+ expect(a).assertEqual(a);
+ })
+ })
+}
\ No newline at end of file
diff --git a/RefreshLib/src/ohosTest/ets/test/List.test.ets b/RefreshLib/src/ohosTest/ets/test/List.test.ets
new file mode 100644
index 0000000..794c7dc
--- /dev/null
+++ b/RefreshLib/src/ohosTest/ets/test/List.test.ets
@@ -0,0 +1,5 @@
+import abilityTest from './Ability.test';
+
+export default function testsuite() {
+ abilityTest();
+}
\ No newline at end of file
diff --git a/RefreshLib/src/ohosTest/module.json5 b/RefreshLib/src/ohosTest/module.json5
new file mode 100644
index 0000000..ed5081e
--- /dev/null
+++ b/RefreshLib/src/ohosTest/module.json5
@@ -0,0 +1,13 @@
+{
+ "module": {
+ "name": "refreshlib",
+ "type": "feature",
+ "deviceTypes": [
+ "default",
+ "tablet",
+ "2in1"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false
+ }
+}
diff --git a/RefreshLib/src/test/List.test.ets b/RefreshLib/src/test/List.test.ets
new file mode 100644
index 0000000..bb5b5c3
--- /dev/null
+++ b/RefreshLib/src/test/List.test.ets
@@ -0,0 +1,5 @@
+import localUnitTest from './LocalUnit.test';
+
+export default function testsuite() {
+ localUnitTest();
+}
\ No newline at end of file
diff --git a/RefreshLib/src/test/LocalUnit.test.ets b/RefreshLib/src/test/LocalUnit.test.ets
new file mode 100644
index 0000000..165fc16
--- /dev/null
+++ b/RefreshLib/src/test/LocalUnit.test.ets
@@ -0,0 +1,33 @@
+import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
+
+export default function localUnitTest() {
+ describe('localUnitTest', () => {
+ // Defines a test suite. Two parameters are supported: test suite name and test suite function.
+ beforeAll(() => {
+ // Presets an action, which is performed only once before all test cases of the test suite start.
+ // This API supports only one parameter: preset action function.
+ });
+ beforeEach(() => {
+ // Presets an action, which is performed before each unit test case starts.
+ // The number of execution times is the same as the number of test cases defined by **it**.
+ // This API supports only one parameter: preset action function.
+ });
+ afterEach(() => {
+ // Presets a clear action, which is performed after each unit test case ends.
+ // The number of execution times is the same as the number of test cases defined by **it**.
+ // This API supports only one parameter: clear action function.
+ });
+ afterAll(() => {
+ // Presets a clear action, which is performed after all test cases of the test suite end.
+ // This API supports only one parameter: clear action function.
+ });
+ it('assertContain', 0, () => {
+ // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
+ let a = 'abc';
+ let b = 'b';
+ // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
+ expect(a).assertContain(b);
+ expect(a).assertEqual(a);
+ });
+ });
+}
\ No newline at end of file
diff --git a/build-profile.json5 b/build-profile.json5
index 6287d99..48391dc 100644
--- a/build-profile.json5
+++ b/build-profile.json5
@@ -77,6 +77,14 @@
"name": "scene_single_video",
"srcPath": "./scene_single_video"
},
+ {
+ "name": "patient",
+ "srcPath": "./features/patient",
+ },
+ {
+ "name": "refreshlib",
+ "srcPath": "./RefreshLib"
+ },
{
"name": "corekit",
"srcPath": "./corekit"
diff --git a/commons/basic/Index.ets b/commons/basic/Index.ets
index bb41880..59bdaab 100644
--- a/commons/basic/Index.ets
+++ b/commons/basic/Index.ets
@@ -26,7 +26,7 @@ export { DataWebModel,DataWebModels } from './src/main/ets/models/DataWebModel'
export { PromptActionClass } from './src/main/ets/components/PromptActionClass'
-export { RequestDefaultModel,ExpertData } from './src/main/ets/models/RequestDefaultModel'
+export { RequestDefaultModel,ExpertData} from './src/main/ets/models/RequestDefaultModel'
export { HdList, HdListController } from './src/main/ets/components/HdList'
@@ -56,8 +56,23 @@ export { huanzheDb } from './src/main/ets/utils/HuanzhelasDbHelper'
export { HdSearchNav } from './src/main/ets/components/HdSearchNav'
+export { HdHomeNav } from './src/main/ets/components/HdHomeNav'
+
export { DefaultHintProWindows } from './src/main/ets/Views/DefaultHintProWindows'
+export { TimestampUtil } from './src/main/ets/utils/TimestampUtil'
+
+export { SignPopWindow } from './src/main/ets/Views/SignPopWindow'
+
+// 患者数据库管理相关导出
+export {
+ patientDbManager,
+ PatientDatabaseManager,
+ PatientEntity,
+ PatientDao,
+ PatientData
+} from './src/main/ets/utils/PatientsEntity'
+
export { PermissionsUtils } from './src/main/ets/utils/PermissionsUtils'
export { calculateExactAge } from './src/main/ets/utils/DateUtils'
diff --git a/commons/basic/src/main/ets/Views/SignPopWindow.ets b/commons/basic/src/main/ets/Views/SignPopWindow.ets
new file mode 100644
index 0000000..b88518d
--- /dev/null
+++ b/commons/basic/src/main/ets/Views/SignPopWindow.ets
@@ -0,0 +1,75 @@
+import { router } from '@kit.ArkUI'
+import { BasicConstant } from '../../../../Index';
+
+@CustomDialog
+export struct SignPopWindow {
+ controller: CustomDialogController;
+
+ @Prop signDay:string = '今天是我们相识的第1天';
+ @Prop signWeek:string = '1';
+ @Prop signMouth:string = '1';
+
+ @Prop signNews:string = '卫健委:鼓励医务人员做兼职!可以做哪些兼职,有哪些法律分险?';
+ @Prop signHtml:string = '';
+
+ build() {
+ Column(){
+ Image($r('app.media.sign_popwindow_close'))
+ .margin({right:10}).width(20).height(70)
+ .onClick(()=>{
+ this.controller.close()
+ })
+ Stack(){
+ Column(){
+ Text('签到成功')
+ .textAlign(TextAlign.Center)
+ .fontSize(21).fontColor(Color.White).backgroundColor('rgb(255, 210,3)').height(50).width('100%')
+ .borderRadius({topLeft:8,topRight:8})
+ Text(this.signDay)
+ .textAlign(TextAlign.Center)
+ .fontSize(16).fontColor(Color.Black).height(18).width('100%').margin({top:15})
+ Row(){
+ Text('本周共签到')
+ .fontSize(14).fontColor('#333333')
+ Text(this.signWeek)
+ .fontSize(20).fontColor(Color.Red)
+ Text('次')
+ .fontSize(14).fontColor('#333333')
+ }.width('100%').justifyContent(FlexAlign.Center).height(20).margin({top:15,bottom:3}).alignItems(VerticalAlign.Bottom)
+ Row(){
+ Text('已连续签到')
+ .fontSize(14).fontColor('#333333')
+ Text(this.signMouth)
+ .fontSize(20).fontColor(Color.Red)
+ Text('天')
+ .fontSize(14).fontColor('#333333')
+ }.width('100%').justifyContent(FlexAlign.Center).height(20).alignItems(VerticalAlign.Bottom)
+ Text('连续签到获得更多积分')
+ .textAlign(TextAlign.Center)
+ .fontSize(14).fontColor(Color.Red).margin({top:15,bottom:10})
+ }.width('90%').backgroundColor(Color.White).borderRadius(8).margin({top:30})
+ Row(){
+ Image($r('app.media.sign_back_leftright_icon')).width(18).height(18)
+ Blank()
+ .width('50%').height(18)
+ Image($r('app.media.sign_back_leftright_icon')).width(18).height(18)
+ }.margin({top:20})
+ Column(){
+ Image($r('app.media.sign_back_news_icon'))
+ .width(36).height(36)
+ Text(this.signNews)
+ .width('100%')
+ .fontSize(16).fontColor(Color.Black)
+ .backgroundColor(Color.White).borderRadius(8).padding({left:10,top:10,right:10,bottom:10}).margin({top:5})
+ }.width('90%').margin({top:215,bottom:15})
+ .onClick(()=>{
+ this.controller.close();
+ router.pushUrl({
+ url: 'pages/WebView/WebPage',
+ params: {'title':this.signNews,'url':BasicConstant.urlHtml+this.signHtml}
+ })
+ })
+ }.width('100%').backgroundColor('rgb(255, 161, 24)').borderRadius(8).alignContent(Alignment.Top)
+ }.width('100%').alignItems(HorizontalAlign.End)
+ }
+}
diff --git a/commons/basic/src/main/ets/components/HdHomeNav.ets b/commons/basic/src/main/ets/components/HdHomeNav.ets
new file mode 100644
index 0000000..c9021be
--- /dev/null
+++ b/commons/basic/src/main/ets/components/HdHomeNav.ets
@@ -0,0 +1,59 @@
+import { ChangeUtil } from "../../../../Index";
+
+@Component
+export struct HdHomeNav {
+ @StorageProp('topHeight')
+ topHeight: number = 0
+ @Prop
+ bgColor: ResourceStr = $r('app.color.top_bg')
+ @Prop
+ hasBorder: boolean = false
+ @Prop
+ leftIcon: ResourceStr = $r('app.media.home_no_qiandao_icon')
+ @Prop
+ isFocus:boolean = false;
+ @Prop
+ placeholder:string = ''
+
+ @State textInputContent:string = ''
+ @Prop alpha: number = 0; // 接收透明度值
+ @Prop backColor: string = ''
+ private leftItemAction: () => void = () => {};
+ // 添加左侧点击函数
+ private searchItemAction:()=> void = () => {};
+
+ build() {
+ Row() {
+ Row() {
+ Image(this.leftIcon)
+ .size({ width: 24, height: 24 })
+ .fillColor($r('app.color.black'))
+ }.size({ width: 40, height: 50 })
+ .onClick(()=>this.leftItemAction())
+
+ Row(){
+ Image($r('app.media.selected_hospital_ws'))
+ .width(20).height(20)
+ .margin({left:10})
+ Text(this.placeholder)
+ .defaultFocus(this.isFocus)
+ .fontSize(15)
+ .fontColor(Color.Gray)
+ .backgroundColor(Color.White)
+ .height('100%').width('calc(100% - 40vp)')
+ .padding({ left: 0 })
+ .margin({left:5})
+ }
+ .backgroundColor(Color.White)
+ .borderWidth(0.5).borderRadius(5).borderColor($r('app.color.common_main_color')).justifyContent(FlexAlign.Start)
+ .width('calc(100% - 40vp)').height(40)
+ .onClick(()=>{
+ this.searchItemAction();
+ })
+ }
+ .padding({ left: 16, right: 16, top: this.topHeight })
+ .height(56 + this.topHeight)
+ .width('100%')
+ .backgroundColor(ChangeUtil.hexToRgba(this.backColor,this.alpha))
+ }
+}
\ No newline at end of file
diff --git a/commons/basic/src/main/ets/components/HdNav.ets b/commons/basic/src/main/ets/components/HdNav.ets
index fc13b31..0823ec1 100644
--- a/commons/basic/src/main/ets/components/HdNav.ets
+++ b/commons/basic/src/main/ets/components/HdNav.ets
@@ -29,13 +29,20 @@ export struct HdNav {
showRightText: boolean = false
@Prop
rightText: string = ''
+ @Prop
+ rightBackColor:ResourceColor = this.bgColor
+ @Prop
+ rightTextColor:ResourceColor = this.textColor
@BuilderParam
titleBuilder: () => void = defaultBuilder
@BuilderParam
menuBuilder: () => void = defaultBuilder
-
+ @Prop
+ isLeftAction: boolean = false
// 添加右侧点击函数
private rightItemAction:()=> void = () => {};
+ // 添加左侧点击函数
+ private leftItemAction:()=> void = () => {};
build() {
Row() {
@@ -44,7 +51,7 @@ export struct HdNav {
{
Image(this.leftIcon)
.size({ width: 24, height: 24 })
- .onClick(() => router.back())
+ .onClick(() => this.isLeftAction? this.leftItemAction():router.back())
.fillColor($r('app.color.black'))
}
.size({ width: 50, height: 50 })
@@ -84,11 +91,16 @@ export struct HdNav {
} else if (this.showRightText) {
Text(this.rightText)
- .fontSize(16)
- .fontColor(this.textColor)
+ .maxFontSize(16)
+ .minFontSize(6)
+ .maxLines(1)
+ .fontColor(this.rightTextColor)
.onClick(()=>this.rightItemAction())
.width(50)
.textAlign(TextAlign.Center)
+ .borderRadius(5)
+ .backgroundColor(this.rightBackColor)
+ .height(35)
// .margin({right:10})
} else {
Blank()
diff --git a/commons/basic/src/main/ets/components/HdSearchNav.ets b/commons/basic/src/main/ets/components/HdSearchNav.ets
index 5197ff0..f2b3808 100644
--- a/commons/basic/src/main/ets/components/HdSearchNav.ets
+++ b/commons/basic/src/main/ets/components/HdSearchNav.ets
@@ -50,7 +50,6 @@ export struct HdSearchNav {
.fontSize(15)
.backgroundColor(this.bgColor)
.height('100%').width('calc(100% - 40vp)')
- .padding({ left: 0 })
.margin({left:5})
.onChange((value:string)=>{
this.textInputContent = value;
diff --git a/commons/basic/src/main/ets/constants/BasicConstant.ets b/commons/basic/src/main/ets/constants/BasicConstant.ets
index becfc16..4a221f9 100644
--- a/commons/basic/src/main/ets/constants/BasicConstant.ets
+++ b/commons/basic/src/main/ets/constants/BasicConstant.ets
@@ -24,6 +24,22 @@ export class BasicConstant {
// static readonly polvId = "21";//保利威视学员id
+ static readonly addBonusPoints = BasicConstant.urlExpertApp+'addBonusPoints'
+ static readonly indexV2 = BasicConstant.urlExpertAPI+'indexV2';//首页轮播
+ static readonly applyList = BasicConstant.urlExpert+'applyList'
+ static readonly groupList = BasicConstant.urlExpertApp+'groupList'
+ static readonly isMaiLanExpert = BasicConstant.urlExpertAPI+'isMaiLanExpert'
+ static readonly patientListByGroup = BasicConstant.urlExpertApp+'patientListByGroup'
+ static readonly deleteGroup = BasicConstant.urlExpertApp+'deleteGroup'
+ static readonly addGroup = BasicConstant.urlExpertApp+'addGroup'
+ static readonly updateGroup = BasicConstant.urlExpertApp+'updateGroup'
+ static readonly relationRecordLately = BasicConstant.urlExpertAPI+'relationRecordLately'
+ static readonly updateNicknameNote = BasicConstant.urlExpertAPI+'updateNicknameNote'
+ static readonly applyListOperate = BasicConstant.urlExpert+'applyListOperate'
+ static readonly patientListNoInThisGroup = BasicConstant.urlExpertAPI+'patientListNoInThisGroup'
+ static readonly patientCard = BasicConstant.urlExpertAPI+'patientCard'
+ static readonly toAddNickname = BasicConstant.urlExpert+'toAddNickname'
+ static readonly patientDetail = BasicConstant.urlExpert+'patientDetail'
static readonly getStartpage=BasicConstant.urlExpertApp + "startpage";
static readonly meetingListV2=BasicConstant.urlExpertAPI + "meetingListV2";
static readonly meetingV2Video=BasicConstant.urlExpertAPI + "meetingV2Video";
@@ -53,7 +69,6 @@ export class BasicConstant {
static readonly listOutPatient = BasicConstant.urlExpertApp+'listOutPatient'//出诊
static readonly urlOutpatientnew = BasicConstant.wxUrl+"wxPatient/index.htm#/outPatient?link=share&expertUuid=";//出诊
static readonly getNewWa="https://wx.igandan.com/shop_notify/";//肝胆商城妞娃购买链接
- static readonly patientDetail= BasicConstant.urlExpert + "patientDetail";// 患者详情
static readonly newConsultList = BasicConstant.urlExpertAPI + "newConsultList";// 新的公益咨询列表
static readonly consultListHis = BasicConstant.urlExpertAPI +"consultListHis";// 公益咨询历史列表
static readonly consultDetail = BasicConstant.urlExpertAPI + "consultDetail";// 公益咨询详情
diff --git a/commons/basic/src/main/ets/models/RequestDefaultModel.ets b/commons/basic/src/main/ets/models/RequestDefaultModel.ets
index 55be9bb..32ebc35 100644
--- a/commons/basic/src/main/ets/models/RequestDefaultModel.ets
+++ b/commons/basic/src/main/ets/models/RequestDefaultModel.ets
@@ -1,4 +1,3 @@
-
export interface RequestDefaultModel{
code:string;
data:DefaulyData[];
diff --git a/commons/basic/src/main/ets/utils/PatientsEntity.ets b/commons/basic/src/main/ets/utils/PatientsEntity.ets
new file mode 100644
index 0000000..655516d
--- /dev/null
+++ b/commons/basic/src/main/ets/utils/PatientsEntity.ets
@@ -0,0 +1,862 @@
+import { Entity, Id, Columns, ColumnType } from '@ohos/dataorm';
+import { authStore } from './auth';
+import { hdHttp, HdResponse } from '../utils/request';
+import { BasicConstant } from '../constants/BasicConstant';
+import { BusinessError } from '@kit.BasicServicesKit';
+import relationalStore from '@ohos.data.relationalStore';
+import common from '@ohos.app.ability.common';
+
+// 患者数据接口
+export interface PatientData {
+ uuid: string;
+ nickname: string;
+ mobile: string;
+ realName: string;
+ nation: string | null;
+ sex: number;
+ type: number;
+ photo: string;
+ expertUuid: string; // 关联的专家UUID
+}
+
+// 服务器响应接口
+interface updateExtraData {
+ expertUuid: string
+}
+
+// 患者实体类
+@Entity('patients')
+export class PatientEntity {
+ @Id()
+ @Columns({ columnName: 'id', types: ColumnType.num })
+ id: number = 0;
+
+ @Columns({ columnName: 'uuid', types: ColumnType.str })
+ uuid: string = '';
+
+ @Columns({ columnName: 'nickname', types: ColumnType.str })
+ nickname: string = '';
+
+ @Columns({ columnName: 'mobile', types: ColumnType.str })
+ mobile: string = '';
+
+ @Columns({ columnName: 'realName', types: ColumnType.str })
+ realName: string = '';
+
+ @Columns({ columnName: 'nation', types: ColumnType.str })
+ nation: string = '';
+
+ @Columns({ columnName: 'sex', types: ColumnType.num })
+ sex: number = 0;
+
+ @Columns({ columnName: 'type', types: ColumnType.num })
+ type: number = 0;
+
+ @Columns({ columnName: 'photo', types: ColumnType.str })
+ photo: string = '';
+
+ @Columns({ columnName: 'expertUuid', types: ColumnType.str })
+ expertUuid: string = '';
+
+ @Columns({ columnName: 'createTime', types: ColumnType.str })
+ createTime: string = '';
+
+ @Columns({ columnName: 'updateTime', types: ColumnType.str })
+ updateTime: string = '';
+
+ constructor(
+ uuid: string = '',
+ nickname: string = '',
+ mobile: string = '',
+ realName: string = '',
+ nation: string = '',
+ sex: number = 0,
+ type: number = 0,
+ photo: string = '',
+ expertUuid: string = ''
+ ) {
+ this.uuid = uuid;
+ this.nickname = nickname;
+ this.mobile = mobile;
+ this.realName = realName;
+ this.nation = nation;
+ this.sex = sex;
+ this.type = type;
+ this.photo = photo;
+ this.expertUuid = expertUuid;
+ this.createTime = new Date().toISOString();
+ this.updateTime = new Date().toISOString();
+ }
+}
+
+// 患者DAO类 - 使用原生 relationalStore
+export class PatientDao {
+ private rdbStore: relationalStore.RdbStore | null = null;
+ private context: common.Context;
+ private isInitialized: boolean = false;
+
+ constructor(context: common.Context) {
+ this.context = context;
+ }
+
+ // 初始化数据库
+ async initDatabase(): Promise {
+ try {
+ console.info('开始初始化患者数据库...');
+
+ const storeConfig: relationalStore.StoreConfig = {
+ name: 'patient_database',
+ securityLevel: relationalStore.SecurityLevel.S1
+ };
+
+ this.rdbStore = await relationalStore.getRdbStore(this.context, storeConfig);
+ console.info('数据库连接创建成功');
+
+ // 创建表
+ await this.createTable();
+ this.isInitialized = true;
+ console.info('患者数据库初始化完成');
+ } catch (error) {
+ console.error('初始化患者数据库失败:', error);
+ this.isInitialized = false;
+ if (error instanceof Error) {
+ throw error;
+ } else {
+ throw new Error('初始化患者数据库失败');
+ }
+ }
+ }
+
+ // 创建患者表
+ private async createTable(): Promise {
+ if (!this.rdbStore) {
+ throw new Error('数据库未初始化');
+ }
+
+ try {
+ const createTableSql = `
+ CREATE TABLE IF NOT EXISTS patients (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ uuid TEXT NOT NULL,
+ nickname TEXT,
+ mobile TEXT,
+ realName TEXT,
+ nation TEXT,
+ sex INTEGER DEFAULT 0,
+ type INTEGER DEFAULT 0,
+ photo TEXT,
+ expertUuid TEXT NOT NULL,
+ createTime TEXT,
+ updateTime TEXT
+ )
+ `;
+
+ await this.rdbStore.executeSql(createTableSql);
+ console.info('患者表创建成功');
+
+ // 验证表是否存在
+ await this.verifyTableExists();
+ } catch (error) {
+ console.error('创建患者表失败:', error);
+ if (error instanceof Error) {
+ throw error;
+ } else {
+ throw new Error('创建患者表失败');
+ }
+ }
+ }
+
+ // 验证表是否存在
+ private async verifyTableExists(): Promise {
+ if (!this.rdbStore) {
+ throw new Error('数据库未初始化');
+ }
+
+ try {
+ const checkTableSql = "SELECT name FROM sqlite_master WHERE type='table' AND name='patients'";
+ const resultSet = await this.rdbStore.querySql(checkTableSql, []);
+
+ if (resultSet.rowCount === 0) {
+ throw new Error('患者表创建失败');
+ }
+
+ resultSet.close();
+ console.info('患者表验证成功');
+ } catch (error) {
+ console.error('验证患者表失败:', error);
+ if (error instanceof Error) {
+ throw error;
+ } else {
+ throw new Error('验证患者表失败');
+ }
+ }
+ }
+
+ // 检查数据库状态
+ private checkDatabaseState(): void {
+ if (!this.rdbStore) {
+ throw new Error('数据库未初始化');
+ }
+ if (!this.isInitialized) {
+ throw new Error('数据库初始化未完成');
+ }
+ }
+
+ // 根据专家UUID查询所有患者
+ async getPatientsByExpertUuid(expertUuid: string): Promise {
+ try {
+ this.checkDatabaseState();
+ console.info(`开始查询专家 ${expertUuid} 的患者数据`);
+
+ if (!this.rdbStore) {
+ throw new Error('数据库连接为空');
+ }
+
+ const sql = 'SELECT * FROM patients WHERE expertUuid = ? ORDER BY createTime DESC';
+ const resultSet = await this.rdbStore.querySql(sql, [expertUuid]);
+
+ const patients: PatientEntity[] = [];
+ if (resultSet.rowCount > 0) {
+ resultSet.goToFirstRow();
+ do {
+ try {
+ const patient = this.parseResultSet(resultSet);
+ patients.push(patient);
+ } catch (parseError) {
+ console.error('解析患者数据失败:', parseError);
+ }
+ } while (resultSet.goToNextRow());
+ }
+ resultSet.close();
+
+ console.info(`成功查询到 ${patients.length} 个患者`);
+ return patients;
+ } catch (error) {
+ console.error('查询患者数据失败:', error);
+ if (error instanceof Error) {
+ throw error;
+ } else {
+ throw new Error('查询患者数据失败');
+ }
+ }
+ }
+
+ // 根据患者UUID查询单个患者
+ async getPatientByUuid(uuid: string): Promise {
+ try {
+ this.checkDatabaseState();
+
+ if (!this.rdbStore) {
+ throw new Error('数据库连接为空');
+ }
+
+ const sql = 'SELECT * FROM patients WHERE uuid = ?';
+ const resultSet = await this.rdbStore.querySql(sql, [uuid]);
+
+ if (resultSet.rowCount > 0) {
+ resultSet.goToFirstRow();
+ const patient = this.parseResultSet(resultSet);
+ resultSet.close();
+ return patient;
+ }
+ resultSet.close();
+ return null;
+ } catch (error) {
+ console.error('根据UUID查询患者失败:', error);
+ if (error instanceof Error) {
+ throw error;
+ } else {
+ throw new Error('根据UUID查询患者失败');
+ }
+ }
+ }
+
+ // 根据手机号查询患者
+ async getPatientByMobile(mobile: string, expertUuid: string): Promise {
+ try {
+ this.checkDatabaseState();
+
+ if (!this.rdbStore) {
+ throw new Error('数据库连接为空');
+ }
+
+ const sql = 'SELECT * FROM patients WHERE mobile = ? AND expertUuid = ?';
+ const resultSet = await this.rdbStore.querySql(sql, [mobile, expertUuid]);
+
+ if (resultSet.rowCount > 0) {
+ resultSet.goToFirstRow();
+ const patient = this.parseResultSet(resultSet);
+ resultSet.close();
+ return patient;
+ }
+ resultSet.close();
+ return null;
+ } catch (error) {
+ console.error('根据手机号查询患者失败:', error);
+ if (error instanceof Error) {
+ throw error;
+ } else {
+ throw new Error('根据手机号查询患者失败');
+ }
+ }
+ }
+
+ // 删除指定专家的所有患者数据
+ async deletePatientsByExpertUuid(expertUuid: string): Promise {
+ try {
+ this.checkDatabaseState();
+ console.info(`开始删除专家 ${expertUuid} 的所有患者数据`);
+
+ if (!this.rdbStore) {
+ throw new Error('数据库连接为空');
+ }
+
+ const sql = 'DELETE FROM patients WHERE expertUuid = ?';
+ await this.rdbStore.executeSql(sql, [expertUuid]);
+ console.info('删除专家患者数据成功');
+ } catch (error) {
+ console.error('删除专家患者数据失败:', error);
+ if (error instanceof Error) {
+ throw error;
+ } else {
+ throw new Error('删除专家患者数据失败');
+ }
+ }
+ }
+
+ // 删除单个患者
+ async deletePatientByUuid(uuid: string): Promise {
+ try {
+ this.checkDatabaseState();
+
+ if (!this.rdbStore) {
+ throw new Error('数据库连接为空');
+ }
+
+ const sql = 'DELETE FROM patients WHERE uuid = ?';
+ await this.rdbStore.executeSql(sql, [uuid]);
+ } catch (error) {
+ console.error('删除患者失败:', error);
+ if (error instanceof Error) {
+ throw error;
+ } else {
+ throw new Error('删除患者失败');
+ }
+ }
+ }
+
+ // 更新患者信息
+ async updatePatient(patient: PatientEntity): Promise {
+ try {
+ this.checkDatabaseState();
+
+ if (!this.rdbStore) {
+ throw new Error('数据库连接为空');
+ }
+
+ patient.updateTime = new Date().toISOString();
+ const sql = `
+ UPDATE patients
+ SET nickname = ?, mobile = ?, realName = ?, nation = ?,
+ sex = ?, type = ?, photo = ?, updateTime = ?
+ WHERE uuid = ?
+ `;
+
+ await this.rdbStore.executeSql(sql, [
+ patient.nickname,
+ patient.mobile,
+ patient.realName,
+ patient.nation,
+ patient.sex,
+ patient.type,
+ patient.photo,
+ patient.updateTime,
+ patient.uuid
+ ]);
+ } catch (error) {
+ console.error('更新患者信息失败:', error);
+ if (error instanceof Error) {
+ throw error;
+ } else {
+ throw new Error('更新患者信息失败');
+ }
+ }
+ }
+
+ // 插入单个患者
+ async insertPatient(patient: PatientEntity): Promise {
+ try {
+ this.checkDatabaseState();
+
+ if (!this.rdbStore) {
+ throw new Error('数据库连接为空');
+ }
+
+ const sql = `
+ INSERT INTO patients (uuid, nickname, mobile, realName, nation, sex, type, photo, expertUuid, createTime, updateTime)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ `;
+
+ await this.rdbStore.executeSql(sql, [
+ patient.uuid,
+ patient.nickname,
+ patient.mobile,
+ patient.realName,
+ patient.nation,
+ patient.sex,
+ patient.type,
+ patient.photo,
+ patient.expertUuid,
+ patient.createTime,
+ patient.updateTime
+ ]);
+ } catch (error) {
+ console.error('插入患者数据失败:', error);
+ if (error instanceof Error) {
+ throw error;
+ } else {
+ throw new Error('插入患者数据失败');
+ }
+ }
+ }
+
+ // 批量插入患者数据
+ async insertPatients(patients: PatientEntity[]): Promise {
+ try {
+ this.checkDatabaseState();
+ console.info(`开始批量插入 ${patients.length} 个患者数据`);
+
+ if (!this.rdbStore) {
+ throw new Error('数据库连接为空');
+ }
+
+ await this.rdbStore.beginTransaction();
+ try {
+ for (const patient of patients) {
+ await this.insertPatient(patient);
+ }
+ await this.rdbStore.commit();
+ console.info('批量插入患者数据成功');
+ } catch (error) {
+ await this.rdbStore.rollBack();
+ console.error('批量插入患者数据失败,已回滚:', error);
+ if (error instanceof Error) {
+ throw error;
+ } else {
+ throw new Error('批量插入患者数据失败');
+ }
+ }
+ } catch (error) {
+ console.error('批量插入患者数据失败:', error);
+ if (error instanceof Error) {
+ throw error;
+ } else {
+ throw new Error('批量插入患者数据失败');
+ }
+ }
+ }
+
+ // 解析查询结果
+ private parseResultSet(resultSet: relationalStore.ResultSet): PatientEntity {
+ try {
+ const uuidIndex = resultSet.getColumnIndex('uuid');
+ const nicknameIndex = resultSet.getColumnIndex('nickname');
+ const mobileIndex = resultSet.getColumnIndex('mobile');
+ const realNameIndex = resultSet.getColumnIndex('realName');
+ const nationIndex = resultSet.getColumnIndex('nation');
+ const sexIndex = resultSet.getColumnIndex('sex');
+ const typeIndex = resultSet.getColumnIndex('type');
+ const photoIndex = resultSet.getColumnIndex('photo');
+ const expertUuidIndex = resultSet.getColumnIndex('expertUuid');
+
+ return new PatientEntity(
+ uuidIndex >= 0 ? (resultSet.getString(uuidIndex) ?? '') : '',
+ nicknameIndex >= 0 ? (resultSet.getString(nicknameIndex) ?? '') : '',
+ mobileIndex >= 0 ? (resultSet.getString(mobileIndex) ?? '') : '',
+ realNameIndex >= 0 ? (resultSet.getString(realNameIndex) ?? '') : '',
+ nationIndex >= 0 ? (resultSet.getString(nationIndex) ?? '') : '',
+ sexIndex >= 0 ? (resultSet.getDouble(sexIndex) ?? 0) : 0,
+ typeIndex >= 0 ? (resultSet.getDouble(typeIndex) ?? 0) : 0,
+ photoIndex >= 0 ? (resultSet.getString(photoIndex) ?? '') : '',
+ expertUuidIndex >= 0 ? (resultSet.getString(expertUuidIndex) ?? '') : ''
+ );
+ } catch (error) {
+ console.error('解析查询结果失败:', error);
+ if (error instanceof Error) {
+ throw error;
+ } else {
+ throw new Error('解析查询结果失败');
+ }
+ }
+ }
+
+ // 关闭数据库连接
+ async closeDatabase(): Promise {
+ try {
+ if (this.rdbStore) {
+ await this.rdbStore.close();
+ this.rdbStore = null;
+ this.isInitialized = false;
+ console.info('数据库连接已关闭');
+ }
+ } catch (error) {
+ console.error('关闭数据库连接失败:', error);
+ }
+ }
+
+ // 检查数据库是否已初始化
+ isDatabaseInitialized(): boolean {
+ return this.isInitialized && this.rdbStore !== null;
+ }
+
+ // 开始事务
+ async beginTransaction(): Promise {
+ this.checkDatabaseState();
+ if (this.rdbStore) {
+ await this.rdbStore.beginTransaction();
+ }
+ }
+
+ // 提交事务
+ async commitTransaction(): Promise {
+ this.checkDatabaseState();
+ if (this.rdbStore) {
+ await this.rdbStore.commit();
+ }
+ }
+
+ // 回滚事务
+ async rollbackTransaction(): Promise {
+ this.checkDatabaseState();
+ if (this.rdbStore) {
+ await this.rdbStore.rollBack();
+ }
+ }
+}
+
+// 数据库管理类
+export class PatientDatabaseManager {
+ private static instance: PatientDatabaseManager;
+ private patientDao: PatientDao | null = null;
+ private context: common.Context | null = null;
+ private isInitialized: boolean = false;
+
+ private constructor() {}
+
+ public static getInstance(): PatientDatabaseManager {
+ if (!PatientDatabaseManager.instance) {
+ PatientDatabaseManager.instance = new PatientDatabaseManager();
+ }
+ return PatientDatabaseManager.instance;
+ }
+
+ // 初始化数据库
+ async initDatabase(context: common.Context): Promise {
+ try {
+ console.info('开始初始化患者数据库管理器...');
+ this.context = context;
+ this.patientDao = new PatientDao(context);
+ await this.patientDao.initDatabase();
+ this.isInitialized = true;
+ console.info('患者数据库管理器初始化成功');
+ } catch (error) {
+ console.error('初始化患者数据库管理器失败:', error);
+ this.isInitialized = false;
+ if (error instanceof Error) {
+ throw error;
+ } else {
+ throw new Error('初始化患者数据库管理器失败');
+ }
+ }
+ }
+
+ // 获取PatientDao实例
+ getPatientDao(): PatientDao {
+ if (!this.patientDao || !this.isInitialized) {
+ throw new Error('数据库未初始化。请先调用 initDatabase() 方法。');
+ }
+ return this.patientDao;
+ }
+
+ // 检查数据库状态
+ isDatabaseReady(): boolean {
+ return this.isInitialized && this.patientDao !== null && this.patientDao.isDatabaseInitialized();
+ }
+
+ // 从服务器获取患者列表并存储到数据库
+ async patientsToFMDB(): Promise {
+ try {
+ if (!this.isDatabaseReady()) {
+ console.error('数据库未准备好,无法获取患者数据');
+ return;
+ }
+
+ const expertUuid = authStore.getUser().uuid;
+ if (!expertUuid) {
+ console.error('专家UUID未找到');
+ return;
+ }
+
+ console.info('开始从服务器获取患者数据...');
+
+ hdHttp.post(BasicConstant.urlExpert + 'patientList', {
+ expertUuid: authStore.getUser().uuid
+ } as updateExtraData).then(async (res: HdResponse) => {
+ try {
+ let json: Record = JSON.parse(res + '') as Record;
+ console.log('服务器返回的患者数据:', json);
+
+ if (json.code == '1' && json.data && Array.isArray(json.data)) {
+ console.info(`服务器返回 ${json.data.length} 个患者数据`);
+
+ for (const item of json.data as PatientData[]) {
+ const patientEntity = new PatientEntity(
+ item.uuid || '',
+ item.nickname || '',
+ item.mobile || '',
+ item.realName || '',
+ item.nation || '',
+ item.sex || 0,
+ item.type || 0,
+ item.photo || '',
+ expertUuid
+ );
+ // 检查本地是否已存在
+ const localPatient = await this.getPatientDao().getPatientByUuid(item.uuid || '');
+ if (localPatient) {
+ // 已存在,更新
+ await this.getPatientDao().updatePatient(patientEntity);
+ } else {
+ // 不存在,插入
+ await this.getPatientDao().insertPatient(patientEntity);
+ }
+ }
+ } else {
+ console.error('服务器返回数据格式错误:', json);
+ }
+ } catch (parseError) {
+ console.error('解析服务器响应失败:', parseError);
+ }
+ }).catch((err: BusinessError) => {
+ console.error('请求患者数据失败:', err);
+ });
+ } catch (error) {
+ console.error('获取患者数据过程中发生错误:', error);
+ }
+ }
+
+ // 获取所有患者数据
+ async getAllPatients(): Promise {
+ try {
+ if (!this.isDatabaseReady()) {
+ console.error('数据库未准备好,无法获取患者数据');
+ return [];
+ }
+
+ const expertUuid = authStore.getUser().uuid;
+ if (!expertUuid) {
+ console.error('专家UUID未找到');
+ return [];
+ }
+
+ console.info('开始获取所有患者数据...');
+ const patients = await this.getPatientDao().getPatientsByExpertUuid(expertUuid);
+ console.info(`成功获取 ${patients.length} 个患者数据`);
+ return patients;
+ } catch (error) {
+ console.error('获取所有患者数据失败:', error);
+ return [];
+ }
+ }
+
+ // 根据UUID获取单个患者
+ async getPatientByUuid(uuid: string): Promise {
+ try {
+ if (!this.isDatabaseReady()) {
+ console.error('数据库未准备好,无法获取患者数据');
+ return null;
+ }
+
+ return await this.getPatientDao().getPatientByUuid(uuid);
+ } catch (error) {
+ console.error('根据UUID获取患者失败:', error);
+ return null;
+ }
+ }
+
+ // 根据手机号获取患者
+ async getPatientByMobile(mobile: string): Promise {
+ try {
+ if (!this.isDatabaseReady()) {
+ console.error('数据库未准备好,无法获取患者数据');
+ return null;
+ }
+
+ const expertUuid = authStore.getUser().uuid;
+ if (!expertUuid) {
+ console.error('专家UUID未找到');
+ return null;
+ }
+ return await this.getPatientDao().getPatientByMobile(mobile, expertUuid);
+ } catch (error) {
+ console.error('根据手机号获取患者失败:', error);
+ return null;
+ }
+ }
+
+ // 更新患者信息
+ async updatePatient(patient: PatientEntity): Promise {
+ try {
+ if (!this.isDatabaseReady()) {
+ console.error('数据库未准备好,无法更新患者数据');
+ return false;
+ }
+
+ await this.getPatientDao().updatePatient(patient);
+ return true;
+ } catch (error) {
+ console.error('更新患者信息失败:', error);
+ return false;
+ }
+ }
+
+ // 删除患者
+ async deletePatient(uuid: string): Promise {
+ try {
+ if (!this.isDatabaseReady()) {
+ console.error('数据库未准备好,无法删除患者数据');
+ return false;
+ }
+
+ await this.getPatientDao().deletePatientByUuid(uuid);
+ return true;
+ } catch (error) {
+ console.error('删除患者失败:', error);
+ return false;
+ }
+ }
+
+ // 删除当前专家的所有患者数据
+ async deleteAllPatients(): Promise {
+ try {
+ if (!this.isDatabaseReady()) {
+ console.error('数据库未准备好,无法删除患者数据');
+ return false;
+ }
+
+ const expertUuid = authStore.getUser().uuid;
+ if (!expertUuid) {
+ console.error('专家UUID未找到');
+ return false;
+ }
+ await this.getPatientDao().deletePatientsByExpertUuid(expertUuid);
+ return true;
+ } catch (error) {
+ console.error('删除所有患者失败:', error);
+ return false;
+ }
+ }
+
+ // 搜索患者(根据姓名或昵称)
+ async searchPatients(keyword: string): Promise {
+ try {
+ if (!this.isDatabaseReady()) {
+ console.error('数据库未准备好,无法搜索患者数据');
+ return [];
+ }
+
+ const expertUuid = authStore.getUser().uuid;
+ if (!expertUuid) {
+ console.error('专家UUID未找到');
+ return [];
+ }
+
+ const allPatients = await this.getPatientDao().getPatientsByExpertUuid(expertUuid);
+ return allPatients.filter(patient =>
+ patient.realName.toLowerCase().includes(keyword.toLowerCase()) ||
+ patient.nickname.toLowerCase().includes(keyword.toLowerCase())
+ );
+ } catch (error) {
+ console.error('搜索患者失败:', error);
+ return [];
+ }
+ }
+
+ // 添加患者信息(支持单个或多个)
+ async addPatients(patients: PatientData | PatientData[]): Promise {
+ try {
+ if (!this.isDatabaseReady()) {
+ console.error('数据库未准备好,无法添加患者数据');
+ return false;
+ }
+
+ const expertUuid = authStore.getUser().uuid;
+ if (!expertUuid) {
+ console.error('专家UUID未找到');
+ return false;
+ }
+
+ // 将单个患者转换为数组
+ const patientsArray = Array.isArray(patients) ? patients : [patients];
+
+ if (patientsArray.length === 0) {
+ console.warn('没有患者数据需要添加');
+ return true;
+ }
+
+ console.info(`开始处理 ${patientsArray.length} 个患者数据...`);
+
+ // 批量处理患者数据
+ const patientDao = this.getPatientDao();
+ await patientDao.beginTransaction();
+ try {
+ for (const patientData of patientsArray) {
+ // 转换为PatientEntity对象
+ const patientEntity = new PatientEntity(
+ patientData.uuid || '',
+ patientData.nickname || '',
+ patientData.mobile || '',
+ patientData.realName || '',
+ patientData.nation || '',
+ patientData.sex || 0,
+ patientData.type || 0,
+ patientData.photo || '',
+ expertUuid
+ );
+
+ // 检查患者是否已存在(根据UUID)
+ const existingPatient = await patientDao.getPatientByUuid(patientData.uuid || '');
+
+ if (existingPatient) {
+ // 患者已存在,更新信息
+ console.info(`患者 ${patientData.uuid} 已存在,执行更新操作`);
+ await patientDao.updatePatient(patientEntity);
+ } else {
+ // 患者不存在,插入新记录
+ console.info(`患者 ${patientData.uuid} 不存在,执行插入操作`);
+ await patientDao.insertPatient(patientEntity);
+ }
+ }
+
+ await patientDao.commitTransaction();
+ console.info(`成功处理 ${patientsArray.length} 个患者数据`);
+ return true;
+ } catch (error) {
+ await patientDao.rollbackTransaction();
+ console.error('处理患者数据失败,已回滚:', error);
+ return false;
+ }
+ } catch (error) {
+ console.error('添加患者数据失败:', error);
+ return false;
+ }
+ }
+
+ // 添加单个患者信息(便捷方法)
+ async addPatient(patient: PatientData): Promise {
+ return await this.addPatients(patient);
+ }
+}
+
+// 导出单例实例
+export const patientDbManager = PatientDatabaseManager.getInstance();
diff --git a/commons/basic/src/main/ets/utils/TimestampUtil.ets b/commons/basic/src/main/ets/utils/TimestampUtil.ets
new file mode 100644
index 0000000..124e5b7
--- /dev/null
+++ b/commons/basic/src/main/ets/utils/TimestampUtil.ets
@@ -0,0 +1,50 @@
+import { systemDateTime } from '@kit.BasicServicesKit';
+
+export class TimestampUtil {
+
+ static format(timestamp: number | string, formatStr: string): string {
+ const date = new Date(timestamp);
+ const padZero = (num: number, len: number = 2) => num.toString().padStart(len, '0');
+ const map: Record = {
+ 'YYYY': date.getFullYear().toString(),
+ 'MM': padZero(date.getMonth() + 1),
+ 'DD': padZero(date.getDate()),
+ 'HH': padZero(date.getHours()),
+ 'mm': padZero(date.getMinutes()),
+ 'ss': padZero(date.getSeconds()),
+ };
+ let result = formatStr;
+ Object.keys(map).forEach((key) => {
+ result = result.replace(new RegExp(key, 'g'), map[key]);
+ });
+ return result;
+ }
+
+ static isToday(dateStr: string): boolean {
+ try {
+ // 1. 解析目标日期字符串 → Date对象
+ const targetDate = new Date(dateStr);
+
+ // 2. 获取当前系统日期(年、月、日)
+ const now = new Date(systemDateTime.getTime());
+ const currentYear = now.getFullYear();
+ const currentMonth = now.getMonth();
+ const currentDay = now.getDate();
+
+ // 3. 提取目标日期的年、月、日
+ const targetYear = targetDate.getFullYear();
+ const targetMonth = targetDate.getMonth();
+ const targetDay = targetDate.getDate();
+
+ // 4. 比较日期是否相同
+ return (
+ targetYear === currentYear &&
+ targetMonth === currentMonth &&
+ targetDay === currentDay
+ );
+ } catch (e) {
+ console.error("日期解析失败", e);
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/commons/basic/src/main/resources/base/media/home_no_qiandao_icon.png b/commons/basic/src/main/resources/base/media/home_no_qiandao_icon.png
new file mode 100644
index 0000000..8388c76
Binary files /dev/null and b/commons/basic/src/main/resources/base/media/home_no_qiandao_icon.png differ
diff --git a/commons/basic/src/main/resources/base/media/home_qiandao_icon.png b/commons/basic/src/main/resources/base/media/home_qiandao_icon.png
new file mode 100644
index 0000000..cc02287
Binary files /dev/null and b/commons/basic/src/main/resources/base/media/home_qiandao_icon.png differ
diff --git a/commons/basic/src/main/resources/base/media/sign_back_leftright_icon.png b/commons/basic/src/main/resources/base/media/sign_back_leftright_icon.png
new file mode 100644
index 0000000..5024682
Binary files /dev/null and b/commons/basic/src/main/resources/base/media/sign_back_leftright_icon.png differ
diff --git a/commons/basic/src/main/resources/base/media/sign_back_news_icon.png b/commons/basic/src/main/resources/base/media/sign_back_news_icon.png
new file mode 100644
index 0000000..8b94e65
Binary files /dev/null and b/commons/basic/src/main/resources/base/media/sign_back_news_icon.png differ
diff --git a/commons/basic/src/main/resources/base/media/sign_popwindow_close.png b/commons/basic/src/main/resources/base/media/sign_popwindow_close.png
new file mode 100644
index 0000000..ffa02b9
Binary files /dev/null and b/commons/basic/src/main/resources/base/media/sign_popwindow_close.png differ
diff --git a/features/Home/src/main/ets/components/FlipperComp.ets b/features/Home/src/main/ets/components/FlipperComp.ets
new file mode 100644
index 0000000..8f6154c
--- /dev/null
+++ b/features/Home/src/main/ets/components/FlipperComp.ets
@@ -0,0 +1,108 @@
+import { FlipperOptions,FlipperView,FlipperOptionsBuilder } from '@hshare/hshare-flipper'
+import { promptAction } from '@kit.ArkUI';
+import { router } from '@kit.ArkUI'
+import { meetingModel } from '../model/HomeModel'
+import { TimestampUtil } from '@itcast/basic'
+
+@Component
+export struct FlipperComp {
+ @Prop bankAdBeans: meetingModel[];
+ @State processedData: meetingModel[] = [];
+
+ options: FlipperOptions = FlipperOptionsBuilder.getInstance()
+ .setHeight(56)//View高度
+ .setInterval(3000)//上下滚动间隔,单位为毫秒
+ .setAnimateParam(500)//动画持续时间,单位为毫秒
+ .setOnItemClicked((item: meetingModel, index: number) => {
+ router.pushUrl({
+ url: 'pages/WebView/WebPage', // 目标url
+ params: {url:item.liveurl,title:item.title}
+ })
+ })
+ .setOnItemScrolled((item: meetingModel, index: number) => {
+ //滚动事件
+ })
+ .build()
+
+ aboutToAppear() {
+ if (this.bankAdBeans.length === 1) {
+ this.processedData = [...this.bankAdBeans, ...this.bankAdBeans, ...this.bankAdBeans];
+ } else {
+ this.processedData = this.bankAdBeans;
+ }
+ }
+
+ build() {
+ Column() {
+ Row() {
+ FlipperView({
+ options: this.options,
+ sourceBeans: this.processedData,
+ itemContentView: (data: meetingModel, index: number) => {
+ this.itemContentView(data, index)
+ }
+ })
+ }
+ }
+ .width('100%').height('100%').backgroundColor('#F4F4F4')
+ }
+
+ @Builder
+ itemContentView(item: meetingModel, index: number) {
+ Stack({alignContent:Alignment.Center}){
+ Image($r('app.media.meeting_back_icon'))
+ .width('100%').height(46).margin({top:10})
+ Row(){
+ Column() {
+ Row({space:2}){
+ Image($r('app.media.meeting_live_icon')).width(30).height(13)
+ if (item.state === '1') {
+ if (TimestampUtil.isToday(item.begin_date_timestamp)) {
+ Image($r('app.media.meeting_begin_play_icon')).width(16).height(16)
+ } else {
+ Image($r('app.media.meeting_noPlay_icon')).width(16).height(16)
+ }
+ } else if (item.state === '0' || item.state === '3') {
+ if (TimestampUtil.isToday(item.begin_date_timestamp)) {
+ Image($r('app.media.meeting_begin_play_icon')).width(16).height(16)
+ } else {
+ Image($r('app.media.meeting_noPlay_icon')).width(16).height(16)
+ }
+ } else {
+ Image($r('app.media.meeting_begin_play_icon')).width(16).height(16)
+ }
+ }
+ Stack({alignContent:Alignment.Center}){
+ Image($r('app.media.meeting_timeBack_icon')).width(74).height(15).margin({top:-3})
+ if (item.state === '1') {
+ if (TimestampUtil.isToday(item.begin_date_timestamp)) {
+ Text('开播'+TimestampUtil.format(item.begin_date_timestamp,'HH:mm'))
+ .fontSize(12).fontColor(Color.White)
+ } else {
+ Text(TimestampUtil.format(item.begin_date_timestamp,'MM月dd日'))
+ .fontSize(12).fontColor(Color.White)
+ }
+ } else if (item.state === '0' || item.state === '3') {
+ if (TimestampUtil.isToday(item.begin_date_timestamp)) {
+ Text('开播'+TimestampUtil.format(item.begin_date_timestamp,'HH:mm'))
+ .fontSize(12).fontColor(Color.White)
+ } else {
+ Text(TimestampUtil.format(item.begin_date_timestamp,'MM月dd日'))
+ .fontSize(12).fontColor(Color.White)
+ }
+ } else {
+ Text('正在直播')
+ .fontSize(12).fontColor(Color.White)
+ }
+ }
+ }.margin({left:10}).alignItems(HorizontalAlign.Start)
+ Blank()
+ .width(1).height(28).margin({left:9}).backgroundColor('#C5C5C5')
+ Text(item.title)
+ .fontSize(14)
+ .fontColor('#333333')
+ .margin({left:9})
+ }.margin({top:10}).width('100%').height(46)
+ }
+ }
+}
diff --git a/features/Home/src/main/ets/components/HomeIconComp.ets b/features/Home/src/main/ets/components/HomeIconComp.ets
new file mode 100644
index 0000000..b66b616
--- /dev/null
+++ b/features/Home/src/main/ets/components/HomeIconComp.ets
@@ -0,0 +1,65 @@
+import { iconsModel } from '../model/HomeModel'
+import { patientDbManager, PatientEntity } from '@itcast/basic';
+import { promptAction } from '@kit.ArkUI';
+
+// interface iconsModel {
+// img:string;
+// name:string;
+// isRed:boolean;
+// }
+
+@Component
+export struct HomeIconComp {
+ @Prop iconList: iconsModel[];
+ @State patientIcon: string = '';
+ @State patientName: string = '我的患者';
+ @State videoIcon: string = '';
+ @State videoName: string = '肝胆视频';
+
+ aboutToAppear(): void {
+ for (const icons of this.iconList) {
+ if (icons.name === '我的患者') {
+ this.patientIcon = icons.img;
+ } else if (icons.name === '肝胆视频') {
+ this.videoIcon = icons.img;
+ }
+ }
+
+ for (let index = 0; index < this.iconList!.length; index++) {
+ const iconModel = this.iconList![index] as iconsModel ;
+ if (index == 0) {
+ iconModel.isRed = true;
+ }
+ }
+ }
+
+ build() {
+ Row() {
+ Grid() {
+ ForEach(this.iconList, (item: iconsModel) => {//[{ 'img': this.patientIcon, 'name': this.patientName },{ 'img': this.videoIcon, 'name': this.videoName}]
+ GridItem(){
+ Stack() {
+ Column() {
+ Image(item.img)
+ .width(24).height(24)
+ .objectFit(ImageFit.Auto)
+ Text(item.name)
+ .fontSize(14)
+ .fontColor('#333333')
+ .margin({ top: 10 })
+ }.width('100%')
+ if (item.isRed) {
+ Text().backgroundColor(Color.Red).width(10).height(10).borderRadius(5)
+ }
+ }.width('25%').alignContent(Alignment.TopEnd)
+ }.margin({top:20,bottom:20})
+ .onClick(async ()=>{
+ const patients = await patientDbManager.getAllPatients();
+ console.info(`添加了 ${patients.length} 个患者`);
+ promptAction.showToast({message:`添加了 ${patients.length} 个患者`})
+ })
+ })
+ }.width('100%').backgroundColor(Color.White)
+ }
+ }
+}
diff --git a/features/Home/src/main/ets/components/HomeReplayVideoComp.ets b/features/Home/src/main/ets/components/HomeReplayVideoComp.ets
new file mode 100644
index 0000000..8c0e108
--- /dev/null
+++ b/features/Home/src/main/ets/components/HomeReplayVideoComp.ets
@@ -0,0 +1,56 @@
+import { videoModel } from '../model/HomeModel'
+import { router } from '@kit.ArkUI';
+import { BasicConstant } from '@itcast/basic'
+import { videoTools } from '../polyv/VideoUtil'
+import { getDisplayWindowWidth } from 'media-player-common'
+
+@Component
+export struct HomeReplayVideoComp {
+ @Prop videoList: videoModel[];
+ @State newVideosList: videoModel[] = this.videoList.slice(0, 4);
+
+ build() {
+ Column() {
+ Row(){
+ Text('精彩回放')
+ .fontSize(17)
+ .fontWeight(FontWeight.Bold)
+ .margin({left:15})
+ Blank()
+ .layoutWeight(1)
+ Row(){
+ Text('更多 ')
+ .fontSize(15)
+ .fontColor('#999999')
+ Image($r('app.media.course_invoice_to_details'))
+ .width(15).height(15)
+ }.margin({right:15})
+ .onClick(()=>{
+ router.pushUrl({
+ url:'pages/VideoPage/VideoGandanPage',
+ })
+ })
+ }.height(50)
+ Grid(){
+ ForEach(this.newVideosList,(item:videoModel,index:number)=>{
+ GridItem(){
+ Column() {
+ Image(item.imgpath).alt($r('app.media.default_video')).width('100%').height(102)
+ .objectFit(ImageFit.Fill)
+ Text(item.name).maxLines(2).fontSize(15).fontColor('app.color.666666').textAlign(TextAlign.Start)
+ .textOverflow({ overflow: TextOverflow.Ellipsis }).width('100%').height(56).padding({left:10,top:10,right:10,bottom:10})
+ }.backgroundColor(Color.White)
+ .borderRadius(5)
+ .height('auto')
+ .clip(true)
+ .width('calc((100% - 45vp)/2)')
+ .margin({left:15,bottom:15})
+ .onClick(()=>{
+ videoTools.getVideoDetail(item.uuid)
+ })
+ }
+ })
+ }.width('100%')
+ }.width('100%')
+ }
+}
diff --git a/features/Home/src/main/ets/components/HomeSwiperComp.ets b/features/Home/src/main/ets/components/HomeSwiperComp.ets
new file mode 100644
index 0000000..5cc692b
--- /dev/null
+++ b/features/Home/src/main/ets/components/HomeSwiperComp.ets
@@ -0,0 +1,57 @@
+import { newsModel,expertDetailModel } from '../model/HomeModel'
+import { router } from '@kit.ArkUI'
+
+@Preview
+@Component
+export struct HomeSwiperComp {
+ @Prop newslist: newsModel[];
+ @Prop expertData:expertDetailModel;
+
+ build() {
+ Column() {
+ Swiper() {
+ ForEach(this.newslist, (item: newsModel,index:number) => {
+ Stack({alignContent:Alignment.Center}) {
+ Image(item.headImg)
+ .objectFit(ImageFit.Fill)// 图片填充模式
+ .width('100%').height('100%')
+ if (index == 0) {
+ Column({space:5}){
+ Text(this.expertData.realName+'专家工作室')
+ .fontSize(19)
+ .fontColor(Color.White)
+ .margin({left:20,top:60})
+ Text(this.expertData.hospitalName)
+ .fontSize(16)
+ .fontColor(Color.White)
+ .margin({left:20})
+ }.width('100%').alignItems(HorizontalAlign.Start)
+ }
+ }.onClick(()=>{
+ if (index == 0) {
+ router.pushUrl({url:'pages/MinePage/EditUserDataPage'})
+ } else {
+ router.pushUrl({
+ url: 'pages/WebView/WebPage', // 目标url
+ params: {url:item.path,title:item.title}
+ })
+ }
+ })
+ }, (item: newsModel) => JSON.stringify(item))
+ }
+ .indicator(
+ Indicator.dot()
+ .itemWidth(8)
+ .itemHeight(8)
+ .selectedItemWidth(8)
+ .selectedItemHeight(8)
+ .color(Color.Gray)
+ .selectedColor($r('app.color.main_color'))
+ )
+ .loop(true)
+ .autoPlay(true)
+ .interval(5000)
+ }
+ .width('100%')
+ }
+}
\ No newline at end of file
diff --git a/features/Home/src/main/ets/components/SpeciallyEStandingComp.ets b/features/Home/src/main/ets/components/SpeciallyEStandingComp.ets
new file mode 100644
index 0000000..15831a3
--- /dev/null
+++ b/features/Home/src/main/ets/components/SpeciallyEStandingComp.ets
@@ -0,0 +1,92 @@
+import { esiteModel } from '../model/HomeModel'
+import { router } from '@kit.ArkUI'
+
+@Component
+export struct SpeciallyEStandingComp {
+ @Prop esiteArray: esiteModel[];
+ @State newEsiteArr: esiteModel[][] = [];
+ @State selectedIndex:number = 0;
+
+ aboutToAppear(): void {
+ this.newEsiteArr = convertTo2DArray(this.esiteArray)
+ }
+
+ changeGroup() {
+ animateTo({
+ duration: 500,
+ curve: Curve.EaseIn,
+ }, () => {
+ if (this.selectedIndex === this.newEsiteArr.length - 1) {
+ this.selectedIndex = 0;
+ } else {
+ this.selectedIndex++;
+ }
+ })
+ }
+
+ build() {
+ Column() {
+ Row(){
+ Text('专题E站')
+ .fontSize(17)
+ .fontWeight(FontWeight.Bold)
+ .margin({left:15})
+ Blank()
+ .layoutWeight(1)
+ if (this.newEsiteArr.length>=2) {
+ Row(){
+ Image($r('app.media.new_home_choose_icon'))
+ .width(15).height(15)
+ Text(' 换一换')
+ .fontSize(15)
+ .fontColor('#999999')
+ }.margin({right:15})
+ .onClick(()=>{
+ this.changeGroup();
+ })
+ }
+ }.height(50)
+ Column(){
+ ForEach(getSubArray(this.newEsiteArr,this.selectedIndex),(item:esiteModel,index:number)=>{
+ Image(item.img_path)
+ .objectFit(ImageFit.Cover)
+ .margin({left:10,top:10,right:10})
+ .height(45).width('95%')
+ .onClick(()=>{
+ router.pushUrl({
+ url: 'pages/WebView/WebPage', // 目标url
+ params: {url:item.url,title:item.name}
+ })
+ })
+ })
+ }.backgroundColor(Color.White).borderRadius(4).height(175).width('93%')
+ .margin({left:15,right:15})
+ }.width('100%').alignItems(HorizontalAlign.Start)
+ }
+}
+
+function convertTo2DArray(sourceArray: T[], groupSize: number = 3): T[][] {
+ const resultArray: T[][] = [];
+
+ for (let i = 0; i < sourceArray.length; i += groupSize) {
+ const group = sourceArray.slice(i, i + groupSize);
+
+ if (group.length < groupSize) {
+ const emptyObj: esiteModel = {} as esiteModel;
+ const emptyItems = Array(groupSize - group.length).fill(emptyObj) as T[];
+ resultArray.push([...group, ...emptyItems]);
+ } else {
+ resultArray.push(group);
+ }
+ }
+ return resultArray;
+}
+
+// 获取指定下标的子数组(带边界检查)
+function getSubArray(data: T[][], index: number): T[] {
+ if (index < 0 || index >= data.length) {
+ // 越界返回空数组(或抛异常)
+ return [];
+ }
+ return data[index];
+}
\ No newline at end of file
diff --git a/features/Home/src/main/ets/model/HomeModel.ets b/features/Home/src/main/ets/model/HomeModel.ets
new file mode 100644
index 0000000..437cbfb
--- /dev/null
+++ b/features/Home/src/main/ets/model/HomeModel.ets
@@ -0,0 +1,126 @@
+export interface HomeModel {
+ code:string;
+ data:dataModel;
+ message:string;
+}
+
+export interface dataModel{
+ consult_list?:consultModel;
+ news_list?:newsModel[];
+ gandanfile_list?:gandanfileModel[];
+ isOnlineToday?:string;
+ has_unread?:string;
+ guide_ist?:guideModel[];
+ expertDetail?:expertDetailModel;
+ excellencourse_list?:excellencourseModel[];
+ video_list?:videoModel[];
+ meeting_list?:meetingModel[];
+ icons_list?:iconsModel[];
+ welfare_notice?:welfareModel;
+ esite_list?:esiteModel[];
+ sign_in?:string
+}
+
+export interface consultModel {
+ yetDayTotalNum:string;
+ count:string;
+ list:[];
+ yetDayTotalnumEPNum:string
+}
+
+export interface newsModel {
+ uuid:string;
+ title:string;
+ headImg:string;
+ color:string;
+ type:string;
+ path:string;
+}
+
+export interface gandanfileModel {
+ type:string;
+ article_uuid:string;
+ title:string;
+ tags:string;
+ path:string
+}
+
+export interface guideModel {
+ create_date:string;
+ guide_type_uuid:string;
+ guide_type:string;
+ guide_uuid:string;
+ article_uuid:string;
+ title:string;
+ tags:string;
+ path:string
+}
+
+export interface expertDetailModel {
+ photo:string;
+ officeName:string;
+ positionName:string;
+ hospitalName:string;
+ qrcode:string;
+ realName:string;
+}
+
+export interface excellencourseModel {
+ video_num:string;
+ discount_type:string;
+ account:string;
+ discount_price:string;
+ title:string;
+ search_second_list:string;
+ study_num:string;
+ sroll_img:string;
+ index_img:string;
+ upload_num:string;
+ back_bon:string;
+ fuli_bon:string;
+ special_type_name:string;
+ tags:string;
+ id:string;
+}
+
+export interface videoModel {
+ readnum:string;
+ uuid:string;
+ imgpath:string;
+ polyv_uuid:string;
+ public_name:string;
+ imgUrl:string;
+ note:string;
+ name:string;
+ path:string;
+ content:string;
+}
+
+export interface meetingModel {
+ title:string;
+ begin_date_timestamp:string;
+ end_date_timestamp:string;
+ liveurl:string;
+ begin_date:string;
+ end_date:string;
+ state:string;
+ path:string;
+}
+
+export interface iconsModel {
+ fixed:string;
+ img:string;
+ name:string;
+ isRed:boolean;
+}
+
+export interface welfareModel {
+ one_last_notice:boolean;
+ receive_notice:boolean;
+}
+
+export interface esiteModel {
+ url:string;
+ img_path:string;
+ name:string;
+}
\ No newline at end of file
diff --git a/features/Home/src/main/ets/pages/HomePage.ets b/features/Home/src/main/ets/pages/HomePage.ets
index 2ca217e..1c34d49 100644
--- a/features/Home/src/main/ets/pages/HomePage.ets
+++ b/features/Home/src/main/ets/pages/HomePage.ets
@@ -1,19 +1,185 @@
+import { FlipperComp } from '../components/FlipperComp'
+import { HomeIconComp } from '../components/HomeIconComp'
+import { HomeSwiperComp } from '../components/HomeSwiperComp'
+import { SpeciallyEStandingComp } from '../components/SpeciallyEStandingComp'
+import { HomeReplayVideoComp } from '../components/HomeReplayVideoComp'
+import { getDisplayWindowWidth } from 'media-player-common'
+import { BusinessError } from '@kit.BasicServicesKit';
+import HashMap from '@ohos.util.HashMap';
+import { BasicConstant,hdHttp, HdResponse ,logger,HdHomeNav} from '@itcast/basic/Index'
+import { HomeModel,dataModel, newsModel,iconsModel } from '../model/HomeModel';
+import { DefaultHintProWindows,SignPopWindow,HdLoadingDialog } from '@itcast/basic'
+import { promptAction, router } from '@kit.ArkUI';
+
+@Entry
@Component
export struct HomePage {
- @State message: string = 'Hello World';
+ @State homeData:dataModel = {} as dataModel;
+ @State navAlpha: number = 0;
+ @State navBackColor: string = 'FFFFFF'
+ @Consume@Watch('gotoTop')
+ toTop:boolean;
+ @State hintMessage:string = '';
+ @State signData:Record = {};
+
+ scroller:Scroller = new Scroller()
+ private hintWindowDialog!: CustomDialogController;
+ private signWindowDialog!:CustomDialogController;
+
+ dialog: CustomDialogController = new CustomDialogController({
+ builder: HdLoadingDialog({ message: '加载中...' }),
+ customStyle: true,
+ alignment: DialogAlignment.Center
+ })
+
+ private hintPopWindowDialog() {
+ this.hintWindowDialog = new CustomDialogController({
+ builder:DefaultHintProWindows({
+ controller:this.hintWindowDialog,
+ message:this.hintMessage,
+ cancleTitle:'',
+ confirmTitle:'关闭',
+ confirmTitleColor: '#000000',
+ selectedButton: (index:number)=>{
+ this.hintWindowDialog.close();
+ }
+ }),
+ alignment: DialogAlignment.Center,
+ cornerRadius:24,
+ autoCancel:false,
+ backgroundColor: ('rgba(0,0,0,0.5)'),
+ })
+ }
+
+ private signPopWindowDialog(){
+ this.signWindowDialog = new CustomDialogController({
+ builder:SignPopWindow({
+ controller:this.signWindowDialog,
+ signDay:'今天是我们相识的第'+this.signData.gdxzday+'天',
+ signWeek:this.signData.totalDay,
+ signMouth:this.signData.continuous_day,
+ signNews:this.signData.news['title'],
+ signHtml:this.signData.news['path'],
+ }),
+ alignment: DialogAlignment.Center,
+ cornerRadius:8,
+ autoCancel:false,
+ backgroundColor: Color.Transparent,
+ backgroundBlurStyle: BlurStyle.NONE,
+ })
+ }
+
+ gotoTop() {
+ this.scroller.scrollToIndex(0);
+ this.initData()
+ }
+
+ aboutToAppear(): void {
+ this.initData()
+ this.hintPopWindowDialog();
+ this.signPopWindowDialog();
+ }
+
+ initData() {
+ const hashMap: HashMap = new HashMap();
+ this.dialog.open()
+ hashMap.clear();
+ hdHttp.httpReq(BasicConstant.indexV2,hashMap).then(async (res: HdResponse) => {
+ logger.info('Response indexV2'+res);
+ let json:HomeModel = JSON.parse(res+'') as HomeModel;
+ this.dialog.close();
+ this.homeData = json.data;
+ for (const item of this.homeData.news_list as newsModel[]) {
+ if (item.type == '1') {
+ this.navBackColor = item.color;
+ }
+ }
+ }).catch((err: BusinessError) => {
+ this.dialog.close();
+ })
+ }
+
+ getSignData() {
+ const hashMap: HashMap = new HashMap();
+ this.dialog.open()
+ hashMap.clear();
+ hashMap.set('score_type','1');
+ hdHttp.httpReq(BasicConstant.addBonusPoints,hashMap).then(async (res: HdResponse) => {
+ logger.info('Response addBonusPoints'+res);
+ this.dialog.close();
+ let json:Record = JSON.parse(res+'') as Record;
+ if (json.code == '1') {
+ this.homeData.sign_in = '1';
+ this.signData = json;
+ this.signWindowDialog.open();
+ } else if (json.code == '201') {
+ this.homeData.sign_in = '1';
+ this.hintMessage = '今日已签到,每日只能签到一次。\n请明日继续哦~';
+ this.hintWindowDialog.open();
+ }
+ }).catch((err: BusinessError) => {
+ this.dialog.close();
+ })
+ }
build() {
- Row() {
- Column() {
- Text(this.message)
- .fontSize($r('app.float.page_text_font_size'))
- .fontWeight(FontWeight.Bold)
- .onClick(() => {
- this.message = 'Welcome';
- })
+ Stack(){
+ Scroll(this.scroller) {
+ Column() {
+ if (this.homeData.news_list && this.homeData.news_list.length > 0) {
+ HomeSwiperComp({ newslist: this.homeData.news_list, expertData: this.homeData.expertDetail })
+ .height(getDisplayWindowWidth().vp / 16 * 9)
+ }
+ if (this.homeData.icons_list && this.homeData.icons_list.length > 0) {
+ HomeIconComp({iconList:this.homeData.icons_list})
+ }
+ if (this.homeData.meeting_list && this.homeData.meeting_list.length > 0) {
+ FlipperComp({ bankAdBeans: this.homeData.meeting_list })
+ .height(56)
+ .backgroundColor(Color.Yellow)
+ }
+ if (this.homeData.esite_list && this.homeData.esite_list.length > 0) {
+ SpeciallyEStandingComp({ esiteArray: this.homeData.esite_list })
+ }
+ if (this.homeData.video_list && this.homeData.video_list.length > 0) {
+ HomeReplayVideoComp({ videoList: this.homeData.video_list })
+ }
+ }
}
- .width('100%')
+ .edgeEffect(EdgeEffect.Spring)
+ .scrollBar(BarState.Off)
+ .onWillScroll(() => {
+ const yOffset = this.scroller.currentOffset().yOffset;
+ const threshold = 56;
+ if (yOffset <= 0) {
+ this.navAlpha = 0;
+ } else if (yOffset >= threshold) {
+ this.navAlpha = 1;
+ } else {
+ this.navAlpha = yOffset / threshold;
+ }
+ })
+ HdHomeNav({
+ leftIcon:this.homeData.sign_in == '0'?$r('app.media.home_no_qiandao_icon'):$r('app.media.home_qiandao_icon'),
+ placeholder:'搜索视频、会议',
+ alpha:this.navAlpha,
+ backColor:this.navBackColor,
+ leftItemAction:()=>{
+ this.getSignData();
+ },
+ searchItemAction:()=>{
+ router.pushUrl({
+ url:'pages/SearchPage/VideoSearchPage',
+ params: {
+ params:{'pageName':'视频'}
+ }
+ })
+ }
+ })
}
+ .alignContent(Alignment.Top)
+ .backgroundColor('#f4f4f4')
+ .width('100%')
.height('100%')
}
}
diff --git a/features/Home/src/main/resources/base/element/float.json b/features/Home/src/main/resources/base/element/float.json
index 33ea223..a0a93dd 100644
--- a/features/Home/src/main/resources/base/element/float.json
+++ b/features/Home/src/main/resources/base/element/float.json
@@ -5,4 +5,4 @@
"value": "50fp"
}
]
-}
+}
\ No newline at end of file
diff --git a/features/Home/src/main/resources/base/media/course_invoice_to_details.png b/features/Home/src/main/resources/base/media/course_invoice_to_details.png
new file mode 100644
index 0000000..1f64e91
Binary files /dev/null and b/features/Home/src/main/resources/base/media/course_invoice_to_details.png differ
diff --git a/features/Home/src/main/resources/base/media/meeting_back_icon.png b/features/Home/src/main/resources/base/media/meeting_back_icon.png
new file mode 100644
index 0000000..08e9005
Binary files /dev/null and b/features/Home/src/main/resources/base/media/meeting_back_icon.png differ
diff --git a/features/Home/src/main/resources/base/media/meeting_begin_play_icon.gif b/features/Home/src/main/resources/base/media/meeting_begin_play_icon.gif
new file mode 100644
index 0000000..27ee658
Binary files /dev/null and b/features/Home/src/main/resources/base/media/meeting_begin_play_icon.gif differ
diff --git a/features/Home/src/main/resources/base/media/meeting_live_icon.png b/features/Home/src/main/resources/base/media/meeting_live_icon.png
new file mode 100644
index 0000000..bae6d98
Binary files /dev/null and b/features/Home/src/main/resources/base/media/meeting_live_icon.png differ
diff --git a/features/Home/src/main/resources/base/media/meeting_noPlay_icon.png b/features/Home/src/main/resources/base/media/meeting_noPlay_icon.png
new file mode 100644
index 0000000..af73810
Binary files /dev/null and b/features/Home/src/main/resources/base/media/meeting_noPlay_icon.png differ
diff --git a/features/Home/src/main/resources/base/media/meeting_timeBack_icon.png b/features/Home/src/main/resources/base/media/meeting_timeBack_icon.png
new file mode 100644
index 0000000..25cbe0a
Binary files /dev/null and b/features/Home/src/main/resources/base/media/meeting_timeBack_icon.png differ
diff --git a/features/Home/src/main/resources/base/media/new_home_choose_icon.png b/features/Home/src/main/resources/base/media/new_home_choose_icon.png
new file mode 100644
index 0000000..0591c54
Binary files /dev/null and b/features/Home/src/main/resources/base/media/new_home_choose_icon.png differ
diff --git a/features/mypage/src/main/ets/model/MyPageSectionClass.ets b/features/mypage/src/main/ets/model/MyPageSectionClass.ets
index daf0b21..4f3b888 100644
--- a/features/mypage/src/main/ets/model/MyPageSectionClass.ets
+++ b/features/mypage/src/main/ets/model/MyPageSectionClass.ets
@@ -4,11 +4,13 @@ export class MyPageSectionClass {
imageSrc:ResourceStr = '';
title:string = '';
path:string = '';
+ status:boolean = false;
- constructor(id:string,imageSrc:ResourceStr,title:string,path:string) {
+ constructor(id:string,imageSrc:ResourceStr,title:string,path:string,status:boolean) {
this.id = id;
this.imageSrc = imageSrc;
this.title = title;
this.path = path;
+ this.status = status;
}
}
diff --git a/features/mypage/src/main/ets/pages/MyHomePage.ets b/features/mypage/src/main/ets/pages/MyHomePage.ets
index 1f22076..4506afe 100644
--- a/features/mypage/src/main/ets/pages/MyHomePage.ets
+++ b/features/mypage/src/main/ets/pages/MyHomePage.ets
@@ -1,11 +1,17 @@
import { HeaderView } from '../view/HeaderView'
-import { HdNav, getTimeText, hdHttp, HdUser } from '@itcast/basic'
import { OneSection } from '../view/OneSection'
import { TwoSection } from '../view/TwoSection'
import { ThreeSection } from '../view/ThreeSection'
import { FourSection } from '../view/FourSection'
import { OtherList } from '../view/OtherList'
-import { emitter } from '@kit.BasicServicesKit'
+import { HdNav,hdHttp,HdResponse,BasicConstant,ExpertData,authStore,RequestDefaultModel } from '@itcast/basic'
+import { BusinessError, emitter } from '@kit.BasicServicesKit';
+import HashMap from '@ohos.util.HashMap'
+
+interface heroFirst {
+ id: string;
+ nick_name: string;
+}
@Component
export struct MyHomePage {
@@ -13,21 +19,64 @@ export struct MyHomePage {
@StorageProp('topHeight')
topHeight: number = 0
+ @State myInfoBackGround:string = '';
+ @State heroArray:Array