diff --git a/commons/basic/Index.ets b/commons/basic/Index.ets index c147c74..42fd37f 100644 --- a/commons/basic/Index.ets +++ b/commons/basic/Index.ets @@ -113,4 +113,16 @@ export { compressedImage,saveImageToGallery,CompressedImageInfo } from './src/ma export { ScreeningView } from './src/main/ets/Views/ScreeningView' -export { TagListModel, TagList} from './src/main/ets/models/TagListModel' \ No newline at end of file +export { TagListModel, TagList} from './src/main/ets/models/TagListModel' + +export { AppUtil } from './src/main/ets/utils/AppUtil' + +export { OnWXResp,WXApi,WXEventHandler } from './src/main/ets/models/WXApiWrap' + +// export { DBManager,FileDownloadManager } from './src/main/ets/utils/FileManager' + +export { FileManager } from './src/main/ets/utils/FileManager' + +export { FileInfo,DownloadCallback,PreviewConfig } from './src/main/ets/models/FileModel' + +export { FileDownloadManager } from './src/main/ets/utils/FileDownloadManager' \ No newline at end of file diff --git a/commons/basic/src/main/ets/Views/DefaultHintProWindows.ets b/commons/basic/src/main/ets/Views/DefaultHintProWindows.ets index 6c0c59c..bd89201 100644 --- a/commons/basic/src/main/ets/Views/DefaultHintProWindows.ets +++ b/commons/basic/src/main/ets/Views/DefaultHintProWindows.ets @@ -1,3 +1,5 @@ +import { ChangeUtil } from "../../../../Index"; +import { promptAction } from "@kit.ArkUI"; @CustomDialog export struct DefaultHintProWindows { @@ -12,16 +14,23 @@ export struct DefaultHintProWindows { @Prop cancleColor:ResourceStr = '#FFFFFF'; @Prop confirmColor:ResourceStr = '#FFFFFF'; controller: CustomDialogController; + @State inputContent:string = '' - // 添加回调函数属性 private selectedButton: (index: number) => void = () => {}; + private selectedButtonAndContent: (index: number,content:string) => void = () => {}; + // 修改构造函数 constructor(controller: CustomDialogController, selectedButton: (index:number) => void) { super(); this.controller = controller; this.selectedButton = selectedButton; } + constructor(controller:CustomDialogController,selectedButtonAndContent: (index: number,content:string) => void) { + super(); + this.controller = controller + this.selectedButtonAndContent = selectedButtonAndContent + } build() { Row(){ @@ -32,10 +41,21 @@ export struct DefaultHintProWindows { .textAlign(TextAlign.Center) .margin({ top: 20 }) - Text(this.message) - .fontSize(this.messageFont) - .textAlign(TextAlign.Center) - .margin({ top: 10 }) + if (!ChangeUtil.stringIsUndefinedAndNull(this.message)) { + Text(this.message) + .fontSize(this.messageFont) + .textAlign(TextAlign.Center) + .margin({ top: 10 }) + .padding({left:10,right:10}) + } else { + TextInput({placeholder:'请输入您的登录密码'}) + .margin(10) + .backgroundColor('#f4f4f4') + .borderRadius(5) + .onChange((value:string)=>{ + this.inputContent = value + }) + } Row({ space: 20 }) { Button({ buttonStyle: ButtonStyleMode.TEXTUAL }) { @@ -45,7 +65,11 @@ export struct DefaultHintProWindows { } .width('45%').height(30).backgroundColor(this.cancleColor) .onClick(() => { - this.selectedButton(0) + if (ChangeUtil.stringIsUndefinedAndNull(this.message)) { + this.selectedButtonAndContent(0,'') + } else { + this.selectedButton(0) + } }) .visibility(this.cancleTitle?Visibility.Visible:Visibility.None) @@ -55,7 +79,15 @@ export struct DefaultHintProWindows { .fontColor(this.confirmTitleColor) }.width('45%').height(30).backgroundColor(this.confirmColor) .onClick(() => { - this.selectedButton(1) + if (ChangeUtil.stringIsUndefinedAndNull(this.message)) { + if (ChangeUtil.stringIsUndefinedAndNull(this.inputContent)) { + promptAction.showToast({ message: '请输入您的登录密码', duration: 1000 }) + return + } + this.selectedButtonAndContent(1,this.inputContent) + } else { + this.selectedButton(1) + } }) }.margin({ top: 20, bottom: 20 }) } diff --git a/commons/basic/src/main/ets/constants/BasicConstant.ets b/commons/basic/src/main/ets/constants/BasicConstant.ets index 00aecf9..6820839 100644 --- a/commons/basic/src/main/ets/constants/BasicConstant.ets +++ b/commons/basic/src/main/ets/constants/BasicConstant.ets @@ -14,6 +14,7 @@ export class BasicConstant { static readonly wxUrl = "https://dev-wx.igandan.com/"; static readonly polvId = "11";//保利威视学员id static readonly urlApp="https://dev-app.igandan.com/app/" + static readonly expertPay = "https://dev-app.igandan.com/app/expertPay/" //正式环境 // static readonly urlExpertAPI = "https://app.igandan.com/app/expertAPI/"; // static readonly urlExpertApp = "http://app.igandan.com/app/expertApp/" @@ -23,7 +24,9 @@ export class BasicConstant { // static readonly wxUrl = "https://wx.igandan.com/";// 微信服务器地址 // static readonly polvId = "21";//保利威视学员id // static readonly urlApp="http://app.igandan.com/app/" + // static readonly expertPay = "https://app.igandan.com/app/expertPay/" + static readonly getSystemTime = BasicConstant.urlApp + 'manager/' + 'getSystemTime' static readonly getSystemTimeStamp = BasicConstant.urlApp+'manager/getSystemTimeStamp' static readonly addBonusPoints = BasicConstant.urlExpertApp+'addBonusPoints' static readonly indexV2 = BasicConstant.urlExpertAPI+'indexV2';//首页轮播 @@ -72,12 +75,33 @@ export class BasicConstant { static readonly deleteComment = BasicConstant.urlExpertApp+'deleteComment';//删除评论 static readonly meetingHistoryList = BasicConstant.urlExpertAPI + "meetingHistoryList"; static readonly videoRoll = BasicConstant.urlExpertAPI + "videoRoll"; + static readonly newsRollNew = BasicConstant.urlExpert + 'newsRollNew' + static readonly newsTagList = BasicConstant.urlExpert + 'newsTagList' + static readonly newsListNew = BasicConstant.urlExpert + 'newsListNew' + static readonly defaultNewsListNew = BasicConstant.urlExpert + 'defaultNewsListNew' static readonly expertVideoTypeList = BasicConstant.urlExpertAPI + "expertVideoTypeList"; static readonly patientVideoNew = BasicConstant.urlExpertApp + 'patientVideoNew'; static readonly videoByKeyWordsNew = BasicConstant.urlExpertApp + "videoByKeyWordsNew"; static readonly patientVideoByKeyWordsNew = BasicConstant.urlExpertApp + 'patientVideoByKeyWordsNew' static readonly feedBack = BasicConstant.urlExpert+'feedBack' static readonly ganDanFileByKeyWords = BasicConstant.urlExpertAPI+'ganDanFileByKeyWords'//肝胆课件 + static readonly getKePuCollection = BasicConstant.urlExpert+'getKePuCollection' + static readonly discollection = BasicConstant.urlExpert + 'discollection' + static readonly collection = BasicConstant.urlExpert + 'collection' + static readonly disagree = BasicConstant.urlExpert + 'disagree' + static readonly agree = BasicConstant.urlExpert + 'agree' + static readonly ganDanFileDetials = BasicConstant.urlExpertAPI + 'ganDanFileDetials' + static readonly newsDetial = BasicConstant.urlExpert + 'newsDetial' + static readonly createGanDanFileOrder = BasicConstant.expertPay + 'createGanDanFileOrder' + static readonly getBalance = BasicConstant.expertPay + 'getBalance' + static readonly payGanDanFileOrder = BasicConstant.expertPay + 'payGanDanFileOrder' + static readonly getOrderStatus = BasicConstant.expertPay + 'getOrderStatus' + static readonly useWelfareNum = BasicConstant.urlExpertAPI + 'useWelfareNum' + static readonly allXinyiList = BasicConstant.expertPay + 'allXinyiList' + static readonly xinyiPrice = BasicConstant.expertPay + 'xinyiPrice' + static readonly createXinYiOrder = BasicConstant.expertPay + 'createXinYiOrder' + static readonly payXinYiOrder = BasicConstant.expertPay + 'payXinYiOrder' + static readonly getFlowerList = BasicConstant.expertPay + 'getFlowerList' static readonly tagList = BasicConstant.urlExpertApp + "tagList"; static readonly meetingListBySearch = BasicConstant.urlExpertAPI + "meetingListBySearch"; static readonly videoBySearchNew = BasicConstant.urlExpertApp+'videoBySearchNew';//搜索肝胆视频列表 @@ -135,6 +159,8 @@ export class BasicConstant { static readonly notification_back_refreshData = 250529;//返回上页通知刷新数据 //首页tabContent切换事件通知 static readonly notification_home_tab_change = 25060413; + //课件支付成功返回上一页下载科技 + static readonly notification_kejian_pay_success = 202509041128; static readonly YX_accid='YX_accid'//云信 diff --git a/commons/basic/src/main/ets/constants/Constants.ets b/commons/basic/src/main/ets/constants/Constants.ets new file mode 100644 index 0000000..370a2cf --- /dev/null +++ b/commons/basic/src/main/ets/constants/Constants.ets @@ -0,0 +1,2 @@ +export const APP_ID = "wxbf3658f5e674667c" +export const APP_SECRET = "c4505a04a9910c65efea8e11ffc93f92" diff --git a/commons/basic/src/main/ets/models/FileModel.ets b/commons/basic/src/main/ets/models/FileModel.ets new file mode 100644 index 0000000..dfa75bc --- /dev/null +++ b/commons/basic/src/main/ets/models/FileModel.ets @@ -0,0 +1,51 @@ +// 文件信息接口 +export interface FileInfo { + fileId: string; + fileName: string; + fileType: string; + fileSize: number; + fileUrl: string; + filePath?: string; + downloadStatus: 'pending' | 'downloading' | 'downloaded' | 'failed' | 'cancelled'; + downloadProgress: number; + createTime: number; + updateTime: number; +} + +// 下载进度回调 +export interface DownloadCallback { + onProgress?: (progress: number) => void; + onSuccess?: (filePath: string) => void; + onFailed?: (error: string) => void; + onCancelled?: () => void; +} + +// 文件预览配置 +export interface PreviewConfig { + enableCache?: boolean; + maxCacheSize?: number; + supportedTypes?: string[]; +} + +// 定义下载状态枚举 +export enum DownloadStatus { + PENDING = 'pending', // 等待中 + DOWNLOADING = 'downloading', // 下载中 + PAUSED = 'paused', // 已暂停 + COMPLETED = 'completed', // 已完成 + FAILED = 'failed', // 失败 +} + +// 定义文件项接口 +export interface DownloadFileItem { + id: string; // 文件唯一标识 + name: string; // 文件名 + url: string; // 下载地址 + localPath: string; // 本地存储路径 + size: number; // 文件大小(字节) + downloadedSize: number; // 已下载大小(字节) + status: DownloadStatus; // 下载状态 + createTime: number; // 创建时间 + finishTime?: number; // 完成时间 + error?: string; // 错误信息(失败时) +} \ No newline at end of file diff --git a/commons/basic/src/main/ets/models/WXApiWrap.ets b/commons/basic/src/main/ets/models/WXApiWrap.ets new file mode 100644 index 0000000..af411be --- /dev/null +++ b/commons/basic/src/main/ets/models/WXApiWrap.ets @@ -0,0 +1,44 @@ +import * as wxopensdk from '@tencent/wechat_open_sdk'; +import { APP_ID } from '../constants/Constants'; + +export type OnWXReq = (req: wxopensdk.BaseReq) => void +export type OnWXResp = (resp: wxopensdk.BaseResp) => void + +const kTag = "WXApiEventHandlerImpl" + +class WXApiEventHandlerImpl implements wxopensdk.WXApiEventHandler { + private onReqCallbacks: Map = new Map + private onRespCallbacks: Map = new Map + + registerOnWXReqCallback(on: OnWXReq) { + this.onReqCallbacks.set(on, on) + } + unregisterOnWXReqCallback(on: OnWXReq) { + this.onReqCallbacks.delete(on) + } + + registerOnWXRespCallback(on: OnWXResp) { + this.onRespCallbacks.set(on, on) + } + unregisterOnWXRespCallback(on: OnWXResp) { + this.onRespCallbacks.delete(on) + } + + onReq(req: wxopensdk.BaseReq): void { + wxopensdk.Log.i(kTag, "onReq:%s", JSON.stringify(req)) + this.onReqCallbacks.forEach((on) => { + on(req) + }) + } + + onResp(resp: wxopensdk.BaseResp): void { + wxopensdk.Log.i(kTag, "onResp:%s", JSON.stringify(resp)) + this.onRespCallbacks.forEach((on) => { + on(resp) + }) + } +} + +export const WXApi = wxopensdk.WXAPIFactory.createWXAPI(APP_ID) +export const WXEventHandler = new WXApiEventHandlerImpl + diff --git a/commons/basic/src/main/ets/utils/AESEncryptionDecryption.ets b/commons/basic/src/main/ets/utils/AESEncryptionDecryption.ets index 1dad307..b080901 100644 --- a/commons/basic/src/main/ets/utils/AESEncryptionDecryption.ets +++ b/commons/basic/src/main/ets/utils/AESEncryptionDecryption.ets @@ -111,7 +111,7 @@ export class AESEncryptionDecryption { globalResult = AESEncryptionDecryption.uint8ArrayToString(result.data); console.info('解密后的明文:' + globalResult); } catch (err) { - console.info(err.message); + console.info('解密失败:',err.message); } } diff --git a/commons/basic/src/main/ets/utils/AppUtil.ets b/commons/basic/src/main/ets/utils/AppUtil.ets index 222c4d7..3dc1615 100644 --- a/commons/basic/src/main/ets/utils/AppUtil.ets +++ b/commons/basic/src/main/ets/utils/AppUtil.ets @@ -3,6 +3,7 @@ import { BusinessError } from '@ohos.base' import { KeyboardAvoidMode, window } from '@kit.ArkUI' import { resourceManager } from '@kit.LocalizationKit' import { common } from '@kit.AbilityKit' +import display from '@ohos.display' export class AppUtil { @@ -236,6 +237,22 @@ export class AppUtil { } + /** + * display width + * @returns width in px + */ + static getDisplayWindowWidth(): Pixels { + return px(display.getDefaultDisplaySync().width) + } + + /** + * display height + * @returns height in px + */ + static getDisplayWindowHeight(): Pixels { + return px(display.getDefaultDisplaySync().height) + } + /** * 设置沉浸式状态栏 * @param isLayoutFullScreen 窗口的布局是否为沉浸式布局(该沉浸式布局状态栏、导航栏仍然显示)。true表示沉浸式布局;false表示非沉浸式布局。 @@ -342,6 +359,29 @@ export class AppUtil { AppUtil.getContext().terminateSelf(); AppUtil.getContext().getApplicationContext().killAllProcesses(); } +} +export function px(value: number) { + return new Pixels(value) +} + +export class Pixels { + value: number; + + constructor(value: number) { + this.value = value; + } + + get px() { + return this.value + } + + get vp() { + return px2vp(this.value) + } + + toString() { + return `${this.value}px` + } } \ No newline at end of file diff --git a/commons/basic/src/main/ets/utils/ChangeUtil.ets b/commons/basic/src/main/ets/utils/ChangeUtil.ets index 7726f50..f6b4a20 100644 --- a/commons/basic/src/main/ets/utils/ChangeUtil.ets +++ b/commons/basic/src/main/ets/utils/ChangeUtil.ets @@ -315,6 +315,18 @@ export class ChangeUtil { return height } + static formatPrice(priceStr: string): string { + let priceInFen = parseFloat(priceStr); + let priceInYuan = priceInFen / 100; + return `${priceInYuan.toFixed(2)}`; + } + + static formatPrice2(priceStr: string,discount: string): string { + let priceInFen = parseFloat(priceStr); + let priceInYuan = priceInFen / 100 * parseFloat(discount); + return `${priceInYuan.toFixed(2)}`; + } + static Logout(phone:string) { authStore.delUser(); diff --git a/commons/basic/src/main/ets/utils/FileDownloadManager.ets b/commons/basic/src/main/ets/utils/FileDownloadManager.ets new file mode 100644 index 0000000..2e2d2ba --- /dev/null +++ b/commons/basic/src/main/ets/utils/FileDownloadManager.ets @@ -0,0 +1,366 @@ +import fileIO from '@ohos.fileio'; +import request from '@ohos.request'; +import common from '@ohos.app.ability.common'; +import hilog from '@ohos.hilog'; + +/** + * 文件下载状态枚举 + */ +export enum DownloadStatus { + DOWNLOADING = 'downloading', + COMPLETED = 'completed', + FAILED = 'failed' +} + +/** + * 文件信息接口 + */ +export interface FileInfo { + id: string; + name: string; + url: string; + format: string; + size: number; + status: DownloadStatus; + localPath: string; + downloadTime: number; +} + +/** + * 文件下载管理类 + */ +export class FileDownloadManager { + private static instance: FileDownloadManager; + private downloadQueue: Map = new Map(); + private downloadTasks: Map = new Map(); + private context: common.UIAbilityContext; + private downloadDir: string = ''; + + private constructor(context: common.UIAbilityContext) { + this.context = context; + this.initDownloadDirectory(); + } + + /** + * 获取单例实例 + */ + public static getInstance(context: common.UIAbilityContext): FileDownloadManager { + if (!FileDownloadManager.instance) { + FileDownloadManager.instance = new FileDownloadManager(context); + } + return FileDownloadManager.instance; + } + + /** + * 初始化下载目录 + */ + private async initDownloadDirectory(): Promise { + try { + const filesDir = this.context.filesDir; + this.downloadDir = `${filesDir}/downloads`; + + // 检查并创建下载目录 + try { + await fileIO.access(this.downloadDir); + } catch { + await fileIO.mkdir(this.downloadDir); + } + } catch (error) { + hilog.error(0x0000, 'FileDownloadManager', '初始化下载目录失败: %{public}s', String(error)); + } + } + + /** + * 开始下载文件 + */ + public async downloadFile(url: string,fileId:string, fileName: string, fileType:string): Promise { + try { + // 从URL中提取文件格式 + const format = this.extractFileFormat(url); + + // 生成文件名 + const finalFileName = `${fileName}/${fileId}.${fileType}`; + + // 创建文件信息 + const fileInfo: FileInfo = { + id: fileId, + name: finalFileName, + url: url, + format: format, + size: 0, + status: DownloadStatus.DOWNLOADING, + localPath: `${this.downloadDir}/${finalFileName}`, + downloadTime: Date.now() + }; + + // 添加到下载队列 + this.downloadQueue.set(fileId, fileInfo); + + // 开始下载 + await this.startDownload(fileInfo); + + return fileId; + } catch (error) { + hilog.error(0x0000, 'FileDownloadManager', '下载文件失败: %{public}s', String(error)); + throw new Error(String(error)); + } + } + + /** + * 开始下载任务 + */ + private async startDownload(fileInfo: FileInfo): Promise { + try { + const config: request.DownloadConfig = { + url: fileInfo.url, + title: fileInfo.name, + description: '文件下载中...', + filePath: fileInfo.localPath, + header: { + 'User-Agent': 'HarmonyOS File Downloader' + } + }; + + const downloadTask = await request.downloadFile(this.context, config); + this.downloadTasks.set(fileInfo.id, downloadTask); + + // 监听下载进度 + downloadTask.on('progress', (receivedSize: number, totalSize: number) => { + fileInfo.size = totalSize; + this.updateFileInfo(fileInfo); + }); + + // 监听下载完成 + downloadTask.on('complete', () => { + fileInfo.status = DownloadStatus.COMPLETED; + this.updateFileInfo(fileInfo); + this.downloadTasks.delete(fileInfo.id); + }); + + // 监听下载失败 + downloadTask.on('fail', (err: number) => { + fileInfo.status = DownloadStatus.FAILED; + this.updateFileInfo(fileInfo); + this.downloadTasks.delete(fileInfo.id); + hilog.error(0x0000, 'FileDownloadManager', '下载失败: %{public}d', err); + }); + + } catch (error) { + fileInfo.status = DownloadStatus.FAILED; + this.updateFileInfo(fileInfo); + throw new Error(String(error)); + } + } + + /** + * 更新文件信息 + */ + private updateFileInfo(fileInfo: FileInfo): void { + this.downloadQueue.set(fileInfo.id, fileInfo); + } + + /** + * 从URL提取文件格式 + */ + private extractFileFormat(url: string): string { + const supportedFormats = ['docx', 'txt', 'ppt', 'pdf', 'doc', 'pptx', 'xls', 'xlsx']; + const urlParts = url.split('.'); + const format = urlParts[urlParts.length - 1].toLowerCase(); + + return supportedFormats.includes(format) ? format : 'bin'; + } + + /** + * 获取下载状态 + */ + public getDownloadStatus(fileId: string): DownloadStatus | null { + const fileInfo = this.downloadQueue.get(fileId); + return fileInfo ? fileInfo.status : null; + } + + /** + * 获取文件信息 + */ + public getFileInfo(fileId: string): FileInfo | null { + return this.downloadQueue.get(fileId) || null; + } + + /** + * 获取所有文件信息 + */ + public getAllFiles(): FileInfo[] { + return Array.from(this.downloadQueue.values()); + } + + /** + * 获取指定状态的文件 + */ + public getFilesByStatus(status: DownloadStatus): FileInfo[] { + return Array.from(this.downloadQueue.values()).filter(file => file.status === status); + } + + /** + * 查看单个文件 + */ + public async viewFile(fileId: string): Promise { + try { + const fileInfo = this.downloadQueue.get(fileId); + if (!fileInfo || fileInfo.status !== DownloadStatus.COMPLETED) { + throw new Error('文件不存在或下载未完成'); + } + + // 检查文件是否存在 + try { + await fileIO.access(fileInfo.localPath); + } catch { + throw new Error('文件不存在'); + } + + // 获取文件信息 + const stat = await fileIO.stat(fileInfo.localPath); + hilog.info(0x0000, 'FileDownloadManager', '文件信息: %{public}s', JSON.stringify(stat)); + + return true; + } catch (error) { + hilog.error(0x0000, 'FileDownloadManager', '查看文件失败: %{public}s', String(error)); + return false; + } + } + + /** + * 查看所有文件 + */ + public async viewAllFiles(): Promise { + try { + const allFiles = this.getAllFiles(); + const completedFiles: FileInfo[] = []; + + for (const file of allFiles) { + if (file.status === DownloadStatus.COMPLETED) { + try { + await fileIO.access(file.localPath); + completedFiles.push(file); + } catch { + // 文件不存在,跳过 + } + } + } + + return completedFiles; + } catch (error) { + hilog.error(0x0000, 'FileDownloadManager', '查看所有文件失败: %{public}s', String(error)); + return []; + } + } + + /** + * 删除单个文件 + */ + public async deleteFile(fileId: string): Promise { + try { + const fileInfo = this.downloadQueue.get(fileId); + if (!fileInfo) { + throw new Error('文件不存在'); + } + + // 取消下载任务 + const downloadTask = this.downloadTasks.get(fileId); + if (downloadTask) { + downloadTask.off('progress'); + downloadTask.off('complete'); + downloadTask.off('fail'); + this.downloadTasks.delete(fileId); + } + + // 删除本地文件 + if (fileInfo.status === DownloadStatus.COMPLETED) { + try { + await fileIO.unlink(fileInfo.localPath); + } catch { + // 文件可能不存在,忽略错误 + } + } + + // 从队列中移除 + this.downloadQueue.delete(fileId); + + return true; + } catch (error) { + hilog.error(0x0000, 'FileDownloadManager', '删除文件失败: %{public}s', String(error)); + return false; + } + } + + /** + * 删除所有文件 + */ + public async deleteAllFiles(): Promise { + try { + const allFiles = Array.from(this.downloadQueue.keys()); + + for (const fileId of allFiles) { + await this.deleteFile(fileId); + } + + return true; + } catch (error) { + hilog.error(0x0000, 'FileDownloadManager', '删除所有文件失败: %{public}s', String(error)); + return false; + } + } + + /** + * 暂停下载 + */ + public pauseDownload(fileId: string): boolean { + const downloadTask = this.downloadTasks.get(fileId); + if (downloadTask) { + downloadTask.off('progress'); + downloadTask.off('complete'); + downloadTask.off('fail'); + this.downloadTasks.delete(fileId); + return true; + } + return false; + } + + /** + * 恢复下载 + */ + public async resumeDownload(fileId: string): Promise { + try { + const fileInfo = this.downloadQueue.get(fileId); + if (!fileInfo || fileInfo.status !== DownloadStatus.DOWNLOADING) { + return false; + } + + await this.startDownload(fileInfo); + return true; + } catch (error) { + hilog.error(0x0000, 'FileDownloadManager', '恢复下载失败: %{public}s', String(error)); + return false; + } + } + + /** + * 清理下载目录 + */ + // public async cleanDownloadDirectory(): Promise { + // try { + // // 使用fileIO.listDir替代readdir + // const files = await fileIO.listDir(this.downloadDir); + // for (const file of files) { + // const filePath = `${this.downloadDir}/${file}`; + // try { + // await fileIO.unlink(filePath); + // } catch { + // // 忽略删除失败的文件 + // } + // } + // return true; + // } catch (error) { + // hilog.error(0x0000, 'FileDownloadManager', '清理下载目录失败: %{public}s', String(error)); + // return false; + // } + // } +} \ No newline at end of file diff --git a/commons/basic/src/main/ets/utils/FileManager.ets b/commons/basic/src/main/ets/utils/FileManager.ets new file mode 100644 index 0000000..63fea47 --- /dev/null +++ b/commons/basic/src/main/ets/utils/FileManager.ets @@ -0,0 +1,799 @@ +import { http } from '@kit.NetworkKit'; +import { promptAction, router } from '@kit.ArkUI'; +import { common } from '@kit.AbilityKit'; +import { fileIo, fileUri } from '@kit.CoreFileKit'; +import { FileInfo,DownloadCallback } from '../models/FileModel' +import preferences from '@ohos.data.preferences'; +import { Want } from '@kit.AbilityKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { filePreview } from '@kit.PreviewKit'; +import request from '@ohos.request'; + +// 下载任务接口 +export interface DownloadTask { + httpRequest: http.HttpRequest; + filePath: string; +} + +// 文件统计信息接口 +export interface FileStat { + size: number; + ctime: number; + mtime: number; +} + +/** + * 文件管理类 - 提供下载、查询、预览等功能 + */ +export class FileManager { + private context: common.Context; + private downloadTasks: Map = new Map(); + private downloadCallbacks: Map = new Map(); + private fileCache: Map = new Map(); + private downloadDir: string; + private isDownloading: boolean = false; + private preferencesHelper: preferences.Preferences | null = null; + private readonly STORE_NAME = 'file_manager_store'; + private readonly FILE_INFO_KEY = 'file_info_cache'; + + constructor(context: common.Context) { + this.context = context; + this.downloadDir = `${this.context.filesDir}/downloads`; + this.initDownloadDirectory(); + this.initPreferences(); + this.loadFileInfoFromStorage(); + } + + /** + * 初始化偏好设置存储 + */ + private async initPreferences(): Promise { + try { + this.preferencesHelper = await preferences.getPreferences(this.context, this.STORE_NAME); + } catch (error) { + console.error('初始化偏好设置失败:', error); + } + } + + /** + * 从存储加载文件信息 + */ + private async loadFileInfoFromStorage(): Promise { + try { + if (this.preferencesHelper) { + const fileInfoJson = await this.preferencesHelper.get(this.FILE_INFO_KEY, '[]'); + const fileInfoArray: FileInfo[] = JSON.parse(fileInfoJson as string); + + // 恢复缓存 + for (const fileInfo of fileInfoArray) { + this.fileCache.set(fileInfo.fileId, fileInfo); + } + + console.info(`从存储加载了 ${fileInfoArray.length} 个文件信息`); + } + } catch (error) { + console.error('加载文件信息失败:', error); + } + } + + /** + * 保存文件信息到存储 + */ + private async saveFileInfoToStorage(): Promise { + try { + if (this.preferencesHelper) { + const fileInfoArray = Array.from(this.fileCache.values()); + const fileInfoJson = JSON.stringify(fileInfoArray); + await this.preferencesHelper.put(this.FILE_INFO_KEY, fileInfoJson); + await this.preferencesHelper.flush(); + } + } catch (error) { + console.error('保存文件信息失败:', error); + } + } + + /** + * 初始化下载目录 + */ + private async initDownloadDirectory(): Promise { + try { + if (!fileIo.accessSync(this.downloadDir)) { + fileIo.mkdirSync(this.downloadDir); + } + } catch (error) { + console.error('创建下载目录失败:', error); + } + } + + /** + * 下载文件 + * @param fileInfo 文件信息 + * @param callback 下载回调 + * @returns Promise + */ + async downloadFile(fileInfo: FileInfo, callback?: DownloadCallback): Promise { + try { + // 检查是否正在下载 + if (this.downloadTasks.has(fileInfo.fileId)) { + promptAction.showToast({ message: '文件正在下载中,请勿重复操作', duration: 2000 }); + return false; + } + + // 检查文件是否已下载 + if (await this.isFileDownloaded(fileInfo.fileId)) { + promptAction.showToast({ message: '文件已下载', duration: 2000 }); + return true; + } + + // 设置下载状态 + this.isDownloading = true; + fileInfo.downloadStatus = 'downloading'; + fileInfo.downloadProgress = 0; + + // 保存回调 + if (callback) { + this.downloadCallbacks.set(fileInfo.fileId, callback); + } + + // 创建下载任务 + const downloadTask = await this.createDownloadTask(fileInfo); + this.downloadTasks.set(fileInfo.fileId, downloadTask); + + // 开始下载 + const success = await this.startDownload(downloadTask, fileInfo); + + if (success) { + fileInfo.downloadStatus = 'downloaded'; + fileInfo.downloadProgress = 100; + this.onDownloadSuccess(fileInfo); + } else { + fileInfo.downloadStatus = 'failed'; + this.onDownloadFailed(fileInfo, '下载失败'); + } + + return success; + } catch (error) { + console.error('下载文件失败:', error); + fileInfo.downloadStatus = 'failed'; + this.onDownloadFailed(fileInfo, '下载失败'); + return false; + } finally { + this.isDownloading = false; + this.downloadTasks.delete(fileInfo.fileId); + } + } + + /** + * 创建下载任务 + */ + private async createDownloadTask(fileInfo: FileInfo): Promise { + try { + // 创建文件名目录 + const fileNameDir = `${fileInfo.fileName}`; + const fileNameDirPath = `${this.downloadDir}/${fileNameDir}`; + + // 确保目录存在 + if (!fileIo.accessSync(fileNameDirPath)) { + fileIo.mkdirSync(fileNameDirPath); + } + + // 设置文件保存路径 - 格式:文件名/文件ID.文件格式 + const fileName = `${fileInfo.fileId}.${fileInfo.fileType}`; + const filePath = `${fileNameDirPath}/${fileName}`; + fileInfo.filePath = filePath; + + // 创建HTTP请求 + const httpRequest = http.createHttp(); + + const task: DownloadTask = { + httpRequest: httpRequest, + filePath: filePath + }; + + return task; + } catch (error) { + console.error('创建下载任务失败:', error); + throw new Error('创建下载任务失败'); + } + } + + /** + * 开始下载 + */ + private async startDownload(downloadTask: DownloadTask, fileInfo: FileInfo): Promise { + // + // let contexta = getContext(this) as common.UIAbilityContext; + // request.downloadFile(contexta.getApplicationContext(), { + // url: fileInfo.fileUrl, + // filePath: downloadTask.filePath, + // enableMetered:true + // }).then((downloadTask: request.DownloadTask) => { + // let progresCallback = (receivedSize: number, totalSize: number) => { + // console.info("downloadddd1 receivedSize:" + receivedSize + " totalSize:" + totalSize); + // }; + // let pauseCallback = () => { + // console.info('Downloadddd task pause.'); + // }; + // //开启进度回调 + // downloadTask.on('progress', progresCallback); + // //开启暂停回调 + // downloadTask.on('pause', pauseCallback); + // //开启下载完成回调 + // downloadTask.on('complete', async () => { + // const taskInfo = await downloadTask.getTaskInfo(); + // console.info('downloaddddTask1 complete:'+`status: ${taskInfo.status}`); + // // downloadTask.getTaskInfo(); + // + // }) + // return true + // }).catch((err: BusinessError) => { + // console.error(`Invoke downloadTask failed, code is ${err.code}, message is ${err.message}`); + // return false; + // }) + try { + const httpRequest = downloadTask.httpRequest; + const filePath = downloadTask.filePath; + + // 配置下载选项 - 参考iOS代码的请求头设置 + const options: http.HttpRequestOptions = { + method: http.RequestMethod.GET, + header: { + 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; HarmonyOS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', + 'Accept-Language': 'zh-CN,zh;q=0.9', + }, + expectDataType: http.HttpDataType.ARRAY_BUFFER, + usingCache: false, + connectTimeout: 60000, + readTimeout: 300000 + }; + + console.info(`开始下载文件: ${fileInfo.fileUrl}`); + console.info(`目标路径: ${filePath}`); + + // 发起下载请求 + const response = await httpRequest.request(fileInfo.fileUrl, options); + + console.info(`响应状态码: ${response.responseCode}`); + console.info(`响应头: ${JSON.stringify(response.header)}`); + + if (response.responseCode === 200) { + try { + // 验证响应数据 + if (!response.result) { + console.error('下载响应数据为空'); + return false; + } + + // 检查数据类型和大小 + if (!(response.result instanceof ArrayBuffer)) { + console.error('响应数据不是ArrayBuffer类型:', typeof response.result); + return false; + } + + const dataSize = response.result.byteLength; + console.info(`下载数据大小: ${dataSize} 字节`); + + if (dataSize <= 10) { + console.error('文件有误:',response.result) + return false + } + + // 确保目标目录存在 + const dirPath = filePath.substring(0, filePath.lastIndexOf('/')); + if (!fileIo.accessSync(dirPath)) { + fileIo.mkdirSync(dirPath); + } + + // 将下载的数据写入文件 + const file = fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY); + + try { + // 写入文件数据 + const bytesWritten = fileIo.writeSync(file.fd, response.result); + console.info(`写入文件成功,写入字节数: ${bytesWritten}`); + + // 验证写入的字节数 + if (bytesWritten !== dataSize) { + console.error(`写入字节数不匹配: 期望 ${dataSize}, 实际 ${bytesWritten}`); + fileIo.closeSync(file); + fileIo.unlinkSync(filePath); // 删除损坏的文件 + return false; + } + + // 同步文件到磁盘 + fileIo.fsyncSync(file.fd); + + // 关闭文件 + fileIo.closeSync(file); + + // 验证文件是否创建成功 + if (!fileIo.accessSync(filePath)) { + console.error('文件创建失败'); + return false; + } + + // 获取文件统计信息 + const stat = fileIo.statSync(filePath); + console.info(`文件创建成功: ${filePath}, 大小: ${stat.size} 字节`); + + // 验证文件大小 + if (stat.size !== dataSize) { + console.error(`文件大小不匹配: 期望 ${dataSize}, 实际 ${stat.size}`); + fileIo.unlinkSync(filePath); + return false; + } + + // 更新文件信息 + fileInfo.fileSize = stat.size; + await this.saveFileInfo(fileInfo); + + return true; + } catch (writeError) { + console.error('写入文件失败:', writeError); + fileIo.closeSync(file); + // 清理可能创建的文件 + try { + if (fileIo.accessSync(filePath)) { + fileIo.unlinkSync(filePath); + } + } catch (cleanupError) { + console.error('清理文件失败:', cleanupError); + } + return false; + } + } catch (writeError) { + console.error('文件写入过程失败:', writeError); + return false; + } + } else { + console.error(`下载失败,状态码: ${response.responseCode}`); + return false; + } + } catch (error) { + console.error('下载执行失败:', error); + return false; + } finally { + // 销毁HTTP请求 + downloadTask.httpRequest.destroy(); + } + } + + /** + * 取消下载 + */ + async cancelDownload(fileId: string): Promise { + try { + const task = this.downloadTasks.get(fileId); + if (task) { + // 销毁HTTP请求 + task.httpRequest.destroy(); + this.downloadTasks.delete(fileId); + + // 更新状态 + const fileInfo = this.fileCache.get(fileId); + if (fileInfo) { + fileInfo.downloadStatus = 'cancelled'; + fileInfo.downloadProgress = 0; + this.onDownloadCancelled(fileInfo); + } + + return true; + } + return false; + } catch (error) { + console.error('取消下载失败:', error); + return false; + } + } + + /** + * 查询已下载文件 + */ + async getDownloadedFiles(): Promise { + try { + const files: FileInfo[] = []; + + // 从缓存获取 + for (const fileInfo of this.fileCache.values()) { + if (fileInfo.downloadStatus === 'downloaded') { + files.push(fileInfo); + } + } + + // 从本地目录扫描 + const localFiles = await this.scanLocalFiles(); + files.push(...localFiles); + + // 按时间排序 + return files.sort((a, b) => b.updateTime - a.updateTime); + } catch (error) { + console.error('查询下载文件失败:', error); + return []; + } + } + + /** + * 扫描本地文件 + */ + private async scanLocalFiles(): Promise { + try { + const files: FileInfo[] = []; + + if (!fileIo.accessSync(this.downloadDir)) { + return files; + } + + const entryList = fileIo.listFileSync(this.downloadDir); + + for (let i = 0; i < entryList.length; i++) { + const entryName = entryList[i]; + const entryPath = `${this.downloadDir}/${entryName}`; + + // 优先按目录处理(当前下载结构是 按文件名建目录,目录下放置 文件ID.扩展名) + let childNames: string[] | null = null; + try { + childNames = fileIo.listFileSync(entryPath); + } catch (_e) { + childNames = null; + } + + if (childNames && childNames.length > 0) { + // 目录:遍历子文件 + for (let j = 0; j < childNames.length; j++) { + const childName = childNames[j]; + const childPath = `${entryPath}/${childName}`; + try { + const childStat = fileIo.statSync(childPath); + const fileInfo = this.parseFileName(childName, childPath, childStat); + if (fileInfo) { + files.push(fileInfo); + } + } catch (childErr) { + console.error('读取子文件失败:', childErr); + } + } + } else { + // 非目录项(兼容旧结构,直接作为文件解析) + try { + const stat = fileIo.statSync(entryPath); + const fileInfo = this.parseFileName(entryName, entryPath, stat); + if (fileInfo) { + files.push(fileInfo); + } + } catch (fileErr) { + console.error('读取文件失败:', fileErr); + } + } + } + + return files; + } catch (error) { + console.error('扫描本地文件失败:', error); + return []; + } + } + + /** + * 解析文件名 + */ + private parseFileName(fileName: string, filePath: string, stat: fileIo.Stat): FileInfo | null { + try { + // 新的文件名格式: 文件名/文件ID.扩展名 + const pathParts = filePath.split('/'); + if (pathParts.length < 2) return null; + + const fileNameDir = pathParts[pathParts.length - 2]; // 文件名目录 + const fileNameWithExt = pathParts[pathParts.length - 1]; // 文件ID.扩展名 + + const fileIdParts = fileNameWithExt.split('.'); + if (fileIdParts.length < 2) return null; + + const fileId = fileIdParts[0]; + const extension = fileIdParts[1]; + + return { + fileId, + fileName: fileNameDir, // 使用目录名作为文件名 + fileType: extension, + fileSize: stat.size, + fileUrl: '', + filePath: filePath, + downloadStatus: 'downloaded', + downloadProgress: 100, + createTime: stat.ctime, + updateTime: stat.mtime + }; + } catch (error) { + console.error('解析文件名失败:', error); + return null; + } + } + + /** + * 检查文件是否已下载 + */ + private async isFileDownloaded(fileId: string): Promise { + try { + // 检查缓存 + const cachedFile = this.fileCache.get(fileId); + if (cachedFile && cachedFile.downloadStatus === 'downloaded') { + return true; + } + + // 检查本地文件 + const localFiles = await this.scanLocalFiles(); + for (let i = 0; i < localFiles.length; i++) { + if (localFiles[i].fileId === fileId) { + return true; + } + } + return false; + } catch (error) { + console.error('检查文件下载状态失败:', error); + return false; + } + } + + /** + * 预览文件 - 使用系统原生方法 + */ + async previewFile(identifier: string) { + try { + // 1) 优先按 uuid 在缓存中查找 + let target: FileInfo | null = null; + const cachedById = this.fileCache.get(identifier); + if (cachedById) { + target = cachedById; + } + + // 2) 若未命中,再按文件名在缓存中查找(取最近更新) + if (!target) { + let latest: FileInfo | null = null; + for (const info of this.fileCache.values()) { + if (info.fileName === identifier) { + if (!latest || info.updateTime > latest.updateTime) { + latest = info; + } + } + } + target = latest; + } + + // 3) 若缓存仍未命中,扫描本地目录 + if (!target) { + const localFiles = await this.scanLocalFiles(); + let latest: FileInfo | null = null; + for (const info of localFiles) { + if (info.fileId === identifier || info.fileName === identifier) { + if (!latest || info.updateTime > latest.updateTime) { + latest = info; + } + } + } + target = latest; + } + + if (!target || !target.filePath) { + promptAction.showToast({ message: '未找到对应文件', duration: 2000 }); + return; + } + + const filePath = target.filePath; + const fileType = target.fileType || ''; + + // 文件是否存在 + if (!fileIo.accessSync(filePath)) { + promptAction.showToast({ message: '文件不存在', duration: 2000 }); + return; + } + + let uri = fileUri.getUriFromPath(filePath); + filePreview.canPreview(this.context, uri).then((result) => { // 传入支持的文件类型且项目存在时会返回true + console.info(`Succeeded in obtaining the result of whether it can be previewed. result = ${result}`); + }).catch((err: BusinessError) => { + console.error(`Failed to obtain the result of whether it can be previewed, err.code = ${err.code}, err.message = ${err.message}`); + }); + + let fileInfo: filePreview.PreviewInfo = { + title: target.fileName, + uri: uri, + mimeType: this.getMimeType(fileType) + }; + let files: Array = new Array(); + files.push(fileInfo); + filePreview.openPreview(this.context, files, 0).then(() => { + console.info('Succeeded in opening preview'); + }).catch((err: BusinessError) => { + console.error(`Failed to open preview, err.code = ${err.code}, err.message = ${err.message}`); + }); + } catch (error) { + console.error('打开文件异常:', error); + } + } + + /** + * 获取MIME类型 + */ + private getMimeType(fileType: string): string { + const mimeTypes: Record = { + 'pdf': 'application/pdf', + 'doc': 'application/msword', + 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'ppt': 'application/vnd.ms-powerpoint', + 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'xls': 'application/vnd.ms-excel', + 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'txt': 'text/plain', + 'jpg': 'image/jpeg', + 'jpeg': 'image/jpeg', + 'png': 'image/png', + 'gif': 'image/gif', + 'bmp': 'image/bmp', + 'mp4': 'video/mp4', + 'avi': 'video/x-msvideo', + 'mov': 'video/quicktime', + 'wmv': 'video/x-ms-wmv' + }; + return mimeTypes[fileType.toLowerCase()] || '*/*'; + } + + /** + * 删除文件 + */ + async deleteFile(identifier: string): Promise { + try { + // 1) 优先按 fileId 在缓存中查找 + let target: FileInfo | null = null; + const cachedById = this.fileCache.get(identifier); + if (cachedById) { + target = cachedById; + } + + // 2) 若未命中,再按文件名在缓存中查找(取最近更新) + if (!target) { + let latest: FileInfo | null = null; + for (const info of this.fileCache.values()) { + if (info.fileName === identifier) { + if (!latest || info.updateTime > latest.updateTime) { + latest = info; + } + } + } + target = latest; + } + + // 3) 若缓存仍未命中,扫描本地目录 + if (!target) { + const localFiles = await this.scanLocalFiles(); + let latest: FileInfo | null = null; + for (const info of localFiles) { + if (info.fileId === identifier || info.fileName === identifier) { + if (!latest || info.updateTime > latest.updateTime) { + latest = info; + } + } + } + target = latest; + } + + if (!target || !target.filePath) { + console.error('未找到要删除的文件:', identifier); + return false; + } + + // 删除本地文件 + if (fileIo.accessSync(target.filePath)) { + fileIo.unlinkSync(target.filePath); + console.info(`成功删除文件: ${target.filePath}`); + + // 删除文件后,检查并删除空的文件夹 + await this.cleanupEmptyDirectory(target.filePath); + } + + // 从缓存中移除 + this.fileCache.delete(target.fileId); + + // 更新持久化存储 + await this.saveFileInfoToStorage(); + + return true; + } catch (error) { + console.error('删除文件失败:', error); + return false; + } + } + + /** + * 清理空文件夹 + */ + private async cleanupEmptyDirectory(filePath: string): Promise { + try { + // 获取文件所在目录 + const dirPath = filePath.substring(0, filePath.lastIndexOf('/')); + + // 检查目录是否存在 + if (!fileIo.accessSync(dirPath)) { + return; + } + + // 检查目录是否为空 + const files = fileIo.listFileSync(dirPath); + if (files.length === 0) { + // 目录为空,删除目录 + fileIo.rmdirSync(dirPath); + console.info(`成功删除空文件夹: ${dirPath}`); + + // 递归检查父目录是否也为空(但不要删除下载根目录) + const parentDir = dirPath.substring(0, dirPath.lastIndexOf('/')); + if (parentDir !== this.downloadDir && fileIo.accessSync(parentDir)) { + const parentFiles = fileIo.listFileSync(parentDir); + if (parentFiles.length === 0) { + fileIo.rmdirSync(parentDir); + console.info(`成功删除空父文件夹: ${parentDir}`); + } + } + } + } catch (error) { + console.error('清理空文件夹失败:', error); + } + } + + /** + * 获取下载状态 + */ + isDownloadingFile(): boolean { + return this.isDownloading; + } + + /** + * 保存文件信息 + */ + private async saveFileInfo(fileInfo: FileInfo): Promise { + try { + this.fileCache.set(fileInfo.fileId, fileInfo); + // 保存到持久化存储 + await this.saveFileInfoToStorage(); + } catch (error) { + console.error('保存文件信息失败:', error); + } + } + + /** + * 下载成功回调 + */ + private onDownloadSuccess(fileInfo: FileInfo): void { + const callback = this.downloadCallbacks.get(fileInfo.fileId); + if (callback && callback.onSuccess) { + callback.onSuccess(fileInfo.filePath || ''); + } + this.downloadCallbacks.delete(fileInfo.fileId); + + promptAction.showToast({ message: '下载完成', duration: 2000 }); + } + + /** + * 下载失败回调 + */ + private onDownloadFailed(fileInfo: FileInfo, error: string): void { + const callback = this.downloadCallbacks.get(fileInfo.fileId); + if (callback && callback.onFailed) { + callback.onFailed(error); + } + this.downloadCallbacks.delete(fileInfo.fileId); + + promptAction.showToast({ message: error, duration: 2000 }); + } + + /** + * 下载取消回调 + */ + private onDownloadCancelled(fileInfo: FileInfo): void { + const callback = this.downloadCallbacks.get(fileInfo.fileId); + if (callback && callback.onCancelled) { + callback.onCancelled(); + } + this.downloadCallbacks.delete(fileInfo.fileId); + + promptAction.showToast({ message: '下载已取消', duration: 2000 }); + } +} \ No newline at end of file diff --git a/commons/basic/src/main/ets/utils/FileManagerCopy.ets b/commons/basic/src/main/ets/utils/FileManagerCopy.ets new file mode 100644 index 0000000..d4f0b67 --- /dev/null +++ b/commons/basic/src/main/ets/utils/FileManagerCopy.ets @@ -0,0 +1,346 @@ +// 导入必要的鸿蒙模块 +import http from '@ohos.net.http'; +import fs from '@ohos.file.fs'; +import relationalStore from '@ohos.data.relationalStore'; +import common from '@ohos.app.ability.common'; +import security from '@ohos.security.cryptoFramework'; +import util from '@ohos.util'; +import { BusinessError } from '@ohos.base'; +import promptAction from '@ohos.promptAction'; +import { cryptoFramework } from '@kit.CryptoArchitectureKit'; +import { authStore } from './auth'; +import { AESEncryptionDecryption } from './AESEncryptionDecryption'; +import { BasicConstant } from '../constants/BasicConstant'; +import { rcp } from '@kit.RemoteCommunicationKit'; +import fileIo from '@ohos.file.fs'; + +// 1. 定义数据模型和数据库结构 +// 文件信息实体 +class WPSFile { + fileId: string = ''; // 对应fileID + fileTitle: string = ''; // 对应fileTitle + fileType: string = ''; // 对应fileType + filePath: string = ''; // 本地存储路径 + downloadType: string = ''; // 下载状态: 'downloading', 'downloadFalse', 'downloaded' + md5?: string = ''; // 文件MD5值 +} + +// 数据库管理器 +export class DBManager { + private rdbStore: relationalStore.RdbStore | null = null; + private readonly TABLE_NAME: string = 'WPSFILELIST'; + private readonly STORE_CONFIG: relationalStore.StoreConfig = { + name: 'WPSFileDB.db', + securityLevel: relationalStore.SecurityLevel.S1, + }; + private context: common.Context; + + constructor(context: common.Context) { + this.context = context; + } + + // 初始化数据库 + async initializeDB(): Promise { + try { + this.rdbStore = await relationalStore.getRdbStore(this.context, this.STORE_CONFIG); + // 创建表 + const sql = ` + CREATE TABLE IF NOT EXISTS ${this.TABLE_NAME} ( + fileId TEXT PRIMARY KEY, + fileTitle TEXT NOT NULL, + fileType TEXT NOT NULL, + filePath TEXT, + downloadType TEXT NOT NULL, + md5 TEXT + )`; + await this.rdbStore.executeSql(sql); + console.log('数据库初始化成功'); + } catch (err) { + console.error(`数据库初始化失败: ${err.code}, ${err.message}`); + } + } + + // 插入文件记录 + async insertFile(file: WPSFile): Promise { + if (!this.rdbStore) { + await this.initializeDB(); + } + + try { + const valueBucket: relationalStore.ValuesBucket = { + 'fileId': file.fileId, + 'fileTitle': file.fileTitle, + 'fileType': file.fileType, + 'filePath': file.filePath, + 'downloadType': file.downloadType, + 'md5': file.md5 || '' + }; + + await this.rdbStore?.insert(this.TABLE_NAME, valueBucket); + console.log('文件记录插入成功'); + return true; + } catch (err) { + console.error(`插入文件记录失败: ${err.code}, ${err.message}`); + return false; + } + } + + // 更新下载状态 + async updateDownloadStatus(fileId: string, status: string): Promise { + if (!this.rdbStore) { + await this.initializeDB(); + } + + try { + const valueBucket: relationalStore.ValuesBucket = { + 'downloadType': status + }; + + let predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates(this.TABLE_NAME); + predicates.equalTo('fileId', fileId); + + await this.rdbStore?.update(valueBucket, predicates); + console.log('下载状态更新成功'); + return true; + } catch (err) { + console.error(`更新下载状态失败: ${err.code}, ${err.message}`); + return false; + } + } + + // 删除文件记录 + async deleteFile(fileId: string): Promise { + if (!this.rdbStore) { + await this.initializeDB(); + } + + try { + let predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates(this.TABLE_NAME); + predicates.equalTo('fileId', fileId); + + await this.rdbStore?.delete(predicates); + console.log('文件记录删除成功'); + return true; + } catch (err) { + console.error(`删除文件记录失败: ${err.code}, ${err.message}`); + return false; + } + } + + // 根据ID查询文件 + async getFileById(fileId: string): Promise { + if (!this.rdbStore) { + await this.initializeDB(); + } + + try { + let predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates(this.TABLE_NAME); + predicates.equalTo('fileId', fileId); + + let resultSet = await this.rdbStore?.query(predicates, ['fileId', 'fileTitle', 'fileType', 'filePath', 'downloadType', 'md5']); + if (resultSet && resultSet.rowCount > 0) { + await resultSet.goToFirstRow(); + let file = new WPSFile(); + file.fileId = resultSet.getString(resultSet.getColumnIndex('fileId')) || ''; + file.fileTitle = resultSet.getString(resultSet.getColumnIndex('fileTitle')) || ''; + file.fileType = resultSet.getString(resultSet.getColumnIndex('fileType')) || ''; + file.filePath = resultSet.getString(resultSet.getColumnIndex('filePath')) || ''; + file.downloadType = resultSet.getString(resultSet.getColumnIndex('downloadType')) || ''; + file.md5 = resultSet.getString(resultSet.getColumnIndex('md5')) || ''; + resultSet.close(); + return file; + } + return null; + } catch (err) { + console.error(`查询文件失败: ${err.code}, ${err.message}`); + return null; + } + } +} + +// 1. 计算文件 MD5 (使用路径创建流) +async function calculateFileMD5(filePath: string): Promise { + let stream: fs.Stream = fs.createStreamSync(filePath, 'r'); + let md5AlgName: string = "MD5"; + let hash: cryptoFramework.Md = cryptoFramework.createMd(md5AlgName); + + let dataBuff: ArrayBuffer = new ArrayBuffer(4096); + let readCount: number = stream.readSync(dataBuff); + while (readCount > 0) { + let messageData: Uint8Array = new Uint8Array(dataBuff.slice(0, readCount)); + let updateMessageBlob: cryptoFramework.DataBlob = { data: messageData }; + hash.updateSync(updateMessageBlob); + readCount = stream.readSync(dataBuff); + } + stream.closeSync(); + + let md5Result: cryptoFramework.DataBlob = hash.digestSync(); + let md5Hex: string = Array.from(md5Result.data).map(byte => byte.toString(16).padStart(2, '0')).join(''); + return md5Hex; +} + +// 2. 文件下载管理器 +export class FileManagerCopy { + private httpRequest: http.HttpRequest = http.createHttp(); + private context: common.Context; + private dbManager: DBManager; + + constructor(context: common.Context) { + this.context = context; + this.dbManager = new DBManager(context); + } + + private generateRandomString(): string { + // 生成1到10之间的随机整数作为字符串长度[6,8](@ref) + const minLength: number = 1; + const maxLength: number = 10; + const targetLength: number = Math.floor(Math.random() * (maxLength - minLength + 1)) + minLength; + // 初始化一个空数组用于存储字符 + let charArray: string[] = []; + // 循环生成指定数量的随机大写字母[6](@ref) + for (let i = 0; i < targetLength; i++) { + // 生成65('A')到90('Z')之间的随机数[6](@ref) + const randomCharCode: number = Math.floor(Math.random() * 26) + 65; + // 将Unicode编码转换为字符并添加到数组 + charArray.push(String.fromCharCode(randomCharCode)); + } + return charArray.join(''); + } + + async downloadFile(wpsUuid: string, wpsTitle: string, wpsType: string, fileMd5: string, order_id:string , orderStatus: number, timestamp: string) { + // 检查是否正在下载 + let existingFile = await this.dbManager.getFileById(wpsUuid); + if (existingFile && existingFile.downloadType === 'downloading') { + promptAction.showToast({ message: '正在下载,请勿重复点击', duration: 2000 }); + // return false; + } + + let daijiami:string = '' + if (orderStatus == 1) {//免费 + daijiami = `${wpsUuid}|${order_id}|${authStore.getUser().uuid}|${timestamp}` + } + // else if (orderStatus == 8) {//复制iOS代码逻辑,有这样的内容 + // daijiami = `${wpsUuid}|${orderStatus}|${authStore.getUser().uuid}|${timestamp}` + // } + else { + daijiami = `${wpsUuid}|${order_id}|${authStore.getUser().uuid}|${timestamp}` + } + // f6f25104fa0345b38074710c9356948b|USEWELFARENUM|1CBMDQbuOX3xbxAcxE5|2025-09-01 11:02:42 + //ios-//f6f25104fa0345b38074710c9356948b|USEWELFARENUM|GA5LeMOXChsKxMrqFnL|2025-08-29 17:25:54 + const scanData = await AESEncryptionDecryption.aesEncrypt(daijiami,BasicConstant.ExpertAesKey) + //hGaao+F44L7qAHJW3s6SuuJHMpP5jrLrFbPRA6AuJY5jF7eBABjSJDjIJL7uvAusvVMJT9371Bvey44xs48MtAaBPRdKMufmGMUkiOiS/916uiWz8iNWCZMYLa4iKXw3 + const encodedString = encodeURIComponent(scanData) + // hGaao%2BF44L7qAHJW3s6SuuJHMpP5jrLrFbPRA6AuJY5jF7eBABjSJDjIJL7uvAusvVMJT9371Bvey44xs48MtAaBPRdKMufmGMUkiOiS%2F916uiWz8iNWCZMYLa4iKXw3 + //ios-//hGaao+F44L7qAHJW3s6SuuJHMpP5jrLrFbPRA6AuJY7QHyYsqbbyOz0oyH4orYtJgPbG1eN9syk0G2vJ1oxq9/0V/ZZMHwb7Qu6H1TEVKLsZOInUm1rOJBhY1/hac4Dv + let pinString = 'X'+encodedString + //XhGaao%2BF44L7qAHJW3s6SuuJHMpP5jrLrFbPRA6AuJY5jF7eBABjSJDjIJL7uvAusvVMJT9371Bvey44xs48MtAaBPRdKMufmGMUkiOiS%2F90X6zMHwPph2jDuPjTMzXqP + //ios-//XhGaao+F44L7qAHJW3s6SuuJHMpP5jrLrFbPRA6AuJY7QHyYsqbbyOz0oyH4orYtJgPbG1eN9syk0G2vJ1oxq9/0V/ZZMHwb7Qu6H1TEVKLsZOInUm1rOJBhY1/hac4Dv + const targetLength: number = Math.floor(Math.random() * (10 - 1 + 1)) + 1; + let downloadUrl = `${BasicConstant.urlExpertApp}downloadGanDanFile?&gdf=${pinString}&a=${targetLength}` + //https://dev-app.igandan.com/app/expertApp/downloadGanDanFile?&gdf=XhGaao%2BF44L7qAHJW3s6SuuJHMpP5jrLrFbPRA6AuJY5jF7eBABjSJDjIJL7uvAusvVMJT9371Bvey44xs48MtAaBPRdKMufmGMUkiOiS%2F90X6zMHwPph2jDuPjTMzXqP&a=10 + //ios-//https://dev-app.igandan.com/app/expertApp/downloadGanDanFile?&gdf=XhGaao+F44L7qAHJW3s6SuuJHMpP5jrLrFbPRA6AuJY7QHyYsqbbyOz0oyH4orYtJgPbG1eN9syk0G2vJ1oxq9/0V/ZZMHwb7Qu6H1TEVKLsZOInUm1rOJBhY1/hac4Dv&a=1 + + // 创建下载记录 + let wpsFile = new WPSFile(); + wpsFile.fileId = wpsUuid; + wpsFile.fileTitle = wpsTitle; + wpsFile.fileType = wpsType; + wpsFile.downloadType = 'downloading'; + wpsFile.md5 = fileMd5; + + //下载文件保存的文件夹路径,仅为示例,请按需求进行替换。 + const DOWNLOAD_TO_PATH = `/data/storage/el2/base/haps/entry/files`; + // 创建了一个安全配置对象,其中remoteValidation设置为'skip',表示将跳过远程验证。 + const securityConfig: rcp.SecurityConfiguration = { + remoteValidation: 'skip' + } + + // 创建了一个下载配置对象,其中kind设置为'folder',表示下载的目标是文件夹,path设置为之前定义的DOWNLOAD_TO_PATH。 + let downloadToFile: rcp.DownloadToFile = { + kind: 'folder', + path: DOWNLOAD_TO_PATH + } + + // 创建一个HTTP会话,其中请求配置包括传输超时设置和安全配置(配置可自定义) + const session = rcp.createSession({ + requestConfiguration: { + transfer: { timeout: { connectMs: 6000, transferMs: 6000, inactivityMs: 6000 } }, + security: securityConfig + } + }) + + // 检查目标路径是否存在 + if (fileIo.accessSync(DOWNLOAD_TO_PATH)) { + fileIo.rmdirSync(DOWNLOAD_TO_PATH); + } + // 发起请求,执行下载操作 + session.downloadToFile(downloadUrl, downloadToFile) + .then((response: rcp.Response) => { + console.info(`Successfully received the response, statusCode: ${JSON.stringify(response.statusCode)}`); + + }).catch((err: BusinessError) => { + console.error(`Failed, the error message is ${JSON.stringify(err)}`) + + }) + + // // 文件保存路径 + // let filesDir = this.context.filesDir; + // wpsFile.filePath = `${filesDir}/${wpsUuid}.${wpsType}`; + // + // // 插入数据库记录 + // let insertSuccess = await this.dbManager.insertFile(wpsFile); + // if (!insertSuccess) { + // promptAction.showToast({ message: '下载初始化失败', duration: 2000 }); + // return false; + // } + // + // promptAction.showToast({ message: '开始下载,请勿重复点击', duration: 2000 }); + // + // try { + // // 配置下载选项 + // let options: http.HttpRequestOptions = { + // method: http.RequestMethod.GET, + // header: { + // 'Content-Type': 'application/octet-stream' + // }, + // expectDataType: http.HttpDataType.ARRAY_BUFFER, + // usingCache: false, + // }; + // + // // 执行下载请求 + // let response = await this.httpRequest.request(downloadUrl, options); + // if (response.responseCode === 200) { + // // 保存文件到本地 + // let file = fs.openSync(wpsFile.filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); + // fs.writeSync(file.fd, response.result as ArrayBuffer); + // fs.closeSync(file); + // + // // 验证MD5 + // let localMd5 = await calculateFileMD5(wpsFile.filePath) + // if (localMd5 === fileMd5) { + // // 更新数据库状态为下载成功 + // await this.dbManager.updateDownloadStatus(wpsUuid, 'downloaded'); + // promptAction.showToast({ message: '下载完成', duration: 2000 }); + // return true; + // } else { + // // MD5不匹配,删除文件 + // fs.unlinkSync(wpsFile.filePath); + // await this.dbManager.deleteFile(wpsUuid); + // promptAction.showToast({ message: '下载失败,文件校验错误', duration: 2000 }); + // return false; + // } + // } else { + // // 下载失败 + // await this.dbManager.deleteFile(wpsUuid); + // promptAction.showToast({ message: '下载失败,请重试', duration: 2000 }); + // return false; + // } + // } catch (err) { + // // 异常处理 + // await this.dbManager.deleteFile(wpsUuid); + // console.error(`下载失败: ${err.code}, ${err.message}`); + // promptAction.showToast({ message: '下载失败,请重试', duration: 2000 }); + // return false; + // } + } +} \ No newline at end of file diff --git a/commons/basic/src/main/ets/utils/request.ets b/commons/basic/src/main/ets/utils/request.ets index 92c8fb4..132d4ea 100644 --- a/commons/basic/src/main/ets/utils/request.ets +++ b/commons/basic/src/main/ets/utils/request.ets @@ -12,6 +12,7 @@ import { deviceInfo } from '@kit.BasicServicesKit'; import { cryptoFramework } from '@kit.CryptoArchitectureKit'; import { rcp } from '@kit.RemoteCommunicationKit'; import { BasicConstant } from '../../../../Index'; +import { connection } from '@kit.NetworkKit'; interface HdRequestOptions { baseURL?: string @@ -26,8 +27,14 @@ export interface HdResponse { } export interface TimestampBean { timestamp:string +} - +export enum NetworkStatus { + type_default = 0,//无网络 + type_wifi = 1,//wifi + type_traffic = 2,//蜂窝网络类型(2G/3G/4G/5G) + type_unknown = 3,//未知网络 + type_error = -1,//失败 } class HdHttp { @@ -431,6 +438,30 @@ class HdHttp { return result === 0 ? (a < b ? -1 : 1) : result; }); } + + getNetworkType():number { + try { + let netHandle = connection.getDefaultNetSync() + if (!netHandle || netHandle.netId === 0) { + console.info('当前网络状态:无网络') + return NetworkStatus.type_default; + } + let netCap = connection.getNetCapabilitiesSync(netHandle); + if (netCap.bearerTypes.includes(connection.NetBearType.BEARER_WIFI)) { + console.info('当前网络状态:wifi') + return NetworkStatus.type_wifi; + } else if (netCap.bearerTypes.includes(connection.NetBearType.BEARER_CELLULAR)) { + console.info('当前网络状态:2G/3G/4G/5G') + return NetworkStatus.type_traffic; + } else { + console.info('当前网络状态:未知网络') + return NetworkStatus.type_unknown; + } + } catch (error) { + console.error("Get net type failed, error: " + JSON.stringify(error)); + return NetworkStatus.type_error; + } + } } interface DataBlob { diff --git a/commons/basic/src/main/resources/base/media/fenxiang.png b/commons/basic/src/main/resources/base/media/fenxiang.png new file mode 100644 index 0000000..c56556c Binary files /dev/null and b/commons/basic/src/main/resources/base/media/fenxiang.png differ diff --git a/commons/basic/src/main/resources/base/media/huise_kongquan.png b/commons/basic/src/main/resources/base/media/huise_kongquan.png new file mode 100644 index 0000000..f6adad9 Binary files /dev/null and b/commons/basic/src/main/resources/base/media/huise_kongquan.png differ diff --git a/commons/basic/src/main/resources/base/media/wei_dian_zan.png b/commons/basic/src/main/resources/base/media/wei_dian_zan.png new file mode 100644 index 0000000..acd2f42 Binary files /dev/null and b/commons/basic/src/main/resources/base/media/wei_dian_zan.png differ diff --git a/commons/basic/src/main/resources/base/media/wei_shou_cang.png b/commons/basic/src/main/resources/base/media/wei_shou_cang.png new file mode 100644 index 0000000..1cdd222 Binary files /dev/null and b/commons/basic/src/main/resources/base/media/wei_shou_cang.png differ diff --git a/commons/basic/src/main/resources/base/media/yi_dian_zan.png b/commons/basic/src/main/resources/base/media/yi_dian_zan.png new file mode 100644 index 0000000..a284a7f Binary files /dev/null and b/commons/basic/src/main/resources/base/media/yi_dian_zan.png differ diff --git a/commons/basic/src/main/resources/base/media/yi_shou_cang.png b/commons/basic/src/main/resources/base/media/yi_shou_cang.png new file mode 100644 index 0000000..8df79d1 Binary files /dev/null and b/commons/basic/src/main/resources/base/media/yi_shou_cang.png differ diff --git a/features/Home/src/main/ets/components/HomeGanDanFileComp.ets b/features/Home/src/main/ets/components/HomeGanDanFileComp.ets new file mode 100644 index 0000000..0809168 --- /dev/null +++ b/features/Home/src/main/ets/components/HomeGanDanFileComp.ets @@ -0,0 +1,179 @@ +import { gandanfileModel, guideModel } from "../model/HomeModel"; + +@Component +export struct HomeGanDanFileComp { + @Prop gandanFileList:gandanfileModel[] + @Prop guideList:guideModel[] + @State currentIndex: number = 0 + private tabsController: TabsController = new TabsController() + + build() { + Column() { + this.headerView() + this.tabContent() + } + .width('100%') + .backgroundColor('#F5F5F5') + } + + @Builder + headerView() { + Stack() { + Row() { + Image($r('app.media.home_guideAndShare_icon')) + .width('70%') + .height(50) + .objectFit(ImageFit.Cover) + } + .width('100%') + .height(50) + .justifyContent(FlexAlign.End) + + Row() { + Row() { + Column() { + Text('实用指南') + .fontSize(this.currentIndex === 0 ? 17 :14) + .fontColor(this.currentIndex === 0 ? '#000000' : '#666666') + .fontWeight(this.currentIndex === 0 ? FontWeight.Bold : FontWeight.Normal) + Blank() + .width(32) + .height(2) + .margin({top:5}) + .backgroundColor($r('app.color.main_color')) + .visibility(this.currentIndex == 0? Visibility.Visible: Visibility.Hidden) + } + .width(80) + .height(50) + .justifyContent(FlexAlign.Center) + .onClick(() => { + this.currentIndex = 0 + this.tabsController.changeIndex(0) + }) + + Column() { + Text('课件分享') + .fontSize(this.currentIndex === 1 ? 17 : 14) + .fontColor(this.currentIndex === 1 ? '#000000' : '#666666') + .fontWeight(this.currentIndex === 1 ? FontWeight.Bold : FontWeight.Normal) + Blank() + .width(32) + .height(2) + .margin({top:5}) + .backgroundColor($r('app.color.main_color')) + .visibility(this.currentIndex == 1? Visibility.Visible: Visibility.Hidden) + } + .width(80) + .height(50) + .justifyContent(FlexAlign.Center) + .onClick(() => { + this.currentIndex = 1 + this.tabsController.changeIndex(1) + }) + } + .width('80%') + .justifyContent(FlexAlign.Start) + .margin({ left: 15 }) + + Row() { + Text('更多') + .fontSize(15) + .fontColor('#999999') + Image($r('app.media.arrow_right')) + .width(10) + .height(15) + .margin({ left: 5 }) + } + .width(100) + .height('100%') + .padding({right:10}) + } + .width('100%') + .height(60) + .alignItems(VerticalAlign.Center) + } + .width('100%') + .height(50) + .backgroundColor(Color.White) + } + + @Builder + tabContent() { + Tabs({ index: this.currentIndex, controller: this.tabsController }) { + TabContent() { + this.BuildGuideList() + } + TabContent() { + this.BuildShareList() + } + } + .onChange((index: number) => { + this.currentIndex = index + }) + .width('100%') + .barWidth(0) + .animationDuration(0) + .backgroundColor(Color.White) + } + + @Builder + BuildGuideList() { + Column() { + this.BuildListItem( + '一学就会的keynote教程番外篇】keynote 线下实训你们要的带练来了', + '查看' + ) + + this.BuildListItem( + '胆源性肝脏疾病一专题研讨会成功举行', + '查看' + ) + } + .width('100%') + } + + // 构建课件分享列表 + @Builder + BuildShareList() { + Column() { + this.BuildListItem( + '肝病学新领域:肝硬化合并慢性肾脏病 | 深度综述大的并慢性肾脏病 | 深度综述并慢并慢性...', + '↓' + ) + + this.BuildListItem( + '【一学就会的keynote教程番外篇】keynote 线下实训你们要的带练来了', + '↓' + ) + + this.BuildListItem( + '段钟平教授:终末期肝病营养治疗的价值及研究进展', + '↓' + ) + } + .width('100%') + } + + // 构建列表项 + @Builder + BuildListItem(title: string, action: string) { + Row() { + Text(title) + .fontSize(14) + .fontColor('#000000') + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .maxLines(2) + .layoutWeight(1) + + Text(action) + .fontSize(14) + .fontColor('#FF0000') + .margin({ left: 10 }) + } + .width('95%') + .padding(10) + .backgroundColor('#f4f4f4') + .borderRadius(8) + .margin({ left: 10, bottom: 10, right:10 }) + } +} diff --git a/features/Home/src/main/ets/components/HomeIconComp.ets b/features/Home/src/main/ets/components/HomeIconComp.ets index fe430b5..c208ce3 100644 --- a/features/Home/src/main/ets/components/HomeIconComp.ets +++ b/features/Home/src/main/ets/components/HomeIconComp.ets @@ -26,6 +26,7 @@ export struct HomeIconComp { @State consultation: string = '公益咨询'; @State consultationRed: boolean = false; @State iconListtmp:iconsModel[]=[]; + listener: (info: uiObserver.RouterPageInfo) => void = (info: uiObserver.RouterPageInfo) => { let routerInfo: uiObserver.RouterPageInfo | undefined = this.queryRouterPageInfo(); if (info.pageId == routerInfo?.pageId) { @@ -36,6 +37,7 @@ export struct HomeIconComp { } } } + aboutToAppear(): void { let uiObserver: UIObserver = this.getUIContext().getUIObserver(); uiObserver.on('routerPageUpdate', this.listener); @@ -79,52 +81,39 @@ export struct HomeIconComp { { consultcount++ } - } } } unreadCount=unreadCount-consultcount - if(unreadCount>0) - { + if(unreadCount>0) { this.patientRed=true - } - else - { + } else { this.patientRed=false hdHttp.post(BasicConstant.applyList,{ expertUuid: authStore.getUser().uuid, } as updateExtraData).then(async (res: HdResponse) => { let json:applyListCallBacl = JSON.parse(res+'') as applyListCallBacl; if(json.code == 1) { - if(json.data!=null&&json.data.length>0) - { + if(json.data!=null&&json.data.length>0) { this.patientRed=true } - } }).catch((err: BusinessError) => { }) } - if(consultcount>0) - { + if(consultcount>0) { this.consultationRed=true } - this.initList() const hashMap: HashMap = new HashMap(); hdHttp.httpReq(BasicConstant.indexV2,hashMap).then(async (res: HdResponse) => { let json:HomeModel = JSON.parse(res+'') as HomeModel; - if(json.data!=null&&json.data.consult_list!=null&&json.data.consult_list.list!=null) - { + if(json.data!=null&&json.data.consult_list!=null&&json.data.consult_list.list!=null) { consultcount=consultcount>0?consultcount:json.data.consult_list.list.length - if(consultcount>0) - { + if(consultcount>0) { this.consultationRed=true - - } - else - { + } else { this.consultationRed=false } this.initList() @@ -132,17 +121,18 @@ export struct HomeIconComp { }).catch((err: BusinessError) => { }) - } - initList() - { + initList() { + const targetNews:iconsModel | undefined = this.iconList.find((model: iconsModel) => model.name === '肝胆新闻'); + const targekejian:iconsModel | undefined = this.iconList.find((model: iconsModel) => model.name === '肝胆课件'); this.iconListtmp=[...[{ 'img': this.patientIcon, 'name': this.patientName,'isRed':this.patientRed } as iconsModel, { 'img': this.videoIcon, 'name': this.videoName} as iconsModel, - {'img':this.zixunIcon,'name':this.consultation,'isRed':this.consultationRed} as iconsModel]] + {'img':this.zixunIcon,'name':this.consultation,'isRed':this.consultationRed} as iconsModel], + {'img':targekejian == undefined?$r('app.media.home_kejian_icon'):targekejian?.img,'name':'肝胆课件','isRed':false} as iconsModel, + {'img':targetNews == undefined?$r('app.media.home_news_icon'):targetNews?.img,'name':'肝胆新闻','isRed':false} as iconsModel] } - build() { Row() { Grid() { @@ -173,8 +163,15 @@ export struct HomeIconComp { url:'pages/VideoPage/VideoGandanPage', params:{"page":"首页"} }) + } else if (item.name == '肝胆新闻') { + router.pushUrl({ + url:'pages/News/GandanNewsPages' + }) + } else if (item.name == '肝胆课件') { + router.pushUrl({ + url:'pages/Courseware/CoursewarePage' + }) } - }) }) }.width('100%').backgroundColor(Color.White) diff --git a/features/Home/src/main/ets/pages/HomePage.ets b/features/Home/src/main/ets/pages/HomePage.ets index 98ecc1f..ecabc1e 100644 --- a/features/Home/src/main/ets/pages/HomePage.ets +++ b/features/Home/src/main/ets/pages/HomePage.ets @@ -10,6 +10,7 @@ import { BasicConstant,hdHttp, HdResponse ,logger,HdHomeNav, ChangeUtil} from '@ import { HomeModel,dataModel, newsModel,iconsModel } from '../model/HomeModel'; import { DefaultHintProWindows,SignPopWindow,HdLoadingDialog } from '@itcast/basic' import { promptAction, router } from '@kit.ArkUI'; +import { HomeGanDanFileComp } from '../components/HomeGanDanFileComp' @Entry @Component @@ -158,6 +159,9 @@ export struct HomePage { if (this.homeData.video_list && this.homeData.video_list.length > 0) { HomeReplayVideoComp({ videoList: this.homeData.video_list }) } + if (this.homeData.gandanfile_list && this.homeData.gandanfile_list.length > 0) { + HomeGanDanFileComp({ gandanFileList:this.homeData.gandanfile_list,guideList:this.homeData.guide_ist }) + } } } .edgeEffect(EdgeEffect.Spring) diff --git a/features/Home/src/main/ets/pages/VideoGandan.ets b/features/Home/src/main/ets/pages/VideoGandan.ets index b354a4b..fa0dc88 100644 --- a/features/Home/src/main/ets/pages/VideoGandan.ets +++ b/features/Home/src/main/ets/pages/VideoGandan.ets @@ -75,6 +75,18 @@ export struct VideoGandan { }) }) + Image($r('app.media.send_follow_icon')) + .width(48) + .height(48) + .position({ x: '100%', y: '100%' }) + .translate({ x: -48, y: -176 }) + .visibility(ChangeUtil.stringIsUndefinedAndNull(this.rightBottom)?Visibility.None:Visibility.Visible) + .onClick(()=>{ + router.pushUrl({ + url: 'pages/Pay/SendFollowPage' + }) + }) + Image(BasicConstant.urlImage+this.rightBottom) .width(76) .height(40) diff --git a/features/Home/src/main/ets/pages/VideoPage.ets b/features/Home/src/main/ets/pages/VideoPage.ets index b0019a2..977dcfc 100644 --- a/features/Home/src/main/ets/pages/VideoPage.ets +++ b/features/Home/src/main/ets/pages/VideoPage.ets @@ -12,7 +12,7 @@ import { http } from '@kit.NetworkKit'; @Entry @Component export struct VideoPage { - + @State params:Record = router.getParams() as Record @State notselectImg: ResourceStr = $r('app.media.triangle_normal'); @State selectImg: ResourceStr = $r('app.media.triangle_green_theme'); @State monthWords:Array =[] @@ -42,7 +42,7 @@ export struct VideoPage { { Column() { - HdNav({ title: '肝胆会议', showRightIcon: false, showLeftIcon: false,showRightText:true,rightText:'扫一扫',rightTextColor:$r('app.color.main_color'),rightItemAction:()=>{ + HdNav({ title: '肝胆会议',showLeftIcon:this.params?true:false, showRightIcon: false,showRightText:true,rightText:'扫一扫',rightTextColor:$r('app.color.main_color'),rightItemAction:()=>{ if (ScanUtil.canIUseScan()) { ScanUtil.startScanForResult().then((scanResult) => { decryptString(scanResult.originalValue) diff --git a/features/Home/src/main/resources/base/media/home_guideAndShare_icon.png b/features/Home/src/main/resources/base/media/home_guideAndShare_icon.png new file mode 100644 index 0000000..8982c04 Binary files /dev/null and b/features/Home/src/main/resources/base/media/home_guideAndShare_icon.png differ diff --git a/features/Home/src/main/resources/base/media/home_kejian_icon.png b/features/Home/src/main/resources/base/media/home_kejian_icon.png new file mode 100644 index 0000000..0858fa2 Binary files /dev/null and b/features/Home/src/main/resources/base/media/home_kejian_icon.png differ diff --git a/features/Home/src/main/resources/base/media/home_news_icon.png b/features/Home/src/main/resources/base/media/home_news_icon.png new file mode 100644 index 0000000..d120576 Binary files /dev/null and b/features/Home/src/main/resources/base/media/home_news_icon.png differ diff --git a/features/Home/src/main/resources/base/media/kejian_download_white.png b/features/Home/src/main/resources/base/media/kejian_download_white.png new file mode 100644 index 0000000..984daa4 Binary files /dev/null and b/features/Home/src/main/resources/base/media/kejian_download_white.png differ diff --git a/features/Home/src/main/resources/base/media/send_follow_icon.png b/features/Home/src/main/resources/base/media/send_follow_icon.png new file mode 100644 index 0000000..6eb5179 Binary files /dev/null and b/features/Home/src/main/resources/base/media/send_follow_icon.png differ diff --git a/features/mypage/src/main/ets/pages/MyHomePage.ets b/features/mypage/src/main/ets/pages/MyHomePage.ets index 4506afe..701b5a2 100644 --- a/features/mypage/src/main/ets/pages/MyHomePage.ets +++ b/features/mypage/src/main/ets/pages/MyHomePage.ets @@ -4,9 +4,13 @@ import { TwoSection } from '../view/TwoSection' import { ThreeSection } from '../view/ThreeSection' import { FourSection } from '../view/FourSection' import { OtherList } from '../view/OtherList' -import { HdNav,hdHttp,HdResponse,BasicConstant,ExpertData,authStore,RequestDefaultModel } from '@itcast/basic' +import { HdNav,hdHttp,HdResponse,BasicConstant,ExpertData,authStore,RequestDefaultModel, + ChangeUtil, + logger, + HdLoadingDialog} from '@itcast/basic' import { BusinessError, emitter } from '@kit.BasicServicesKit'; import HashMap from '@ohos.util.HashMap' +import { router } from '@kit.ArkUI' interface heroFirst { id: string; @@ -18,13 +22,23 @@ export struct MyHomePage { @State title: string = '我的'; @StorageProp('topHeight') topHeight: number = 0 + @State rightBottom:string = '' + @State rightBottomPath:string = '' + @State rightBottomTitle:string = '' @State myInfoBackGround:string = ''; @State heroArray:Array = []; @State expertData:object | string = new Object; scroller = new Scroller() + dialog: CustomDialogController = new CustomDialogController({ + builder: HdLoadingDialog({ message: '加载中...' }), + customStyle: true, + alignment: DialogAlignment.Center + }) + aboutToAppear(): void { + this.getAppActivity() this.uploadBackImgAction(); emitter.on({ eventId: BasicConstant.notification_home_tab_change }, (eventData: emitter.EventData) => { if (eventData.data?.changeIndex === 2) { @@ -61,6 +75,25 @@ export struct MyHomePage { }) } + getAppActivity() { + const hashMap: HashMap = new HashMap() + this.dialog.open() + hashMap.clear() + hashMap.set('id','6') + hdHttp.httpReq(BasicConstant.getAppActivity,hashMap).then(async (res: HdResponse) => { + logger.info('Response addBonusPoints'+res) + this.dialog.close() + let json:Record> = JSON.parse(res+'') as Record> + if (json.code == '200') { + this.rightBottom = json.data['img'] + this.rightBottomPath = json.data['url_path'] + this.rightBottomTitle = json.data['name'] + } + }).catch((err: BusinessError) => { + this.dialog.close() + }) + } + build() { Column() { HdNav({ title: '我的', showLeftIcon:false , showRightIcon: false, hasBorder: true }) @@ -73,6 +106,8 @@ export struct MyHomePage { Column() { HeaderView({heroArray:this.heroArray,expertData:this.expertData}) OneSection() + TwoSection() + ThreeSection() FourSection() OtherList() } @@ -83,6 +118,20 @@ export struct MyHomePage { .layoutWeight(1) .scrollBar(BarState.Off) .align(Alignment.TopStart) + + + Image(BasicConstant.urlImage+this.rightBottom) + .width(76) + .height(40) + .position({ x: '100%', y: '100%' }) + .translate({ x: -76, y: -76 }) + .visibility(ChangeUtil.stringIsUndefinedAndNull(this.rightBottom)?Visibility.None:Visibility.Visible) + .onClick(()=>{ + router.pushUrl({ + url: 'pages/WebView/WebPage', + params: {'title':this.rightBottomTitle,'url':this.rightBottomPath} + }) + }) } .width('100%') .height('100%') diff --git a/features/mypage/src/main/ets/view/ThreeSection.ets b/features/mypage/src/main/ets/view/ThreeSection.ets index bd46de9..7718880 100644 --- a/features/mypage/src/main/ets/view/ThreeSection.ets +++ b/features/mypage/src/main/ets/view/ThreeSection.ets @@ -1,6 +1,7 @@ import { it } from "@ohos/hypium"; import { MyPageSectionClass } from "../model/MyPageSectionClass"; import { MyPageSectionItem } from '../view/MyPageSectionItem' +import { router } from "@kit.ArkUI"; @Preview @Component @@ -9,12 +10,12 @@ export struct ThreeSection { @State currentIndex: number = 0; @State threeSectionList:Array = [ - 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) + // 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('oneItem',$r('app.media.my_page_myFlower'),'我的鲜花','pages/Flower/FlowerDetailsPage',false), + new MyPageSectionClass('twoItem',$r("app.media.my_page_coursewareDetails"),'课件明细','/pages/MyHomePage',false), + // new MyPageSectionClass('fiveItem',$r('app.media.app_icon'),'课程明细','/pages/MyHomePage',false) ]; private getPagedItems(): Array> { @@ -43,6 +44,11 @@ export struct ThreeSection { ForEach(pageItems, (item: MyPageSectionClass) => { GridItem() { MyPageSectionItem({ sectionItem: item }) + .onClick(()=>{ + router.pushUrl({ + url:item.path + }) + }) } }, (item: MyPageSectionClass) => item.id) } diff --git a/features/mypage/src/main/ets/view/TwoSection.ets b/features/mypage/src/main/ets/view/TwoSection.ets index f6930bb..b68229b 100644 --- a/features/mypage/src/main/ets/view/TwoSection.ets +++ b/features/mypage/src/main/ets/view/TwoSection.ets @@ -1,6 +1,7 @@ import { it } from "@ohos/hypium"; import { MyPageSectionClass } from "../model/MyPageSectionClass"; import { MyPageSectionItem } from '../view/MyPageSectionItem' +import { router } from "@kit.ArkUI"; @Preview @Component @@ -9,10 +10,10 @@ export struct TwoSection { @State currentIndex: number = 0; @State twoSectionList:Array = [ - 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('oneItem',$r('app.media.app_icon'),'我的视频','pages/MyHomePage',false), + // new MyPageSectionClass('twoItem',$r('app.media.app_icon'),'我的课程','pages/MyHomePage',false), + new MyPageSectionClass('oneItem',$r('app.media.my_page_download'),'我的下载','pages/Download/AllDownloadPage',false), + // new MyPageSectionClass('fourItem',$r('app.media.app_icon'),'我的收藏','pages/MyHomePage',false) ]; private getPagedItems(): Array> { @@ -41,6 +42,11 @@ export struct TwoSection { ForEach(pageItems, (item: MyPageSectionClass) => { GridItem() { MyPageSectionItem({ sectionItem: item }) + .onClick(()=>{ + router.pushUrl({ + url:item.path + }) + }) } }, (item: MyPageSectionClass) => item.id) } diff --git a/features/mypage/src/main/resources/base/media/my_page_coursewareDetails.png b/features/mypage/src/main/resources/base/media/my_page_coursewareDetails.png new file mode 100644 index 0000000..15e4eb1 Binary files /dev/null and b/features/mypage/src/main/resources/base/media/my_page_coursewareDetails.png differ diff --git a/features/mypage/src/main/resources/base/media/my_page_download.png b/features/mypage/src/main/resources/base/media/my_page_download.png new file mode 100644 index 0000000..cea5074 Binary files /dev/null and b/features/mypage/src/main/resources/base/media/my_page_download.png differ diff --git a/features/mypage/src/main/resources/base/media/my_page_myFlower.png b/features/mypage/src/main/resources/base/media/my_page_myFlower.png new file mode 100644 index 0000000..6251e04 Binary files /dev/null and b/features/mypage/src/main/resources/base/media/my_page_myFlower.png differ diff --git a/features/study/Index.ets b/features/study/Index.ets index 053c309..7cdbc61 100644 --- a/features/study/Index.ets +++ b/features/study/Index.ets @@ -2,4 +2,18 @@ export { SchoolhouseComp } from './src/main/ets/components/SchoolhouseComp'; export { KeepStudyComp } from './src/main/ets/components/KeepStudyComp'; -export { CoursewareComp } from './src/main/ets/components/CoursewareComp'; \ No newline at end of file +export { CoursewareComp } from './src/main/ets/components/CoursewareComp'; + +export { PayComp } from './src/main/ets/components/PayComp' + +export { NewsComp } from './src/main/ets/components/NewsComp' + +export { newsUtil } from './src/main/ets/utils/NewsUtil' + +export { newsRollNewRequest,newsRequestOfData } from './src/main/ets/models/NewsModel' + +export { NewsListComp } from './src/main/ets/components/NewsListComp' + +export { SendFollowComp } from './src/main/ets/components/SendFollowComp' + +export { FlowerDetailsComp } from './src/main/ets/components/FlowerDetailsComp' \ No newline at end of file diff --git a/features/study/src/main/ets/components/CoursewareComp.ets b/features/study/src/main/ets/components/CoursewareComp.ets index 05d1425..180b9ed 100644 --- a/features/study/src/main/ets/components/CoursewareComp.ets +++ b/features/study/src/main/ets/components/CoursewareComp.ets @@ -242,6 +242,9 @@ export struct CoursewareComp { ForEach(this.data, (item: KeJianModel) => { ListItem() { KeJianItemComp({item:item}) + .onClick(()=>{ + router.pushUrl({url:"pages/WebView/KeJianDetailsWebPage",params:{"model":item}}) + }) } }) } diff --git a/features/study/src/main/ets/components/FlowerDetailsComp.ets b/features/study/src/main/ets/components/FlowerDetailsComp.ets new file mode 100644 index 0000000..75cbf93 --- /dev/null +++ b/features/study/src/main/ets/components/FlowerDetailsComp.ets @@ -0,0 +1,232 @@ +import { BasicConstant, ChangeUtil, EmptyViewComp, hdHttp, HdLoadingDialog, HdNav, HdResponse } from '@itcast/basic'; +import { PullToRefreshLayout, RefreshController } from 'refreshlib'; +import { HashMap } from '@kit.ArkTS'; +import { promptAction } from '@kit.ArkUI'; +import { BusinessError } from '@kit.BasicServicesKit'; + +@Component +export struct FlowerDetailsComp { + public controller:RefreshController = new RefreshController(); + scroller = new Scroller(); + @State pageNumber:number = 1; + @State totalPageNumer:number = 1; + @State flowerList:flowerListData[] = [] + @State isEmptyViewVisible: boolean = false; // 控制显隐的状态变量 + @State allFlower:string = '0' + @State allTotalNum:string = '0.00' + + dialog: CustomDialogController = new CustomDialogController({ + builder: HdLoadingDialog({ message: '加载中...' }), + customStyle: true, + alignment: DialogAlignment.Center + }) + + aboutToAppear(): void { + this.getFlowetListData() + } + + getFlowetListData() { + const hashMap: HashMap = new HashMap(); + hashMap.set('page',this.pageNumber.toString()); + this.dialog.open() + hdHttp.httpReq(BasicConstant.getFlowerList,hashMap).then(async (res: HdResponse) => { + this.dialog.close(); + let json:flowerRequest = JSON.parse(res+'') as flowerRequest; + if(json.code == 200) { + if(this.pageNumber==1) { + this.flowerList=[] + if(json.data!=null) { + this.flowerList = json.data.flower_data.list; + } + } else if(this.pageNumber>1) { + this.flowerList.push(...json.data.flower_data.list) + } + this.totalPageNumer = json.data.flower_data.pages + if (ChangeUtil.stringIsUndefinedAndNull(json.data.total_num.toString())) { + this.allFlower = '0' + } else { + this.allFlower = json.data.total_num.toString() + } + this.allTotalNum = ChangeUtil.formatPrice(json.data.total_amount.toString()) + } else { + promptAction.showToast({ message: json.message, duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + this.controller.refreshError(); + console.info(`Response fails: ${err}`); + }) + } + + build() { + Column() { + HdNav({title:'我的鲜花',showRightIcon:false,showRightText:false}) + + if (this.isEmptyViewVisible){ + EmptyViewComp({promptText:'您暂未收到鲜花',isVisibility:this.isEmptyViewVisible}).layoutWeight(1) + } else { + PullToRefreshLayout({ + scroller:this.scroller, + viewKey:"ListPage", + controller:this.controller, + contentView:()=>{ + this.contentView() + }, + + onRefresh:()=>{ + this.pageNumber = 1; + this.getFlowetListData(); + setTimeout(() => { + this.controller.refreshSuccess() + }, 1000) + }, + onCanPullRefresh:()=>{ + if (!this.scroller.currentOffset()) { + /*处理无数据,为空的情况*/ + return true + } + //如果列表到顶,返回true,表示可以下拉,返回false,表示无法下拉 + return this.scroller.currentOffset().yOffset <= 0 + }, + onLoad:()=>{ + this.pageNumber++; + this.getFlowetListData(); + setTimeout(() => { + this.controller.loadSuccess() + }, 1000) + + }, + onCanPullLoad: () => { + if (this.pageNumber >= this.totalPageNumer) { + return false; + } else { + return true; + } + } + }).width('100%').layoutWeight(1).clip(true) + } + } + .height('100%') + .width('100%') + .backgroundColor('#f1f1f1') + } + + @Builder + contentView() { + List({scroller:this.scroller}) { + ListItemGroup({header:this.headerView()}) { + ForEach(this.flowerList,(item:flowerListData)=>{ + ListItem() { + Row() { + Text(item.patient_name) + .fontColor(Color.Gray) + .width(130) + Blank() + .layoutWeight(1) + Text(item.create_date.substring(0,10)) + .fontColor(Color.Gray) + .width(180) + Blank() + .layoutWeight(1) + Text(item.num.toString()) + .textAlign(TextAlign.Center) + .fontColor(Color.Gray) + .width(40) + } + .padding(10) + .width('100%') + .height(50) + .backgroundColor(Color.White) + } + }) + } + } + .width('100%') + .height('100%') + } + + @Builder + headerView() { + Column() { + Row() { + Row() { + Image($r('app.media.flower_expertFlower_icon')) + .size({ width: 20, height: 20 }) + Text(this.allFlower) + .fontColor($r('app.color.main_color')) + .margin({ left: 10 }) + } + + Row() { + Image($r('app.media.flower_accountJinbi_icon')) + .size({ width: 20, height: 20 }) + Text(this.allTotalNum) + .fontColor($r('app.color.main_color')) + .margin({ left: 10 }) + } + .margin({ left: 50 }) + } + .padding(10) + .width('100%') + .height(45) + .backgroundColor(Color.White) + + Row(){ + Text('姓名') + .fontColor(Color.White) + Blank() + .layoutWeight(1) + Text('时间') + .fontColor(Color.White) + Blank() + .layoutWeight(1) + Text('数量') + .fontColor(Color.White) + } + .padding(10) + .width('100%') + .height(45) + .backgroundColor($r('app.color.main_color')) + } + .width('100%') + .height(90) + } +} + +export interface flowerRequest { + 'code':number + 'message':string + 'data':flowerData +} + +export interface flowerData { + 'flower_data':flower_data, + 'total_num':number + 'total_amount':number +} + +export interface flower_data { + 'isFirstPage':number + 'isLastPage':number + 'pageNum':number + 'pages':number + 'pageSize':number + 'total':number + 'list':flowerListData[] +} + +export interface flowerListData { + 'patiet_photo':string + 'expert_photo':string + 'expert_name':string + 'patient_name':string + 'patient_uuid':string + 'expert_uuid':string + 'num':number + 'create_date':string + 'trade_no':string + 'order_status':string + 'amount':number + 'message':string + 'id':string +} \ No newline at end of file diff --git a/features/study/src/main/ets/components/KeepStudyComp.ets b/features/study/src/main/ets/components/KeepStudyComp.ets index 9ca7a6d..060a735 100644 --- a/features/study/src/main/ets/components/KeepStudyComp.ets +++ b/features/study/src/main/ets/components/KeepStudyComp.ets @@ -79,6 +79,6 @@ export struct KeepStudyComp { } .width('100%') .height('100%') - .backgroundColor('#F1F3F5') + .backgroundColor('#f1f1f1') } } diff --git a/features/study/src/main/ets/components/MainPage.ets b/features/study/src/main/ets/components/MainPage.ets new file mode 100644 index 0000000..9de5eb3 --- /dev/null +++ b/features/study/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/study/src/main/ets/components/NewsComp.ets b/features/study/src/main/ets/components/NewsComp.ets new file mode 100644 index 0000000..fae24e2 --- /dev/null +++ b/features/study/src/main/ets/components/NewsComp.ets @@ -0,0 +1,85 @@ +import { AppUtil, HdNav } from "@itcast/basic" +import { NewsSwiperView } from "../views/NewsSwiperView" +import { router } from "@kit.ArkUI" + +@Component +export struct NewsComp { + build() { + Column() { + HdNav({title:'肝胆新闻',showRightIcon:false,showRightText:false}) + NewsSwiperView().height(AppUtil.getDisplayWindowWidth().vp / 16 * 9) + + Row(){ + Image($r('app.media.news_new_icon')) + .width(50) + .height(50) + .margin({left:10}) + .objectFit(ImageFit.Fill) + Column(){ + Text('肝胆新闻') + .fontSize(16) + Text('新鲜资讯,一触即达') + .fontSize(14) + .margin({top:5}) + .fontColor($r('app.color.common_gray_02')) + } + .alignItems(HorizontalAlign.Start) + .margin({left:10,right:10}) + .layoutWeight(1) + + Image($r('app.media.arrow_right')) + .width(12) + .height(15) + .margin({right:10}) + } + .width('95%') + .height(90) + .borderRadius(3) + .backgroundColor(Color.White) + .margin({left:10,top:10,right:10}) + .onClick(()=>{ + router.pushUrl({ + url:'pages/News/GandanNewsListPage', + }) + }) + + Row(){ + Image($r('app.media.news_meeting_icon')) + .width(50) + .height(50) + .margin({left:10}) + .objectFit(ImageFit.Fill) + Column(){ + Text('肝胆会议') + .fontSize(16) + Text('了解会议信息的最佳入口') + .fontSize(14) + .margin({top:5}) + .fontColor($r('app.color.common_gray_02')) + } + .alignItems(HorizontalAlign.Start) + .margin({left:10,right:10}) + .layoutWeight(1) + .onClick(()=>{ + router.pushUrl({ + url:'pages/Meeting/MeetingPage', + params:{"isLeft":"需要"} + }) + }) + + Image($r('app.media.arrow_right')) + .width(12) + .height(15) + .margin({right:10}) + } + .width('95%') + .height(90) + .borderRadius(3) + .backgroundColor(Color.White) + .margin({left:10,top:10,right:10}) + } + .backgroundColor('#f1f1f1') + .width('100%') + .height('100%') + } +} diff --git a/features/study/src/main/ets/components/NewsListComp.ets b/features/study/src/main/ets/components/NewsListComp.ets new file mode 100644 index 0000000..6035709 --- /dev/null +++ b/features/study/src/main/ets/components/NewsListComp.ets @@ -0,0 +1,188 @@ +import { AppUtil, BasicConstant, + EmptyViewComp, + hdHttp, HdLoadingDialog, HdNav, HdResponse, logger } from "@itcast/basic" +import { NewsSwiperView } from "../views/NewsSwiperView" +import { promptAction, router } from "@kit.ArkUI" +import { HashMap } from "@kit.ArkTS"; +import { + newsListRequest, + newsRequestOfData, newsRollNewRequest, newsTagRequest, newsTagsRequestData } from "../models/NewsModel"; +import { BusinessError } from "@kit.BasicServicesKit"; +import { PullToRefreshLayout, RefreshController } from "refreshlib"; +import { NewsItemView } from "../views/NewsItemView"; + +@Component +export struct NewsListComp { + @State tagsList:newsTagsRequestData[] = [] + @State newsList:newsRequestOfData[] = [] + @State isEmptyViewVisible: boolean = false; // 控制显隐的状态变量 + public controller:RefreshController = new RefreshController(); + scroller = new Scroller(); + @State pageNumber:number = 1; + @State totalPageNumer:number = 1; + @State selectedTagId:string = ''; + + dialog: CustomDialogController = new CustomDialogController({ + builder: HdLoadingDialog({ message: '加载中...' }), + customStyle: true, + alignment: DialogAlignment.Center + }) + + aboutToAppear(): void { + this.getHeaderTags() + this.getNewsListData(true) + } + + getHeaderTags() { + const entity = {} as Record + this.dialog.open() + hdHttp.post(BasicConstant.newsTagList, entity).then(async (res: HdResponse) => { + logger.info('Response newsTagList'+res); + let json:newsTagRequest = JSON.parse(res+'') as newsTagRequest; + this.dialog.close(); + this.tagsList = json.data + }).catch((err: BusinessError) => { + this.dialog.close(); + }) + } + + getNewsListData(isDefault:boolean) { + const hashMap: HashMap = new HashMap(); + this.dialog.open() + hashMap.clear(); + hashMap.set('page',this.pageNumber.toString()) + if (!isDefault) + hashMap.set('newstagid', this.selectedTagId)//点击标签的ID + hdHttp.httpReq(isDefault?BasicConstant.defaultNewsListNew:BasicConstant.newsListNew,hashMap).then(async (res: HdResponse) => { + logger.info('Response newsListNew/defaultNewsListNew'+res); + this.dialog.close(); + let json:newsListRequest = JSON.parse(res+'') as newsListRequest; + if(json.code == '1') { + if(this.pageNumber==1) { + this.newsList = [] + if(json.data!=null) { + this.newsList = json.data; + } + } else if(this.pageNumber>1) { + this.newsList.push(...json.data) + } + this.totalPageNumer =json.totalPage; + if (this.newsList.length > 0) { + this.isEmptyViewVisible = false; + } else { + this.isEmptyViewVisible = true; + } + } else { + promptAction.showToast({ message: json.message, duration: 1000 }) + } + this.isEmptyViewVisible = !this.newsList || this.newsList.length === 0 + }).catch((err: BusinessError) => { + this.dialog.close(); + }) + } + + build() { + Column() { + HdNav({ title: '肝胆新闻',showRightIcon: true,rightIcon:$r('app.media.selected_hospital_ws') ,showRightText:false, + rightItemAction:()=>{ + router.pushUrl({ + url:'pages/SearchPage/VideoSearchPage', + params:{'pageName':'视频'} + }) + }}) + + //标签 + if (this.tagsList && this.tagsList.length > 0) { + Scroll() { + Row() { + ForEach(this.tagsList, (tag:newsTagsRequestData) => { + Text(tag.NAME) + .padding({ left: 12, right: 12, top: 6, bottom: 6 }) + .margin({ left: 6, right: 6, top: 10, bottom: 10 }) + .fontSize(16) + .fontColor(this.selectedTagId == tag.ID?$r('app.color.main_color'):Color.Black) + .borderRadius(16) + .onClick(() => { + if (this.selectedTagId !== tag.ID) { + this.selectedTagId = tag.ID + this.pageNumber = 1 + this.getNewsListData(false) + } + }) + }) + } + } + .scrollable(ScrollDirection.Horizontal) + .width('100%') + .height(48) + .scrollBar(BarState.Off) + .backgroundColor('#ffffff') + } + + //轮播 + NewsSwiperView().height(AppUtil.getDisplayWindowWidth().vp / 16 * 9) + + //列表 + if (this.isEmptyViewVisible){ + EmptyViewComp({promptText:'暂无肝胆资讯',isVisibility:this.isEmptyViewVisible}).layoutWeight(1) + } else { + PullToRefreshLayout({ + scroller:this.scroller, + viewKey:"ListPage", + controller:this.controller, + contentView:()=>{ + this.contentView() + }, + + onRefresh:()=>{ + this.pageNumber = 1; + this.getNewsListData(this.selectedTagId==='');//标签点击的第一个为true,后面是false + setTimeout(() => { + this.controller.refreshSuccess() + }, 1000) + }, + onCanPullRefresh:()=>{ + if (!this.scroller.currentOffset()) { + /*处理无数据,为空的情况*/ + return true + } + //如果列表到顶,返回true,表示可以下拉,返回false,表示无法下拉 + return this.scroller.currentOffset().yOffset <= 0 + }, + onLoad:()=>{ + this.pageNumber++; + this.getNewsListData(this.selectedTagId==='');//标签点击的第一个为true,后面是false + setTimeout(() => { + this.controller.loadSuccess() + }, 1000) + + }, + onCanPullLoad: () => { + if (this.pageNumber >= this.totalPageNumer) { + return false; + } else { + return true; + } + } + }).width('100%').layoutWeight(1).clip(true) + } + } + .backgroundColor('#f1f1f1') + .width('100%') + .height('100%') + } + + @Builder + contentView(){ + List({ scroller: this.scroller }) { + ForEach(this.newsList, (item: newsRequestOfData, index) => { + ListItem() { + NewsItemView({item:item}) + } + }) + } + .width('100%') + .height('100%') + .edgeEffect(EdgeEffect.None) + } +} diff --git a/features/study/src/main/ets/components/PayComp.ets b/features/study/src/main/ets/components/PayComp.ets new file mode 100644 index 0000000..1432e61 --- /dev/null +++ b/features/study/src/main/ets/components/PayComp.ets @@ -0,0 +1,323 @@ +import { BasicConstant, ChangeUtil, + DefaultHintProWindows, + hdHttp, HdLoadingDialog, HdNav, HdResponse, + OnWXResp, + WXApi, + WXEventHandler} from "@itcast/basic"; +import { promptAction, router } from "@kit.ArkUI"; +import { BusinessError, emitter } from "@kit.BasicServicesKit"; +import { HashMap } from "@kit.ArkTS"; +import * as wxopensdk from '@tencent/wechat_open_sdk'; +import { common } from "@kit.AbilityKit"; + +@Component +export struct PayComp { + @State params:Record | FileOrderData> = router.getParams() as Record | FileOrderData> + @State payType:number = 0//0没有选择支付;1;选择余额支付;2:选择微信支付 + @State balanceEnough: boolean = false + @State balanceMoney: string = '' + @State name:string = '' + @State hintMessage:string = '' + @State pwd:string = '' + + @State kejianData:FileOrderData = {} as FileOrderData + @State songhuaData:Record> = {} + + private wxApi = WXApi + private wxEventHandler = WXEventHandler + + private onWXResp: OnWXResp = (resp) => { + console.info('wxResp:'+JSON.stringify(resp ?? {}, null, 2)) + if (resp.errCode == 0) { + this.notificationRequest() + } else if (resp.errCode == -2) { + this.hintMessage = '支付取消' + this.alertView.open() + } + } + + dialog: CustomDialogController = new CustomDialogController({ + builder: HdLoadingDialog({ message: '加载中...' }), + customStyle: true, + alignment: DialogAlignment.Center + }) + + alertView:CustomDialogController = new CustomDialogController({ + builder:DefaultHintProWindows({ + title:'提示', + message:this.hintMessage, + cancleTitle: '', + confirmTitleColor: '#666666', + selectedButton: (index:number)=>{ + this.alertView.close(); + if (this.hintMessage == '支付成功' || '您已经成功送给肝胆相照送出心意') { + let innerEvent: emitter.InnerEvent = { + eventId: BasicConstant.notification_kejian_pay_success + }; + let eventData: emitter.EventData = { + data:{"status":'success'} + }; + emitter.emit(innerEvent, eventData); + router.back() + } + } + }), + alignment: DialogAlignment.Center, + cornerRadius:24, + backgroundColor: ('rgba(0,0,0,0.5)'), + }) + + inputAlertView:CustomDialogController = new CustomDialogController({ + builder:DefaultHintProWindows({ + title:'提示', + message:'', + cancleTitleColor: '#333333', + confirmTitleColor: $r('app.color.main_color'), + selectedButtonAndContent:(index:number,content:string)=>{ + if (index == 0) { + this.inputAlertView.close() + } else { + if (!ChangeUtil.stringIsUndefinedAndNull(content)) { + this.pwd = content + this.inputAlertView.close() + this.payOrderRequest() + } + } + } + }), + alignment: DialogAlignment.Center, + cornerRadius:24, + backgroundColor: ('rgba(0,0,0,0.5)'), + }) + + aboutToDisappear(): void { + this.wxEventHandler.unregisterOnWXRespCallback(this.onWXResp) + } + + aboutToAppear(): void { + if (this.params.page == '送花') { + this.name = '肝胆相照' + this.songhuaData = this.params.data as Record> + } else if (this.params.page == '课件详情') { + this.kejianData = this.params.data as FileOrderData + this.name = this.kejianData.provider_name + } + this.getAccountData() + this.wxEventHandler.registerOnWXRespCallback(this.onWXResp) + } + + build() { + Column() { + HdNav({title:'在线支付',showRightIcon:false,showRightText:false}) + + Column() { + Row() { + Row() { + Text(this.params.page == '课件详情'?`下载${this.name}医生的`:`给${this.name}`) + .fontSize(16) + + Text(this.params.page == '课件详情'?'课件':'送心意') + .borderRadius(8) + .fontColor(Color.White) + .margin({ left: 20 }) + .size({ width: 50, height: 30 }) + .textAlign(TextAlign.Center) + .backgroundColor($r('app.color.main_color')) + } + .layoutWeight(1) + + Text(this.params.page == '课件详情'?ChangeUtil.formatPrice(this.kejianData.amount).toString():ChangeUtil.formatPrice(String(this.songhuaData.amount)).toString()+' 元') + .fontColor(Color.Red) + .width('40%') + .textAlign(TextAlign.End) + } + .padding(10) + .width('100%') + .backgroundColor(Color.White) + + Text('请通过以下方式支付') + .width('100%') + .padding(10) + + Row() { + Row() { + Image($r('app.media.pay_account_icon')) + .size({ width: 35, height: 35 }) + Text('余额支付') + .fontSize(16) + .margin({ left: 10 }) + Text(` ¥ ${ChangeUtil.formatPrice(this.balanceMoney)}`) + .fontSize(16) + } + .layoutWeight(1) + + Row() { + if (!this.balanceEnough) { + Text('余额不足') + .fontSize(16) + .fontColor(Color.Red) + .margin({ right: 20 }) + } + Image(this.payType == 1 ? $r('app.media.patiemts_list_selected') : $r('app.media.huise_kongquan')) + .width(15) + .height(15) + .enabled(this.balanceEnough)// 余额不足时禁用 + } + .width('40%') + .justifyContent(FlexAlign.End) + .onClick(() => { + if (this.balanceEnough) { + this.payType = 1 + } + }) + } + .width('100%') + .padding(10) + .backgroundColor(Color.White) + + Text('选择付款方式') + .width('100%') + .padding(10) + Row() { + Image($r('app.media.pay_wx_icon')) + .size({ width: 35, height: 35 }) + Text('微信支付') + .fontSize(16) + .margin({ left: 10 }) + .layoutWeight(1) + Image(this.payType == 2 ? $r('app.media.patiemts_list_selected') : $r('app.media.huise_kongquan')) + .width(15) + .height(15) + } + .width('100%') + .padding(10) + .backgroundColor(Color.White) + .onClick(() => { + this.payType = 2 + }) + } + .width('100%') + .layoutWeight(1) + + Button('立即支付') + .width('90%') + .height(40) + .fontColor(Color.White) + .backgroundColor($r('app.color.main_color')) + .margin({left:20,bottom:50,right:20}) + .onClick(() => { + if (this.payType == 0) { + this.hintMessage = '请选择支付方式' + this.alertView.open() + return + } else if (this.payType == 1) { + this.inputAlertView.open() + return + } + this.payOrderRequest() + }) + } + .backgroundColor('#f4f4f4') + .width('100%') + .height('100%') + } + + getAccountData() { + const hashMap: HashMap = new HashMap() + this.dialog.open() + hashMap.clear() + hdHttp.httpReq(BasicConstant.getBalance,hashMap).then(async (res: HdResponse) => { + console.info('Response getBalance'+res) + this.dialog.close() + let json:Record = JSON.parse(res+'') as Record; + if(json.code == '200') { + this.balanceMoney = json.data + let amount = this.params.page == '课件详情'?ChangeUtil.formatPrice(this.kejianData.amount):ChangeUtil.formatPrice(String(this.songhuaData.amount)) + if (ChangeUtil.formatPrice(this.balanceMoney)> amount) { + this.balanceEnough = true + } + } else { + promptAction.showToast({ message: String(json.message), duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close() + console.error(`Response fails: ${err}`); + }) + } + + payOrderRequest() { + const hashMap: HashMap = new HashMap() + this.dialog.open() + let trade_no = this.params.page == '课件详情'?this.kejianData.trade_no:String(this.songhuaData.trade_no) + hashMap.clear() + hashMap.set('channel', this.payType == 1?'balance':'wx') + hashMap.set('trade_no',trade_no) + hashMap.set('pwd',this.pwd) + hdHttp.httpReq(this.params.page == '课件详情'?BasicConstant.payGanDanFileOrder:BasicConstant.payXinYiOrder,hashMap).then(async (res: HdResponse) => { + console.info('Response payGanDanFileOrder'+res) + this.dialog.close() + let json:Record> = JSON.parse(res+'') as Record>; + if(json.code == '200') { + if (json.data != undefined) { + let req = new wxopensdk.PayReq + req.partnerId = json.data['partnerid'] + req.appId = json.data['appid'] + req.packageValue = json.data['package_str'] + req.prepayId = json.data['prepayid'] + req.nonceStr = json.data['noncestr'] + req.timeStamp = json.data['timestamp'] + req.sign = json.data['sign'] + req.extData = 'extData' + + let finished = await this.wxApi.sendReq(getContext(this) as common.UIAbilityContext, req) + console.info("send request finished: ", finished) + } else { + if (this.payType == 1) { + this.hintMessage = this.params.page == '课件详情'?'支付成功':'您已经成功送给肝胆相照送出心意' + this.alertView.open() + } + } + } else { + promptAction.showToast({ message: String(json.message), duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close() + console.error(`Response fails: ${err}`); + }) + } + + notificationRequest() { + const hashMap: HashMap = new HashMap() + let trade_no = this.params.page == '课件详情'?this.kejianData.trade_no:String(this.songhuaData.trade_no) + this.dialog.open() + hashMap.clear() + hashMap.set('trade_no',trade_no) + hdHttp.httpReq(BasicConstant.getOrderStatus,hashMap).then(async (res: HdResponse) => { + console.info('Response getOrderStatus'+res) + this.dialog.close() + let json:Record> = JSON.parse(res+'') as Record>; + if(json.code == '200') { + if (this.payType == 2) { + let tradeState = json.data['tradeState'] as string + if (tradeState == 'SUCCESS') { + this.hintMessage = this.params.page == '课件详情'?'支付成功':'您已经成功送给肝胆相照送出心意' + this.alertView.open() + } + } + } else { + promptAction.showToast({ message: String(json.message), duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close() + console.error(`Response fails: ${err}`); + }) + } +} + +interface FileOrderData { + trade_no:string + order_id:string + amount:string + account:string + provider_name:string +} \ No newline at end of file diff --git a/features/study/src/main/ets/components/SchoolhouseComp.ets b/features/study/src/main/ets/components/SchoolhouseComp.ets index ec81301..fb7ad1f 100644 --- a/features/study/src/main/ets/components/SchoolhouseComp.ets +++ b/features/study/src/main/ets/components/SchoolhouseComp.ets @@ -258,7 +258,6 @@ export struct SchoolhouseComp { ForEach(this.data, (item: PatientTBean, index) => { ListItem() { ItemCompTeach({item:item}) - .onClick(()=>{this.pushDetailsView(item)}) } }) } @@ -266,25 +265,4 @@ export struct SchoolhouseComp { .height('100%') .edgeEffect(EdgeEffect.None) } - - private pushDetailsView(item: PatientTBean) { - const entity = { - "news_article_uuid":item.uuid, - "user_uuid": authStore.getUser().uuid, - "type":'2' - } as Record - this.dialog.open() - hdHttp.post(BasicConstant.read, entity).then(async (res: HdResponse) => { - this.dialog.close(); - console.info('Response delConditionRecord'+res); - let json:Record | Array>> = JSON.parse(res+'') as Record | Array>>; - if(json.code == '1') { - router.pushUrl({url:"pages/WebView/EducationDetailsWebPage",params:{"model":item}}) - } - }).catch((err: BusinessError) => { - this.dialog.close(); - console.error(`Response fails: ${err}`); - promptAction.showToast({ message: String('患教文库数据请求失败!'), duration: 1000 }) - }) - } } diff --git a/features/study/src/main/ets/components/SendFollowComp.ets b/features/study/src/main/ets/components/SendFollowComp.ets new file mode 100644 index 0000000..dcc9e41 --- /dev/null +++ b/features/study/src/main/ets/components/SendFollowComp.ets @@ -0,0 +1,343 @@ +import { BasicConstant, ChangeUtil, + DefaultHintProWindows, + hdHttp, HdLoadingDialog, HdNav, HdResponse } from "@itcast/basic"; +import { PullToRefreshLayout, RefreshController } from "refreshlib"; +import { HashMap } from "@kit.ArkTS"; +import { xinyiListData, xinyiRequest } from "../models/XinYiModel"; +import { promptAction, router } from "@kit.ArkUI"; +import { BusinessError, emitter } from "@kit.BasicServicesKit"; + +@Component +export struct SendFollowComp { + public controller:RefreshController = new RefreshController(); + scroller = new Scroller(); + @State pageNumber:number = 1; + @State totalPageNumer:number = 1; + @State xinyiList:xinyiListData[] = [] + @State xinyiPrice:string = '' + @State sendPrice:string = '' + @State selectedAmount:number = 5 + @State message:string = '祝肝胆相照平台越来越好!' + moneyOptions:number[] = [1,2,5,10,50] + + dialog: CustomDialogController = new CustomDialogController({ + builder: HdLoadingDialog({ message: '加载中...' }), + customStyle: true, + alignment: DialogAlignment.Center + }) + + alertView:CustomDialogController = new CustomDialogController({ + builder:DefaultHintProWindows({ + title:'提示', + message:'您确定是否送出', + cancleTitle:'否', + confirmTitle:'是', + cancleTitleColor: '#666666', + confirmTitleColor: $r('app.color.main_color'), + selectedButton: (index:number)=>{ + this.alertView.close(); + if (index == 1) { + this.createXinYiOrder() + } + } + }), + alignment: DialogAlignment.Center, + cornerRadius:24, + backgroundColor: ('rgba(0,0,0,0.5)'), + }) + + private refreshDataCallback = (eventData?: emitter.EventData): void => { + if (Object(eventData?.data)['status'] == 'success') { + this.pageNumber = 1; + this.getXinYiListData() + } + }; + + aboutToAppear(): void { + this.getXinyiPriceData() + this.getXinYiListData() + let innerEvent: emitter.InnerEvent = { + eventId: BasicConstant.notification_kejian_pay_success + } + emitter.on(innerEvent, this.refreshDataCallback) + } + + aboutToDisappear(): void { + emitter.off(BasicConstant.notification_kejian_pay_success, this.refreshDataCallback) + } + + getXinYiListData() { + const hashMap: HashMap = new HashMap(); + hashMap.set('page',this.pageNumber.toString()); + this.dialog.open() + hdHttp.httpReq(BasicConstant.allXinyiList,hashMap).then(async (res: HdResponse) => { + this.dialog.close(); + let json:xinyiRequest = JSON.parse(res+'') as xinyiRequest; + if(json.code == '200') { + if(this.pageNumber==1) { + this.xinyiList=[] + if(json.data!=null) { + this.xinyiList = json.data.list; + } + } else if(this.pageNumber>1) { + this.xinyiList.push(...json.data.list) + } + this.totalPageNumer = json.data.total; + } else { + promptAction.showToast({ message: json.message, duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + this.controller.refreshError(); + console.info(`Response fails: ${err}`); + }) + } + + getXinyiPriceData() { + const hashMap: HashMap = new HashMap(); + this.dialog.open() + hdHttp.httpReq(BasicConstant.xinyiPrice,hashMap).then(async (res: HdResponse) => { + this.dialog.close(); + let json:Record = JSON.parse(res+'') as Record; + if(json.code == '200') { + this.xinyiPrice = String(json.data) + this.sendPrice = ChangeUtil.formatPrice2(this.xinyiPrice,'5') + } + }).catch((err: BusinessError) => { + this.dialog.close(); + this.controller.refreshError(); + console.info(`Response fails: ${err}`); + }) + } + + createXinYiOrder() { + const hashMap: HashMap = new HashMap(); + this.dialog.open() + hashMap.set('message',ChangeUtil.stringIsUndefinedAndNull(this.message)?'祝肝胆相照平台越来越好!':this.message) + hashMap.set('amount',this.selectedAmount.toString()) + hdHttp.httpReq(BasicConstant.createXinYiOrder,hashMap).then(async (res: HdResponse) => { + this.dialog.close(); + let json:Record> = JSON.parse(res+'') as Record> + if(json.code == '200') { + router.pushUrl({ + url:"pages/Pay/PayPage", + params:{"data":json.data,"page":"送花"} + }) + } else { + promptAction.showToast({message:'服务器异常',duration:1000}) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + this.controller.refreshError(); + console.info(`Response fails: ${err}`); + }) + } + + build() { + Row() { + Column() { + HdNav({title:'表达暖暖心意',showRightIcon:false,showRightText:false}) + + PullToRefreshLayout({ + scroller:this.scroller, + viewKey:"ListPage", + controller:this.controller, + contentView:()=>{ + this.contentView() + }, + + onRefresh:()=>{ + this.pageNumber = 1; + setTimeout(() => { + this.controller.refreshSuccess() + }, 1000) + }, + onCanPullRefresh:()=>{ + if (!this.scroller.currentOffset()) { + /*处理无数据,为空的情况*/ + return true + } + //如果列表到顶,返回true,表示可以下拉,返回false,表示无法下拉 + return this.scroller.currentOffset().yOffset <= 0 + }, + onLoad:()=>{ + this.pageNumber++; + this.getXinYiListData(); + setTimeout(() => { + this.controller.loadSuccess() + }, 1000) + }, + onCanPullLoad: () => { + if (this.pageNumber >= this.totalPageNumer) { + return false; + } else { + return true; + } + } + }).width('100%').layoutWeight(1).clip(true) + } + .width('100%') + } + .height('100%') + } + + @Builder + contentView() { + Column() { + List({scroller:this.scroller}) { + ListItemGroup({header:this.headerView()}) { + ForEach(this.xinyiList, (item: xinyiListData, index: number) => { + ListItem() { + Column() { + Row() { + Image(BasicConstant.urlImage + item.user_photo) + .alt($r('app.media.userPhoto_default')) + .width(50) + .height(50) + .borderRadius(5) + + Column() { + Text(ChangeUtil.stringIsUndefinedAndNull(item.user_name) ? '账号已注销' : item.user_name) + .fontColor($r('app.color.main_color')) + .fontSize(16) + Text(item.message) + .fontSize(14) + .fontColor(Color.Grey) + } + .width('60%') + .alignItems(HorizontalAlign.Start) + .justifyContent(FlexAlign.Start) + .margin({left:10}) + + Text(item.create_date.length > 10 ? item.create_date.substring(0,10) : item.create_date) + .fontSize(12) + .textAlign(TextAlign.End) + .margin({top:10}) + } + .width('100%') + + Blank() + .height(1) + .width('100%') + .margin({top:10}) + .backgroundColor('rgb(227,227,227)') + } + .width('100%') + .padding(10) + } + }, (item: xinyiListData) => item.create_date + item.user_name) + } + } + .width('100%') + .layoutWeight(1) + } + } + + @Builder + headerView() { + Column() { + Blank() + .backgroundColor('rgb(227,228,229)') + .width('100%') + .height(10) + Image($r('app.media.patientLogo')) + .size({width:40,height:40}) + .margin({top:10}) + Text('肝胆相照') + .fontSize(16) + .margin({top:10}) + .fontColor($r('app.color.main_color')) + Text('您的心意将用于肝胆相照平台的持续发展,有了您的支持,肝胆相照会越来越好,同时将给大家带来更多、更好的服务。') + .fontSize(11) + .fontColor('rgb(102,102,102)') + .margin({ left: 8, right: 8, top: 10 }) + + Blank() + .backgroundColor('rgb(227,227,227)') + .width('100%') + .height(1) + .margin({top:10}) + + Image($r("app.media.send_header_follow_icon")) + .size({width:75,height:75}) + .margin({top:25}) + + Text(`¥ ${this.sendPrice}元`) + .fontSize(14) + .textAlign(TextAlign.Center) + .fontColor(Color.Red) + .margin({ top: 20 }) + + // 金额选项 + Row() { + ForEach(this.moneyOptions, (money:number) => { + Text(`${money}`) + .backgroundColor(this.selectedAmount === money ? $r('app.color.main_color') : Color.White) + .fontColor(this.selectedAmount === money ? Color.White : $r('app.color.main_color')) + .fontSize(16) + .border({ width: 1, color: $r('app.color.main_color') }) + .width(30) + .height(30) + .borderRadius(15) + .textAlign(TextAlign.Center) + .margin({ left: 10, right: 10, top: 12 }) + .onClick(() => { + this.selectedAmount = money + this.sendPrice = ChangeUtil.formatPrice2(this.xinyiPrice,String(money)) + }) + }) + } + .justifyContent(FlexAlign.Center) + .width('100%') + } + .width('100%') + + // 留言输入 + TextInput({ text: this.message, placeholder: '请输入您的祝福' }) + .onChange((val:string) => this.message = val) + .margin({ left: 10, right: 10, top: 15 }) + .height(55) + .borderRadius(8) + .fontSize(12) + .maxLength(100) + .backgroundColor('rgb(227,227,227)') + .onChange((value:string)=>{ + this.message = value + }) + + // 送出按钮 + Text('送出') + .fontSize(16) + .backgroundColor($r('app.color.main_color')) + .fontColor(Color.White) + .margin({ top: 10 }) + .height(40) + .textAlign(TextAlign.Center) + .width('100%') + .onClick(() => { + this.alertView.open() + }) + + Blank() + .width('100%') + .height(10) + .backgroundColor('rgb(227,228,229)') + + // 心意墙标题 + Row() { + Image($r('app.media.send_follow_list_icon')) + .size({width:25,height:25}) + Text('肝胆相照的心意墙') + .fontSize(18) + .fontColor($r('app.color.main_color')) + .margin({ left: 10 }) + } + .width('100%') + .padding(10) + + Blank() + .backgroundColor('rgb(227,228,229)') + .width('100%') + .height(1) + } +} diff --git a/features/study/src/main/ets/models/NewsModel.ets b/features/study/src/main/ets/models/NewsModel.ets new file mode 100644 index 0000000..45404e6 --- /dev/null +++ b/features/study/src/main/ets/models/NewsModel.ets @@ -0,0 +1,49 @@ +export interface newsRollNewRequest { + "code":string + "data":newsRequestOfData[] + "message":string +} + +export interface newsRequestOfData { + "path":string + "title":string + "imgs":string + "imgpath":string + "author":string + "headImg":string + "name":string + "uuid":string + "projectName":string + "content":string + "projectType":string + "createDateStr":string + "createDate":string + "create_date":string + "contentText":string + "endDate":string + "summary":string + "readnum":string + "agreenum":string + "public_name":string + "HEAD_IMG":string + "TITLE":string + "editType":string +} + +export interface newsTagRequest { + 'code':string + 'data':newsTagsRequestData[] +} + +export interface newsTagsRequestData { + "NAME":string + "ID":string +} + +export interface newsListRequest { + 'code':string + 'data':newsRequestOfData[] + 'message':string + 'totalPage':number +} + diff --git a/features/study/src/main/ets/models/XinYiModel.ets b/features/study/src/main/ets/models/XinYiModel.ets new file mode 100644 index 0000000..a104928 --- /dev/null +++ b/features/study/src/main/ets/models/XinYiModel.ets @@ -0,0 +1,17 @@ +export interface xinyiRequest { + 'code':string + 'message':string + 'data':xinyiRequestOfData +} + +export interface xinyiRequestOfData { + 'total':number + 'list':xinyiListData[] +} + +export interface xinyiListData { + 'user_photo':string + 'user_name':string + 'message':string + 'create_date':string +} \ No newline at end of file diff --git a/features/study/src/main/ets/utils/NewsUtil.ets b/features/study/src/main/ets/utils/NewsUtil.ets new file mode 100644 index 0000000..7c21320 --- /dev/null +++ b/features/study/src/main/ets/utils/NewsUtil.ets @@ -0,0 +1,33 @@ +import { BasicConstant,authStore } from '@itcast/basic' +import HashMap from '@ohos.util.HashMap'; +import { hdHttp, HdResponse} from '@itcast/basic/Index' +import { BusinessError } from '@kit.BasicServicesKit' +import { newsRequestOfData } from '../models/NewsModel'; +import { router } from '@kit.ArkUI'; + +class NewsUtil { + hashMap: HashMap = new HashMap(); + readNews(uuid:string,model:newsRequestOfData) { + hdHttp.post(BasicConstant.read, { + user_uuid: authStore.getUser().uuid, + news_article_uuid:uuid, + type: '1', + } as readExtraData).then(async (res: HdResponse) => { + console.info('Response delConditionRecord'+res); + let json:Record | Array>> = JSON.parse(res+'') as Record | Array>>; + if(json.code == '1') { + router.pushUrl({url:"pages/WebView/NewsDetailsWebPage",params:{"model":model}}) + } + }).catch((err: BusinessError) => { + console.error(`Response fails: ${err}`); + }) + } +} + +interface readExtraData{ + user_uuid:string, + news_article_uuid:string, + type:string, +} + +export const newsUtil = new NewsUtil() \ No newline at end of file diff --git a/features/study/src/main/ets/views/ItemCompTeach.ets b/features/study/src/main/ets/views/ItemCompTeach.ets index bc8a891..769c208 100644 --- a/features/study/src/main/ets/views/ItemCompTeach.ets +++ b/features/study/src/main/ets/views/ItemCompTeach.ets @@ -1,5 +1,7 @@ -import { BasicConstant, TimestampUtil } from '@itcast/basic'; +import { authStore, BasicConstant, hdHttp, HdLoadingDialog, HdResponse, TimestampUtil } from '@itcast/basic'; import { PatientTBean } from '@itcast/basic/src/main/ets/models/TeachModel'; +import { promptAction, router } from '@kit.ArkUI'; +import { BusinessError } from '@kit.BasicServicesKit'; @Preview @@ -7,9 +9,12 @@ import { PatientTBean } from '@itcast/basic/src/main/ets/models/TeachModel'; export struct ItemCompTeach { @Prop item:PatientTBean; - aboutToAppear(): void { + dialog: CustomDialogController = new CustomDialogController({ + builder: HdLoadingDialog({ message: '加载中...' }), + customStyle: true, + alignment: DialogAlignment.Center + }) - } build() { Column() { Row() { @@ -58,11 +63,32 @@ export struct ItemCompTeach { .width('100%') .padding(10) .onClick(() => { - + this.pushDetailsView(this.item) }) Text().backgroundColor($r('app.color.efefef')).width('100%').height(1) } .backgroundColor(Color.White) } + + private pushDetailsView(item: PatientTBean) { + const entity = { + "news_article_uuid":item.uuid, + "user_uuid": authStore.getUser().uuid, + "type":'2' + } as Record + this.dialog.open() + hdHttp.post(BasicConstant.read, entity).then(async (res: HdResponse) => { + this.dialog.close(); + console.info('Response delConditionRecord'+res); + let json:Record | Array>> = JSON.parse(res+'') as Record | Array>>; + if(json.code == '1') { + router.pushUrl({url:"pages/WebView/EducationDetailsWebPage",params:{"model":item,"isAgree":json.isAgree}}) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + console.error(`Response fails: ${err}`); + promptAction.showToast({ message: String('患教文库数据请求失败!'), duration: 1000 }) + }) + } } diff --git a/features/study/src/main/ets/views/KeJianItemComp.ets b/features/study/src/main/ets/views/KeJianItemComp.ets index ab36506..9c0f695 100644 --- a/features/study/src/main/ets/views/KeJianItemComp.ets +++ b/features/study/src/main/ets/views/KeJianItemComp.ets @@ -55,7 +55,7 @@ export struct KeJianItemComp { .margin({ left: 2 }) .fontSize(13) .fontColor(Color.Red) - Text(this.formatPrice(this.item.price)) + Text(ChangeUtil.formatPrice(this.item.price)) .fontColor(Color.Red) .fontSize(16) } @@ -79,14 +79,14 @@ export struct KeJianItemComp { .margin({ left: 2 }) .fontSize(13) .fontColor(Color.Red) - Text(this.formatPrice2(this.item.price, this.item.discount)) + Text(ChangeUtil.formatPrice2(this.item.price, this.item.discount)) .fontColor(Color.Red) .fontSize(16) Text('原价') .margin({ left: 10 }) .fontSize(12) .fontColor('#999999') - Text('¥'+this.formatPrice(this.item.price)) + Text('¥'+ChangeUtil.formatPrice(this.item.price)) .fontSize(12) .fontColor('#999999') .decoration({ type: TextDecorationType.LineThrough }) @@ -113,18 +113,6 @@ export struct KeJianItemComp { .backgroundColor(Color.White) } - private formatPrice(priceStr: string): string { - let priceInFen = parseFloat(priceStr); - let priceInYuan = priceInFen / 100; - return `${priceInYuan.toFixed(2)}`; - } - - private formatPrice2(priceStr: string,discount: string): string { - let priceInFen = parseFloat(priceStr); - let priceInYuan = priceInFen / 100 * parseFloat(discount); - return `${priceInYuan.toFixed(2)}`; - } - private contentShow(name:string,hospital:string):string { let newname:string = '' let newhospital:string = '' diff --git a/features/study/src/main/ets/views/NewsItemView.ets b/features/study/src/main/ets/views/NewsItemView.ets new file mode 100644 index 0000000..00d0b56 --- /dev/null +++ b/features/study/src/main/ets/views/NewsItemView.ets @@ -0,0 +1,67 @@ +import { authStore, BasicConstant, + ChangeUtil, + hdHttp, HdLoadingDialog, HdResponse, TimestampUtil } from '@itcast/basic'; +import { newsRequestOfData } from '../models/NewsModel'; +import { newsUtil } from '../utils/NewsUtil'; + +@Preview +@Component +export struct NewsItemView { + @Prop item:newsRequestOfData; + + build() { + Column() { + Row() { + Image(ChangeUtil.stringIsUndefinedAndNull(this.item.headImg)?BasicConstant.urlHtml+this.item.imgs:BasicConstant.urlHtml+this.item.headImg).width(114).height(76).alt($r('app.media.home_scroll_default1')) + .alt($r('app.media.home_top_scroll_default')) + Column() { + Text(ChangeUtil.stringIsUndefinedAndNull(this.item.title)?this.item.projectName:this.item.title).fontColor($r('app.color.common_gray_01')).fontSize(16) .textOverflow({ overflow: TextOverflow.Ellipsis }).height(40) + .ellipsisMode(EllipsisMode.END).maxLines(2) .textAlign(TextAlign.Start).align(Alignment.TopStart) + .width('100%') + Row() { + Row() { + Text('今日') + .borderRadius(30) + .fontColor(Color.White) + .backgroundColor('#f24d57') + .fontSize(11) + .padding({ left: 5, right: 5,top:2,bottom:2 }) + .visibility(TimestampUtil.isToday(this.item.createDate) ? Visibility.Visible : Visibility.None) + Text(this.item.createDate.length > 10 ? this.item.createDate.substring(5, 10) : this.item.createDate) + .fontColor($r('app.color.common_gray_03')) + .fontSize(12) + .visibility(!TimestampUtil.isToday(this.item.createDate) ? Visibility.Visible : Visibility.None) + }.width(80).align(Alignment.Start) + + Row() { + Image($r('app.media.read_commient')).width(10).height(10) + Text(Number(this.item.readnum) > 100000 ? Number(this.item.readnum) * 1.000 / 10000.00 + '万' : this.item.readnum + '') + .fontColor($r('app.color.common_gray_03')).padding({left:3}) + .fontSize(12) + }.width(80).align(Alignment.Start) + + Row() { + Image($r('app.media.argee_commient')).width(10).height(10) + Text(Number(this.item.agreenum) > 100000 ? Number(this.item.agreenum) * 1.000 / 10000.00 + '万' : this.item.agreenum + '') + .fontColor($r('app.color.common_gray_03')).padding({left:3}) + .fontSize(12) + }.width(80).align(Alignment.Start) + } + .margin({top:10}) + .width('100%') + }.padding({left:10}) + .layoutWeight(1) + + }.alignSelf(ItemAlign.Start) + + .width('100%') + .padding(10) + .onClick(() => { + newsUtil.readNews(this.item.uuid,this.item) + }) + Text().backgroundColor($r('app.color.efefef')).width('100%').height(1) + } + .backgroundColor(Color.White) + } +} + diff --git a/features/study/src/main/ets/views/NewsSwiperView.ets b/features/study/src/main/ets/views/NewsSwiperView.ets new file mode 100644 index 0000000..ba7235d --- /dev/null +++ b/features/study/src/main/ets/views/NewsSwiperView.ets @@ -0,0 +1,79 @@ +import { AppUtil, BasicConstant,hdHttp, HdResponse ,logger} from '@itcast/basic/Index' +import { BusinessError } from '@kit.BasicServicesKit'; +import { HdLoadingDialog } from '@itcast/basic' +import HashMap from '@ohos.util.HashMap'; +import { newsRequestOfData, newsRollNewRequest } from '../models/NewsModel'; +import { newsUtil } from '../utils/NewsUtil'; + +@Component +export struct NewsSwiperView { + @State list: newsRequestOfData[] = [] + + dialog: CustomDialogController = new CustomDialogController({ + builder: HdLoadingDialog({ message: '加载中...' }), + customStyle: true, + alignment: DialogAlignment.Center + }) + + aboutToAppear(): void { + this.initData() + } + + initData() { + const hashMap: HashMap = new HashMap(); + this.dialog.open() + hashMap.clear(); + hdHttp.httpReq(BasicConstant.newsRollNew,hashMap).then(async (res: HdResponse) => { + logger.info('Response newsRollNew'+res); + let json:newsRollNewRequest = JSON.parse(res+'') as newsRollNewRequest; + this.dialog.close(); + this.list = json.data + }).catch((err: BusinessError) => { + this.dialog.close(); + }) + } + + build() { + Column() { // 使用堆叠布局实现按钮覆盖 + Swiper() { + ForEach(this.list, (item: newsRequestOfData) => { + Stack({alignContent:Alignment.Bottom}) { + Image(BasicConstant.urlHtml + item.headImg) + .objectFit(ImageFit.Fill)// 图片填充模式 + .width('100%') + .height(AppUtil.getDisplayWindowWidth().vp / 16 * 9) + Text(item.title) + .maxLines(1) + .height(30) + .fontColor('#F6F6F6') + .textAlign(TextAlign.Start) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .backgroundColor('#88000000') + .width('100%') + .padding({right:100,left:5}) + .margin({ bottom: 0 }) + }.onClick(()=>{ + newsUtil.readNews(item.uuid,item) + }) + }, (item: newsRequestOfData) => JSON.stringify(item)) + } + .indicator( + Indicator.dot() + .right(0) + .itemWidth(4) + .itemHeight(4) + .selectedItemWidth(4) + .selectedItemHeight(4) + .color($r('app.color.common_gray_02')) + .selectedColor('#3cc9c0') + ) + .loop(true) + .autoPlay(true) + .interval(3000) + .onChange((index: number) => { + + }) + } + .width('100%') + } +} \ No newline at end of file diff --git a/features/study/src/main/resources/base/media/flower_accountJinbi_icon.png b/features/study/src/main/resources/base/media/flower_accountJinbi_icon.png new file mode 100644 index 0000000..18f225a Binary files /dev/null and b/features/study/src/main/resources/base/media/flower_accountJinbi_icon.png differ diff --git a/features/study/src/main/resources/base/media/flower_expertFlower_icon.png b/features/study/src/main/resources/base/media/flower_expertFlower_icon.png new file mode 100644 index 0000000..fbf2c65 Binary files /dev/null and b/features/study/src/main/resources/base/media/flower_expertFlower_icon.png differ diff --git a/features/study/src/main/resources/base/media/home_top_scroll_default.png b/features/study/src/main/resources/base/media/home_top_scroll_default.png new file mode 100644 index 0000000..3328995 Binary files /dev/null and b/features/study/src/main/resources/base/media/home_top_scroll_default.png differ diff --git a/features/study/src/main/resources/base/media/news_meeting_icon.png b/features/study/src/main/resources/base/media/news_meeting_icon.png new file mode 100644 index 0000000..7e37400 Binary files /dev/null and b/features/study/src/main/resources/base/media/news_meeting_icon.png differ diff --git a/features/study/src/main/resources/base/media/news_new_icon.png b/features/study/src/main/resources/base/media/news_new_icon.png new file mode 100644 index 0000000..4f28d80 Binary files /dev/null and b/features/study/src/main/resources/base/media/news_new_icon.png differ diff --git a/features/study/src/main/resources/base/media/patiemts_list_selected.png b/features/study/src/main/resources/base/media/patiemts_list_selected.png new file mode 100644 index 0000000..c60f6bb Binary files /dev/null and b/features/study/src/main/resources/base/media/patiemts_list_selected.png differ diff --git a/features/study/src/main/resources/base/media/patientLogo.png b/features/study/src/main/resources/base/media/patientLogo.png new file mode 100644 index 0000000..2c48035 Binary files /dev/null and b/features/study/src/main/resources/base/media/patientLogo.png differ diff --git a/features/study/src/main/resources/base/media/patients_list_noSelect.png b/features/study/src/main/resources/base/media/patients_list_noSelect.png new file mode 100644 index 0000000..5600189 Binary files /dev/null and b/features/study/src/main/resources/base/media/patients_list_noSelect.png differ diff --git a/features/study/src/main/resources/base/media/pay_account_icon.png b/features/study/src/main/resources/base/media/pay_account_icon.png new file mode 100644 index 0000000..d3106f1 Binary files /dev/null and b/features/study/src/main/resources/base/media/pay_account_icon.png differ diff --git a/features/study/src/main/resources/base/media/pay_wx_icon.png b/features/study/src/main/resources/base/media/pay_wx_icon.png new file mode 100644 index 0000000..3fa52bd Binary files /dev/null and b/features/study/src/main/resources/base/media/pay_wx_icon.png differ diff --git a/features/study/src/main/resources/base/media/send_follow_list_icon.png b/features/study/src/main/resources/base/media/send_follow_list_icon.png new file mode 100644 index 0000000..28a0f01 Binary files /dev/null and b/features/study/src/main/resources/base/media/send_follow_list_icon.png differ diff --git a/features/study/src/main/resources/base/media/send_header_follow_icon.png b/features/study/src/main/resources/base/media/send_header_follow_icon.png new file mode 100644 index 0000000..4c7ec96 Binary files /dev/null and b/features/study/src/main/resources/base/media/send_header_follow_icon.png differ diff --git a/products/expert/src/main/ets/entryability/EntryAbility.ets b/products/expert/src/main/ets/entryability/EntryAbility.ets index 583f81d..a762e7e 100644 --- a/products/expert/src/main/ets/entryability/EntryAbility.ets +++ b/products/expert/src/main/ets/entryability/EntryAbility.ets @@ -4,7 +4,7 @@ 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'; +import { patientDbManager, WXApi, WXEventHandler } from '@itcast/basic'; import { HMRouterMgr } from '@hadss/hmrouter' import { BusinessError } from '@kit.BasicServicesKit' @@ -16,6 +16,8 @@ export default class EntryAbility extends UIAbility { this.checkNotificationStatus(); // 首次检查 // 初始化患者数据库 this.initPatientDatabase(); + //微信 + this.handleWeChatCallIfNeed(want) this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); @@ -89,4 +91,12 @@ export default class EntryAbility extends UIAbility { // 错误处理,例如保持当前状态或设置为默认值 } } + + onNewWant(want: Want, _launchParam: AbilityConstant.LaunchParam): void { + this.handleWeChatCallIfNeed(want) + } + + private handleWeChatCallIfNeed(want: Want) { + WXApi.handleWant(want, WXEventHandler) + } } \ No newline at end of file diff --git a/products/expert/src/main/ets/pages/Download/AllDownloadPage.ets b/products/expert/src/main/ets/pages/Download/AllDownloadPage.ets new file mode 100644 index 0000000..341b67b --- /dev/null +++ b/products/expert/src/main/ets/pages/Download/AllDownloadPage.ets @@ -0,0 +1,37 @@ +import { HdNav } from '@itcast/basic' +import { router } from '@kit.ArkUI' + +@Entry +@Component +struct AllDownloadPage { + + build() { + Column() { + HdNav({title:'我的下载',showRightIcon:false,showRightText:false}) + + Column() { + Row(){ + Text('课件文档') + .fontSize(16) + .layoutWeight(1) + Image($r('app.media.arrow_right')) + .width(7) + .height(10) + .margin({right:10}) + } + .width('100%') + .height('calc(100% - 1vp)') + } + .width('100%') + .height(50) + .padding(10) + .backgroundColor(Color.White) + .onClick(()=>{ + router.pushUrl({url:'pages/Download/KeJianDownloadPage'}) + }) + } + .height('100%') + .width('100%') + .backgroundColor('#f1f1f1') + } +} \ No newline at end of file diff --git a/products/expert/src/main/ets/pages/Download/KeJianDownloadPage.ets b/products/expert/src/main/ets/pages/Download/KeJianDownloadPage.ets new file mode 100644 index 0000000..c6a686c --- /dev/null +++ b/products/expert/src/main/ets/pages/Download/KeJianDownloadPage.ets @@ -0,0 +1,149 @@ +import { DefaultHintProWindows, HdNav } from '@itcast/basic' +import { FileManager } from '@itcast/basic' +import { FileInfo } from '@itcast/basic' +import { common } from '@kit.AbilityKit' +import { promptAction } from '@kit.ArkUI' + +@Entry +@Component +struct KeJianDownloadPage { + @State fileList: FileInfo[] = [] + private fileManager: FileManager | null = null + @State selected:number = 0 + + alertView:CustomDialogController = new CustomDialogController({ + builder:DefaultHintProWindows({ + title:'提示', + message:'确定删除该课件?', + cancleTitleColor: '#666666', + confirmTitleColor: $r('app.color.main_color'), + selectedButton: (index:number)=>{ + this.alertView.close(); + if (index == 1) { + this.deleteFile(this.fileList[this.selected].fileId) + } + } + }), + alignment: DialogAlignment.Center, + cornerRadius:24, + backgroundColor: ('rgba(0,0,0,0.5)'), + }) + + aboutToAppear() { + this.initFileManager() + this.loadDownloadedFiles() + } + + private initFileManager() { + try { + const context = getContext(this) as common.UIAbilityContext + this.fileManager = new FileManager(context) + } catch (error) { + console.error('初始化文件管理器失败:', error) + promptAction.showToast({ message: '初始化失败', duration: 2000 }) + } + } + + private async loadDownloadedFiles() { + try { + if (this.fileManager) { + this.fileList = await this.fileManager.getDownloadedFiles() + console.info(`加载了 ${this.fileList.length} 个已下载文件`) + } + } catch (error) { + console.error('加载文件列表失败:', error) + promptAction.showToast({ message: '加载失败', duration: 2000 }) + } + } + + private async previewFile(fileId: string) { + try { + if (this.fileManager) { + await this.fileManager.previewFile(fileId) + } + } catch (error) { + console.error('预览文件失败:', error) + promptAction.showToast({ message: '预览失败', duration: 2000 }) + } + } + + private async deleteFile(fileId: string) { + try { + if (this.fileManager) { + const success = await this.fileManager.deleteFile(fileId) + if (success) { + this.fileList = this.fileList.filter(file => file.fileId !== fileId) + } + } + } catch (error) { + console.error('删除文件失败:', error) + } + } + + build() { + Column() { + HdNav({title:'课件文档',showRightIcon:false,showRightText:false}) + + if (this.fileList.length === 0) { + Column() { + Text('暂无下载课件') + .fontSize(16) + .fontColor('#999999') + .margin({ top: 10 }) + } + .width('100%') + .height(200) + .justifyContent(FlexAlign.Center) + } else { + List() { + ForEach(this.fileList, (file: FileInfo, index: number) => { + ListItem() { + Row() { + Text(file.fileName) + .fontSize(16) + .fontColor('#333333') + .fontWeight(FontWeight.Medium) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .layoutWeight(1) + } + .height(50) + .width('100%') + .padding(10) + .backgroundColor(Color.White) + .onClick(() => { + this.previewFile(file.fileId) + }) + } + .swipeAction({ + end: { + builder: () => { this.itemEnd(index) }, + } + }) + }, (file: FileInfo) => file.fileId) + } + .width('100%') + .height('100%') + .backgroundColor('#f1f1f1') + } + } + .height('100%') + .width('100%') + .backgroundColor('#F5F5F5') + } + + @Builder + itemEnd(index: number) { + Text('删除') + .fontSize(14) + .fontColor(Color.White) + .backgroundColor(Color.Red) + .textAlign(TextAlign.Center) + .size({width:60,height:50}) + .borderRadius(4) + .onClick(() => { + this.selected = index + this.alertView.open() + }) + } +} \ No newline at end of file diff --git a/products/expert/src/main/ets/pages/Flower/FlowerDetailsPage.ets b/products/expert/src/main/ets/pages/Flower/FlowerDetailsPage.ets new file mode 100644 index 0000000..5ff8872 --- /dev/null +++ b/products/expert/src/main/ets/pages/Flower/FlowerDetailsPage.ets @@ -0,0 +1,14 @@ +import { FlowerDetailsComp } from 'study' + +@Entry +@Component +struct FlowerDetailsPage { + + build() { + Column() { + FlowerDetailsComp() + } + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/products/expert/src/main/ets/pages/LoginPage/LoginPage.ets b/products/expert/src/main/ets/pages/LoginPage/LoginPage.ets index 15e2cee..b9c1d1d 100644 --- a/products/expert/src/main/ets/pages/LoginPage/LoginPage.ets +++ b/products/expert/src/main/ets/pages/LoginPage/LoginPage.ets @@ -3,7 +3,7 @@ import { NimRepository } from '../../entryability/NimRepository' import { AppConfig } from '../../constants/AppConfig' import { PerfactInputSheet } from '@itcast/basic/src/main/ets/Views/PerfactInputSheet' import { LengthMetrics, router } from '@kit.ArkUI' -import { TimestampUtil } from '@itcast/basic' +import { TimestampUtil, WXApi } from '@itcast/basic' @Entry @Component @@ -65,6 +65,7 @@ struct LoginPage { this.dialog.open() } + console.info('是否安装微信:',WXApi.isWXAppInstalled()) } } diff --git a/products/expert/src/main/ets/pages/Meeting/MeetingPage.ets b/products/expert/src/main/ets/pages/Meeting/MeetingPage.ets new file mode 100644 index 0000000..e3d0d45 --- /dev/null +++ b/products/expert/src/main/ets/pages/Meeting/MeetingPage.ets @@ -0,0 +1,14 @@ +import { VideoPage } from 'home' + +@Entry +@Component +struct MeetingPage { + + build() { + Column(){ + VideoPage() + } + .width('100%') + .height('100%') + } +} \ No newline at end of file diff --git a/products/expert/src/main/ets/pages/MinePage/SettingPage.ets b/products/expert/src/main/ets/pages/MinePage/SettingPage.ets index 8385810..b435c4f 100644 --- a/products/expert/src/main/ets/pages/MinePage/SettingPage.ets +++ b/products/expert/src/main/ets/pages/MinePage/SettingPage.ets @@ -36,7 +36,7 @@ struct SettingPage { getVersion() { bundleManager.getBundleInfoForSelf( bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION | - bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_METADATA + bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_METADATA | bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_SIGNATURE_INFO ) .then((res) => { this.version = 'V' + res.versionName diff --git a/products/expert/src/main/ets/pages/News/GandanNewsListPage.ets b/products/expert/src/main/ets/pages/News/GandanNewsListPage.ets new file mode 100644 index 0000000..20a3dba --- /dev/null +++ b/products/expert/src/main/ets/pages/News/GandanNewsListPage.ets @@ -0,0 +1,14 @@ +import { NewsListComp } from 'study' + +@Entry +@Component +struct GandanNewsListPage { + + build() { + Column() { + NewsListComp() + } + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/products/expert/src/main/ets/pages/News/GandanNewsPages.ets b/products/expert/src/main/ets/pages/News/GandanNewsPages.ets new file mode 100644 index 0000000..7ddf0c8 --- /dev/null +++ b/products/expert/src/main/ets/pages/News/GandanNewsPages.ets @@ -0,0 +1,14 @@ +import { NewsComp } from 'study' + +@Entry +@Component +struct GandanNewsPages { + + build() { + Column() { + NewsComp() + } + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/products/expert/src/main/ets/pages/Pay/PayPage.ets b/products/expert/src/main/ets/pages/Pay/PayPage.ets new file mode 100644 index 0000000..b91515e --- /dev/null +++ b/products/expert/src/main/ets/pages/Pay/PayPage.ets @@ -0,0 +1,14 @@ +import { PayComp } from 'study'; + +@Entry +@Component +struct PayPage { + + build() { + Column() { + PayComp() + } + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/products/expert/src/main/ets/pages/Pay/SendFollowPage.ets b/products/expert/src/main/ets/pages/Pay/SendFollowPage.ets new file mode 100644 index 0000000..20f4dbb --- /dev/null +++ b/products/expert/src/main/ets/pages/Pay/SendFollowPage.ets @@ -0,0 +1,14 @@ +import { SendFollowComp } from 'study' + +@Entry +@Component +struct SendFollowPage { + + build() { + Column() { + SendFollowComp() + } + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/products/expert/src/main/ets/pages/WebView/EducationDetailsWebPage.ets b/products/expert/src/main/ets/pages/WebView/EducationDetailsWebPage.ets index 35436ef..48cb9af 100644 --- a/products/expert/src/main/ets/pages/WebView/EducationDetailsWebPage.ets +++ b/products/expert/src/main/ets/pages/WebView/EducationDetailsWebPage.ets @@ -1,10 +1,7 @@ import webview from '@ohos.web.webview'; -import { BasicConstant, HdNav, preferenceStore } from '@itcast/basic'; +import { authStore, BasicConstant, hdHttp, HdLoadingDialog, HdNav, HdResponse, preferenceStore } from '@itcast/basic'; import router from '@ohos.router'; -import { image } from '@kit.ImageKit'; -import { photoAccessHelper } from '@kit.MediaLibraryKit'; import { promptAction } from '@kit.ArkUI'; -import { fileIo, fileUri } from '@kit.CoreFileKit'; import { BusinessError } from '@kit.BasicServicesKit'; import { PatientTBean } from '@itcast/basic/src/main/ets/models/TeachModel'; @@ -13,6 +10,10 @@ import { PatientTBean } from '@itcast/basic/src/main/ets/models/TeachModel'; struct EducationDetailsWebPage { private controller: webview.WebviewController = new webview.WebviewController(); @State params:RouteParams = router.getParams() as RouteParams; + @State isDianZan:boolean = String(this.params.isAgree)=='1'?true:false + @State isShouCang:boolean = false + @State agreenum:string = this.params.model.agreenum.toString() + @State readnum:string = this.params.model.readnum.toString() // 修改为移动端User-Agent,解决链接中有视频的问题 private customUserAgent: string = 'Mozilla/5.0 (Linux; Android 10; HarmonyOS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 gdxz-expert'; @@ -22,6 +23,16 @@ struct EducationDetailsWebPage { @State url: string = BasicConstant.urlHtml+this.params.model.path; @State title: string = '患教详情'; + dialog: CustomDialogController = new CustomDialogController({ + builder: HdLoadingDialog({ message: '加载中...' }), + customStyle: true, + alignment: DialogAlignment.Center + }) + + aboutToAppear(): void { + this.getKepuDetailsData() + } + onBackPress(): boolean | void { if (this.controller.accessStep(-1)) { this.controller.backward(); @@ -64,24 +75,173 @@ struct EducationDetailsWebPage { }) .width('100%') - .layoutWeight(1) - .height('calc(100% - 80vp)') + .height('calc(100% - 156vp)') + .clip(true) Row(){ Row(){ - Image('') + Image($r('app.media.eye_main_color')) .size({width:20,height:20}) + .objectFit(ImageFit.Contain) + Text(this.readnum) + .margin({left:10}) + .fontSize(15) + .fontColor(Color.Gray) } - .margin({left:15,top:12}) + .justifyContent(FlexAlign.Center) + .width(80) + .margin({top:12}) + .layoutWeight(1) + Row(){ + Image(this.isDianZan?$r('app.media.yi_dian_zan'):$r('app.media.wei_dian_zan')) + .objectFit(ImageFit.Contain) + .size({width:20,height:20}) + Text(this.agreenum) + .margin({left:10}) + .fontSize(15) + .fontColor(Color.Gray) + } + .justifyContent(FlexAlign.Center) + .width(80) + .margin({top:12}) + .layoutWeight(1) + .onClick(()=>{ + this.setAgreeData(this.isDianZan?"0":"1") + }) + Row(){ + Image(this.isShouCang?$r('app.media.yi_shou_cang'):$r('app.media.wei_shou_cang')) + .objectFit(ImageFit.Contain) + .size({width:20,height:20}) + Text('收藏') + .margin({left:10}) + .fontSize(15) + .fontColor(Color.Gray) + } + .justifyContent(FlexAlign.Center) + .width(80) + .margin({top:12}) + .layoutWeight(1) + .onClick(()=>{ + this.setCollectionData(this.isShouCang?"0":"1") + }) + Row(){ + Image($r('app.media.fenxiang')) + .objectFit(ImageFit.Contain) + .size({width:20,height:20}) + Text('分享') + .margin({left:10}) + .fontSize(15) + .fontColor(Color.Gray) + } + .justifyContent(FlexAlign.Center) + .width(80) + .margin({top:12}) + .layoutWeight(1) + .onClick(()=>{ + promptAction.showToast({ message:'敬请期待', duration: 1000 }) + }) } + .width('100%') .alignItems(VerticalAlign.Top) - .backgroundColor(Color.White) } .width('100%') .height('100%') } + + getKepuDetailsData() { + const entity = { + "uuid":this.params.model.uuid, + "user_uuid": authStore.getUser().uuid + } as Record + this.dialog.open() + hdHttp.post(BasicConstant.getKePuCollection, entity).then(async (res: HdResponse) => { + this.dialog.close(); + console.info('Response delConditionRecord'+res); + let json:Record | Array>> = JSON.parse(res+'') as Record | Array>>; + if(json.code == '1') { + let isCollection = json.isCollection + if (isCollection == '1') { + this.isShouCang = true + } else { + this.isShouCang = false + } + this.readnum = String(json.readnum) + this.agreenum = String(json.agreenum) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + console.error(`Response fails: ${err}`); + promptAction.showToast({ message: String('患教文库数据请求失败!'), duration: 1000 }) + }) + } + + setCollectionData(type:string) {//0取消收藏;1收藏 + const entity = { + "other_uuid":this.params.model.uuid, + "user_uuid": authStore.getUser().uuid, + "title":this.params.model.topic, + "path":this.params.model.path, + "imgpath":this.params.model.imgPath, + "readnum":this.readnum, + "type":"2" + } as Record + this.dialog.open() + hdHttp.post(type=='0'?BasicConstant.discollection:BasicConstant.collection, entity).then(async (res: HdResponse) => { + this.dialog.close(); + console.info('Response delConditionRecord'+res); + let json:Record | Array>> = JSON.parse(res+'') as Record | Array>>; + if(json.code == '1') { + if (type == '0') { + this.isShouCang = false + promptAction.showToast({ message: '取消收藏成功', duration: 1000 }) + } else { + this.isShouCang = true + promptAction.showToast({ message: '收藏成功', duration: 1000 }) + } + } else { + promptAction.showToast({ message: String(json.message), duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + console.error(`Response fails: ${err}`); + promptAction.showToast({ message: String('患教文库数据请求失败!'), duration: 1000 }) + }) + } + + setAgreeData(type:string) {//0取消点赞;1点赞 + const entity = { + "news_article_uuid":this.params.model.uuid, + "user_uuid": authStore.getUser().uuid, + "type":"2" + } as Record + this.dialog.open() + hdHttp.post(type=='0'?BasicConstant.disagree:BasicConstant.agree, entity).then(async (res: HdResponse) => { + this.dialog.close(); + console.info('Response delConditionRecord'+res); + let json:Record | Array>> = JSON.parse(res+'') as Record | Array>>; + if(json.code == '1') { + if (type == '0') { + this.agreenum = String(Number(this.agreenum)-1) + this.isDianZan = false + promptAction.showToast({ message: '取消点赞成功', duration: 1000 }) + } else { + this.agreenum = String(Number(this.agreenum)+1) + this.isDianZan = true + promptAction.showToast({ message: '点赞成功', duration: 1000 }) + } + } else { + promptAction.showToast({ message: String(json.message), duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + console.error(`Response fails: ${err}`); + promptAction.showToast({ message: String('患教文库数据请求失败!'), duration: 1000 }) + }) + } + } interface RouteParams { model:PatientTBean + isAgree:string } \ No newline at end of file diff --git a/products/expert/src/main/ets/pages/WebView/KeJianDetailsWebPage.ets b/products/expert/src/main/ets/pages/WebView/KeJianDetailsWebPage.ets new file mode 100644 index 0000000..6c6c350 --- /dev/null +++ b/products/expert/src/main/ets/pages/WebView/KeJianDetailsWebPage.ets @@ -0,0 +1,553 @@ +import webview from '@ohos.web.webview'; +import { + authStore, BasicConstant, + ChangeUtil, + DefaultHintProWindows, + FileManager, + FileInfo, + hdHttp, HdLoadingDialog, HdNav, HdResponse, preferenceStore, + WXApi, + AESEncryptionDecryption} from '@itcast/basic'; +import router from '@ohos.router'; +import { promptAction } from '@kit.ArkUI'; +import { BusinessError, emitter } from '@kit.BasicServicesKit'; +import { KeJianModel } from 'study/src/main/ets/models/KeJianModel'; +import { HashMap } from '@kit.ArkTS'; +import * as wxopensdk from '@tencent/wechat_open_sdk'; +import { common, Want } from '@kit.AbilityKit'; +import { http } from '@kit.NetworkKit'; +import { fileIo } from '@kit.CoreFileKit'; +import request from '@ohos.request'; + +export enum OrderStatus { + status_default = 0,/**不能点击 */ + status_pay = 1,/**付费下载 */ + status_free = 2,/**免费下载 */ + status_download = 3,/**重新下载 */ + status_check = 4,/**查看课件 */ +} + +@Entry +@Component +struct KeJianDetailsWebPage { + private controller: webview.WebviewController = new webview.WebviewController(); + @State params:RouteParams = router.getParams() as RouteParams; + + // 修改为移动端User-Agent,解决链接中有视频的问题 + private customUserAgent: string = 'Mozilla/5.0 (Linux; Android 10; HarmonyOS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 gdxz-expert'; + private hcp_token:string = preferenceStore.getItemString(BasicConstant.EXPERT_HCP_TOKEN) + + private wxApi = WXApi + @State btnStatus: number = OrderStatus.status_default + @State contentWidth: number = 0; + @State contentHeight: number = 0; + @State url: string = BasicConstant.urlHtml+this.params.model.preview_path; + @State title: string = '课件详情'; + @State downloadImage: ResourceStr = '' + @State isShowDownloadImage: boolean = false + @State downloadState: string = '' + @State collectionIcon: ResourceStr = $r('app.media.wei_shou_cang') + @State kejianDetailsData : RequestData = {} as RequestData + @State hintMessage:string = '' + @State downType:string = '' + @State down_order_id:string = '' + @State isUseFreeRecord:boolean = false + @State isUserWelfareNum:boolean = false + @State system_time:string = '' + + private context: common.Context = getContext(this) as common.Context; + private fileManager: FileManager = new FileManager(this.context); + + dialog: CustomDialogController = new CustomDialogController({ + builder: HdLoadingDialog({ message: '加载中...' }), + customStyle: true, + alignment: DialogAlignment.Center + }) + + alertView:CustomDialogController = new CustomDialogController({ + builder:DefaultHintProWindows({ + title:'提示', + message:this.hintMessage, + cancleTitleColor: '#666666', + confirmTitleColor: $r('app.color.main_color'), + selectedButton: (index:number)=>{ + this.alertView.close(); + if (index == 1) {//确定按钮 + if (this.btnStatus == OrderStatus.status_free) { + if (this.hintMessage.includes('确定要下载')) { + this.chackNetStatusAction() + } + } else if (this.btnStatus == OrderStatus.status_pay) { + if (this.hintMessage.includes('免费下载机会')) { + if (this.kejianDetailsData.welfareNum !== '0') { + this.isUserWelfareNum = true + this.useWelfareData() + } else { + if (this.kejianDetailsData.freeRecord !== '0') { + this.isUseFreeRecord = true + this.chackNetStatusAction() + } else { + this.chackNetStatusAction() + } + } + } else if (this.hintMessage.includes('确定要下载')) { + this.creatOrderData() + } else if (this.hintMessage.includes('正使用手机网络')) { + this.downloadFileAction() + } + } + } else {//取消按钮 + if (this.btnStatus == OrderStatus.status_pay) { + if (this.hintMessage.includes('免费下载机会')) { + this.creatOrderData() + } + } + } + } + }), + alignment: DialogAlignment.Center, + cornerRadius:24, + backgroundColor: ('rgba(0,0,0,0.5)'), + }) + + private refreshDataCallback = (eventData?: emitter.EventData): void => { + if (Object(eventData?.data)['status'] == 'success') { + this.chackNetStatusAction() + } + }; + + onPageShow(): void { + this.getKeJianDetailsData() + } + + aboutToDisappear(): void { + emitter.off(BasicConstant.notification_kejian_pay_success, this.refreshDataCallback) + } + + aboutToAppear(): void { + let innerEvent: emitter.InnerEvent = { + eventId: BasicConstant.notification_kejian_pay_success + } + emitter.on(innerEvent, this.refreshDataCallback) + if (this.params.model.preview_path.includes('http')) { + this.url = this.params.model.preview_path + } + } + + onBackPress(): boolean | void { + if (this.controller.accessStep(-1)) { + this.controller.backward(); + return true; + } + return false; + } + + build() { + Column() { + if (WXApi.isWXAppInstalled()) { + HdNav({ + title: this.title, + showRightIcon: true, + rightIcon: $r('app.media.fenxiang'), + twoRightIcon: this.collectionIcon, + hasBorder: true, + isLeftAction: true, + showTwoRightItem: true, + leftItemAction: () => { + if (this.controller.accessBackward()) { + this.controller.backward(); + } else { + if (this.fileManager.isDownloadingFile()) { + promptAction.showToast({message:'当前有课件正在下载,请稍等...'}) + } else { + router.back(); + } + } + }, + rightItemAction: () => { + let shareUrl = new wxopensdk.WXWebpageObject + shareUrl.type = 5 + shareUrl.webpageUrl = `${this.url}&fromtype=doctor` + let mediaMessage = new wxopensdk.WXMediaMessage() + mediaMessage.title = this.params.model.title + mediaMessage.description = '肝胆相照-国内专业优质肝胆课件共享平台' + mediaMessage.mediaObject = shareUrl + let req = new wxopensdk.SendMessageToWXReq() + req.scene = 0 + req.message = mediaMessage + let finished = this.wxApi.sendReq(getContext(this) as common.UIAbilityContext, req) + console.log("send request finished: ", finished) + }, + twoRightItemAction: () => { + this.setCollectionData(this.kejianDetailsData.iscollection == '0' ? '1' : '0') + } + }) + } else { + HdNav({ + title: this.title, + showRightIcon: true, + rightIcon: this.collectionIcon, + hasBorder: true, + isLeftAction: true, + leftItemAction: () => { + if (this.controller.accessBackward()) { + this.controller.backward(); + } else { + if (this.fileManager.isDownloadingFile()) { + promptAction.showToast({message:'当前有课件正在下载,请稍等...'}) + } else { + router.back(); + } + } + }, + rightItemAction: () => { + this.setCollectionData(this.kejianDetailsData.iscollection == '0' ? '1' : '0') + } + }) + } + Stack() { + 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); + }) + .onPageBegin(() => { + this.dialog.open() + this.controller.runJavaScript(`document.cookie = 'hcp_from=expert_app; domain=.igandan.com; path=/';`) + this.controller.runJavaScript(`document.cookie = 'hcp_token=${this.hcp_token}; domain=.igandan.com; path=/';`) + }) + .onPageEnd(() => { + this.dialog.close() + // 注入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) + + Row(){ + Image(this.downloadImage) + .size({width:16,height:16}) + .visibility(this.isShowDownloadImage?Visibility.Visible:Visibility.None) + Text(this.downloadState) + .fontSize(18) + .fontColor(Color.White) + } + .justifyContent(FlexAlign.Center) + .backgroundColor(Color.Gray) + .width('100%') + .height(44) + .onClick(()=>{ + if (this.btnStatus == OrderStatus.status_check) { + // 查看已下载文件 + this.fileManager.previewFile(this.params.model.uuid) + } else if (this.btnStatus == OrderStatus.status_download) { + // 重新下载 + this.downloadFileAction() + } else if (this.btnStatus == OrderStatus.status_pay) { + if (this.kejianDetailsData.welfareNum == '0') { + if (this.kejianDetailsData.freeRecord == '0') { + this.hintMessage = '您确定要下载该课件吗' + this.alertView.open() + } else { + this.hintMessage = `您还有${this.getFreeDownLoadCount()}次免费下载机会,希望本次下载免费吗?` + this.alertView.open() + } + } else { + this.hintMessage = `您还有${this.getFreeDownLoadCount()}次免费下载机会,希望本次下载免费吗?` + this.alertView.open() + } + } else if (this.btnStatus == OrderStatus.status_free) { + this.hintMessage = '您确定要下载该课件吗' + this.alertView.open() + } + }) + } + .alignContent(Alignment.Top) + } + .width('100%') + .height('100%') + } + + getKeJianDetailsData() { + const hashMap: HashMap = new HashMap() + this.dialog.open() + hashMap.clear() + hashMap.set('file_uuid', this.params.model.uuid) + hdHttp.httpReq(BasicConstant.ganDanFileDetials,hashMap).then(async (res: HdResponse) => { + console.info('Response ganDanFileDetials'+res) + this.dialog.close() + let json:Record = JSON.parse(res+'') as Record; + if(json.code == '200') { + this.kejianDetailsData = json.data as RequestData + if (this.kejianDetailsData.iscollection == '0') { + this.collectionIcon = $r('app.media.wei_shou_cang') + } else { + this.collectionIcon = $r('app.media.yi_shou_cang') + } + if (this.kejianDetailsData.order == null || this.kejianDetailsData.order == undefined) {//没有订单 + if (parseFloat(this.kejianDetailsData.price) < 0) { + this.btnStatus = 0 + this.downloadImage = '' + this.isShowDownloadImage = false + this.downloadState = '本课件不支持下载' + } else { + this.btnStatus = 2 + this.downloadImage = $r('app.media.kejian_download_white') + this.isShowDownloadImage = true + this.downloadState = ' 本课件免费下载' + } + if (parseFloat(this.kejianDetailsData.price) > 0) { + this.btnStatus = 1 + this.downloadImage = $r('app.media.kejian_download_white') + this.isShowDownloadImage = true + this.downloadState = `本课件下载${ChangeUtil.formatPrice(this.kejianDetailsData.price)}元` + } + } else {//有订单 + try { + const downloadedFiles = await this.fileManager.getDownloadedFiles(); + const downloadedFile = downloadedFiles.find(file => file.fileId === this.params.model.uuid); + if (downloadedFile && downloadedFile.filePath && fileIo.accessSync(downloadedFile.filePath)) { + // 本地已存在,显示查看文件 + this.btnStatus = OrderStatus.status_check + this.downloadImage = '' + this.isShowDownloadImage = false + this.downloadState = '查看课件' + } else { + // 本地不存在,显示重新下载 + this.down_order_id = `${this.kejianDetailsData.order['order_id']}&R` + this.btnStatus = OrderStatus.status_download + this.downloadImage = '' + this.isShowDownloadImage = false + this.downloadState = '重新下载本课件' + } + } catch (e) { + // 异常情况下,默认提供重新下载 + this.btnStatus = OrderStatus.status_download + this.downloadImage = '' + this.isShowDownloadImage = false + this.downloadState = ' 重新下载' + } + } + } else { + promptAction.showToast({ message: String(json.message), duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close() + console.error(`Response fails: ${err}`); + }) + } + + chackNetStatusAction() { + if (hdHttp.getNetworkType() == 2) { + this.hintMessage = '您当前正使用手机网络,是否继续下载?' + this.alertView.open() + } else if (hdHttp.getNetworkType() == 1 || hdHttp.getNetworkType() == 3) { + this.downloadFileAction() + } else if (hdHttp.getNetworkType() == 0 || hdHttp.getNetworkType() == -1) { + this.hintMessage = '未连接网络,请设置' + this.alertView.open() + } + } + + downloadFileAction() { + let httpRequest = http.createHttp(); + let timeStampurl = BasicConstant.getSystemTime; + let promise = httpRequest.request(timeStampurl, { + method: http.RequestMethod.GET, + connectTimeout: 60000, + readTimeout: 60000, + header: { + 'Content-Type': 'application/json' + } + }); + promise.then(async (data) => { + if (data.responseCode === http.ResponseCode.OK) { + let json:TimestampBean = JSON.parse(data.result.toString()) as TimestampBean + let daijiami:string = '' + if (this.btnStatus == OrderStatus.status_free) { + daijiami = `${this.params.model.uuid}|FREE|${authStore.getUser().uuid}|${json.system_time}` + } else if (this.btnStatus == OrderStatus.status_download) { + daijiami = `${this.params.model.uuid}|${this.down_order_id}|${authStore.getUser().uuid}|${json.system_time}` + } + if (this.isUseFreeRecord) { + daijiami = `${this.params.model.uuid}|FREERECORD|${authStore.getUser().uuid}|${json.system_time}` + } + if (this.kejianDetailsData.welfareNum != '0') { + if (this.isUserWelfareNum) { + daijiami = `${this.params.model.uuid}|USEWELFARENUM|${authStore.getUser().uuid}|${json.system_time}` + } + } + const scanData = await AESEncryptionDecryption.aesEncrypt(daijiami,BasicConstant.ExpertAesKey) + let x: number = 10; + let valueINTT: number = Math.floor(Math.random() * x) + 1; + let pinString = `${getChars(valueINTT)}${scanData}` + let downloadUrl = `${BasicConstant.urlExpertApp}downloadGanDanFile?&gdf=${pinString}&a=${valueINTT}` + console.info('开始下载文件,URL:', downloadUrl); + const fileInfo: FileInfo = { + fileId: this.params.model.uuid, + fileName: this.params.model.title, + fileType: this.params.model.type, + fileSize: 0, + fileUrl: downloadUrl, + downloadStatus: 'pending', + downloadProgress: 0, + createTime: Date.now(), + updateTime: Date.now() + }; + promptAction.showToast({ message: '开始下载,请勿重复点击', duration: 1000 }) + this.fileManager.downloadFile(fileInfo, { + onSuccess: (filePath: string) => { + this.btnStatus = OrderStatus.status_check + this.downloadImage = '' + this.downloadState = '查看课件' + }, + onFailed: (error: string) => { + console.info('fileDownload,error:',error) + } + }); + } + } + ).catch((err: BusinessError) => { + return Promise.reject(err); + }).finally(() => { + httpRequest.destroy() + }) + } + + setCollectionData(type:string) {//0取消收藏;1收藏 + const entity = { + "other_uuid":this.params.model.uuid, + "user_uuid": authStore.getUser().uuid, + "title":this.params.model.title, + "path":this.params.model.preview_path, + "type":"6" + } as Record + this.dialog.open() + hdHttp.post(type=='0'?BasicConstant.discollection:BasicConstant.collection, entity).then(async (res: HdResponse) => { + this.dialog.close(); + console.info('Response delConditionRecord'+res); + let json:Record | Array>> = JSON.parse(res+'') as Record | Array>>; + if(json.code == '1') { + if (type == '0') { + this.collectionIcon = $r('app.media.wei_shou_cang') + promptAction.showToast({ message: '取消收藏成功', duration: 1000 }) + } else { + this.collectionIcon = $r('app.media.yi_shou_cang') + promptAction.showToast({ message: '收藏成功', duration: 1000 }) + } + this.getKeJianDetailsData() + } else { + promptAction.showToast({ message: String(json.message), duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + console.error(`Response fails: ${err}`); + promptAction.showToast({ message: String('患教文库数据请求失败!'), duration: 1000 }) + }) + } + + creatOrderData() { + const hashMap: HashMap = new HashMap() + this.dialog.open() + hashMap.clear() + hashMap.set('file_uuid', this.params.model.uuid) + hdHttp.httpReq(BasicConstant.createGanDanFileOrder,hashMap).then(async (res: HdResponse) => { + console.info('Response ganDanFileDetials'+res) + this.dialog.close() + let json:Record = JSON.parse(res+'') as Record; + if(json.code == '200') { + router.pushUrl({ + url:"pages/Pay/PayPage", + params:{"data":json.data,"page":"课件详情"} + }) + } else { + promptAction.showToast({ message: String(json.message), duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close() + console.error(`Response fails: ${err}`); + }) + } + + useWelfareData() { + const hashMap: HashMap = new HashMap() + this.dialog.open() + hashMap.clear() + hashMap.set('type', '2') + hashMap.set('other_uuid',this.params.model.uuid) + hdHttp.httpReq(BasicConstant.useWelfareNum,hashMap).then(async (res: HdResponse) => { + console.info('Response useWelfareNum'+res) + this.dialog.close() + let json:Record = JSON.parse(res+'') as Record; + if(json.code == '1') { + this.downloadFileAction() + } else { + promptAction.showToast({ message: String(json.message), duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close() + console.error(`Response fails: ${err}`); + }) + } + + private getFreeDownLoadCount():string { + let count = Number(this.kejianDetailsData.welfareNum)+Number(this.kejianDetailsData.freeRecord) + return count.toString() + } +} + +interface RouteParams { + model:KeJianModel +} + +interface RequestData { + iscollection:string + welfareNum:string + freeRecord:string + fileSize:string + fileMD5:string + price:string + order:object +} + +interface FileOrderData { + trade_no:string + order_id:string + amount:string + account:string + provider_name:string + file_uuid:string + order_status:string +} + +export interface TimestampBean { + system_time:string +} + +export function getChars(number_od_chars:number):string { + // 创建字符数组并填充随机大写字母 + let data: string[] = []; + for (let i: number = 0; i < number_od_chars; i++) { + // 生成A-Z的随机字母 + let randomChar: string = String.fromCharCode(65 + Math.floor(Math.random() * 26)); + data.push(randomChar); + } + + // 将字符数组转换为字符串 + let stringValue: string = data.join(''); + return stringValue +} \ No newline at end of file diff --git a/products/expert/src/main/ets/pages/WebView/NewsDetailsWebPage.ets b/products/expert/src/main/ets/pages/WebView/NewsDetailsWebPage.ets new file mode 100644 index 0000000..8333bef --- /dev/null +++ b/products/expert/src/main/ets/pages/WebView/NewsDetailsWebPage.ets @@ -0,0 +1,247 @@ +import webview from '@ohos.web.webview'; +import { authStore, BasicConstant, hdHttp, HdLoadingDialog, HdNav, HdResponse } from '@itcast/basic'; +import router from '@ohos.router'; +import { promptAction } from '@kit.ArkUI'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { newsRequestOfData } from 'study'; + +@Entry +@Component +struct NewsDetailsWebPage { + private controller: webview.WebviewController = new webview.WebviewController(); + @State params:RouteParams = router.getParams() as RouteParams; + @State isDianZan:boolean = String(this.params.isAgree)=='1'?true:false + @State isShouCang:boolean = false + @State agreenum:string = this.params.model.agreenum.toString() + @State readnum:string = this.params.model.readnum.toString() + + // 修改为移动端User-Agent,解决链接中有视频的问题 + private customUserAgent: string = 'Mozilla/5.0 (Linux; Android 10; HarmonyOS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 gdxz-expert'; + + @State contentWidth: number = 0; + @State contentHeight: number = 0; + @State url: string = BasicConstant.urlHtml+this.params.model.path; + @Prop title: string = '新闻详情'; + + dialog: CustomDialogController = new CustomDialogController({ + builder: HdLoadingDialog({ message: '加载中...' }), + customStyle: true, + alignment: DialogAlignment.Center + }) + + aboutToAppear(): void { + this.getNewsDetailsData() + } + + 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 ,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%') + .height('calc(100% - 156vp)') + .clip(true) + + Row(){ + Row(){ + Image($r('app.media.eye_main_color')) + .size({width:20,height:20}) + .objectFit(ImageFit.Contain) + Text(this.readnum) + .margin({left:10}) + .fontSize(15) + .fontColor(Color.Gray) + } + .justifyContent(FlexAlign.Center) + .width(80) + .margin({top:12}) + .layoutWeight(1) + Row(){ + Image(this.isDianZan?$r('app.media.yi_dian_zan'):$r('app.media.wei_dian_zan')) + .objectFit(ImageFit.Contain) + .size({width:20,height:20}) + Text(this.agreenum) + .margin({left:10}) + .fontSize(15) + .fontColor(Color.Gray) + } + .justifyContent(FlexAlign.Center) + .width(80) + .margin({top:12}) + .layoutWeight(1) + .onClick(()=>{ + this.setAgreeData(this.isDianZan?"0":"1") + }) + Row(){ + Image(this.isShouCang?$r('app.media.yi_shou_cang'):$r('app.media.wei_shou_cang')) + .objectFit(ImageFit.Contain) + .size({width:20,height:20}) + Text('收藏') + .margin({left:10}) + .fontSize(15) + .fontColor(Color.Gray) + } + .justifyContent(FlexAlign.Center) + .width(80) + .margin({top:12}) + .layoutWeight(1) + .onClick(()=>{ + this.setCollectionData(this.isShouCang?"0":"1") + }) + Row(){ + Image($r('app.media.fenxiang')) + .objectFit(ImageFit.Contain) + .size({width:20,height:20}) + Text('分享') + .margin({left:10}) + .fontSize(15) + .fontColor(Color.Gray) + } + .justifyContent(FlexAlign.Center) + .width(80) + .margin({top:12}) + .layoutWeight(1) + .onClick(()=>{ + promptAction.showToast({ message:'敬请期待', duration: 1000 }) + }) + } + .width('100%') + .alignItems(VerticalAlign.Top) + } + .width('100%') + .height('100%') + } + + getNewsDetailsData() { + const entity = { + "uuid":this.params.model.uuid, + "user_uuid": authStore.getUser().uuid + } as Record + this.dialog.open() + hdHttp.post(BasicConstant.newsDetial, entity).then(async (res: HdResponse) => { + this.dialog.close(); + console.info('Response delConditionRecord'+res); + let json:Record | Array>> = JSON.parse(res+'') as Record | Array>>; + if(json.code == '1') { + let isCollection = json.isCollection + if (isCollection == '1') { + this.isShouCang = true + } else { + this.isShouCang = false + } + this.readnum = String(json.readnum) + this.agreenum = String(json.agreenum) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + console.error(`Response fails: ${err}`); + promptAction.showToast({ message: String('患教文库数据请求失败!'), duration: 1000 }) + }) + } + + setCollectionData(type:string) {//0取消收藏;1收藏 + const entity = { + "other_uuid":this.params.model.uuid, + "user_uuid": authStore.getUser().uuid, + "title":this.params.model.title, + "path":this.params.model.path, + "imgpath":this.params.model.imgpath, + "readnum":this.readnum, + "type":"1" + } as Record + this.dialog.open() + hdHttp.post(type=='0'?BasicConstant.discollection:BasicConstant.collection, entity).then(async (res: HdResponse) => { + this.dialog.close(); + console.info('Response delConditionRecord'+res); + let json:Record | Array>> = JSON.parse(res+'') as Record | Array>>; + if(json.code == '1') { + if (type == '0') { + this.isShouCang = false + promptAction.showToast({ message: '取消收藏成功', duration: 1000 }) + } else { + this.isShouCang = true + promptAction.showToast({ message: '收藏成功', duration: 1000 }) + } + } else { + promptAction.showToast({ message: String(json.message), duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + console.error(`Response fails: ${err}`); + promptAction.showToast({ message: String('患教文库数据请求失败!'), duration: 1000 }) + }) + } + + setAgreeData(type:string) {//0取消点赞;1点赞 + const entity = { + "news_article_uuid":this.params.model.uuid, + "user_uuid": authStore.getUser().uuid, + "type":"1" + } as Record + this.dialog.open() + hdHttp.post(type=='0'?BasicConstant.disagree:BasicConstant.agree, entity).then(async (res: HdResponse) => { + this.dialog.close(); + console.info('Response delConditionRecord'+res); + let json:Record | Array>> = JSON.parse(res+'') as Record | Array>>; + if(json.code == '1') { + if (type == '0') { + this.agreenum = String(Number(this.agreenum)-1) + this.isDianZan = false + promptAction.showToast({ message: '取消点赞成功', duration: 1000 }) + } else { + this.agreenum = String(Number(this.agreenum)+1) + this.isDianZan = true + promptAction.showToast({ message: '点赞成功', duration: 1000 }) + } + } else { + promptAction.showToast({ message: String(json.message), duration: 1000 }) + } + }).catch((err: BusinessError) => { + this.dialog.close(); + console.error(`Response fails: ${err}`); + promptAction.showToast({ message: String('患教文库数据请求失败!'), duration: 1000 }) + }) + } + +} + +interface RouteParams { + model:newsRequestOfData + isAgree:string +} \ No newline at end of file diff --git a/products/expert/src/main/module.json5 b/products/expert/src/main/module.json5 index 4b22400..41dbb39 100644 --- a/products/expert/src/main/module.json5 +++ b/products/expert/src/main/module.json5 @@ -72,7 +72,16 @@ "name": "ohos.permission.WRITE_MEDIA", "reason": "$string:media_reason", "usedScene": {} + }, + { + "name": "ohos.permission.READ_WRITE_USER_FILE", + "reason": "$string:write_file", + "usedScene": {} } + ], + "querySchemes": [ + "weixin", + "wxopensdk" ] } } \ No newline at end of file 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 c3e1b4b..0ac9ef7 100644 --- a/products/expert/src/main/resources/base/profile/main_pages.json +++ b/products/expert/src/main/resources/base/profile/main_pages.json @@ -49,6 +49,17 @@ "pages/VideoPage/CustomScanResultPage", "pages/VideoPage/EducationVideoPage", "pages/Courseware/CoursewarePage", - "pages/WebView/EducationDetailsWebPage" + "pages/WebView/EducationDetailsWebPage", + "pages/WebView/KeJianDetailsWebPage", + "pages/Pay/PayPage", + "pages/News/GandanNewsPages", + "pages/WebView/NewsDetailsWebPage", + "pages/Meeting/MeetingPage", + "pages/News/GandanNewsListPage", + "pages/Pay/SendFollowPage", + "pages/Download/AllDownloadPage", + "pages/Download/KeJianDownloadPage", + "pages/Flower/FlowerDetailsPage", + "pages/Courseware/CourseDetailsPage" ] } \ No newline at end of file