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 = []; + @State expertData:object | string = new Object; scroller = new Scroller() + aboutToAppear(): void { + this.uploadBackImgAction(); + emitter.on({ eventId: BasicConstant.notification_home_tab_change }, (eventData: emitter.EventData) => { + if (eventData.data?.changeIndex === 2) { + this.uploadBackImgAction(); + } + }); + } + + uploadBackImgAction() { + const hashMap: HashMap = new HashMap(); + hdHttp.httpReq(BasicConstant.myData,hashMap).then(async (res: HdResponse) => { + console.info(`我的背景图: ${res}`); + let json:Record = JSON.parse(res+'') as Record; + if(json.code == '200') { + this.heroArray = json.data["honor_list"]; + this.myInfoBackGround = json.data['myInfoBackGround']; + this.expertData = json.data; + // 获取ranking值(带安全类型转换) + const ranking: string = json.data["ranking"]?.toString() ?? ""; + if (Number(ranking) > 0) { + // 创建排名对象 + const rankDic: heroFirst = { + "id": "ranking", + "nick_name": `随访达人 I 排名${ranking}` + }; + // 插入数组首位 + this.heroArray.unshift(rankDic); + } + } else { + console.error('我的背景图:'+json.message) + } + }).catch((err: BusinessError) => { + console.info(`Response login fail: ${err}`); + }) + } + build() { Column() { HdNav({ title: '我的', showLeftIcon:false , showRightIcon: false, hasBorder: true }) Scroll(this.scroller) { - Column() { - HeaderView() - // OneSection() - // TwoSection() - // ThreeSection() - FourSection() - OtherList() - } + Stack() { + Image(this.myInfoBackGround) + .backgroundImageSize(ImageSize.Cover) + .width('100%') + Column() { + HeaderView({heroArray:this.heroArray,expertData:this.expertData}) + OneSection() + FourSection() + OtherList() + } + }.alignContent(Alignment.Top) } .width('100%') .height('100%') diff --git a/features/mypage/src/main/ets/view/FourSection.ets b/features/mypage/src/main/ets/view/FourSection.ets index 3490fa9..c6ca118 100644 --- a/features/mypage/src/main/ets/view/FourSection.ets +++ b/features/mypage/src/main/ets/view/FourSection.ets @@ -21,10 +21,10 @@ export struct FourSection { // new MyPageSectionClass('threeItem',this.pushIconPath,this.pushStatus,''), // new MyPageSectionClass('fourItem',$r('app.media.my_page_version'),'发现新版本','') - new MyPageSectionClass('oneItem',$r('app.media.my_page_choosePhone'),'更换手机号','pages/MinePage/ChangePhonePage'), - new MyPageSectionClass('twoItem',this.pushIconPath,this.pushStatus,''), - new MyPageSectionClass('threeItem',$r('app.media.my_page_guanyu_icon'),'关于肝胆相照','pages/WebView/WebPage'), - new MyPageSectionClass('fourItem',$r('app.media.my_page_zhibo_icon'),'肝胆相照直播群','pages/WebView/WebPage') + new MyPageSectionClass('oneItem',$r('app.media.my_page_choosePhone'),'更换手机号','pages/MinePage/ChangePhonePage',false), + new MyPageSectionClass('twoItem',this.pushIconPath,this.pushStatus,'',false), + new MyPageSectionClass('threeItem',$r('app.media.my_page_guanyu_icon'),'关于肝胆相照','pages/WebView/WebPage',false), + new MyPageSectionClass('fourItem',$r('app.media.my_page_zhibo_icon'),'肝胆相照直播群','pages/WebView/WebPage',false) ]; aboutToAppear() { @@ -60,7 +60,7 @@ export struct FourSection { // 更新数组中的标题 this.fourSectionList = this.fourSectionList.map((item, index) => { if (index === 1) { - return new MyPageSectionClass(item.id, this.pushIconPath, this.pushStatus, item.path); + return new MyPageSectionClass(item.id, this.pushIconPath, this.pushStatus, item.path,false); } return item; }); diff --git a/features/mypage/src/main/ets/view/HeaderView.ets b/features/mypage/src/main/ets/view/HeaderView.ets index 39a3a99..212b55d 100644 --- a/features/mypage/src/main/ets/view/HeaderView.ets +++ b/features/mypage/src/main/ets/view/HeaderView.ets @@ -33,8 +33,8 @@ export struct HeaderView { @State heroIndex: number = 0 // 当前页索引 @State photoPath:string = BasicConstant.urlImage+authStore.getUser().photo; @State name:string = authStore.getUser().realName; - @State myPageData:object = new Object; - @State heroArray:Array = []; + @Prop heroArray:Array = []; + @Prop expertData:object | string= new Object; @State clickHeroId:string = ''; @Consume@Watch('gotoTop') toTop:boolean; @@ -48,11 +48,9 @@ export struct HeaderView { aboutToAppear(): void { this.uploadUserDataAction(); - this.uploadBackImgAction(); emitter.on({ eventId: BasicConstant.notification_home_tab_change }, (eventData: emitter.EventData) => { if (eventData.data?.changeIndex === 2) { this.uploadUserDataAction(); - this.uploadBackImgAction(); } }); } @@ -81,103 +79,82 @@ export struct HeaderView { }) } - uploadBackImgAction() { - const hashMap: HashMap = new HashMap(); - hdHttp.httpReq(BasicConstant.myData,hashMap).then(async (res: HdResponse) => { - console.info(`我的背景图: ${res}`); - let json:RequestDefaultModel = JSON.parse(res+'') as RequestDefaultModel; - if(json.code == '200') { - this.heroArray = json.data["honor_list"]; - this.myPageData = json.data; - // 获取ranking值(带安全类型转换) - const ranking: string = this.myPageData["ranking"]?.toString() ?? ""; - if (Number(ranking) > 0) { - // 创建排名对象 - const rankDic: heroFirst = { - "id": "ranking", - "nick_name": `随访达人 I 排名${ranking}` - }; - // 插入数组首位 - this.heroArray.unshift(rankDic); - } - } else { - console.error('我的背景图:'+json.message) - } - }).catch((err: BusinessError) => { - console.info(`Response login fail: ${err}`); - }) - } - handleAvatarClick() { router.pushUrl({url:'pages/MinePage/EditUserDataPage'}) } build() { - // Row() { - Column({space:5}) { - Row({space:10}) { - Image(this.photoPath) - .alt($r('app.media.userPhoto_default')) - .margin({left:15}) - .width(60) - .height(60) - .borderRadius(5) - .objectFit(ImageFit.Cover) - .onClick(()=>this.handleAvatarClick()) - Column({space:5}) { - Text(this.name) - .fontSize(18) - .fontColor('#FFFFFF') - .onClick(()=>this.handleAvatarClick()) - List({space:5,initialIndex:this.heroIndex,scroller:this.scrollerForList}) { - ForEach(this.heroArray, (item: heroFirst) => { - ListItem() { - Row() { - Image(item.id === 'ranking'?$r('app.media.my_home_hero_ranking'):$r('app.media.my_page_header_hertIcon')) - .width(13) - .height(13) - .margin({left:7}) - Text(item.nick_name) - .fontColor(Color.White) - .fontSize(11) - .margin({left:4,right:10}) - } - .height(18) - .margin({right:5}) - .borderRadius(9) - .borderWidth(1) - .borderColor(Color.White) - .onClick(()=>{ - if (item.id !== 'ranking') { - this.clickHeroId = item.id; - this.dialogController.open(); - } - }) - } - }) - } - .listDirection(Axis.Horizontal) - .scrollBar(BarState.Off) - .width('100%') - .height(18) - } - .alignItems(HorizontalAlign.Start) + Column({space:15}) { + Row({space:10}) { + Image(this.photoPath) + .alt($r('app.media.userPhoto_default')) + .margin({left:15}) + .width(60) .height(60) - .width('70%') - .margin({top:22}) + .borderRadius(5) + .objectFit(ImageFit.Cover) + .onClick(()=>this.handleAvatarClick()) + Column({space:5}) { + Text(this.name) + .fontSize(18) + .fontColor('#FFFFFF') + .onClick(()=>this.handleAvatarClick()) + List({space:5,initialIndex:this.heroIndex,scroller:this.scrollerForList}) { + ForEach(this.heroArray, (item: heroFirst) => { + ListItem() { + Row() { + Image(item.id === 'ranking'?$r('app.media.my_home_hero_ranking'):$r('app.media.my_page_header_hertIcon')) + .width(13) + .height(13) + .margin({left:7}) + Text(item.nick_name) + .fontColor(Color.White) + .fontSize(11) + .margin({left:4,right:10}) + } + .height(18) + .margin({right:5}) + .borderRadius(9) + .borderWidth(1) + .borderColor(Color.White) + .onClick(()=>{ + if (item.id !== 'ranking') { + this.clickHeroId = item.id; + this.dialogController.open(); + } + }) + } + }) + } + .listDirection(Axis.Horizontal) + .scrollBar(BarState.Off) + .width('100%') + .height(18) + }.alignItems(HorizontalAlign.Start).width('calc(100% - 100vp)') + }.width('100%').margin({top:20}) + Column(){ + Row({space:70}){ + ForEach([{'title':'随访患者数','content':this.expertData['expert_apply_num'] || '0'}, + {'title':'公益咨询数','content':this.expertData['consult_total_num'] || '0'}], + // {'title':'患者送花数','content':this.expertData['ping_flowewr_num'] || '0'}], + (item:object)=>{ + Column(){ + Text(item['content']?.toString() || '0') + .fontSize(20) + .fontColor('#000000') + .height(28) + Text(item['title']) + .fontSize(12) + .fontColor('#333333') + .height(17) + }.height('100%').justifyContent(FlexAlign.Center) + }) } - .width('100%') - .height(97) - } - .width('100%') - .height(97) - .backgroundImage(this.myPageData["myInfoBackGround"]).backgroundImageSize(ImageSize.Cover) + }.width('95%').height(65).borderRadius(5).backgroundColor(Color.White) } - // } - gotoTop() - { + .width('100%') + } + gotoTop() { this.photoPath = BasicConstant.urlImage+authStore.getUser().photo; - - } } diff --git a/features/mypage/src/main/ets/view/MyPageSectionItem.ets b/features/mypage/src/main/ets/view/MyPageSectionItem.ets index e6f623c..be76a24 100644 --- a/features/mypage/src/main/ets/view/MyPageSectionItem.ets +++ b/features/mypage/src/main/ets/view/MyPageSectionItem.ets @@ -5,15 +5,19 @@ export struct MyPageSectionItem { @Prop sectionItem: MyPageSectionClass; build() { - Column(){ - Image(this.sectionItem.imageSrc) - .width(35).height(35) - .borderRadius(17.5) - .objectFit(ImageFit.Auto) - Text(this.sectionItem.title) - .fontSize(12) - .fontColor(Color.Black) - .margin({top:6}) - } + Stack() { + Column() { + Image(this.sectionItem.imageSrc) + .width(24).height(24) + .objectFit(ImageFit.Auto) + Text(this.sectionItem.title) + .fontSize(14) + .fontColor('#333333') + .margin({ top: 10 }) + }.width('100%') + if (this.sectionItem.status) { + Text().backgroundColor(Color.Red).width(10).height(10).borderRadius(5) + } + }.width('100%').height('100%').alignContent(Alignment.TopEnd) } } diff --git a/features/mypage/src/main/ets/view/OneSection.ets b/features/mypage/src/main/ets/view/OneSection.ets index 25766f0..c390511 100644 --- a/features/mypage/src/main/ets/view/OneSection.ets +++ b/features/mypage/src/main/ets/view/OneSection.ets @@ -1,19 +1,34 @@ -import { it } from "@ohos/hypium"; import { MyPageSectionClass } from "../model/MyPageSectionClass"; import { MyPageSectionItem } from '../view/MyPageSectionItem' +import { router } from "@kit.ArkUI"; +import { BasicConstant,hdHttp, HdResponse ,logger,authStore} from '@itcast/basic/Index' +import { BusinessError } from '@kit.BasicServicesKit'; +import { it } from "@ohos/hypium"; + +interface extraData { + expertUuid: string +} + +interface requestCallBack { + code:string; + msg:string; + message:string; + data:Array +} -@Preview @Component export struct OneSection { @State sectionTitle: string = "随访服务"; @State currentIndex: number = 0; + @Consume@Watch('gotoTop') + toTop:boolean; @State oneSectionList: Array = [ - new MyPageSectionClass('oneItem', $r('app.media.app_icon'), '患者审核', '/pages/MyHomePage'), - new MyPageSectionClass('twoItem', $r('app.media.app_icon'), '患者分组', '/pages/MyHomePage'), - new MyPageSectionClass('threeItem', $r('app.media.app_icon'), '群发消息', '/pages/MyHomePage'), - new MyPageSectionClass('fourItem', $r('app.media.app_icon'), '随访二维码', '/pages/MyHomePage'), - new MyPageSectionClass('fiveItem', $r('app.media.app_icon'), '出诊计划', '/pages/MyHomePage') + new MyPageSectionClass('oneItem', $r('app.media.my_page_patientAudit'), '患者审核', '/pages/MyHomePage',false), + new MyPageSectionClass('twoItem', $r('app.media.my_page_patientList'), '患者分组', '/pages/MyHomePage',false), + new MyPageSectionClass('threeItem', $r('app.media.my_page_message'), '群发消息', '/pages/MyHomePage',false), + new MyPageSectionClass('fourItem', $r('app.media.my_page_QrCode'), '随访二维码', '/pages/MyHomePage',false), + new MyPageSectionClass('fiveItem', $r('app.media.my_page_visitPlan'), '出诊计划', '/pages/MyHomePage',false) ]; private getPagedItems(): Array> { @@ -25,6 +40,35 @@ export struct OneSection { return pages; } + aboutToAppear(): void { + this.getApplyListData(); + } + + gotoTop() { + this.getApplyListData(); + } + + getApplyListData(){ + hdHttp.post(BasicConstant.applyList, { + expertUuid: authStore.getUser().uuid, + } as extraData).then(async (res: HdResponse) => { + logger.info('Response applyList'+res); + let json:requestCallBack = JSON.parse(res+'') as requestCallBack; + if(json.code == '1') { + if (json.data.length > 0) { + this.oneSectionList = this.oneSectionList.map((item, index) => { + if (index === 0) { + return new MyPageSectionClass(item.id,item.imageSrc,item.title,item.path,true); + } + return item; + }); + } + } + }).catch((err: BusinessError) => { + console.info(`Response fails: ${err}`); + }) + } + build() { Column() { // 标题 @@ -42,6 +86,17 @@ export struct OneSection { ForEach(pageItems, (item: MyPageSectionClass) => { GridItem() { MyPageSectionItem({ sectionItem: item }) + .onClick(()=>{ + if (item.title === '患者审核') { + router.pushUrl({ + url:'pages/PatientsPage/PatientPages' + }) + } else if (item.title === '患者分组') { + router.pushUrl({ + url:'pages/PatientsPage/PatientsGroupPage' + }) + } + }) } }, (item: MyPageSectionClass) => item.id) } @@ -51,13 +106,13 @@ export struct OneSection { .height('100%') .width('100%') }) - } + }.width('100%') .index(this.currentIndex) .onChange((index: number) => { this.currentIndex = index; }) .height(78) - .indicator(false) + .indicator(false).loop(false) Row() { ForEach(new Array(Math.ceil(this.oneSectionList.length / 4)).fill(0), (item: number, idx: number) => { diff --git a/features/mypage/src/main/ets/view/ThreeSection.ets b/features/mypage/src/main/ets/view/ThreeSection.ets index db0d862..bd46de9 100644 --- a/features/mypage/src/main/ets/view/ThreeSection.ets +++ b/features/mypage/src/main/ets/view/ThreeSection.ets @@ -9,12 +9,12 @@ export struct ThreeSection { @State currentIndex: number = 0; @State threeSectionList:Array = [ - new MyPageSectionClass('oneItem',$r('app.media.app_icon'),'我的账户','/pages/MyHomePage'), - new MyPageSectionClass('twoItem',$r('app.media.app_icon'),'我的积分','/pages/MyHomePage'), - new MyPageSectionClass('threeItem',$r('app.media.app_icon'),'我的福利','/pages/MyHomePage'), - new MyPageSectionClass('fourItem',$r('app.media.app_icon'),'我的鲜花','/pages/MyHomePage'), - new MyPageSectionClass('fiveItem',$r('app.media.app_icon'),'课件明细','/pages/MyHomePage'), - new MyPageSectionClass('fiveItem',$r('app.media.app_icon'),'课程明细','/pages/MyHomePage') + new MyPageSectionClass('oneItem',$r('app.media.app_icon'),'我的账户','/pages/MyHomePage',false), + new MyPageSectionClass('twoItem',$r('app.media.app_icon'),'我的积分','/pages/MyHomePage',false), + new MyPageSectionClass('threeItem',$r('app.media.app_icon'),'我的福利','/pages/MyHomePage',false), + new MyPageSectionClass('fourItem',$r('app.media.app_icon'),'我的鲜花','/pages/MyHomePage',false), + new MyPageSectionClass('fiveItem',$r('app.media.app_icon'),'课件明细','/pages/MyHomePage',false), + new MyPageSectionClass('fiveItem',$r('app.media.app_icon'),'课程明细','/pages/MyHomePage',false) ]; private getPagedItems(): Array> { diff --git a/features/mypage/src/main/ets/view/TwoSection.ets b/features/mypage/src/main/ets/view/TwoSection.ets index dd301a0..f6930bb 100644 --- a/features/mypage/src/main/ets/view/TwoSection.ets +++ b/features/mypage/src/main/ets/view/TwoSection.ets @@ -9,10 +9,10 @@ export struct TwoSection { @State currentIndex: number = 0; @State twoSectionList:Array = [ - new MyPageSectionClass('oneItem',$r('app.media.app_icon'),'我的视频','/pages/MyHomePage'), - new MyPageSectionClass('twoItem',$r('app.media.app_icon'),'我的课程','/pages/MyHomePage'), - new MyPageSectionClass('threeItem',$r('app.media.app_icon'),'我的下载','/pages/MyHomePage'), - new MyPageSectionClass('fourItem',$r('app.media.app_icon'),'我的收藏','/pages/MyHomePage') + new MyPageSectionClass('oneItem',$r('app.media.app_icon'),'我的视频','/pages/MyHomePage',false), + new MyPageSectionClass('twoItem',$r('app.media.app_icon'),'我的课程','/pages/MyHomePage',false), + new MyPageSectionClass('threeItem',$r('app.media.app_icon'),'我的下载','/pages/MyHomePage',false), + new MyPageSectionClass('fourItem',$r('app.media.app_icon'),'我的收藏','/pages/MyHomePage',false) ]; private getPagedItems(): Array> { diff --git a/features/mypage/src/main/resources/base/media/my_page_QrCode.png b/features/mypage/src/main/resources/base/media/my_page_QrCode.png new file mode 100644 index 0000000..6bd355d Binary files /dev/null and b/features/mypage/src/main/resources/base/media/my_page_QrCode.png differ diff --git a/features/mypage/src/main/resources/base/media/my_page_message.png b/features/mypage/src/main/resources/base/media/my_page_message.png new file mode 100644 index 0000000..d054ad8 Binary files /dev/null and b/features/mypage/src/main/resources/base/media/my_page_message.png differ diff --git a/features/mypage/src/main/resources/base/media/my_page_patientAudit.png b/features/mypage/src/main/resources/base/media/my_page_patientAudit.png new file mode 100644 index 0000000..5dafee4 Binary files /dev/null and b/features/mypage/src/main/resources/base/media/my_page_patientAudit.png differ diff --git a/features/mypage/src/main/resources/base/media/my_page_patientList.png b/features/mypage/src/main/resources/base/media/my_page_patientList.png new file mode 100644 index 0000000..7ca5bb4 Binary files /dev/null and b/features/mypage/src/main/resources/base/media/my_page_patientList.png differ diff --git a/features/mypage/src/main/resources/base/media/my_page_visitPlan.png b/features/mypage/src/main/resources/base/media/my_page_visitPlan.png new file mode 100644 index 0000000..4eade14 Binary files /dev/null and b/features/mypage/src/main/resources/base/media/my_page_visitPlan.png differ diff --git a/features/patient/.gitignore b/features/patient/.gitignore new file mode 100644 index 0000000..e2713a2 --- /dev/null +++ b/features/patient/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/features/patient/BuildProfile.ets b/features/patient/BuildProfile.ets new file mode 100644 index 0000000..3a501e5 --- /dev/null +++ b/features/patient/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/features/patient/Index.ets b/features/patient/Index.ets new file mode 100644 index 0000000..8751295 --- /dev/null +++ b/features/patient/Index.ets @@ -0,0 +1,17 @@ +export { MainPage } from './src/main/ets/components/MainPage'; + +export { PatientApplyPage } from './src/main/ets/components/PatientApplyPage' + +export { PatientSetMsgPage } from './src/main/ets/components/PatientSetMsgPage' + +export { applyListCallBacl, applyListModel, applyHistoryCallBacl , historyObjectModel, historyModel } from './src/main/ets/models/ApplyModel' + +export { PatientsGroup } from './src/main/ets/components/PatientsGroup' + +export { groupRequest,groupRequestCall,groupModel,patientListModel } from './src/main/ets/models/PatientsGroupModel' + +export { BuildOrEditGroupPage } from './src/main/ets/components/BuildOrEditGroupPage' + +export { PatientsListComp } from './src/main/ets/components/PatientsListComp' + +export { PatientDetailsComp } from './src/main/ets/components/PatientDetailsComp' \ No newline at end of file diff --git a/features/patient/build-profile.json5 b/features/patient/build-profile.json5 new file mode 100644 index 0000000..e6773f9 --- /dev/null +++ b/features/patient/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/features/patient/consumer-rules.txt b/features/patient/consumer-rules.txt new file mode 100644 index 0000000..e69de29 diff --git a/features/patient/hvigorfile.ts b/features/patient/hvigorfile.ts new file mode 100644 index 0000000..4218707 --- /dev/null +++ b/features/patient/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/features/patient/obfuscation-rules.txt b/features/patient/obfuscation-rules.txt new file mode 100644 index 0000000..272efb6 --- /dev/null +++ b/features/patient/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/features/patient/oh-package-lock.json5 b/features/patient/oh-package-lock.json5 new file mode 100644 index 0000000..d504604 --- /dev/null +++ b/features/patient/oh-package-lock.json5 @@ -0,0 +1,25 @@ +{ + "meta": { + "stableOrder": true + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@itcast/basic@../../commons/basic": "@itcast/basic@../../commons/basic", + "refreshlib@../../RefreshLib": "refreshlib@../../RefreshLib" + }, + "packages": { + "@itcast/basic@../../commons/basic": { + "name": "@itcast/basic", + "version": "1.0.0", + "resolved": "../../commons/basic", + "registryType": "local" + }, + "refreshlib@../../RefreshLib": { + "name": "refreshlib", + "version": "1.0.0", + "resolved": "../../RefreshLib", + "registryType": "local" + } + } +} \ No newline at end of file diff --git a/features/patient/oh-package.json5 b/features/patient/oh-package.json5 new file mode 100644 index 0000000..81ee9ae --- /dev/null +++ b/features/patient/oh-package.json5 @@ -0,0 +1,12 @@ +{ + "name": "patient", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "Index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@itcast/basic": "file:../../commons/basic", + "refreshlib": "file:../../RefreshLib" + } +} diff --git a/features/patient/src/main/ets/components/BuildOrEditGroupPage.ets b/features/patient/src/main/ets/components/BuildOrEditGroupPage.ets new file mode 100644 index 0000000..a328f45 --- /dev/null +++ b/features/patient/src/main/ets/components/BuildOrEditGroupPage.ets @@ -0,0 +1,304 @@ +import { authStore, HdNav, PositionSelectedSheet } from '@itcast/basic'; +import { promptAction, router } from '@kit.ArkUI' +import { HdLoadingDialog,DefaultHintProWindows } from '@itcast/basic' +import { BasicConstant,hdHttp, HdResponse ,logger} from '@itcast/basic/Index' +import { BusinessError } from '@kit.BasicServicesKit'; +import { patientListModel } from '../models/PatientsGroupModel' + +@Component +export struct BuildOrEditGroupPage { + @State params:Record = router.getParams() as Record + scrollerCon:Scroller = new Scroller() + @State groupPatientList:patientListModel[] = [] + @State groupName:string = '' + private hintWindowDialog!: CustomDialogController + @Consume@Watch('onRefreshAction') refreshFlag: boolean; + + dialog: CustomDialogController = new CustomDialogController({ + builder: HdLoadingDialog({ message: '加载中...' }), + customStyle: true, + alignment: DialogAlignment.Center + }) + + private hintPopWindowDialog() { + this.hintWindowDialog = new CustomDialogController({ + builder:DefaultHintProWindows({ + controller:this.hintWindowDialog, + message:'确定删除该分组', + cancleTitleColor: '#333333', + confirmTitleColor: '#333333', + selectedButton: (index:number)=>{ + if (index === 1) { + this.deleGroupAction() + } + this.hintWindowDialog.close(); + } + }), + alignment: DialogAlignment.Center, + cornerRadius:24, + backgroundColor: ('rgba(0,0,0,0.5)'), + }) + } + + onRefreshAction(flag: boolean) { + const returnParams = this.getUIContext().getRouter().getParams() as Record; + const patients = returnParams?.selectedPatients as patientListModel[] | undefined; + if (patients?.length) { + for (const model of returnParams.selectedPatients as patientListModel[]) { + if (model.isSelected) { + this.groupPatientList.push(model) + } + } + } + } + + aboutToAppear(): void { + if (this.params.title != '新建分组') { + this.getGroupPatientsData() + } + this.hintPopWindowDialog() + } + + getGroupPatientsData() { + this.dialog.open() + hdHttp.post(BasicConstant.patientListByGroup, { + "expert_uuid": authStore.getUser().uuid, + "group_uuid":this.params.group_uuid + } as Record).then(async (res: HdResponse) => { + this.dialog.close(); + logger.info('Response patientListByGroup'+res); + let json:Record = JSON.parse(res+'') as Record; + if(json.code == '1') { + this.groupPatientList = json.data as patientListModel[]; + } else { + console.error('获取患者分组列表失败:'+json.message) + promptAction.showToast({ message: String(json.message), duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + console.error(`Response fails: ${err}`); + }) + } + + deleGroupAction() { + this.dialog.open() + hdHttp.post(BasicConstant.deleteGroup, { + "expert_uuid": authStore.getUser().uuid, + "group_uuid":this.params.group_uuid + } as Record).then(async (res: HdResponse) => { + this.dialog.close(); + logger.info('Response patientListByGroup'+res); + let json:Record = JSON.parse(res+'') as Record; + if(json.code == '1') { + promptAction.showToast({ message: '删除分组成功', duration: 1000 }) + router.back(); + } else { + console.error('删除患者分组列表失败:'+json.message) + promptAction.showToast({ message: json.message, duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + console.error(`Response fails: ${err}`); + }) + } + + setCreatOrEditGroup(index:number) { + if (this.groupName.length <= 0) { + promptAction.showToast({ message: '请输入分组名称', duration: 1000 }) + return + } + const uuidString:string = this.groupPatientList.map(item => item.uuid).join(","); + this.dialog.open() + hdHttp.post(index == 0 ? BasicConstant.addGroup:BasicConstant.updateGroup, index == 0 ? { + "expert_uuid": authStore.getUser().uuid, + "name":this.groupName, + "patient_uuid":uuidString + } as Record : { + "uuid": this.params.group_uuid, + "name":this.groupName, + "patient_uuid":uuidString + } as Record).then(async (res: HdResponse) => { + this.dialog.close(); + logger.info('Response patientListByGroup'+res); + let json:Record = JSON.parse(res+'') as Record; + if(json.code == '1') { + promptAction.showToast({ message:'分组成功', duration: 1000 }) + router.back(); + } else if (json.code == '2') { + promptAction.showToast({ message:'该分组已存在', duration: 1000 }) + } else { + console.error('删除患者分组列表失败:'+json.message) + promptAction.showToast({ message: json.message, duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + console.error(`Response fails: ${err}`); + }) + + + // const postContent = new rcp.MultipartForm({ + // "uuid": this.params.group_uuid, + // "name":this.groupName, + // "patient_uuid":uuidString + // }) + // const session = rcp.createSession(); + // session.post(BasicConstant.updateGroup, postContent) + // .then((response) => { + // this.dialog.close(); + // logger.info('Response patientListByGroup'+response); + // let json:Record = JSON.parse(response+'') as Record; + // if(json.code == '1') { + // promptAction.showToast({ message:'分组成功', duration: 1000 }) + // router.back(); + // } else if (json.code == '2') { + // promptAction.showToast({ message:'该分组已存在', duration: 1000 }) + // } else { + // console.error('删除患者分组列表失败:'+json.message) + // promptAction.showToast({ message: json.message, duration: 1000 }) + // } + // }) + // .catch((err: BusinessError) => { + // this.dialog.close(); + // console.error(`Response err: Code is ${JSON.stringify(err.code)}, message is ${JSON.stringify(err)}`); + // }) + } + + build() { + Row() { + Column() { + HdNav({ + title: this.params.title, + showRightIcon: false, + hasBorder: true, + rightText: '保存', + showRightText: true, + rightItemAction: () => { + if (this.params.title == '新建分组') { + this.setCreatOrEditGroup(0); + } else { + this.setCreatOrEditGroup(1); + } + } + }) + Scroll(this.scrollerCon){ + Column() { + Text('分组名称') + .fontSize(15) + .fontColor('#333333') + .margin({ left: 15 }) + .height(50) + .textAlign(TextAlign.Start) + + TextInput({placeholder:'设置分组名称',text:this.params.group_name}) + .padding({left:15}) + .width('100%') + .height(50) + .backgroundColor(Color.White) + .onChange((input:string)=>{ + this.groupName = input; + }) + + Text('分组成员') + .fontSize(15) + .fontColor('#333333') + .margin({ left: 15 }) + .height(50) + .textAlign(TextAlign.Start) + + Row(){ + Image($r('app.media.add_patients_to_roup')) + .width(50).height(50) + .margin({left:15}) + + Text('添加组患者') + .fontSize(16) + .fontColor('#333333') + .margin({left:15}) + } + .width('100%') + .height(80) + .backgroundColor(Color.White) + .onClick(()=>{ + router.pushUrl({ + url:'pages/PatientsPage/PatientsListPage', + params:{group_uuid:this.params.group_uuid,selectedPatients:this.groupPatientList} + }) + }) + + List(){ + ListItemGroup({footer:this.footerView()}) { + ForEach(this.groupPatientList,(item:patientListModel,index:number)=>{ + ListItem(){ + this.patientsListItem(item,index) + } + }) + } + } + + }.width('100%').alignItems(HorizontalAlign.Start).justifyContent(FlexAlign.Start) + } + .width('100%').height('calc(100% - 56vp - 55vp)') + .scrollBar(BarState.Off) + .backgroundColor('#f4f4f4') + .align(Alignment.TopStart) + } + .width('100%').height('100%') + } + .height('100%') + } + + @Builder + footerView (){ + Column() { + Text('删除分组') + .fontSize(16) + .fontColor(Color.White) + .backgroundColor($r('app.color.main_color')) + .borderRadius(5) + .height(50) + .textAlign(TextAlign.Center) + .width('90%') + .onClick(()=>{ + this.hintWindowDialog.open(); + }) + .visibility(this.params.title == '新建分组'?Visibility.Hidden:Visibility.Visible) + }.width('100%') + .height(120) + .justifyContent(FlexAlign.End) + } + + @Builder + patientsListItem(item:patientListModel,index:number) { + Column() { + Row() { + Image(BasicConstant.urlImage + item.photo) + .alt($r('app.media.userPhoto_default')) + .borderRadius(6) + .width(50) + .height(50) + .margin({ left: 15 }) + Text(item.nickname ? item.nickname : item.realname) + .fontSize(16) + .fontColor('#333333') + .margin({ left: 15 }) + Blank() + Image($r('app.media.dele_patient_inThe_group')) + .width(22).height(22) + .objectFit(ImageFit.Fill) + .margin({ right: 15 }) + .onClick(()=>{ + this.groupPatientList.splice(index,1); + this.groupPatientList = [...this.groupPatientList]; + }) + } + .width('100%') + .height(80) + .backgroundColor(Color.White) + Blank() + .width('80%') + .height(1) + .backgroundColor(Color.Gray) + .margin({left:60}) + } + } +} diff --git a/features/patient/src/main/ets/components/MainPage.ets b/features/patient/src/main/ets/components/MainPage.ets new file mode 100644 index 0000000..9de5eb3 --- /dev/null +++ b/features/patient/src/main/ets/components/MainPage.ets @@ -0,0 +1,19 @@ +@Component +export struct MainPage { + @State message: string = 'Hello World'; + + build() { + Row() { + Column() { + Text(this.message) + .fontSize($r('app.float.page_text_font_size')) + .fontWeight(FontWeight.Bold) + .onClick(() => { + this.message = 'Welcome'; + }) + } + .width('100%') + } + .height('100%') + } +} diff --git a/features/patient/src/main/ets/components/PatientApplyPage.ets b/features/patient/src/main/ets/components/PatientApplyPage.ets new file mode 100644 index 0000000..84c8b66 --- /dev/null +++ b/features/patient/src/main/ets/components/PatientApplyPage.ets @@ -0,0 +1,222 @@ +import { ApplyViews } from '../views/ApplyViews' +import { authStore, HdNav } from '@itcast/basic'; +import { applyListCallBacl,applyListModel,applyHistoryCallBacl,historyModel } from '../models/ApplyModel' +import HashMap from '@ohos.util.HashMap'; +import { HdLoadingDialog } from '@itcast/basic' +import { BasicConstant,hdHttp, HdResponse ,logger} from '@itcast/basic/Index' +import { BusinessError } from '@kit.BasicServicesKit'; +import { promptAction, router } from '@kit.ArkUI' +import { patientDbManager, PatientData } from '@itcast/basic'; +import { PullToRefreshLayout, RefreshController } from 'refreshlib' + +interface extraData { + expertUuid: string, + page: number, + uuid: string, + status: string +} + +@Component +export struct PatientApplyPage { + public controller:RefreshController = new RefreshController(); + scroller = new Scroller(); + @State applyArray:applyListModel[] = []; + @State historyArray:historyModel[] = []; + @State pageNumber:number = 1; + @State totalPageNumer:number = 1; + + dialog: CustomDialogController = new CustomDialogController({ + builder: HdLoadingDialog({ message: '加载中...' }), + customStyle: true, + alignment: DialogAlignment.Center + }) + + aboutToAppear(): void { + this.getApplyList(); + this.getHistoryApplyList(); + } + + getHistoryApplyList() { + const hashMap: HashMap = new HashMap(); + hashMap.set('page',this.pageNumber.toString()); + this.dialog.open() + hdHttp.httpReq(BasicConstant.relationRecordLately,hashMap).then(async (res: HdResponse) => { + this.dialog.close(); + this.controller.refreshSuccess(); + this.controller.loadSuccess(); + logger.info('Response relationRecordLately'+res); + let json:applyHistoryCallBacl = JSON.parse(res+'') as applyHistoryCallBacl; + if(json.code == 200) { + this.historyArray = json.data.list; + this.totalPageNumer = Number(json.data.total); + } else { + console.error('患者申请记录列表失败:'+json.message) + promptAction.showToast({ message: json.message, duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + this.controller.refreshError(); + console.info(`Response fails: ${err}`); + }) + } + + getApplyList() { + this.dialog.open() + hdHttp.post(BasicConstant.applyList, { + expertUuid: authStore.getUser().uuid, + } as extraData).then(async (res: HdResponse) => { + this.dialog.close(); + this.controller.refreshSuccess(); + this.controller.loadSuccess(); + logger.info('Response applyList'+res); + let json:applyListCallBacl = JSON.parse(res+'') as applyListCallBacl; + if(json.code == 1) { + this.applyArray = json.data; + } else { + console.error('新的患者列表失败:'+json.message) + promptAction.showToast({ message: json.message, duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + this.controller.refreshError(); + console.info(`Response fails: ${err}`); + }) + } + + getApplyListOperate(status: string,model:applyListModel) { + this.dialog.open() + hdHttp.post(BasicConstant.applyListOperate, { + uuid: model.uuid, + status: status + } as extraData).then(async (res: HdResponse) => { + this.dialog.close(); + logger.info('Response applyListOperate'+res); + let json:applyListCallBacl = JSON.parse(res+'') as applyListCallBacl; + if(json.code == 1) { + if (status == '2') { + this.getApplyList(); + // 添加单个患者 + const singlePatient: PatientData = { + uuid:model.patientUuid as string, + nickname: '', + mobile: model.mobile as string, + realName: model.realName as string, + nation: '', + sex: model.sex as number, + type: 1, + photo: model.photo as string, + expertUuid: authStore.getUser().uuid + }; + const success1 = await patientDbManager.addPatient(singlePatient); + if (success1) { + console.info('添加成功'); + const patients = await patientDbManager.getAllPatients(); + promptAction.showToast({message:`现在一共是 ${patients.length} 个患者`}) + } else { + console.info('添加失败'); + } + promptAction.showToast({ message: '消息已处理', duration: 1000 }) + router.pushUrl({ + url:'pages/PatientsPage/PatientMsgSetPage', + params:{ 'model':model } + }) + } else if (status == '3') { + this.pageNumber = 1; + this.getApplyList(); + this.getHistoryApplyList(); + } + } else { + console.error('患者列表申请处理失败:'+json.message) + promptAction.showToast({ message: json.message, duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + console.info(`Response fails: ${err}`); + }) + } + + build() { + Column(){ + HdNav({ title: '新的患者', showRightIcon: false, hasBorder: true }) + PullToRefreshLayout({ + scroller:this.scroller, + viewKey:"ListPage", + controller:this.controller, + contentView:()=>{ + this.contentView() + }, + onRefresh:()=>{ + this.pageNumber = 1; + this.getApplyList(); + this.getHistoryApplyList(); + }, + onCanPullRefresh:()=>{ + if (!this.scroller.currentOffset()) { + /*处理无数据,为空的情况*/ + return true + } + //如果列表到顶,返回true,表示可以下拉,返回false,表示无法下拉 + return this.scroller.currentOffset().yOffset <= 0 + }, + onLoad:()=>{ + this.pageNumber++; + this.getApplyList(); + this.getHistoryApplyList(); + }, + onCanPullLoad: () => { + if (this.pageNumber >= this.totalPageNumer) { + return false; + } else { + return true; + } + } + }).width('100%').height('calc(100% - 156vp)').clip(true) + + Row(){ + Text('加患者') + .fontSize(16) + .fontColor(Color.White) + .backgroundColor('rgb(63,199,193)') + .width('100%').height(50).textAlign(TextAlign.Center) + .onClick(()=>{ + router.pushUrl({ + url: 'pages/WebView/WebPage', // 目标url + params: {url:BasicConstant.wxUrl+'expert/expertcodeimg?expert_uuid='+authStore.getUser().uuid,title:'我的二维码'} + }) + }) + }.width('100%').height(56).backgroundColor(Color.White).alignItems(VerticalAlign.Top) + }.width('100%').height('100%') + } + + @Builder + contentView(){ + Column(){ + Row({space:5}){ + Image($r('app.media.addPatientApply_reminder_icon')) + .width(18).height(21) + Text('提醒: 为了避免不必要的纠纷,请您务必选择线下就诊过的患者') + .fontSize(16).fontColor('#666666') + }.width('100%').padding({left:10,top:10,right:20,bottom:10}).backgroundColor(Color.White) + if (this.applyArray.length > 0) { + Column(){ + Text('随访申请') + .fontSize(15).fontColor('#333333').margin({left:10}).height(42) + ForEach(this.applyArray,(item:applyListModel)=>{ + ApplyViews({applyItme:item,isApply:true,applyItemAction:((status: string,model:applyListModel)=>{ + this.getApplyListOperate(status,model) + })}) + }) + }.width('100%').alignItems(HorizontalAlign.Start) + } + if (this.historyArray.length > 0) { + Column(){ + Text('申请记录(近一月)') + .fontSize(15).fontColor('#333333').margin({left:10}).height(42) + ForEach(this.historyArray,(item:historyModel)=>{ + ApplyViews({historyItem:item,isApply:false}) + }) + }.width('100%').alignItems(HorizontalAlign.Start) + } + }.width('100%').height('100%').backgroundColor('#f4f4f4') + } +} diff --git a/features/patient/src/main/ets/components/PatientDetailsComp.ets b/features/patient/src/main/ets/components/PatientDetailsComp.ets new file mode 100644 index 0000000..e6d037f --- /dev/null +++ b/features/patient/src/main/ets/components/PatientDetailsComp.ets @@ -0,0 +1,198 @@ +import { authStore, HdNav, PositionSelectedSheet } from '@itcast/basic'; +import { promptAction, router } from '@kit.ArkUI' +import { HdLoadingDialog,DefaultHintProWindows } from '@itcast/basic' +import { BasicConstant,hdHttp, HdResponse ,logger} from '@itcast/basic/Index' +import { BusinessError } from '@kit.BasicServicesKit'; +import HashMap from '@ohos.util.HashMap'; +import { patientListModel } from '../models/PatientsGroupModel' +import measure from '@ohos.measure'; + +@Component +export struct PatientDetailsComp { + scroller:Scroller = new Scroller() + @Consume@Watch('onRefreshAction') refreshFlag: boolean + @State params:Record = router.getParams() as Record + @State groupArray:Array> = [] + @State footerArray:Array> = [] + @State patientCase:Array> = [] + @State patientData:Record = {} + @State patientData2:Record = {} + @State medicalHistoryContent:string = '' + @State isExpanded: boolean = false; // 展开状态 + @State showExpandBtn: boolean = false; // 是否显示操作按钮 + + dialog: CustomDialogController = new CustomDialogController({ + builder: HdLoadingDialog({ message: '加载中...' }), + customStyle: true, + alignment: DialogAlignment.Center + }) + + onRefreshAction() { + this.getPatientCardData() + } + + aboutToAppear(): void { + this.getPatientCardData() + this.footerArray = [{"img":$r('app.media.sendMessage_blackBtn'),"title":"发消息"},{"img":$r('app.media.fuifangPlan_blackBtn'),"title":"制定随访计划"},{"img":$r('app.media.listBing_blackBtn'),"title":"记录病情"}] + } + + getPatientCardData(){ + const hashMap: HashMap = new HashMap(); + hashMap.set('patient_uuid',String(this.params.patient_uuid)); + this.dialog.open() + hdHttp.httpReq(BasicConstant.patientCard,hashMap).then(async (res: HdResponse) => { + this.dialog.close(); + logger.info('Response patientCard'+res); + let json:Record = JSON.parse(res+'') as Record + const isFriend = String(json.isFriend) + if (isFriend == '0') { + promptAction.showToast({ message: '随访关系已解除', duration: 1000 }) + router.back() + } else { + if(json.code == '200') { + this.getPatientDetailsData(String(json.group["name"])) + } else { + console.error('患者详情请求失败:'+json.message) + } + } + }).catch((err: BusinessError) => { + this.dialog.close(); + console.info(`Response fails: ${err}`); + }) + } + + getPatientDetailsData(groupType:string){ + this.dialog.open() + hdHttp.post(BasicConstant.toAddNickname, { + "expertUuid": authStore.getUser().uuid, + "patientUuid":String(this.params.patient_uuid) + } as Record).then(async (res: HdResponse) => { + this.dialog.close(); + logger.info('Response toAddNickname'+res); + let json:Record> = JSON.parse(res+'') as Record>; + if(json.code == '1') { + this.getPatientData() + this.patientData = json.patientEx as Record + let nickname = this.patientData.nickname + let note = this.patientData.note + let mobile = this.patientData.mobile + if (nickname.length>0) { + this.groupArray = [{"title":"备注","content":String(nickname),"prompt":"给患者添加备注名"},{"title":"分组","content":String(groupType),"prompt":"通过分组给患者分类"},{"title":"描述","content":String(note),"prompt":"补充患者关键信息,方便随访患者"},{"title":"电话号码","content":String(mobile),"prompt":""}] + } else { + this.groupArray = [{"title":"分组","content":String(groupType),"prompt":"通过分组给患者分类"},{"title":"描述","content":String(note),"prompt":"补充患者关键信息,方便随访患者"},{"title":"电话号码","content":String(mobile),"prompt":""}] + } + } else { + console.error('获取患者信息失败:'+json.message) + promptAction.showToast({ message: String(json.message), duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + console.error(`Response fails: ${err}`); + }) + } + + getPatientData() { + this.dialog.open() + hdHttp.post(BasicConstant.patientDetail, { + "patientUuid":String(this.params.patient_uuid) + } as Record).then(async (res: HdResponse) => { + this.dialog.close(); + logger.info('Response patientDetail'+res); + let json:Record | Array>> = JSON.parse(res+'') as Record | Array>>; + if(json.code == '1') { + this.patientData2 = json.data as Record + this.medicalHistoryContent = String(json.medicalHistoryContent) + this.patientCase = json.patientCase as Array> + } else { + console.error('获取患者信息失败:'+json.message) + promptAction.showToast({ message: String(json.message), duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + console.error(`Response fails: ${err}`); + }) + } + + build() { + Row() { + Column() { + HdNav({ + title: '患者详情', + showRightIcon: true, + hasBorder: true, + rightIcon:$r("app.media.patient_details_navigation_right"), + showRightText: false, + rightItemAction: () => { + router.pushUrl({ + url: 'pages/PatientsPage/BuildOrEditGroupPage', + params:{"title":"新建分组"} + }) + } + }) + Scroll(this.scroller){ + this.historyView() + this.footerView() + }.width('100%').height('calc(100% - 56vp)').backgroundColor('#f4f4f4') + .scrollBar(BarState.Off) + } + .width('100%').height('100%') + } + .height('100%') + } + + @Builder + historyView(){ + Column({space:10}){ + Text('患者病史') + .fontSize(15) + .fontColor('#333333') + + Text(this.medicalHistoryContent) + .fontSize(16) + .lineHeight(22) + .maxLines(this.isExpanded ? 0 : 2) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .onAreaChange((_, area) => { + let fullHeight = measure.measureTextSize({ + textContent: this.medicalHistoryContent, + fontSize: 15, + maxLines:2 + }).height; + this.showExpandBtn = Number(area.height) < Number(fullHeight); + }) + // 操作按钮(独立可点击区域) + if (this.showExpandBtn) { + Text(this.isExpanded ? "...收起" : "..展开全部") + .fontSize(15) + .fontColor($r('app.color.main_color')) // 红色标识可点击 + .onClick(() => { + this.isExpanded = !this.isExpanded; // 切换状态 + }) + } + } + .alignItems(HorizontalAlign.Start) + .backgroundColor(Color.White) + .width('100%') + .padding(15) + } + + @Builder + footerView(){ + List(){ + ForEach(this.footerArray,(item:Record,index:number)=>{ + ListItem(){ + Row(){ + Image(item.img) + .width(20).height(20) + Text(item.title) + .fontSize(15) + } + .height(49) + .width('100%') + .justifyContent(FlexAlign.Center) + .backgroundColor(Color.White) + }.width('100%').height(50) + }) + } + } +} diff --git a/features/patient/src/main/ets/components/PatientSetMsgPage.ets b/features/patient/src/main/ets/components/PatientSetMsgPage.ets new file mode 100644 index 0000000..09c046e --- /dev/null +++ b/features/patient/src/main/ets/components/PatientSetMsgPage.ets @@ -0,0 +1,197 @@ +import { authStore, HdNav } from '@itcast/basic'; +import { applyListModel } from '../models/ApplyModel' +import HashMap from '@ohos.util.HashMap'; +import { HdLoadingDialog } from '@itcast/basic' +import { BasicConstant,hdHttp, HdResponse ,logger} from '@itcast/basic/Index' +import { BusinessError } from '@kit.BasicServicesKit'; +import { Font, promptAction, router } from '@kit.ArkUI' +import { patientDbManager, PatientData } from '@itcast/basic'; + +interface callBackData { + code: string, + data: object, + msg: string, + message: string +} + +interface paramsCallData { + model:applyListModel; +} + +@Component +export struct PatientSetMsgPage { + @State params:paramsCallData = router.getParams() as paramsCallData; + @State noteName: string | undefined = ''; + @State contentFrist:string | undefined = ''; + @State groupName: string = '通过分组给患者分类'; + @State maxDescribe:string = '0'; + @State descibe:string = ''; + @State isNote:boolean = false; + @State isDescibe:boolean = false; + + dialog: CustomDialogController = new CustomDialogController({ + builder: HdLoadingDialog({ message: '加载中...' }), + customStyle: true, + alignment: DialogAlignment.Center + }) + + aboutToAppear(): void { + this.contentFrist = getFirstSegment(this.params.model.content) + } + + patientMsgSubmit(){ + const hashMap: HashMap = new HashMap(); + hashMap.set('patient_uuid',this.params.model.patientUuid); + hashMap.set('nickname',this.noteName); + hashMap.set('note',this.descibe); + this.dialog.open() + hdHttp.httpReq(BasicConstant.updateNicknameNote,hashMap).then(async (res: HdResponse) => { + this.dialog.close(); + logger.info('Response relationRecordLately'+res); + let json:callBackData = JSON.parse(res+'') as callBackData; + if(json.code == '200') { + // 添加单个患者 + const singlePatient: PatientData = { + uuid:this.params.model.patientUuid as string, + nickname: this.noteName as string, + mobile: this.params.model.mobile as string, + realName: this.params.model.realName as string, + nation: '', + sex: this.params.model.sex as number, + type: 1, + photo: this.params.model.photo as string, + expertUuid: authStore.getUser().uuid + }; + const success1 = await patientDbManager.addPatient(singlePatient); + if (success1) { + console.info('添加成功'); + const patients = await patientDbManager.getAllPatients(); + promptAction.showToast({message:`现在一共是 ${patients.length} 个患者`}) + } else { + console.info('添加失败'); + } + promptAction.showToast({ message: '设置成功', duration: 1000 }) + router.back() + } else { + console.error('患者申请记录列表失败:'+json.message) + promptAction.showToast({ message: json.message, duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + console.info(`Response fails: ${err}`); + }) + } + + build() { + Column() { + HdNav({ title: '设置备注和分组', showRightIcon: false, hasBorder: true }) + + Text('备注') + .margin({left:15,top:15}) + .fontSize(15).fontColor('#333333') + + TextInput({placeholder:'给患者添加备注名',text:this.noteName}) + .fontSize(15).fontColor('#333333').placeholderColor('#999999') + .padding({left:10,right:10}) + .width('calc(100% - 30vp)') + .height(50) + .backgroundColor('#f4f4f4') + .borderRadius(4) + .maxLength(20) + .margin({left:15,top:10}) + .onChange((value:string)=>{ + this.noteName = value; + if (value.length > 0) { + this.isNote = true; + }else { + this.isNote = false; + } + }) + + Row() { + Text('申请消息为:'+this.contentFrist) + .fontSize(15).fontColor('#666666') + Text('填入') + .fontSize(15).fontColor('#3CC7C0') + }.justifyContent(FlexAlign.Start).margin({left:15,top:5}) + // .visibility(this.contentFrist?Visibility.Visible:Visibility.Hidden) + .onClick(()=>{ + this.noteName = this.contentFrist?.substring(2); + }) + + Text('分组') + .margin({left:15,top:15}) + .fontSize(15).fontColor('#333333') + + Row(){ + Text(this.groupName) + .fontSize(15).fontColor(this.groupName.includes('通过分组给患者分类')?'#999999':'#333333') + .margin({left:10}) + .layoutWeight(1) + Image($r('sys.media.ohos_ic_public_arrow_right')) + .width(15).height(15).margin({right:10}) + } + .height(50) + .width('calc(100% - 30vp)') + .backgroundColor('#f4f4f4') + .borderRadius(4) + .margin({left:15,top:10}) + .onClick(()=>{ + + }) + + Text('描述') + .margin({left:15,top:15}) + .fontSize(15).fontColor('#333333') + + Column() { + TextArea({ placeholder: '补充患者关键信息,方便随访患者' }) + .fontSize(15).placeholderColor('#999999') + .fontColor('#333333') + .backgroundColor('#f4f4f4') + .borderRadius(4) + .maxLength(100) + .padding({ + left: 10, + top: 10, + right: 10, + bottom: 10 + }) + .width('100%').height(100) + .onChange((value: string) => { + this.maxDescribe = value.length.toString(); + this.descibe = value; + if (value.length > 0) { + this.isDescibe = true; + } else { + this.isDescibe = false; + } + }) + Text('已输入'+this.maxDescribe+'/100') + .fontSize(13).fontColor('#cccccc').textAlign(TextAlign.End).width('90%') + .margin({top:-20}) + } + .margin({ left: 15,top: 10,}).width('calc(100% - 30vp)').height(120).borderRadius(4) + .layoutWeight(1) + + Text('完成') + .width('100%').height(45) + .fontSize(18).fontColor(Color.White).textAlign(TextAlign.Center) + .backgroundColor(this.isNote||this.isDescibe?'#3CC7C0':'#cccccc') + .margin({bottom:10}) + .onClick(()=>{ + if (this.isNote || this.isDescibe) { + this.patientMsgSubmit(); + } + }) + + }.justifyContent(FlexAlign.Start).alignItems(HorizontalAlign.Start) + .height('100%') + } +} + +function getFirstSegment(content?: string): string | undefined { + if (!content) return undefined; + const segments: string[] = content.split(/[\uFF0C,]/); + return segments.find(seg => seg.trim() !== ''); +} \ No newline at end of file diff --git a/features/patient/src/main/ets/components/PatientsGroup.ets b/features/patient/src/main/ets/components/PatientsGroup.ets new file mode 100644 index 0000000..c6f1a9f --- /dev/null +++ b/features/patient/src/main/ets/components/PatientsGroup.ets @@ -0,0 +1,315 @@ +import { authStore, HdNav, PositionSelectedSheet } from '@itcast/basic'; +import { promptAction, router } from '@kit.ArkUI' +import { HdLoadingDialog } from '@itcast/basic' +import { BasicConstant,hdHttp, HdResponse ,logger} from '@itcast/basic/Index' +import { BusinessError } from '@kit.BasicServicesKit'; +import HashMap from '@ohos.util.HashMap'; +import { groupRequest,groupRequestCall,groupModel,patientListModel } from '../models/PatientsGroupModel' + +@Component +export struct PatientsGroup { + @State groupSort: string = '分组排序' + @State innerSort: string = '组内排序' + @State groupSortList:sortModel[] = []; + @State innerSortList:sortModel[] = []; + @State groupSortSelected: boolean = false//是否展开 + @State innerSortSelected: boolean = false//是否展开 + @State isGroupSelected: boolean = false//是否选中 + @State IsInnerSelected: boolean = false//是否选中 + @State isMaiLanHidden:boolean = false;//麦兰项目是否显示 + @State group_sort:string = '0' + @State list_sort:string = '0' + @State groupListArray: groupModel[] = []; + @Consume@Watch('onRefreshAction') refreshFlag: boolean; + + onRefreshAction(flag: boolean) { + this.getGroupData(); + } + + dialog: CustomDialogController = new CustomDialogController({ + builder: HdLoadingDialog({ message: '加载中...' }), + customStyle: true, + alignment: DialogAlignment.Center + }) + + aboutToAppear(): void { + this.getIsMaiLanData(); + this.groupSortList = [{"name":"按首字母","isSeleted":false} as sortModel,{"name":"分组人数","isSeleted":false} as sortModel] + this.innerSortList = [{"name":"按首字母","isSeleted":false} as sortModel,{"name":"随访时间","isSeleted":false} as sortModel] + } + + getGroupData(){ + this.dialog.open() + hdHttp.post(BasicConstant.groupList, { + expert_uuid: authStore.getUser().uuid, + group_sort:this.group_sort, + list_sort:this.list_sort + } as groupRequest).then(async (res: HdResponse) => { + this.dialog.close(); + logger.info('Response groupList'+res); + let json:groupRequestCall = JSON.parse(res+'') as groupRequestCall; + if(json.code == 1) { + this.groupListArray = [] + this.groupListArray = json.data + } else { + console.error('患者分组列表失败:'+json.message) + promptAction.showToast({ message: json.message, duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + console.error(`Response fails: ${err}`); + }) + } + + getIsMaiLanData() { + const hashMap: HashMap = new HashMap(); + this.dialog.open() + hdHttp.httpReq(BasicConstant.isMaiLanExpert,hashMap).then(async (res: HdResponse) => { + this.dialog.close(); + logger.info('Response isMaiLanExpert'+res); + let json:Record = JSON.parse(res+'') as Record; + if(json.code == '200') { + let isMaiLanExpert:string = json.isMaiLanExpert; + if (isMaiLanExpert == '1') { + this.isMaiLanHidden = true; + } + } else { + console.error('麦兰:'+json.message) + promptAction.showToast({ message: json.message, duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + console.info(`Response fails: ${err}`); + }) + } + + build() { + Column() { + HdNav({ + title: '患者分组', + showRightIcon: false, + hasBorder: true, + rightText: '新建', + showRightText: true, + rightItemAction: () => { + router.pushUrl({ + url: 'pages/PatientsPage/BuildOrEditGroupPage', + params:{"title":"新建分组"} + }) + } + }) + Stack() { + Row() { + Row() { + Text(this.groupSort) + .fontSize(16).fontColor(this.isGroupSelected ? $r('app.color.main_color') : '#333333') + Image(this.isGroupSelected ?$r('app.media.triangle_green_theme'):$r('app.media.triangle_normal')).width(10).height(10) + } + .width('50%') + .height(48) + .backgroundColor(Color.White) + .justifyContent(FlexAlign.Center) + .onClick(() => { + this.groupSortSelected = !this.groupSortSelected + this.innerSortSelected = false + }) + + Blank() + .width(1).height(20).margin({ top: 15 }) + Row() { + Text(this.innerSort) + .fontSize(16).fontColor(this.IsInnerSelected ? $r('app.color.main_color') : '#333333') + Image(this.IsInnerSelected ?$r('app.media.triangle_green_theme'):$r('app.media.triangle_normal')).width(10).height(10) + } + .width('50%') + .height(48) + .backgroundColor(Color.White) + .justifyContent(FlexAlign.Center) + .onClick(() => { + this.innerSortSelected = !this.innerSortSelected + this.groupSortSelected = false + }) + }.width('100%').height(55).backgroundColor('#f4f4f4') + + List() { + ForEach(this.groupListArray, (sectionModel: groupModel,index:number) => { + ListItemGroup({ header: this.itemHeaderView(sectionModel,index) }) { + ForEach(sectionModel.isShow ? sectionModel.patientList : [], (rowModel: patientListModel) => { + ListItem() { + Stack() { + Row({ space: 15 }) { + Image(BasicConstant.urlImage + rowModel.photo) + .alt($r('app.media.userPhoto_default')) + .width(54) + .height(54) + .borderRadius(6) + .margin({ left: 15 }) + Text(rowModel.nickname ? rowModel.nickname : rowModel.realName) + .fontSize(16).fontColor('#666666') + if (Number(rowModel.type) === 0) { + Image($r('app.media.group_vip')) + .objectFit(ImageFit.Cover) + .width(10).height(10) + } + }.width('100%').height(80) + + Text('随访于' + rowModel.join_date?.substring(0, 10)) + .fontSize(14) + .fontColor('#999999') + .textAlign(TextAlign.End) + .margin({ right: 15 }) + .height(30) + Row() + .width('95%').height(0.5) + .backgroundColor('#999999') + }.width('100%').height(80).alignContent(Alignment.BottomEnd) + .onClick(()=>{ + router.pushUrl({ + url:'pages/PatientsPage/PatientDetailsPage', + params:{"patient_uuid":rowModel.uuid} + }) + }) + }.width('100%') + }) + } + }) + } + .width('100%') + .height('calc(100% - 55vp - 56vp)') + .backgroundColor('#f4f4f4') + .scrollBar(BarState.Off) + .sticky(StickyStyle.Header) + .margin({top:55}) + + List() { + ForEach(this.groupSortList, (item: sortModel) => { + ListItem() { + Column() { + Row() { + Text(item.name) + .fontSize(16) + .fontColor(item.isSeleted ? $r('app.color.main_color') : 'rgba(144,144,144)') + .margin({ left: 20 }) + Blank() + if (item.isSeleted) { + Image($r('app.media.chose_card')) + .width(20).height(20).margin({ right: 25 }) + } + }.width('100%').height(50).backgroundColor(Color.White) + + Blank() + .width('100%').height(1).backgroundColor($r('app.color.main_color')).margin({left:20}) + }.onClick(()=>{ + this.isGroupSelected = true; + this.innerSortSelected = false + this.groupSortSelected = false + this.groupSort = String(item.name); + this.group_sort = item.name == '按首字母' ? '0' : '1' + this.groupSortList.forEach((element: sortModel) => { + element.isSeleted = false + }) + const indexof = this.groupSortList.indexOf(item) + if (indexof !== -1) { + this.groupSortList[indexof].isSeleted = true + } + this.groupSortList = [...this.groupSortList] + this.getGroupData() + }) + } + }) + }.width('100%').height('calc(100% - 55vp)').backgroundColor('rgba(0,0,0,0.5)').margin({top:55}) + .visibility(this.groupSortSelected?Visibility.Visible:Visibility.Hidden) + + List() { + ForEach(this.innerSortList, (item: sortModel) => { + ListItem() { + Column() { + Row() { + Text(item.name) + .fontSize(16) + .fontColor(item.isSeleted ? $r('app.color.main_color') : 'rgba(144,144,144)') + .margin({ left: 20 }) + Blank() + if (item.isSeleted) { + Image($r('app.media.chose_card')) + .width(20).height(20).margin({ right: 25 }) + } + }.width('100%').height(50).backgroundColor(Color.White) + + Blank() + .width('95%').height(1).backgroundColor($r('app.color.main_color')) + } + .onClick(()=>{ + this.IsInnerSelected = true + this.innerSortSelected = false + this.groupSortSelected = false + this.innerSort = String(item.name); + this.list_sort = item.name == '按首字母' ? '0' : '1' + this.innerSortList.forEach((element: sortModel) => { + element.isSeleted = false + }); + const indexof = this.innerSortList.indexOf(item) + if (indexof !== -1) { + this.innerSortList[indexof].isSeleted = true + } + this.innerSortList = [...this.innerSortList] + this.getGroupData() + }) + } + }) + }.width('100%').height('calc(100% - 55vp)').backgroundColor('rgba(0,0,0,0.5)').margin({top:55}) + .visibility(this.innerSortSelected?Visibility.Visible:Visibility.Hidden) + + Image($r('app.media.lifetime_right_icon')) + .width(76).height(40) + .position({ x: '80%', y: '80%' }) // 定位到右下角 + .visibility(this.isMaiLanHidden?Visibility.Visible:Visibility.Hidden) + + }.width('100%').height('calc(100% - 56vp)').backgroundColor('#f4f4f4').alignContent(Alignment.TopStart) + } + } + + @Builder + itemHeaderView(model:groupModel,index:number) { + Column() { + Row() { + Image(model.isShow ? $r('app.media.group_turnDown') : $r('app.media.group_turnRight')) + .width(model.isShow ? 10 : 5).height(model.isShow ? 5 : 10).margin({ left: 15 }) + Text(model.name + ' | ' + model.patientNum) + .fontSize(16) + .fontColor('#333333') + .margin({ left: 15 }) + .layoutWeight(1) + Text('编辑') + .width(60) + .height(60) + .fontSize(15) + .fontColor('#981308') + .margin({ right: 15 }) + .textAlign(TextAlign.End) + .visibility(model.name != '待分组患者'?Visibility.Visible:Visibility.Hidden) + .onClick(()=>{ + router.pushUrl({ + url: 'pages/PatientsPage/BuildOrEditGroupPage', + params:{"title":"编辑分组","group_uuid":model.uuid,"group_name":model.name} + }) + }) + } + .width('100%') + .height(60) + .onClick(() => { + let newModel = new groupModel(model); + newModel.isShow = !model.isShow; + this.groupListArray[index] = newModel; + this.groupListArray = [...this.groupListArray]; + }) + Blank() + .width('100%').height(1).backgroundColor('#666666') + }.width('100%').height(61).backgroundColor(Color.White) + } +} + +export class sortModel { + name?:string; + isSeleted:boolean = false; +} \ No newline at end of file diff --git a/features/patient/src/main/ets/components/PatientsListComp.ets b/features/patient/src/main/ets/components/PatientsListComp.ets new file mode 100644 index 0000000..4647a7d --- /dev/null +++ b/features/patient/src/main/ets/components/PatientsListComp.ets @@ -0,0 +1,179 @@ +import { authStore, ChangeUtil, HdNav, PositionSelectedSheet, EmptyViewComp,HdLoadingDialog } from '@itcast/basic'; +import { promptAction, router } from '@kit.ArkUI' +import { BasicConstant,hdHttp, HdResponse ,logger} from '@itcast/basic/Index' +import { BusinessError } from '@kit.BasicServicesKit'; +import HashMap from '@ohos.util.HashMap'; +import { patientListModel } from '../models/PatientsGroupModel' + +@Component +export struct PatientsListComp { + @State params:Record = router.getParams() as Record + @State patientsArray:patientListModel[] = [] + @State patientsList:patientListModel[] = [] + @State inputString:string = '' + @State naviRightTitle:string = '确定(0)' + @State isEmptyViewVisible: boolean = false; // 控制显隐的状态变量 + + aboutToAppear(): void { + this.getPatientsListData(); + } + + dialog: CustomDialogController = new CustomDialogController({ + builder: HdLoadingDialog({ message: '加载中...' }), + customStyle: true, + alignment: DialogAlignment.Center + }) + + getPatientsListData() { + const hashMap: HashMap = new HashMap(); + hashMap.set('group_uuid',String(this.params.group_uuid)); + this.dialog.open() + hdHttp.httpReq(BasicConstant.patientListNoInThisGroup,hashMap).then(async (res: HdResponse) => { + this.dialog.close(); + logger.info('Response patientListNoInThisGroup'+res); + let json:Record = JSON.parse(res+'') as Record; + if(json.code == '1') { + const patientsList = this.params?.selectedPatients as patientListModel[] | undefined; + if (patientsList?.length) { + const uuidSet = new Set(patientsList.map(item => item.uuid)); + const dataArray = json.data as patientListModel[]; + for (const model of dataArray) { + if (!uuidSet.has(model.uuid)) { + this.patientsList.push(model); + this.patientsArray.push(model); + } + } + } else { + this.patientsList = json.data as patientListModel[]; + this.patientsArray = json.data as patientListModel[]; + } + this.isEmptyViewVisible = this.patientsList.length>0?false:true + } else { + console.error('分组患者列表失败:'+json.message) + promptAction.showToast({ message: String(json.message), duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + console.info(`Response fails: ${err}`); + }) + } + + searchPatientAction(){ + if (this.inputString.length > 0) { + this.patientsList = [] + for (const model of this.patientsArray) { + if (model.realname?.includes(this.inputString) || model.mobile?.includes(this.inputString) || model.nickname?.includes(this.inputString)) { + this.patientsList.push(model) + } + } + } else { + this.patientsList = this.patientsArray + } + this.isEmptyViewVisible = this.patientsList.length>0?false:true + } + + build() { + Column() { + HdNav({showLeftIcon:true,title:'选择患者',rightText:this.naviRightTitle,showRightText:true,rightTextColor:Color.White,rightBackColor:$r('app.color.main_color'),showRightIcon:false,rightItemAction:()=>{ + router.back({ + url:'pages/PatientsPage/BuildOrEditGroupPage', + params:{'selectedPatients':this.patientsList} + }) + }}) + + Row(){ + Row(){ + TextInput({placeholder:'搜索患者的备注名、昵称或手机号'}) + .fontSize(15) + .backgroundColor(Color.White) + .layoutWeight(1) + .onChange((value:string)=>{ + this.inputString = value + }) + .onSubmit(()=>{ + this.searchPatientAction() + }) + Blank() + .width(0.5) + .height(20) + .backgroundColor($r('app.color.main_color')) + Image($r('app.media.selected_hospital_ws')) + .width(30) + .height(30) + .margin({left:10,right:10}) + .onClick(()=>{ + this.searchPatientAction() + }) + } + .width('95%') + .height(50) + .borderRadius(5) + .borderWidth(1) + .margin({left:10}) + .borderColor($r('app.color.main_color')) + } + .backgroundColor(Color.White) + .width('100%') + .height(70) + + if (this.isEmptyViewVisible) { + EmptyViewComp({promptText:'无搜索结果',isVisibility:this.isEmptyViewVisible}) + .width('100%') + .height('calc(100% - 56vp - 70vp)') + } else { + List(){ + ForEach(this.patientsList,(model:patientListModel)=>{ + ListItem() { + this.patientListItem(model) + } + }) + } + .width('100%') + .height('calc(100% - 56vp - 70vp)') + .backgroundColor('#f4f4f4') + .scrollBar(BarState.Off) + } + } + .width('100%') + .height('calc(100% - 56vp)') + .backgroundColor('#f4f4f4') + .justifyContent(FlexAlign.Start) + } + + @Builder + patientListItem(item:patientListModel) { + Column() { + Row() { + Image(BasicConstant.urlImage + item.photo) + .alt($r('app.media.userPhoto_default')) + .borderRadius(6) + .width(50) + .height(50) + .margin({ left: 15 }) + Text(item.nickname ? item.nickname : item.realname) + .fontSize(16) + .fontColor('#333333') + .margin({ left: 15 }) + Blank() + Image(item.isSelected?$r('app.media.patiemts_list_selected'):$r('app.media.patients_list_noSelect')) + .width(22).height(22) + .objectFit(ImageFit.Fill) + .margin({ right: 15 }) + } + .width('100%') + .height(80) + .backgroundColor(Color.White) + .onClick(()=>{ + item.isSelected = !item.isSelected; + this.patientsList = [...this.patientsList]; + const selectedNum = this.patientsList.filter(item => item.isSelected == true).length; + this.naviRightTitle = '确定('+selectedNum+')' + }) + Blank() + .width('80%') + .height(1) + .backgroundColor(Color.Gray) + .margin({left:60}) + } + } +} diff --git a/features/patient/src/main/ets/models/ApplyModel.ets b/features/patient/src/main/ets/models/ApplyModel.ets new file mode 100644 index 0000000..366fb71 --- /dev/null +++ b/features/patient/src/main/ets/models/ApplyModel.ets @@ -0,0 +1,47 @@ +export interface applyListCallBacl { + code:number, + msg:string, + data:applyListModel[], + message:string, +} + +export interface applyHistoryCallBacl { + code:number, + msg:string, + data:historyObjectModel, + message:string, +} + +export class applyListModel { + mobile?:string; + photo?:string; + birthDate?:string; + sex?:number; + realName?:string; + checkDate?:string; + uuid?:string; + createDate?:string; + patientUuid?:string; + expertUuid?:string; + status?:number; + content?:string; +} + +export interface historyObjectModel { + list:historyModel[]; + isFirstPage?:string; + isLastPage?:string; + pageNum?:string; + pages?:string; + pageSize?:string; + total?:string; +} + +export class historyModel { + status?:string;//审核状态(1.待审核2.审核通过3.拒绝4.已过期 5.患者取消 6专家解除) + patient_photo?:string; + create_date?:string; + content?:string; + nickname?:string; + patient_name?:string; +} \ No newline at end of file diff --git a/features/patient/src/main/ets/models/PatientsGroupModel.ets b/features/patient/src/main/ets/models/PatientsGroupModel.ets new file mode 100644 index 0000000..fdcbdc2 --- /dev/null +++ b/features/patient/src/main/ets/models/PatientsGroupModel.ets @@ -0,0 +1,62 @@ +export interface groupRequest { + expert_uuid:string, + group_sort:string, + list_sort:string, +} + +export interface groupRequestCall { + code:number, + msg:string, + data:groupModel[], + message:string, +} + +export class groupModel { + patientList:patientListModel[] = [] + patientNum:number = 0 + expert_uuid:string = '' + name:string = '' + type:number = 0 + uuid:string = '' + isShow:boolean = false; + + constructor(data: groupModel) { + this.patientList = data.patientList + this.patientNum = data.patientNum + this.expert_uuid = data.expert_uuid + this.name = data.name + this.type = data.type + this.uuid = data.uuid + this.isShow = data.isShow + } +} + +export class patientListModel { + nickname?:string; + is_start?:string; + join_date?:string; + note?:string; + type?:string; + photo?:string; + birthDate?:string; + uuid?:string; + isEnable?:string; + height?:string; + ctdidId?:string; + mobile?:string; + nation?:string; + bloodType?:string; + fixedTelephone?:string; + mailingAddress?:string; + postalCode?:string; + detailed_address?:string; + weight?:string; + diagnosis?:string; + sex?:string; + provId?:string; + countyId?:string; + cityId?:string; + realName?:string; + realname?:string; + isSelected:boolean = false; +} \ No newline at end of file diff --git a/features/patient/src/main/ets/views/ApplyViews.ets b/features/patient/src/main/ets/views/ApplyViews.ets new file mode 100644 index 0000000..d00c0c7 --- /dev/null +++ b/features/patient/src/main/ets/views/ApplyViews.ets @@ -0,0 +1,72 @@ +import { applyListModel,historyModel } from '../models/ApplyModel' +import { BasicConstant } from '@itcast/basic/Index' + +@Component +export struct ApplyViews { + @Prop applyItme:applyListModel; + @Prop historyItem:historyModel; + @Prop isApply:boolean = true;//随访申请的样式还是申请记录的样式 + + private applyItemAction: (status: string,model:applyListModel) => void = () => {}; + + build() { + Row() { + Column(){ + Row({space:10}){ + Image(this.isApply?this.applyItme.photo:BasicConstant.urlImage+this.historyItem.patient_photo) + .alt($r('app.media.userPhoto_default')) + .width(50).height(50).borderRadius(5) + Column(){ + Text(this.isApply?this.applyItme.createDate:this.historyItem.create_date) + .fontSize(14).fontColor('#333333').width('100%').textAlign(TextAlign.End) + if (!this.isApply) { + Text('昵称:'+this.historyItem.patient_name) + .fontSize(16).fontColor($r('app.color.main_color')).width('100%') + } + Text(this.isApply?this.applyItme.content:this.historyItem.content) + .fontSize(16).fontColor('#333333').width('100%').margin({top:this.isApply?10:0}) + .maxLines(this.isApply?3:1).textOverflow({ overflow: TextOverflow.Ellipsis }) + }.width('calc(100% - 60vp)') + }.width('calc(100% - 20vp)').margin({top:10,bottom:5}).alignItems(VerticalAlign.Top) + if (this.isApply) { + Row({ space: 40 }) { + Text('拒绝') + .fontColor($r('app.color.main_color')) + .borderColor($r('app.color.main_color')) + .customTextStyle() + .onClick(()=>this.applyItemAction('3',this.applyItme)) + Text('同意') + .fontColor(Color.White) + .backgroundColor($r('app.color.main_color')) + .customTextStyle() + .onClick(()=>this.applyItemAction('2',this.applyItme)) + }.padding({ top: 5, bottom: 10 }).justifyContent(FlexAlign.Center).width('calc(100% - 20vp)') + } else { + Row(){ + if (this.historyItem.status == '2') { + Image($r('app.media.Patients_Apply_History_Status')).width(14).height(14) + Text('已同意').height(28).fontColor('#999999').fontSize(15) + } else if (this.historyItem.status == '3') { + Text('已拒绝').height(28).fontColor('#999999').fontSize(15) + } else if (this.historyItem.status == '4' || this.historyItem.status == '5') { + Text('已过期').height(28).fontColor('#999999').fontSize(15) + } + }.padding({ top: 5, bottom: 5 }).justifyContent(FlexAlign.End).width('calc(100% - 20vp)') + } + Blank() + .width('100%').height(1) + .backgroundColor('#f4f4f4') + }.width('100%').margin({left:10,right:10}).alignItems(HorizontalAlign.Start) + }.width('100%').backgroundColor(Color.White) + } +} + +@Extend(Text) +function customTextStyle() { + .textAlign(TextAlign.Center) + .fontSize(15) + .borderRadius(4) + .borderWidth(0.5) + .width(77) + .height(30) +} \ No newline at end of file diff --git a/features/patient/src/main/module.json5 b/features/patient/src/main/module.json5 new file mode 100644 index 0000000..bc35643 --- /dev/null +++ b/features/patient/src/main/module.json5 @@ -0,0 +1,11 @@ +{ + "module": { + "name": "patient", + "type": "har", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ] + } +} diff --git a/features/patient/src/main/resources/base/element/float.json b/features/patient/src/main/resources/base/element/float.json new file mode 100644 index 0000000..a0a93dd --- /dev/null +++ b/features/patient/src/main/resources/base/element/float.json @@ -0,0 +1,8 @@ +{ + "float": [ + { + "name": "page_text_font_size", + "value": "50fp" + } + ] +} \ No newline at end of file diff --git a/features/patient/src/main/resources/base/element/string.json b/features/patient/src/main/resources/base/element/string.json new file mode 100644 index 0000000..f51a9c8 --- /dev/null +++ b/features/patient/src/main/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + } + ] +} diff --git a/features/patient/src/main/resources/base/media/Patients_Apply_History_Status.png b/features/patient/src/main/resources/base/media/Patients_Apply_History_Status.png new file mode 100644 index 0000000..22ebe1a Binary files /dev/null and b/features/patient/src/main/resources/base/media/Patients_Apply_History_Status.png differ diff --git a/features/patient/src/main/resources/base/media/addPatientApply_reminder_icon.png b/features/patient/src/main/resources/base/media/addPatientApply_reminder_icon.png new file mode 100644 index 0000000..5ea6125 Binary files /dev/null and b/features/patient/src/main/resources/base/media/addPatientApply_reminder_icon.png differ diff --git a/features/patient/src/main/resources/base/media/add_patients_to_roup.png b/features/patient/src/main/resources/base/media/add_patients_to_roup.png new file mode 100644 index 0000000..04853f3 Binary files /dev/null and b/features/patient/src/main/resources/base/media/add_patients_to_roup.png differ diff --git a/features/patient/src/main/resources/base/media/chose_card.png b/features/patient/src/main/resources/base/media/chose_card.png new file mode 100644 index 0000000..5a40d59 Binary files /dev/null and b/features/patient/src/main/resources/base/media/chose_card.png differ diff --git a/features/patient/src/main/resources/base/media/dele_patient_inThe_group.png b/features/patient/src/main/resources/base/media/dele_patient_inThe_group.png new file mode 100644 index 0000000..6f0fb18 Binary files /dev/null and b/features/patient/src/main/resources/base/media/dele_patient_inThe_group.png differ diff --git a/features/patient/src/main/resources/base/media/fuifangPlan_blackBtn.png b/features/patient/src/main/resources/base/media/fuifangPlan_blackBtn.png new file mode 100644 index 0000000..d9ccce5 Binary files /dev/null and b/features/patient/src/main/resources/base/media/fuifangPlan_blackBtn.png differ diff --git a/features/patient/src/main/resources/base/media/group_turnDown.png b/features/patient/src/main/resources/base/media/group_turnDown.png new file mode 100644 index 0000000..0fb84e4 Binary files /dev/null and b/features/patient/src/main/resources/base/media/group_turnDown.png differ diff --git a/features/patient/src/main/resources/base/media/group_turnRight.png b/features/patient/src/main/resources/base/media/group_turnRight.png new file mode 100644 index 0000000..a620868 Binary files /dev/null and b/features/patient/src/main/resources/base/media/group_turnRight.png differ diff --git a/features/patient/src/main/resources/base/media/group_vip.png b/features/patient/src/main/resources/base/media/group_vip.png new file mode 100644 index 0000000..a67ceb2 Binary files /dev/null and b/features/patient/src/main/resources/base/media/group_vip.png differ diff --git a/features/patient/src/main/resources/base/media/lifetime_right_icon.png b/features/patient/src/main/resources/base/media/lifetime_right_icon.png new file mode 100644 index 0000000..72e7dda Binary files /dev/null and b/features/patient/src/main/resources/base/media/lifetime_right_icon.png differ diff --git a/features/patient/src/main/resources/base/media/listBing_blackBtn.png b/features/patient/src/main/resources/base/media/listBing_blackBtn.png new file mode 100644 index 0000000..c9e8af8 Binary files /dev/null and b/features/patient/src/main/resources/base/media/listBing_blackBtn.png differ diff --git a/features/patient/src/main/resources/base/media/patiemts_list_selected.png b/features/patient/src/main/resources/base/media/patiemts_list_selected.png new file mode 100644 index 0000000..c60f6bb Binary files /dev/null and b/features/patient/src/main/resources/base/media/patiemts_list_selected.png differ diff --git a/features/patient/src/main/resources/base/media/patient_details_navigation_right.png b/features/patient/src/main/resources/base/media/patient_details_navigation_right.png new file mode 100644 index 0000000..dce098a Binary files /dev/null and b/features/patient/src/main/resources/base/media/patient_details_navigation_right.png differ diff --git a/features/patient/src/main/resources/base/media/patients_list_noSelect.png b/features/patient/src/main/resources/base/media/patients_list_noSelect.png new file mode 100644 index 0000000..5600189 Binary files /dev/null and b/features/patient/src/main/resources/base/media/patients_list_noSelect.png differ diff --git a/features/patient/src/main/resources/base/media/sendMessage_blackBtn.png b/features/patient/src/main/resources/base/media/sendMessage_blackBtn.png new file mode 100644 index 0000000..d8bc5c9 Binary files /dev/null and b/features/patient/src/main/resources/base/media/sendMessage_blackBtn.png differ diff --git a/features/patient/src/main/resources/base/media/triangle_green_theme.png b/features/patient/src/main/resources/base/media/triangle_green_theme.png new file mode 100644 index 0000000..d5186b8 Binary files /dev/null and b/features/patient/src/main/resources/base/media/triangle_green_theme.png differ diff --git a/features/patient/src/main/resources/base/media/triangle_normal.png b/features/patient/src/main/resources/base/media/triangle_normal.png new file mode 100644 index 0000000..b4019bd Binary files /dev/null and b/features/patient/src/main/resources/base/media/triangle_normal.png differ diff --git a/features/patient/src/ohosTest/ets/test/Ability.test.ets b/features/patient/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000..85c78f6 --- /dev/null +++ b/features/patient/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/features/patient/src/ohosTest/ets/test/List.test.ets b/features/patient/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000..794c7dc --- /dev/null +++ b/features/patient/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/features/patient/src/ohosTest/module.json5 b/features/patient/src/ohosTest/module.json5 new file mode 100644 index 0000000..a37e6e2 --- /dev/null +++ b/features/patient/src/ohosTest/module.json5 @@ -0,0 +1,13 @@ +{ + "module": { + "name": "patient_test", + "type": "feature", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/features/patient/src/test/List.test.ets b/features/patient/src/test/List.test.ets new file mode 100644 index 0000000..bb5b5c3 --- /dev/null +++ b/features/patient/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/features/patient/src/test/LocalUnit.test.ets b/features/patient/src/test/LocalUnit.test.ets new file mode 100644 index 0000000..165fc16 --- /dev/null +++ b/features/patient/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/oh-package-lock.json5 b/oh-package-lock.json5 index 015f8d4..fd5ec9e 100644 --- a/oh-package-lock.json5 +++ b/oh-package-lock.json5 @@ -5,6 +5,8 @@ "lockfileVersion": 3, "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", "specifiers": { + "@hshare/hshare-flipper@^1.0.0": "@hshare/hshare-flipper@1.0.0", + "@nimsdk/vendor@1.0.0": "@nimsdk/vendor@1.0.0", "@ohos/crypto-js@^2.0.4": "@ohos/crypto-js@2.0.4", "@ohos/dataorm@^2.2.6": "@ohos/dataorm@2.2.6", "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0", @@ -12,6 +14,20 @@ "reflect-metadata@^0.1.13": "reflect-metadata@0.2.1" }, "packages": { + "@hshare/hshare-flipper@1.0.0": { + "name": "@hshare/hshare-flipper", + "version": "1.0.0", + "integrity": "sha512-TzXUyViTzIISLFtS4w6djfFDuiNUA0bQHOERMxQaPpXD8U3mEoLjaDUoEnlwRG2IsDjnW/Q0lO1KFT0QyFwLTw==", + "resolved": "https://repo.harmonyos.com/ohpm/@hshare/hshare-flipper/-/hshare-flipper-1.0.0.har", + "registryType": "ohpm" + }, + "@nimsdk/vendor@1.0.0": { + "name": "@nimsdk/vendor", + "version": "1.0.0", + "integrity": "sha512-q49MJM6PfucNs8jvLP56a2etyqRfZCeJaMa1BT9vO4sIgwt15bin+hpUWZ1qkflBs9YkDb2nMIX5O8zt556muw==", + "resolved": "https://repo.harmonyos.com/ohpm/@nimsdk/vendor/-/vendor-1.0.0.har", + "registryType": "ohpm" + }, "@ohos/crypto-js@2.0.4": { "name": "@ohos/crypto-js", "version": "2.0.4", diff --git a/oh-package.json5 b/oh-package.json5 index 2f5b43a..93432d5 100644 --- a/oh-package.json5 +++ b/oh-package.json5 @@ -3,7 +3,8 @@ "description": "Please describe the basic information.", "dependencies": { "@ohos/crypto-js": "^2.0.4", - "@ohos/dataorm": "^2.2.6" + "@ohos/dataorm": "^2.2.6", + "@hshare/hshare-flipper": "^1.0.0" }, "devDependencies": { "@ohos/hypium": "1.0.21", diff --git a/products/expert/oh-package-lock.json5 b/products/expert/oh-package-lock.json5 index 4ba637f..b2c135e 100644 --- a/products/expert/oh-package-lock.json5 +++ b/products/expert/oh-package-lock.json5 @@ -9,9 +9,33 @@ "@aliyun/httpdns@1.1.1": "@aliyun/httpdns@1.1.1", "@aliyun/logger@1.0.2": "@aliyun/logger@1.0.2", "@itcast/basic@../../commons/basic": "@itcast/basic@../../commons/basic", + "@nimkit/chatkit@../../chatkit": "@nimkit/chatkit@../../chatkit", + "@nimkit/chatkit_ui@../../chatkit_ui": "@nimkit/chatkit_ui@../../chatkit_ui", + "@nimkit/common@../../common": "@nimkit/common@../../common", + "@nimkit/conversationkit_ui@../../conversationkit_ui": "@nimkit/conversationkit_ui@../../conversationkit_ui", + "@nimkit/corekit@../../corekit": "@nimkit/corekit@../../corekit", + "@nimkit/localconversationkit_ui@../../localconversationkit_ui": "@nimkit/localconversationkit_ui@../../localconversationkit_ui", + "@nimkit/markdown@1.1.0": "@nimkit/markdown@1.1.0", + "@nimsdk/base@../../oh_modules/.ohpm/@nimsdk+conversation@10.9.10/oh_modules/@nimsdk/conversation/libs/base.har": "@nimsdk/base@../../oh_modules/.ohpm/@nimsdk+nim@10.9.10/oh_modules/@nimsdk/nim/libs/base.har", + "@nimsdk/base@../../oh_modules/.ohpm/@nimsdk+friend@10.9.10/oh_modules/@nimsdk/friend/libs/base.har": "@nimsdk/base@../../oh_modules/.ohpm/@nimsdk+nim@10.9.10/oh_modules/@nimsdk/nim/libs/base.har", + "@nimsdk/base@../../oh_modules/.ohpm/@nimsdk+localconversation@10.9.10/oh_modules/@nimsdk/localconversation/libs/base.har": "@nimsdk/base@../../oh_modules/.ohpm/@nimsdk+nim@10.9.10/oh_modules/@nimsdk/nim/libs/base.har", + "@nimsdk/base@../../oh_modules/.ohpm/@nimsdk+message@10.9.10/oh_modules/@nimsdk/message/libs/base.har": "@nimsdk/base@../../oh_modules/.ohpm/@nimsdk+nim@10.9.10/oh_modules/@nimsdk/nim/libs/base.har", + "@nimsdk/base@../../oh_modules/.ohpm/@nimsdk+nim@10.9.10/oh_modules/@nimsdk/nim/libs/base.har": "@nimsdk/base@../../oh_modules/.ohpm/@nimsdk+nim@10.9.10/oh_modules/@nimsdk/nim/libs/base.har", + "@nimsdk/base@../../oh_modules/.ohpm/@nimsdk+team@10.9.10/oh_modules/@nimsdk/team/libs/base.har": "@nimsdk/base@../../oh_modules/.ohpm/@nimsdk+nim@10.9.10/oh_modules/@nimsdk/nim/libs/base.har", + "@nimsdk/base@../../oh_modules/.ohpm/@nimsdk+user@10.9.10/oh_modules/@nimsdk/user/libs/base.har": "@nimsdk/base@../../oh_modules/.ohpm/@nimsdk+nim@10.9.10/oh_modules/@nimsdk/nim/libs/base.har", + "@nimsdk/base@10.9.10": "@nimsdk/base@../../oh_modules/.ohpm/@nimsdk+nim@10.9.10/oh_modules/@nimsdk/nim/libs/base.har", + "@nimsdk/conversation@10.9.10": "@nimsdk/conversation@10.9.10", + "@nimsdk/friend@10.9.10": "@nimsdk/friend@10.9.10", + "@nimsdk/localconversation@10.9.10": "@nimsdk/localconversation@10.9.10", + "@nimsdk/message@10.9.10": "@nimsdk/message@10.9.10", + "@nimsdk/nim@10.9.10": "@nimsdk/nim@10.9.10", + "@nimsdk/team@10.9.10": "@nimsdk/team@10.9.10", + "@nimsdk/user@10.9.10": "@nimsdk/user@10.9.10", + "@nimsdk/vendor@1.0.0": "@nimsdk/vendor@1.0.0", "@ohos/crypto-js@2.0.3": "@ohos/crypto-js@2.0.4", "@ohos/crypto-js@^2.0.2": "@ohos/crypto-js@2.0.4", "@ohos/httpclient@2.0.2": "@ohos/httpclient@2.0.2", + "@ohos/pinyin4js@^2.0.0": "@ohos/pinyin4js@2.0.1", "@polyvharmony/httpdns-api@1.0.2": "@polyvharmony/httpdns-api@1.0.2", "@polyvharmony/httpdns-impl-ali@1.0.2": "@polyvharmony/httpdns-impl-ali@1.0.2", "@polyvharmony/httpdns-impl-local@1.0.2": "@polyvharmony/httpdns-impl-local@1.0.2", @@ -22,10 +46,15 @@ "@polyvharmony/media-player-sdk-addon-cache-down@2.5.0": "@polyvharmony/media-player-sdk-addon-cache-down@2.5.0", "@polyvharmony/media-player-sdk@2.5.0": "@polyvharmony/media-player-sdk@2.5.0", "base64-js@^1.5.1": "base64-js@1.5.1", + "class-transformer@^0.5.1": "class-transformer@0.5.1", "home@../../features/Home": "home@../../features/Home", "media-player-common@../../polyv": "media-player-common@../../polyv", "mypage@../../features/mypage": "mypage@../../features/mypage", + "netease@../../features/netease": "netease@../../features/netease", "pako@^2.1.0": "pako@2.1.0", + "patient@../../features/patient": "patient@../../features/patient", + "reflect-metadata@^0.1.13": "reflect-metadata@0.2.1", + "refreshlib@../../RefreshLib": "refreshlib@../../RefreshLib", "register@../../features/register": "register@../../features/register", "scene_single_video@../../scene_single_video": "scene_single_video@../../scene_single_video" }, @@ -61,6 +90,177 @@ "resolved": "../../commons/basic", "registryType": "local" }, + "@nimkit/chatkit@../../chatkit": { + "name": "@nimkit/chatkit", + "version": "10.1.0", + "resolved": "../../chatkit", + "registryType": "local", + "dependencies": { + "@nimsdk/conversation": "10.9.10", + "@nimsdk/message": "10.9.10", + "@nimsdk/team": "10.9.10", + "@nimsdk/user": "10.9.10", + "@nimsdk/friend": "10.9.10", + "@nimsdk/nim": "10.9.10", + "@nimsdk/base": "10.9.10", + "@nimkit/corekit": "file:../corekit", + "class-transformer": "^0.5.1", + "reflect-metadata": "^0.1.13" + } + }, + "@nimkit/chatkit_ui@../../chatkit_ui": { + "name": "@nimkit/chatkit_ui", + "version": "10.1.0", + "resolved": "../../chatkit_ui", + "registryType": "local", + "dependencies": { + "@nimkit/common": "file:../common", + "@nimkit/chatkit": "file:../chatkit", + "@nimkit/corekit": "file:../corekit", + "@nimsdk/base": "10.9.10", + "class-transformer": "^0.5.1", + "reflect-metadata": "^0.1.13", + "@nimkit/markdown": "1.1.0", + "@itcast/basic": "file:../commons/basic" + } + }, + "@nimkit/common@../../common": { + "name": "@nimkit/common", + "version": "1.1.0", + "resolved": "../../common", + "registryType": "local", + "dependencies": { + "@ohos/pinyin4js": "^2.0.0" + } + }, + "@nimkit/conversationkit_ui@../../conversationkit_ui": { + "name": "@nimkit/conversationkit_ui", + "version": "10.1.0", + "resolved": "../../conversationkit_ui", + "registryType": "local", + "dependencies": { + "@nimkit/common": "file:../common", + "@nimkit/chatkit": "file:../chatkit", + "@nimsdk/base": "10.9.10" + } + }, + "@nimkit/corekit@../../corekit": { + "name": "@nimkit/corekit", + "version": "1.1.0", + "resolved": "../../corekit", + "registryType": "local" + }, + "@nimkit/localconversationkit_ui@../../localconversationkit_ui": { + "name": "@nimkit/localconversationkit_ui", + "version": "10.1.0", + "resolved": "../../localconversationkit_ui", + "registryType": "local", + "dependencies": { + "@nimkit/common": "file:../common", + "@nimkit/chatkit": "file:../chatkit", + "@nimsdk/base": "10.9.10" + } + }, + "@nimkit/markdown@1.1.0": { + "name": "@nimkit/markdown", + "version": "1.1.0", + "integrity": "sha512-ITTM5bIkvcK+KsWHxn7vta1W3XGulMQ4vWHT37NidayhTlo04lG6JMABtsxCYYR7H6OiwuUcVpLzDvOyjScYSA==", + "resolved": "https://repo.harmonyos.com/ohpm/@nimkit/markdown/-/markdown-1.1.0.har", + "registryType": "ohpm" + }, + "@nimsdk/base@../../oh_modules/.ohpm/@nimsdk+nim@10.9.10/oh_modules/@nimsdk/nim/libs/base.har": { + "name": "@nimsdk/base", + "version": "10.9.10", + "resolved": "../../oh_modules/.ohpm/@nimsdk+nim@10.9.10/oh_modules/@nimsdk/nim/libs/base.har", + "registryType": "local", + "dependencies": { + "@nimsdk/vendor": "1.0.0" + } + }, + "@nimsdk/conversation@10.9.10": { + "name": "@nimsdk/conversation", + "version": "10.9.10", + "integrity": "sha512-1HLvs19/GJAHeIOCN0OiKlowkg6dzZwvZK0Jqu7tAcYGcLl4+G/Z3pwsGHhv+E2Tzs8FHZCqbESMgSh+LNyt/g==", + "resolved": "https://repo.harmonyos.com/ohpm/@nimsdk/conversation/-/conversation-10.9.10.har", + "registryType": "ohpm", + "dependencies": { + "@nimsdk/base": "file:./libs/base.har", + "@nimsdk/vendor": "1.0.0" + } + }, + "@nimsdk/friend@10.9.10": { + "name": "@nimsdk/friend", + "version": "10.9.10", + "integrity": "sha512-JVACpT8xqLLaN8D26YHmwfsS1dHFQvBnP3Jyk9El89P2trn/2ZFLvnQjxzyBDsqJRUtNFfIrN+TK7Idmud4ACQ==", + "resolved": "https://repo.harmonyos.com/ohpm/@nimsdk/friend/-/friend-10.9.10.har", + "registryType": "ohpm", + "dependencies": { + "@nimsdk/base": "file:./libs/base.har", + "@nimsdk/vendor": "1.0.0" + } + }, + "@nimsdk/localconversation@10.9.10": { + "name": "@nimsdk/localconversation", + "version": "10.9.10", + "integrity": "sha512-TDwE/zBLRX+WKZcpEodCXQATLHS4ZSfRw92FbWoKqxq1LwlJSdumvN1elFlnUmCVi5E2/tFzSxqXUBn8fYAZ8A==", + "resolved": "https://repo.harmonyos.com/ohpm/@nimsdk/localconversation/-/localconversation-10.9.10.har", + "registryType": "ohpm", + "dependencies": { + "@nimsdk/base": "file:./libs/base.har", + "@nimsdk/vendor": "1.0.0" + } + }, + "@nimsdk/message@10.9.10": { + "name": "@nimsdk/message", + "version": "10.9.10", + "integrity": "sha512-f59rWiM4SjhhxNftRUt9vg7lIwkGycV/aL8J3omH+Te4SMbUGolwDGErDr7adtZ3tDUThtxxgU8n5tD28TBRtA==", + "resolved": "https://repo.harmonyos.com/ohpm/@nimsdk/message/-/message-10.9.10.har", + "registryType": "ohpm", + "dependencies": { + "@nimsdk/base": "file:./libs/base.har", + "@nimsdk/vendor": "1.0.0" + } + }, + "@nimsdk/nim@10.9.10": { + "name": "@nimsdk/nim", + "version": "10.9.10", + "integrity": "sha512-WpT8vBTld92ExtH30Ffsm+xq6BW6/UFj8SuhJrcQaZY3AYf9sg+d+euqx/dFzjZin5cWRxd/yoodBiVcGfsM4w==", + "resolved": "https://repo.harmonyos.com/ohpm/@nimsdk/nim/-/nim-10.9.10.har", + "registryType": "ohpm", + "dependencies": { + "@nimsdk/base": "file:./libs/base.har", + "@nimsdk/vendor": "1.0.0" + } + }, + "@nimsdk/team@10.9.10": { + "name": "@nimsdk/team", + "version": "10.9.10", + "integrity": "sha512-T4YSN395VXQr1TDX2B24DmGYuvUgUqE7wndbleR980wEyki9IfhC2VxxJ1yajhxVlVkfmuBjCB/eKWL0zLzu5A==", + "resolved": "https://repo.harmonyos.com/ohpm/@nimsdk/team/-/team-10.9.10.har", + "registryType": "ohpm", + "dependencies": { + "@nimsdk/base": "file:./libs/base.har", + "@nimsdk/vendor": "1.0.0" + } + }, + "@nimsdk/user@10.9.10": { + "name": "@nimsdk/user", + "version": "10.9.10", + "integrity": "sha512-KyWVDDPbymj3qoC8Y0mB8umgvLg89Y2cB02tM35oSG8IW95C936v5ogip2Jk7qAfabXxI/XTyy5wQoW1z950JA==", + "resolved": "https://repo.harmonyos.com/ohpm/@nimsdk/user/-/user-10.9.10.har", + "registryType": "ohpm", + "dependencies": { + "@nimsdk/base": "file:./libs/base.har", + "@nimsdk/vendor": "1.0.0" + } + }, + "@nimsdk/vendor@1.0.0": { + "name": "@nimsdk/vendor", + "version": "1.0.0", + "integrity": "sha512-q49MJM6PfucNs8jvLP56a2etyqRfZCeJaMa1BT9vO4sIgwt15bin+hpUWZ1qkflBs9YkDb2nMIX5O8zt556muw==", + "resolved": "https://repo.harmonyos.com/ohpm/@nimsdk/vendor/-/vendor-1.0.0.har", + "registryType": "ohpm" + }, "@ohos/crypto-js@2.0.4": { "name": "@ohos/crypto-js", "version": "2.0.4", @@ -80,6 +280,13 @@ "base64-js": "^1.5.1" } }, + "@ohos/pinyin4js@2.0.1": { + "name": "@ohos/pinyin4js", + "version": "2.0.1", + "integrity": "sha512-qmYDelku5gcgKVmJyMqa7kWf0a+e8nnGS9ts5FRLA0LdRf+Iz36X/4Vub6hhh/RusuDmmWG9h153KZe+kraIVg==", + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/pinyin4js/-/pinyin4js-2.0.1.har", + "registryType": "ohpm" + }, "@polyvharmony/httpdns-api@1.0.2": { "name": "@polyvharmony/httpdns-api", "version": "1.0.2", @@ -186,6 +393,14 @@ "shasum": "1b1b440160a5bf7ad40b650f095963481903930a", "registryType": "ohpm" }, + "class-transformer@0.5.1": { + "name": "class-transformer", + "version": "0.5.1", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "resolved": "https://repo.harmonyos.com/ohpm/class-transformer/-/class-transformer-0.5.1.tgz", + "shasum": "24147d5dffd2a6cea930a3250a677addf96ab336", + "registryType": "ohpm" + }, "home@../../features/Home": { "name": "home", "version": "1.0.0", @@ -218,6 +433,21 @@ "@itcast/basic": "file:../../commons/basic" } }, + "netease@../../features/netease": { + "name": "netease", + "version": "1.0.0", + "resolved": "../../features/netease", + "registryType": "local", + "dependencies": { + "@itcast/basic": "file:../../commons/basic", + "@nimkit/conversationkit_ui": "file:../../conversationkit_ui", + "@nimkit/chatkit_ui": "file:../../chatkit_ui", + "@nimkit/chatkit": "file:../../chatkit", + "@nimsdk/base": "10.9.10", + "@nimkit/common": "file:../../common", + "@nimkit/localconversationkit_ui": "file:../../localconversationkit_ui" + } + }, "pako@2.1.0": { "name": "pako", "version": "2.1.0", @@ -226,6 +456,30 @@ "shasum": "266cc37f98c7d883545d11335c00fbd4062c9a86", "registryType": "ohpm" }, + "patient@../../features/patient": { + "name": "patient", + "version": "1.0.0", + "resolved": "../../features/patient", + "registryType": "local", + "dependencies": { + "@itcast/basic": "file:../../commons/basic", + "refreshlib": "file:../../RefreshLib" + } + }, + "reflect-metadata@0.2.1": { + "name": "reflect-metadata", + "version": "0.2.1", + "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==", + "resolved": "https://repo.harmonyos.com/ohpm/reflect-metadata/-/reflect-metadata-0.2.1.tgz", + "shasum": "8d5513c0f5ef2b4b9c3865287f3c0940c1f67f74", + "registryType": "ohpm" + }, + "refreshlib@../../RefreshLib": { + "name": "refreshlib", + "version": "1.0.0", + "resolved": "../../RefreshLib", + "registryType": "local" + }, "register@../../features/register": { "name": "register", "version": "1.0.0", diff --git a/products/expert/oh-package.json5 b/products/expert/oh-package.json5 index be8bcb6..83e968c 100644 --- a/products/expert/oh-package.json5 +++ b/products/expert/oh-package.json5 @@ -10,6 +10,7 @@ "mypage":"file:../../features/mypage", "home": 'file:../../features/Home', "register": 'file:../../features/register', + "patient": 'file:../../features/patient', "scene_single_video": "file:../../scene_single_video", "media-player-common": "file:../../polyv", "@polyvharmony/media-player-sdk": "2.5.0", diff --git a/products/expert/src/main/ets/contants/TabBarItems.ets b/products/expert/src/main/ets/contants/TabBarItems.ets index d013770..8eda892 100644 --- a/products/expert/src/main/ets/contants/TabBarItems.ets +++ b/products/expert/src/main/ets/contants/TabBarItems.ets @@ -2,6 +2,11 @@ import { TabBarCompModel } from '../models/TabBarCompModel' export const TabBarItems: TabBarCompModel[] = [ + { + defaultIcon: $r('app.media.home_default_icon'), + activeIcon: $r('app.media.home_selected_icon'), + label: '首页' + }, { defaultIcon: $r('app.media.home_my_meeting_nor'), activeIcon: $r('app.media.home_my_meeting_sel'), diff --git a/products/expert/src/main/ets/entryability/EntryAbility.ets b/products/expert/src/main/ets/entryability/EntryAbility.ets index 2b86595..fc3754b 100644 --- a/products/expert/src/main/ets/entryability/EntryAbility.ets +++ b/products/expert/src/main/ets/entryability/EntryAbility.ets @@ -4,12 +4,15 @@ import { window } from '@kit.ArkUI'; import notificationManager from '@ohos.notificationManager'; import { PLVMediaPlayerStartUp } from '../startup/PLVMediaPlayerStartUp'; import contextConstant from '@ohos.app.ability.contextConstant'; +import { patientDbManager } from '@itcast/basic'; const DOMAIN = 0x0000; export default class EntryAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { AppStorage.Set('notificationEnabled', false); // 假设默认关闭 this.checkNotificationStatus(); // 首次检查 + // 初始化患者数据库 + this.initPatientDatabase(); this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); @@ -19,6 +22,15 @@ export default class EntryAbility extends UIAbility { PLVMediaPlayerStartUp.start(this.context) } + private async initPatientDatabase() { + try { + await patientDbManager.initDatabase(this.context); + console.info('患者数据库初始化成功'); + } catch (error) { + console.error('患者数据库初始化失败:', error); + } + } + onDestroy(): void { hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy'); } diff --git a/products/expert/src/main/ets/pages/Home.ets b/products/expert/src/main/ets/pages/Home.ets index a74fafe..4ec1a4c 100644 --- a/products/expert/src/main/ets/pages/Home.ets +++ b/products/expert/src/main/ets/pages/Home.ets @@ -1,8 +1,10 @@ import { TabBarComp } from '../pages/Tabbar/TabBarComp'; -import { BasicConstant, themeManager } from '@itcast/basic'; +import { BasicConstant, themeManager,hdHttp, HdResponse, authStore } from '@itcast/basic'; import { emitter } from '@kit.BasicServicesKit'; import { common } from '@kit.AbilityKit'; import { promptAction } from '@kit.ArkUI'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { patientDbManager, PatientEntity } from '@itcast/basic'; import { NimRepository } from '../entryability/NimRepository' import { AppConfig } from '../constants/AppConfig' @@ -12,6 +14,7 @@ struct Home { @State @Watch('onChangeIndex') activeIndex: number = 0 + @State patients: PatientEntity[] = []; login = async (accountId: string, token: string) => { @@ -37,11 +40,20 @@ struct Home { else themeManager.settingStatusBarBlack() } + async loadPatients() { + try { + await patientDbManager.patientsToFMDB(); + } catch (error) { + console.error('加载患者数据失败:', error); + } + } + onPageShow(): void { console.log('--onPageShow--'); emitter.emit('notification_status_changed') this.onChangeIndex() // emitter.emit({eventId:BasicConstant.notification_home_tab_change},{data:{'changeIndex':this.activeIndex}}) + this.loadPatients(); } onPageHide(): void { @@ -62,8 +74,6 @@ struct Home { const ctx = getContext() as common.UIAbilityContext ctx.terminateSelf() } - - return true } @@ -73,4 +83,8 @@ struct Home { } .backgroundColor($r('app.color.white')) } -} \ No newline at end of file +} + +interface updateExtraData { + expertUuid: string +} diff --git a/products/expert/src/main/ets/pages/PatientsPage/BuildOrEditGroupPage.ets b/products/expert/src/main/ets/pages/PatientsPage/BuildOrEditGroupPage.ets new file mode 100644 index 0000000..a2f81a0 --- /dev/null +++ b/products/expert/src/main/ets/pages/PatientsPage/BuildOrEditGroupPage.ets @@ -0,0 +1,19 @@ +import { BuildOrEditGroupPage } from 'patient' + +@Entry +@Component +struct BuildGroupPage { + @Provide refreshFlag:boolean = false; + + onPageShow(): void { + this.refreshFlag = !this.refreshFlag; + } + + build() { + RelativeContainer() { + BuildOrEditGroupPage(); + } + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/products/expert/src/main/ets/pages/PatientsPage/PatientDetailsPage.ets b/products/expert/src/main/ets/pages/PatientsPage/PatientDetailsPage.ets new file mode 100644 index 0000000..f4fa52f --- /dev/null +++ b/products/expert/src/main/ets/pages/PatientsPage/PatientDetailsPage.ets @@ -0,0 +1,19 @@ +import { PatientDetailsComp } from 'patient' + +@Entry +@Component +struct PatientDetailsPage { + @Provide refreshFlag:boolean = false; + + onPageShow(): void { + this.refreshFlag = !this.refreshFlag; + } + + build() { + RelativeContainer() { + PatientDetailsComp() + } + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/products/expert/src/main/ets/pages/PatientsPage/PatientMsgSetPage.ets b/products/expert/src/main/ets/pages/PatientsPage/PatientMsgSetPage.ets new file mode 100644 index 0000000..6b0df7b --- /dev/null +++ b/products/expert/src/main/ets/pages/PatientsPage/PatientMsgSetPage.ets @@ -0,0 +1,14 @@ +import { PatientSetMsgPage } from 'patient' + + +@Entry +@Component +struct PatientMsgSetPage { + build() { + RelativeContainer() { + PatientSetMsgPage(); + } + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/products/expert/src/main/ets/pages/PatientsPage/PatientPages.ets b/products/expert/src/main/ets/pages/PatientsPage/PatientPages.ets new file mode 100644 index 0000000..f4b076d --- /dev/null +++ b/products/expert/src/main/ets/pages/PatientsPage/PatientPages.ets @@ -0,0 +1,13 @@ +import { PatientApplyPage } from 'patient' + +@Entry +@Component +struct PatientPages { + build() { + RelativeContainer() { + PatientApplyPage(); + } + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/products/expert/src/main/ets/pages/PatientsPage/PatientsGroupPage.ets b/products/expert/src/main/ets/pages/PatientsPage/PatientsGroupPage.ets new file mode 100644 index 0000000..30c778f --- /dev/null +++ b/products/expert/src/main/ets/pages/PatientsPage/PatientsGroupPage.ets @@ -0,0 +1,19 @@ +import { PatientsGroup } from 'patient' + +@Entry +@Component +struct PatientsGroupPage { + @Provide refreshFlag:boolean = false; + + onPageShow(): void { + this.refreshFlag = !this.refreshFlag; + } + + build() { + RelativeContainer() { + PatientsGroup(); + } + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/products/expert/src/main/ets/pages/PatientsPage/PatientsListPage.ets b/products/expert/src/main/ets/pages/PatientsPage/PatientsListPage.ets new file mode 100644 index 0000000..eda15c3 --- /dev/null +++ b/products/expert/src/main/ets/pages/PatientsPage/PatientsListPage.ets @@ -0,0 +1,12 @@ +import { PatientsListComp } from 'patient' + +@Entry +@Component +struct PatientsListPage { + + build() { + RelativeContainer(){ + PatientsListComp(); + } + } +} \ No newline at end of file diff --git a/products/expert/src/main/ets/pages/Tabbar/TabBarComp.ets b/products/expert/src/main/ets/pages/Tabbar/TabBarComp.ets index bd4d48e..205cdf6 100644 --- a/products/expert/src/main/ets/pages/Tabbar/TabBarComp.ets +++ b/products/expert/src/main/ets/pages/Tabbar/TabBarComp.ets @@ -1,4 +1,4 @@ -import { VideoPage,VideoGandan } from 'home' +import { VideoPage,VideoGandan, HomePage } from 'home' import { MyHomePage } from 'mypage' import { TabBarCompModel } from '../../models/TabBarCompModel' import { TabBarItems } from '../../contants/TabBarItems' @@ -50,10 +50,10 @@ export struct TabBarComp { }) { ForEach(TabBarItems, (item: TabBarCompModel, index: number) => { TabContent() { - if (index === 0) VideoPage() - else if (index === 1) VideoGandan() - else if (index === 2) MyHomePage() - // else MyHomePage() + if (index === 0) HomePage() + else if (index === 1) VideoPage() + else if (index === 2) VideoGandan() + else MyHomePage() } .tabBar(this.TabBarBuilder(item, index)) .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM]) diff --git a/products/expert/src/main/ets/pages/VideoPage/VideoGandanPage.ets b/products/expert/src/main/ets/pages/VideoPage/VideoGandanPage.ets index 96f19cb..bf59844 100644 --- a/products/expert/src/main/ets/pages/VideoPage/VideoGandanPage.ets +++ b/products/expert/src/main/ets/pages/VideoPage/VideoGandanPage.ets @@ -1,9 +1,7 @@ import { VideoGandan } from 'home' + @Entry -@Component struct VideoGandanPage { - - build() { RelativeContainer() { VideoGandan() diff --git a/products/expert/src/main/ets/pages/WebView/WebPage.ets b/products/expert/src/main/ets/pages/WebView/WebPage.ets index 9bb3736..dbc12b3 100644 --- a/products/expert/src/main/ets/pages/WebView/WebPage.ets +++ b/products/expert/src/main/ets/pages/WebView/WebPage.ets @@ -1,6 +1,12 @@ import webview from '@ohos.web.webview'; import { HdNav } from '@itcast/basic'; import router from '@ohos.router'; +import { image } from '@kit.ImageKit'; +import { photoAccessHelper } from '@kit.MediaLibraryKit'; +import { promptAction } from '@kit.ArkUI'; +import { componentSnapshot } from '@kit.ArkUI'; +import { fileIo, fileUri } from '@kit.CoreFileKit'; +const TAG = 'WebViewSaveImage'; @Entry @Component @@ -8,29 +14,117 @@ struct WebPage { private controller: webview.WebviewController = new webview.WebviewController(); @State params:RouteParams = router.getParams() as RouteParams; + @State contentWidth: number = 0; + @State contentHeight: number = 0; @State url: string = this.params.url; @State title: string = this.params.title; customUserAgent: string = 'gdxz-expert'; + onBackPress(): boolean | void { + if (this.controller.accessStep(-1)) { + this.controller.backward(); + return true; + } + return false; + } + build() { Column() { - HdNav({ title: this.title, showRightIcon: false, hasBorder: true }) + HdNav({ title: this.title, showRightIcon: false, hasBorder: true ,isLeftAction:true,leftItemAction:()=>{ + if (this.controller.accessBackward()) { + this.controller.backward(); + } else { + router.back(); + } + }}) Web({ src: this.url, controller: this.controller }) + .id('webView') .mixedMode(MixedMode.All) + .overScrollMode(OverScrollMode.ALWAYS) .domStorageAccess(true) .onControllerAttached(() => { let userAgent = this.controller.getUserAgent() + this.customUserAgent; this.controller.setCustomUserAgent(userAgent); }) + .onPageEnd(() => { + // 注入JS获取body高度 + this.controller.runJavaScript( + 'document.documentElement.scrollWidth', (error, result) => { + if (!error) this.contentWidth = Number(result); + } + ); + this.controller.runJavaScript('document.body.scrollHeight',(error,result)=>{ + if (!error) this.contentHeight = Number(result); + }) + }) .width('100%') .layoutWeight(1)// 占据剩余空间 .height('100%') + if (this.title == '我的二维码') { + Row(){ + SaveButton({text:SaveDescription.SAVE_IMAGE,buttonType:ButtonType.Normal}) + .fontSize(16) + .fontColor(Color.White) + .backgroundColor('rgb(63,199,193)') + .width('100%').height(50) + .onClick(async (event, result) => { + // if (result === SaveButtonOnClickResult.SUCCESS) { + await this.saveWebViewImage(); + // } + }) + }.width('100%').height(56).backgroundColor(Color.White).alignItems(VerticalAlign.Top) + } } .height('100%')// 关键:约束父容器高度 } + + async saveWebViewImage() { + try { + // 1. 获取整个网页的长截图 + this.controller.webPageSnapshot({ + id: "webView", + size: { width: this.contentWidth, height:this.contentHeight } // 确保捕获全尺寸 + }, async (error, result) => { + if (error) { + promptAction.showToast({ message: `截图失败: ${error.message}` }); + console.error('webPageSnapshot error:', error); + return; + } + + if (!result?.imagePixelMap) { + promptAction.showToast({ message: '获取截图数据失败' }); + return; + } + + // 2. 转为JPEG并写入沙箱 + const ctx = getContext(this); + const imagePacker = image.createImagePacker(); + const arrayBuffer = await imagePacker.packToData( + result.imagePixelMap, + { format: 'image/jpeg', quality: 100 } // 平衡质量与大小 + ); + + const sandboxPath = ctx.cacheDir + `/web_fullpage_${Date.now()}.jpg`; + const file = fileIo.openSync(sandboxPath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE); + fileIo.writeSync(file.fd, arrayBuffer); + fileIo.closeSync(file.fd); + + // 3. 保存到相册 + const helper = photoAccessHelper.getPhotoAccessHelper(ctx); + const sandboxUri = fileUri.getUriFromPath(sandboxPath); + const request = photoAccessHelper.MediaAssetChangeRequest.createImageAssetRequest(ctx, sandboxUri); + await helper.applyChanges(request); + + promptAction.showToast({ message: '网页已保存至相册' }); + }); + } catch (e) { + promptAction.showToast({ message: '保存失败: ' + e.message }); + console.error('saveWebViewImage error:', e); + } + } } interface RouteParams { diff --git a/products/expert/src/main/module.json5 b/products/expert/src/main/module.json5 index 306322f..ebe40ea 100644 --- a/products/expert/src/main/module.json5 +++ b/products/expert/src/main/module.json5 @@ -63,6 +63,11 @@ "name": "ohos.permission.READ_MEDIA", "reason": "$string:media_reason", "usedScene": {} + }, + { + "name": "ohos.permission.WRITE_MEDIA", + "reason": "$string:media_reason", + "usedScene": {} } ] } diff --git a/products/expert/src/main/resources/base/media/home_default_icon.png b/products/expert/src/main/resources/base/media/home_default_icon.png new file mode 100644 index 0000000..8f771c1 Binary files /dev/null and b/products/expert/src/main/resources/base/media/home_default_icon.png differ diff --git a/products/expert/src/main/resources/base/media/home_selected_icon.png b/products/expert/src/main/resources/base/media/home_selected_icon.png new file mode 100644 index 0000000..6ef29cc Binary files /dev/null and b/products/expert/src/main/resources/base/media/home_selected_icon.png differ diff --git a/products/expert/src/main/resources/base/profile/main_pages.json b/products/expert/src/main/resources/base/profile/main_pages.json index 3c47502..7347206 100644 --- a/products/expert/src/main/resources/base/profile/main_pages.json +++ b/products/expert/src/main/resources/base/profile/main_pages.json @@ -28,6 +28,12 @@ "pages/VideoPage/CommentReplyPage", "pages/SearchPage/VideoSearchPage", "pages/VideoPage/VideoSelectedPage", + "pages/PatientsPage/PatientPages", + "pages/PatientsPage/PatientMsgSetPage", + "pages/PatientsPage/PatientsGroupPage", + "pages/PatientsPage/BuildOrEditGroupPage", + "pages/PatientsPage/PatientsListPage", + "pages/PatientsPage/PatientDetailsPage", "pages/Netease/imTabPage", "pages/Netease/PublicConsultationPage", "pages/Netease/ConsultationDetailPage",