From 88a079c12a8b10f5434c8da4851b5b8a8cf8fa1e Mon Sep 17 00:00:00 2001 From: gaohongtao Date: Tue, 23 Dec 2025 19:29:44 +0800 Subject: [PATCH] =?UTF-8?q?IssueNo:=20picker=20sample=E5=90=8C=E6=BA=90=20?= =?UTF-8?q?bug=20fix=20https://gitee.com/harmonyos=5Fsamples/guide-snippet?= =?UTF-8?q?s/issues/IDFABH=20Description:=20picker=20sample=E5=90=8C?= =?UTF-8?q?=E6=BA=90=20bug=20fix=20Sig:=20bundleManager=20Feature=20or=20B?= =?UTF-8?q?ugfix:=20Feature=20Binary=20Source:=20No?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: lightaooii --- .../entry/src/main/ets/pages/Index.ets | 132 +++++++++++++---- .../entry/src/main/syscap.json | 3 +- .../entry/src/main/ets/pages/Index.ets | 140 +++++++++++++++--- .../entry/src/main/syscap.json | 3 +- .../PickerControllerEditSample/README.md | 3 +- .../entry/src/main/ets/pages/Index.ets | 51 +++++-- .../entry/src/main/syscap.json | 3 +- .../Picker/PickerMediaLibrarySample/README.md | 2 +- .../common/utils/MediaLibraryPickerUtils.ets | 16 +- .../entry/src/main/ets/pages/Index.ets | 62 ++++++++ .../entry/src/main/syscap.json | 3 +- .../RecentPhotoComponentSample/README.md | 11 +- .../entry/src/main/ets/pages/Index.ets | 31 +++- .../entry/src/main/syscap.json | 3 +- .../test/RecentPhotoComponentSample.test.ets | 9 +- .../RecentPhotoComponentSample/ohosTest.md | 2 +- .../common/utils/SmartPhotoPickerUtils.ets | 9 +- .../entry/src/main/ets/pages/Index.ets | 37 +++++ .../src/main/ets/view/ComponentImplPage.ets | 16 +- .../SmartPicker/entry/src/main/syscap.json | 13 ++ 20 files changed, 445 insertions(+), 104 deletions(-) create mode 100755 Media/Picker/SmartPicker/entry/src/main/syscap.json diff --git a/Media/Picker/AlbumPickerComponentSample/entry/src/main/ets/pages/Index.ets b/Media/Picker/AlbumPickerComponentSample/entry/src/main/ets/pages/Index.ets index f1c38d1c4..aaaadd5eb 100644 --- a/Media/Picker/AlbumPickerComponentSample/entry/src/main/ets/pages/Index.ets +++ b/Media/Picker/AlbumPickerComponentSample/entry/src/main/ets/pages/Index.ets @@ -27,6 +27,11 @@ import { } from '@kit.MediaLibraryKit'; // [End AlbumPicker_import] +import { AsyncCallback } from '@kit.BasicServicesKit'; +import { fileUri } from '@kit.CoreFileKit'; +// 导入Toast模块 +import promptAction from '@ohos.promptAction'; + @Entry @Component struct AlbumPage { @@ -37,7 +42,9 @@ struct AlbumPage { albumOptions1 = new AlbumPickerOptions(); albumOptions2 = new AlbumPickerOptions(); // [End AlbumPicker_create] - + + // 添加一个状态来控制AlbumPickerComponent是否显示 + @State isAlbumPickerVisible: boolean = true; @State Width: string = '100%'; @State Height: string = '100%'; @State isShowAlbum: boolean = false; @@ -45,6 +52,9 @@ struct AlbumPage { @State selectedFontColor: string = '#007DFF'; @State currentIndex: number = 0; private controller: TabsController = new TabsController(); + private lastAlbumClickTime: number = 0; // 记录最后一次相册点击时间,不需要@State + private lastButtonClickTime: number = 0; // 记录最后一次按钮点击时间,不需要@State + private readonly THROTTLE_INTERVAL: number = 1000; // 点击间隔限制为1秒 // [Start AlbumPicker_callbacks] // 3. 实现相关回调 @@ -53,6 +63,21 @@ struct AlbumPage { * AlbumInfo(uri) */ private onAlbumClick(albumInfo: AlbumInfo): boolean { + // 添加点击频控逻辑 + const currentTime: number = Date.now(); + if (currentTime - this.lastAlbumClickTime < this.THROTTLE_INTERVAL) { + // 使用toast提示 + promptAction.showToast({ + message: '点击过于频繁,请稍后再试', + duration: 2000 + }); + // 返回时也需要更新时间 + this.lastAlbumClickTime = currentTime; + return true; + } + + this.lastAlbumClickTime = currentTime; + console.info('onAlbumClick ' + albumInfo?.uri); this.isShowAlbum = false; if (albumInfo?.uri) { // 根据相册url更新宫格页内容。 @@ -79,8 +104,8 @@ struct AlbumPage { @Builder tabBuilder(index: number) { Column() { - Text(index === 0 ? $r('app.string.album_picker_system') : - index === 1 ? $r('app.string.album_picker_light') : + Text(index === 0 ? $r('app.string.album_picker_system') : + index === 1 ? $r('app.string.album_picker_light') : $r('app.string.album_picker_dark')) .fontColor(this.currentIndex === index ? this.selectedFontColor : this.fontColor) .fontSize(16) @@ -103,6 +128,20 @@ struct AlbumPage { .width('95%') .height('5%') .onClick(() => { + // 添加按钮点击频控逻辑 + const currentTime: number = Date.now(); + if (currentTime - this.lastButtonClickTime < this.THROTTLE_INTERVAL) { + // 使用toast提示 + promptAction.showToast({ + message: '按钮点击过于频繁,请稍后再试', + duration: 2000 + }); + // 返回时也需要更新时间 + this.lastButtonClickTime = currentTime; + return; + } + + this.lastButtonClickTime = currentTime; this.isShowAlbum = true; }) } @@ -127,36 +166,61 @@ struct AlbumPage { // Using 3 components to better demonstrate different effects Tabs({ barPosition: BarPosition.Start, index: this.currentIndex, controller: this.controller }) { TabContent() { - AlbumPickerComponent({ - albumPickerOptions: this.albumOptions, - onAlbumClick: (albumInfo: AlbumInfo): boolean => { - return this.onAlbumClick(albumInfo) - }, - }) - .height('100%') - .width('100%') - }.tabBar(this.tabBuilder(0)) + // 使用条件渲染,只在对应标签页显示对应的AlbumPickerComponent + if (this.currentIndex === 0 && this.isAlbumPickerVisible) { + AlbumPickerComponent({ + albumPickerOptions: this.albumOptions, + onAlbumClick: (albumInfo: AlbumInfo): boolean => this.onAlbumClick(albumInfo), + }) + .height('100%') + .width('100%'); + } else { + // 非当前标签页显示占位,避免组件被销毁 + Column() + .height('100%') + .width('100%') + .backgroundColor('#F1F3F5'); + } + } + .tabBar(this.tabBuilder(0)); + TabContent() { - AlbumPickerComponent({ - albumPickerOptions: this.albumOptions1, - onAlbumClick: (albumInfo: AlbumInfo): boolean => { - return this.onAlbumClick(albumInfo) - }, - }) - .height('100%') - .width('100%') - }.tabBar(this.tabBuilder(1)) + // 使用条件渲染,只在对应标签页显示对应的AlbumPickerComponent + if (this.currentIndex === 1 && this.isAlbumPickerVisible) { + AlbumPickerComponent({ + albumPickerOptions: this.albumOptions1, + onAlbumClick: (albumInfo: AlbumInfo): boolean => this.onAlbumClick(albumInfo), + }) + .height('100%') + .width('100%'); + } else { + // 非当前标签页显示占位,避免组件被销毁 + Column() + .height('100%') + .width('100%') + .backgroundColor('#F1F3F5'); + } + } + .tabBar(this.tabBuilder(1)); TabContent() { - AlbumPickerComponent({ - albumPickerOptions: this.albumOptions2, - onAlbumClick: (albumInfo: AlbumInfo): boolean => { - return this.onAlbumClick(albumInfo) - }, - }) - .height('100%') - .width('100%') - }.tabBar(this.tabBuilder(2)) + // 使用条件渲染,只在对应标签页显示对应的AlbumPickerComponent + if (this.currentIndex === 2 && this.isAlbumPickerVisible) { + AlbumPickerComponent({ + albumPickerOptions: this.albumOptions2, + onAlbumClick: (albumInfo: AlbumInfo): boolean => this.onAlbumClick(albumInfo), + }) + .height('100%') + .width('100%'); + } else { + // 非当前标签页显示占位,避免组件被销毁 + Column() + .height('100%') + .width('100%') + .backgroundColor('#F1F3F5'); + } + } + .tabBar(this.tabBuilder(2)); } .vertical(false) .barMode(BarMode.Fixed) @@ -164,7 +228,13 @@ struct AlbumPage { .barHeight(56) .animationDuration(100) .onChange((index: number) => { - this.currentIndex = index; + console.info('onChange ' + this.currentIndex + ' => ' + index); + // 切换时先隐藏再显示,强制组件重新初始化 + this.isAlbumPickerVisible = false; + setTimeout(() => { + this.currentIndex = index; + this.isAlbumPickerVisible = true; + }, 10); }) .width('100%') .height('100%') diff --git a/Media/Picker/AlbumPickerComponentSample/entry/src/main/syscap.json b/Media/Picker/AlbumPickerComponentSample/entry/src/main/syscap.json index 061b4782a..00ccb8376 100644 --- a/Media/Picker/AlbumPickerComponentSample/entry/src/main/syscap.json +++ b/Media/Picker/AlbumPickerComponentSample/entry/src/main/syscap.json @@ -6,7 +6,8 @@ "removedSysCaps": [ "SystemCapability.Security.DataTransitManager", "SystemCapability.Security.DeviceSecurityLevel", - "SystemCapability.Security.DeviceAuth" + "SystemCapability.Security.DeviceAuth", + "SystemCapability.HiviewDFX.HiDumper" ] } } \ No newline at end of file diff --git a/Media/Picker/PhotoPickerComponentSample/entry/src/main/ets/pages/Index.ets b/Media/Picker/PhotoPickerComponentSample/entry/src/main/ets/pages/Index.ets index 65c1c9ba3..880b55961 100644 --- a/Media/Picker/PhotoPickerComponentSample/entry/src/main/ets/pages/Index.ets +++ b/Media/Picker/PhotoPickerComponentSample/entry/src/main/ets/pages/Index.ets @@ -31,6 +31,9 @@ import { ReminderMode, photoAccessHelper } from '@kit.MediaLibraryKit'; +import { dataSharePredicates } from '@kit.ArkData'; +// 导入Toast模块 +import promptAction from '@ohos.promptAction'; // [End PhotoPickerComponent_import] @Entry @@ -54,6 +57,24 @@ struct PhotoPickerComponentDemo { // 是否显示大图。 @State isBrowserShow: boolean = false; + // 存储视频URI与缩略图的映射关系 + @State videoThumbnails: Map = new Map(); + + // 记录上次点击时间,用于控制点击频率 + private lastClickTime: number = 0; + + private phAccessHelper: photoAccessHelper.PhotoAccessHelper | undefined; + + // 定义最小点击间隔时间(毫秒),用于控制选择与反选频率 + private readonly MIN_CLICK_INTERVAL: number = 500; + + // 判断是否为视频资源 + private isVideoResource(uri: string): boolean { + const videoExtensions = ['.mp4', '.mov', '.avi', '.flv', '.wmv', '.mkv']; + const ext = uri.substring(uri.lastIndexOf('.')).toLowerCase(); + return videoExtensions.includes(ext); + } + aboutToAppear() { // [Start PhotoPickerComponent_config] // 3. 初始化选择选项实例 @@ -64,16 +85,62 @@ struct PhotoPickerComponentDemo { // 超出最大选择数量时。 this.pickerOptions.maxSelectedReminderMode = ReminderMode.TOAST; // 是否展示搜索框,默认false。 - this.pickerOptions.isSearchSupported = true; + this.pickerOptions.isSearchSupported = false; // 是否支持拍照,默认false。 this.pickerOptions.isPhotoTakingSupported = true; // [End PhotoPickerComponent_config] } + private async acquireThumbnailByUrl(videoUrl: string): Promise { + try { + if (!this.phAccessHelper) { + this.phAccessHelper = photoAccessHelper.getPhotoAccessHelper(getContext(this)); + } + + // Obtain video resources. + let predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates(); + predicates.equalTo('uri', videoUrl); + let videoFetchResult: photoAccessHelper.FetchResult = + await this.phAccessHelper.getAssets({ + fetchColumns: ['width', 'height', 'orientation'], + predicates: predicates + }); + let photoAsset: photoAccessHelper.PhotoAsset = await videoFetchResult.getFirstObject(); + // Configure thumbnail parameters. + let thumbnailSize: Size = { width: 0, height: 0 }; + if (photoAsset.get(photoAccessHelper.PhotoKeys.ORIENTATION) === 90 || + photoAsset.get(photoAccessHelper.PhotoKeys.ORIENTATION) === 270) { + thumbnailSize.width = photoAsset.get(photoAccessHelper.PhotoKeys.HEIGHT) as number; + thumbnailSize.height = photoAsset.get(photoAccessHelper.PhotoKeys.WIDTH) as number; + } else { + thumbnailSize.width = photoAsset.get(photoAccessHelper.PhotoKeys.WIDTH) as number; + thumbnailSize.height = photoAsset.get(photoAccessHelper.PhotoKeys.HEIGHT) as number; + } + return photoAsset.getThumbnail(thumbnailSize); + } catch (error) { + console.error(`acquireThumbnail failed, error: ${JSON.stringify(error)}`); + return undefined; + } + } + // [Start PhotoPickerComponent_callbacks] // 4. 实现相关回调 // 资源被选中回调,返回资源的信息,以及选中方式。 private onItemClicked(itemInfo: ItemInfo, clickType: ClickType): boolean { + // 检查点击频率 + const currentTime = Date.now(); + if (currentTime - this.lastClickTime < this.MIN_CLICK_INTERVAL) { + // 更新上次点击时间 + this.lastClickTime = currentTime; + promptAction.showToast({ + message: '点击频率过高,请稍后再试', + duration: 2000 + }); + return false; + } + // 更新上次点击时间 + this.lastClickTime = currentTime; + if (!itemInfo) { return false; } @@ -86,8 +153,16 @@ struct PhotoPickerComponentDemo { if (clickType === ClickType.SELECTED) { // 应用做自己的业务处理。 if (uri) { - this.selectUris.push(uri); - this.pickerOptions.preselectedUris = [...this.selectUris]; + // 严格检查是否超过最大选择数量 + const maxSelectNum = this.pickerOptions.maxSelectNumber || 5; + if (this.selectUris.length >= maxSelectNum) { + console.log(`已达到最大选择数量: ${maxSelectNum}`); + return false; // 返回false则不响应勾选 + } + // 确保URI不在已选择列表中(防止重复添加) + if (!this.selectUris.includes(uri)) { + this.selectUris = [...this.selectUris, uri]; + } } return true; // 返回true则勾选,否则则不响应勾选。 } else { @@ -95,7 +170,6 @@ struct PhotoPickerComponentDemo { this.selectUris = this.selectUris.filter((item: string) => { return item != uri; }); - this.pickerOptions.preselectedUris = [...this.selectUris]; } } return true; @@ -105,9 +179,31 @@ struct PhotoPickerComponentDemo { // 进入大图的回调。 private onEnterPhotoBrowser(photoBrowserInfo: PhotoBrowserInfo): boolean { this.isBrowserShow = true; + this.cacheAllVideoThumbnails(); return true; } + // 依次获取并缓存所有视频缩略图 + async cacheAllVideoThumbnails(): Promise { + try { + for (const uri of this.selectUris) { + if (this.isVideoResource(uri) && !this.videoThumbnails.has(uri)) { + // 使用acquireThumbnailByUrl获取并缓存缩略图 + const thumbnail = await this.acquireThumbnailByUrl(uri); + if (thumbnail) { + this.videoThumbnails.set(uri, thumbnail); + console.log(`成功缓存视频缩略图: ${uri}`); + } else { + // 失败时使用视频URI作为备选 + this.videoThumbnails.set(uri, uri); + } + } + } + } catch (error) { + console.error('缓存视频缩略图失败:', error); + } + } + // 退出大图的回调。 private onExitPhotoBrowser(photoBrowserInfo: PhotoBrowserInfo): boolean { this.isBrowserShow = false; @@ -147,19 +243,19 @@ struct PhotoPickerComponentDemo { PhotoPickerComponent({ pickerOptions: this.pickerOptions, pickerController: this.pickerController, - onItemClicked: (itemInfo: ItemInfo, clickType: ClickType): boolean => - this.onItemClicked(itemInfo, clickType), - onEnterPhotoBrowser: (photoBrowserInfo: PhotoBrowserInfo): boolean => - this.onEnterPhotoBrowser(photoBrowserInfo), - onExitPhotoBrowser: (photoBrowserInfo: PhotoBrowserInfo): boolean => - this.onExitPhotoBrowser(photoBrowserInfo), + onItemClicked: (itemInfo: ItemInfo, clickType: ClickType): boolean => + this.onItemClicked(itemInfo, clickType), + onEnterPhotoBrowser: (photoBrowserInfo: PhotoBrowserInfo): boolean => + this.onEnterPhotoBrowser(photoBrowserInfo), + onExitPhotoBrowser: (photoBrowserInfo: PhotoBrowserInfo): boolean => + this.onExitPhotoBrowser(photoBrowserInfo), onPickerControllerReady: (): void => this.onPickerControllerReady(), - onPhotoBrowserChanged: (browserItemInfo: BaseItemInfo): boolean => - this.onPhotoBrowserChanged(browserItemInfo), - onSelectedItemsDeleted: (baseItemInfos: Array) => - this.onSelectedItemsDeleted(baseItemInfos), - onExceedMaxSelected: (exceedMaxCountType: MaxCountType) => - this.onExceedMaxSelected(exceedMaxCountType), + onPhotoBrowserChanged: (browserItemInfo: BaseItemInfo): boolean => + this.onPhotoBrowserChanged(browserItemInfo), + onSelectedItemsDeleted: (baseItemInfos: Array) => + this.onSelectedItemsDeleted(baseItemInfos), + onExceedMaxSelected: (exceedMaxCountType: MaxCountType) => + this.onExceedMaxSelected(exceedMaxCountType), onCurrentAlbumDeleted: () => this.onCurrentAlbumDeleted() }) // [End PhotoPickerComponent_use] @@ -170,20 +266,21 @@ struct PhotoPickerComponentDemo { Row() { ForEach(this.selectUris, (uri: string) => { if (uri === this.currentUri) { - Image(uri) + Image(this.videoThumbnails.get(uri) || uri) .height(50) .width(50) .onClick(() => {}) .borderWidth(1) .borderColor('red') } else { - Image(uri) + Image(this.videoThumbnails.get(uri) || uri) .height(50) .width(50) .onClick(() => { this.pickerController.setData(DataType.SET_SELECTED_URIS, this.selectUris); this.pickerController.setPhotoBrowserItem(uri, PhotoBrowserRange.ALL); }) + .borderWidth(1) } }, (uri: string) => JSON.stringify(uri)) }.alignSelf(ItemAlign.Center).margin(this.selectUris.length ? 10 : 0) @@ -195,9 +292,14 @@ struct PhotoPickerComponentDemo { .height('5%') .margin(10) .onClick(() => { + console.log(`onClick: ${this.selectUris}`); + if (this.selectUris.length > 0) { + // 依次获取并缓存所有已选择视频的缩略图 + this.cacheAllVideoThumbnails(); + this.pickerController.setPhotoBrowserItem( - this.selectUris[0], + this.selectUris[0], PhotoBrowserRange.SELECTED_ONLY ); } diff --git a/Media/Picker/PhotoPickerComponentSample/entry/src/main/syscap.json b/Media/Picker/PhotoPickerComponentSample/entry/src/main/syscap.json index 061b4782a..00ccb8376 100644 --- a/Media/Picker/PhotoPickerComponentSample/entry/src/main/syscap.json +++ b/Media/Picker/PhotoPickerComponentSample/entry/src/main/syscap.json @@ -6,7 +6,8 @@ "removedSysCaps": [ "SystemCapability.Security.DataTransitManager", "SystemCapability.Security.DeviceSecurityLevel", - "SystemCapability.Security.DeviceAuth" + "SystemCapability.Security.DeviceAuth", + "SystemCapability.HiviewDFX.HiDumper" ] } } \ No newline at end of file diff --git a/Media/Picker/PickerControllerEditSample/README.md b/Media/Picker/PickerControllerEditSample/README.md index 3d7f26edd..e28945439 100644 --- a/Media/Picker/PickerControllerEditSample/README.md +++ b/Media/Picker/PickerControllerEditSample/README.md @@ -2,7 +2,7 @@ ## 介绍 -本示例演示了如何在HarmonyOS应用中使用PhotoPicker和PickerController编辑图片并替换原图。同时启用了编辑功能的PhotoPicker组件,允许用户选择图片、编辑图片,并可以直接替换原图片或者保存为一个新的图片文件。 +本示例演示了如何在HarmonyOS应用中使用PhotoPicker和PickerController编辑图片并替换原图。同时启用了编辑功能的PhotoPicker组件,允许用户选择图片、编辑图片,并将更改保存回原始位置或另存为新文件。 ## 使用说明 @@ -45,4 +45,3 @@ 3. DevEco Studio版本:DevEco Studio 5.0.5 Release及以上。 4. 当前仅支持编辑单张图片。 - diff --git a/Media/Picker/PickerControllerEditSample/entry/src/main/ets/pages/Index.ets b/Media/Picker/PickerControllerEditSample/entry/src/main/ets/pages/Index.ets index 640f8f50b..7a1f5ba3a 100644 --- a/Media/Picker/PickerControllerEditSample/entry/src/main/ets/pages/Index.ets +++ b/Media/Picker/PickerControllerEditSample/entry/src/main/ets/pages/Index.ets @@ -33,6 +33,8 @@ import { AlbumPickerOptions } from '@ohos.file.AlbumPickerComponent'; import { common } from '@kit.AbilityKit'; import { AsyncCallback } from '@kit.BasicServicesKit'; import { fileUri } from '@kit.CoreFileKit'; +// 导入Toast模块 +import promptAction from '@ohos.promptAction'; // [End PickerController_import] @Entry @@ -61,6 +63,12 @@ struct Index { @State isShow: boolean = true; @State originUrl: string = ''; @State trustedUris: Array = new Array(); + + // 记录上次点击时间,用于控制点击频率,不需要@State + private lastClickTime: number = 0; + + // 定义最小点击间隔时间(毫秒),用于控制选择与反选频率 + private readonly MIN_CLICK_INTERVAL: number = 500; // [Start PickerController_callbacks] // 5. 实现相关回调 @@ -88,6 +96,20 @@ struct Index { } private onItemClicked(itemInfo: ItemInfo, clickType: ClickType): boolean { + // 检查点击频率 + const currentTime = Date.now(); + if (currentTime - this.lastClickTime < this.MIN_CLICK_INTERVAL) { + promptAction.showToast({ + message: '点击频率过高,请稍后再试', + duration: 2000 + }); + // 返回时也需要更新时间 + this.lastClickTime = currentTime; + return false; + } + // 更新上次点击时间 + this.lastClickTime = currentTime; + if (!itemInfo) { return false; } @@ -101,15 +123,22 @@ struct Index { return false; } if (uri) { - this.selectedUris.push(uri); - this.pickerOptions.preselectedUris = [...this.selectedUris]; + // 严格检查是否超过最大选择数量 + const maxSelectNum = this.pickerOptions.maxSelectNumber || 5; + if (this.selectedUris.length >= maxSelectNum) { + console.log(`已达到最大选择数量: ${maxSelectNum}`); + return false; // 返回false则不响应勾选 + } + // 确保URI不在已选择列表中(防止重复添加) + if (!this.selectedUris.includes(uri)) { + this.selectedUris = [...this.selectedUris, uri]; + } } } else { if (uri) { this.selectedUris = this.selectedUris.filter((item: string) => { return item !== uri; - }) - this.pickerOptions.preselectedUris = [...this.selectedUris]; + }); } } } @@ -139,6 +168,9 @@ struct Index { this.pickerOptions.isPreviewForSingleSelectionSupported = false; this.pickerOptions.isSlidingSelectionSupported = false; this.pickerOptions.isRepeatSelectSupported = false; + this.pickerOptions.maxSelectNumber = 5; + // 隐藏搜索框 + this.pickerOptions.isSearchSupported = false; } @State currentIndex: number = 0; @@ -160,17 +192,6 @@ struct Index { }.width('100%') } - handle(callback: AsyncCallback): Promise { - let num = 1 - return new Promise((resolve, reject) => { - if (num == 1) { - resolve(100); - } else { - reject(100); - } - }) - } - build() { Row() { Stack() { diff --git a/Media/Picker/PickerControllerEditSample/entry/src/main/syscap.json b/Media/Picker/PickerControllerEditSample/entry/src/main/syscap.json index 061b4782a..00ccb8376 100644 --- a/Media/Picker/PickerControllerEditSample/entry/src/main/syscap.json +++ b/Media/Picker/PickerControllerEditSample/entry/src/main/syscap.json @@ -6,7 +6,8 @@ "removedSysCaps": [ "SystemCapability.Security.DataTransitManager", "SystemCapability.Security.DeviceSecurityLevel", - "SystemCapability.Security.DeviceAuth" + "SystemCapability.Security.DeviceAuth", + "SystemCapability.HiviewDFX.HiDumper" ] } } \ No newline at end of file diff --git a/Media/Picker/PickerMediaLibrarySample/README.md b/Media/Picker/PickerMediaLibrarySample/README.md index 6e68b7adc..3d7983c41 100644 --- a/Media/Picker/PickerMediaLibrarySample/README.md +++ b/Media/Picker/PickerMediaLibrarySample/README.md @@ -8,7 +8,7 @@ 1. 进入示例应用,页面将显示一个PhotoPickerComponent组件。 2. 点击组件,会拉起系统相册,显示图片和视频资源。 -3. 在相册中选择一张或多张图片/视频。 +3. 可以在相册中选择一张或多张图片/视频。 4. 选择完成后,页面会显示选择的资源信息。 ## 工程目录 diff --git a/Media/Picker/PickerMediaLibrarySample/entry/src/main/ets/common/utils/MediaLibraryPickerUtils.ets b/Media/Picker/PickerMediaLibrarySample/entry/src/main/ets/common/utils/MediaLibraryPickerUtils.ets index 507bfc5f6..f0db7a74c 100644 --- a/Media/Picker/PickerMediaLibrarySample/entry/src/main/ets/common/utils/MediaLibraryPickerUtils.ets +++ b/Media/Picker/PickerMediaLibrarySample/entry/src/main/ets/common/utils/MediaLibraryPickerUtils.ets @@ -60,17 +60,18 @@ export class MediaAssetDataHandler implements photoAccessHelper.MediaAssetDataHa this.callback = callback; } - onDataPrepared(data: ArrayBuffer) { + // 使用箭头函数确保this引用不会丢失 + onDataPrepared = (data: ArrayBuffer) => { if (data === undefined) { console.error('Error occurred when preparing data'); return; } console.info('on image data prepared'); - // 直接调用回调函数 + // 现在this始终指向MediaAssetDataHandler实例 if (this.callback) { this.callback(data); } - } + }; } // [End PickerMediaLibrary_handler] @@ -156,18 +157,18 @@ export default class MediaLibraryPickerUtils { * @returns 读取的数据和长度 */ static openAndReadFileData(uri: string): FileReadResult | null { - // 打开文件 - 使用类名直接调用静态方法,避免使用this + // 打开文件 const fileObj = MediaLibraryPickerUtils.openFileByUri(uri); if (!fileObj) { return null; } try { - // 读取文件数据 - 使用类名直接调用静态方法 + // 读取文件数据 const result = MediaLibraryPickerUtils.readFileData(fileObj); return result; } finally { - // 确保关闭文件 - 使用类名直接调用静态方法 + // 确保关闭文件 MediaLibraryPickerUtils.closeFile(fileObj); } } @@ -179,7 +180,8 @@ export default class MediaLibraryPickerUtils { * @param callback 数据准备完成回调函数 */ // [Start PickerMediaLibrary_getMediaResource] - static async getMediaResourceByUri(uri: string, context: common.Context, callback?: MediaDataHandlerCallback): Promise { + static async getMediaResourceByUri(uri: string, context: common.Context, callback?: MediaDataHandlerCallback) + : Promise { try { // 创建PhotoAccessHelper实例 const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context); diff --git a/Media/Picker/PickerMediaLibrarySample/entry/src/main/ets/pages/Index.ets b/Media/Picker/PickerMediaLibrarySample/entry/src/main/ets/pages/Index.ets index 24ac7c7f6..e8afe60a7 100644 --- a/Media/Picker/PickerMediaLibrarySample/entry/src/main/ets/pages/Index.ets +++ b/Media/Picker/PickerMediaLibrarySample/entry/src/main/ets/pages/Index.ets @@ -17,6 +17,8 @@ import { common } from '@kit.AbilityKit'; import photoAccessHelper from '@ohos.file.photoAccessHelper'; import MediaLibraryPickerUtils, { PhotoItem } from '../common/utils/MediaLibraryPickerUtils'; +// 导入Toast模块 +import promptAction from '@ohos.promptAction'; @Entry @Component @@ -31,11 +33,27 @@ struct Index { @State fileReadLength: number = 0; @State mediaResourceStatus: string = ''; @State mediaResourceDataLength: number = 0; + private lastOperationTime: number = 0; // 记录最后一次操作时间,不需要@State + private readonly THROTTLE_INTERVAL: number = 1000; // 操作间隔限制为1秒 /** * 调用PhotoViewPicker.select接口拉起图库界面 */ private async pickPhotos(): Promise { + // 添加操作频控逻辑 + const currentTime: number = Date.now(); + if (currentTime - this.lastOperationTime < this.THROTTLE_INTERVAL) { + // 使用toast提示 + promptAction.showToast({ + message: '操作过于频繁,请稍后再试', + duration: 2000 + }); + // 返回时也需要更新时间 + this.lastOperationTime = currentTime; + return; + } + this.lastOperationTime = currentTime; + try { // 创建图库选择器实例 // [Start PickerMediaLibrary_createPicker] @@ -92,6 +110,21 @@ struct Index { * 通过URI打开并读取文件数据 */ private readFileDataByUri(): void { + // 添加操作频控逻辑 + const currentTime: number = Date.now(); + if (currentTime - this.lastOperationTime < this.THROTTLE_INTERVAL) { + // 使用toast提示 + promptAction.showToast({ + message: '操作过于频繁,请稍后再试', + duration: 2000 + }); + this.fileReadStatus = '操作过于频繁,请稍后再试'; + // 返回时也需要更新时间 + this.lastOperationTime = currentTime; + return; + } + this.lastOperationTime = currentTime; + // 指定URI读取文件数据 if (!this.selectedUriForRead) { this.fileReadStatus = '请先选择一个文件'; @@ -120,6 +153,21 @@ struct Index { * 通过URI获取媒体资源 */ private async getMediaResourceByUri(): Promise { + // 添加操作频控逻辑 + const currentTime: number = Date.now(); + if (currentTime - this.lastOperationTime < this.THROTTLE_INTERVAL) { + // 使用toast提示 + promptAction.showToast({ + message: '操作过于频繁,请稍后再试', + duration: 2000 + }); + this.mediaResourceStatus = '操作过于频繁,请稍后再试'; + // 返回时也需要更新时间 + this.lastOperationTime = currentTime; + return; + } + this.lastOperationTime = currentTime; + // 指定URI获取图片或视频资源 if (!this.selectedUriForRead) { this.mediaResourceStatus = '请先选择一个文件'; @@ -149,6 +197,20 @@ struct Index { * 重置选择 */ private resetSelection(): void { + // 添加操作频控逻辑 + const currentTime: number = Date.now(); + if (currentTime - this.lastOperationTime < this.THROTTLE_INTERVAL) { + // 使用toast提示 + promptAction.showToast({ + message: '操作过于频繁,请稍后再试', + duration: 2000 + }); + // 返回时也需要更新时间 + this.lastOperationTime = currentTime; + return; + } + this.lastOperationTime = currentTime; + this.selectedUris = []; this.processedItems = []; this.showProcessedItems = false; diff --git a/Media/Picker/PickerMediaLibrarySample/entry/src/main/syscap.json b/Media/Picker/PickerMediaLibrarySample/entry/src/main/syscap.json index 061b4782a..00ccb8376 100644 --- a/Media/Picker/PickerMediaLibrarySample/entry/src/main/syscap.json +++ b/Media/Picker/PickerMediaLibrarySample/entry/src/main/syscap.json @@ -6,7 +6,8 @@ "removedSysCaps": [ "SystemCapability.Security.DataTransitManager", "SystemCapability.Security.DeviceSecurityLevel", - "SystemCapability.Security.DeviceAuth" + "SystemCapability.Security.DeviceAuth", + "SystemCapability.HiviewDFX.HiDumper" ] } } \ No newline at end of file diff --git a/Media/Picker/RecentPhotoComponentSample/README.md b/Media/Picker/RecentPhotoComponentSample/README.md index 641052cdc..d5644ae79 100644 --- a/Media/Picker/RecentPhotoComponentSample/README.md +++ b/Media/Picker/RecentPhotoComponentSample/README.md @@ -7,11 +7,10 @@ ## 使用说明 1. 进入示例应用,页面将自动显示RecentPhotoComponent组件。 -2. 系统会检查设备中是否存在最近图片。 - -配置显示多久时间段内的最近图片,单位为秒(s)。最长可配置时长为1天(86400s)。 -当值小于等于0、大于86400或者未配置时,默认按最长时间段1天显示最近图片。当配置时间段内无符合的图片或视频时,组件不显示。 - +2. 系统会根据指定时间范围检查是否存在最近图片。 + 配置显示多久时间段内的最近图片,单位为秒(s)。最长可配置时长为1天(86400s)。 + 当值小于等于0、大于86400或者未配置时,默认按最长时间段1天显示最近图片。 + 当配置时间段内无符合的图片时,组件不显示。 3. 如果存在符合条件的最近图片,组件会显示该图片。 4. 点击图片可以查看图片的详细信息,URI会在页面上显示。 5. 如果不存在符合条件的图片,将显示"未找到符合条件的最近图片"提示信息。 @@ -46,4 +45,4 @@ 3. DevEco Studio版本:DevEco Studio 5.0.5 Release及以上。 -4. 当前仅支持获取最近一张图片。 \ No newline at end of file +4. 当前仅支持获取最近一张图片。 diff --git a/Media/Picker/RecentPhotoComponentSample/entry/src/main/ets/pages/Index.ets b/Media/Picker/RecentPhotoComponentSample/entry/src/main/ets/pages/Index.ets index 0f638df10..623b480a3 100644 --- a/Media/Picker/RecentPhotoComponentSample/entry/src/main/ets/pages/Index.ets +++ b/Media/Picker/RecentPhotoComponentSample/entry/src/main/ets/pages/Index.ets @@ -31,6 +31,8 @@ import { import { BaseItemInfo } from '@ohos.file.PhotoPickerComponent'; +// 导入Toast模块 +import promptAction from '@ohos.promptAction'; // [End RecentPhoto_import] @Entry @@ -45,14 +47,16 @@ struct PickerDemo { @State showRecentPhoto: boolean = true; // 设为true,运行时自动显示 @State photoExists: boolean = false; @State selectedPhotoUri: string = ''; + private lastClickTime: number = 0; // 记录最后一次点击时间 + private readonly THROTTLE_INTERVAL: number = 1000; // 点击间隔限制为1秒 aboutToAppear() { // [Start RecentPhotoOptions_config] // 3. 初始化最近图片组件选择选项实例 // 设置数据类型,IMAGE_VIDEO_TYPE:图片和视频(默认值)、IMAGE_TYPE:图片、VIDEO_TYPE:视频、MOVING_PHOTO_IMAGE_TYPE:动态图片。 - this.recentPhotoOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE; + this.recentPhotoOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; - // 设置最近图片的时间范围,单位(秒),0表示所有时间 + // 设置最近图片的时间范围,单位(秒),0表示一天内所有时间 this.recentPhotoOptions.period = 0; // 设置资源的来源,ALL:所有、CAMERA:相机、SCREENSHOT:截图。 @@ -73,13 +77,29 @@ struct PickerDemo { } private onRecentPhotoClick(recentPhotoInfo: BaseItemInfo): boolean { + // 添加点击频控逻辑 + const currentTime: number = Date.now(); + if (currentTime - this.lastClickTime < this.THROTTLE_INTERVAL) { + // 使用toast提示 + promptAction.showToast({ + message: '操作过于频繁,请稍后再试', + duration: 2000 + }); + // 返回时也需要更新时间 + this.lastClickTime = currentTime; + return true; + } + + // 返回时也需要更新时间 + this.lastClickTime = currentTime; + // 照片或视频返回 if (recentPhotoInfo) { console.info('The photo uri is ' + recentPhotoInfo.uri); this.selectedPhotoUri = recentPhotoInfo.uri ?? ''; - return false; // 修改为false,防止点击后隐藏图片 + return true; } - return false; // 修改为false,防止点击后隐藏图片 + return true; } private onRecentPhotoCheckInfo(recentPhotoExists: boolean, info: RecentPhotoInfo): void { @@ -121,8 +141,9 @@ struct PickerDemo { } // 只有在检查完成后且确实不存在照片时才显示提示 + // 添加延迟检查避免初始渲染时就显示无照片提示 if (!this.photoExists && this.selectedPhotoUri === '') { - Text($r('app.string.recent_photo_not_found')) + Text($r('app.string.recent_photo_no_photos')) .fontSize(16) .fontColor(Color.Red) .margin({ top: 50 }) diff --git a/Media/Picker/RecentPhotoComponentSample/entry/src/main/syscap.json b/Media/Picker/RecentPhotoComponentSample/entry/src/main/syscap.json index 061b4782a..00ccb8376 100644 --- a/Media/Picker/RecentPhotoComponentSample/entry/src/main/syscap.json +++ b/Media/Picker/RecentPhotoComponentSample/entry/src/main/syscap.json @@ -6,7 +6,8 @@ "removedSysCaps": [ "SystemCapability.Security.DataTransitManager", "SystemCapability.Security.DeviceSecurityLevel", - "SystemCapability.Security.DeviceAuth" + "SystemCapability.Security.DeviceAuth", + "SystemCapability.HiviewDFX.HiDumper" ] } } \ No newline at end of file diff --git a/Media/Picker/RecentPhotoComponentSample/entry/src/ohosTest/ets/test/RecentPhotoComponentSample.test.ets b/Media/Picker/RecentPhotoComponentSample/entry/src/ohosTest/ets/test/RecentPhotoComponentSample.test.ets index 7f0e39050..d743459f6 100644 --- a/Media/Picker/RecentPhotoComponentSample/entry/src/ohosTest/ets/test/RecentPhotoComponentSample.test.ets +++ b/Media/Picker/RecentPhotoComponentSample/entry/src/ohosTest/ets/test/RecentPhotoComponentSample.test.ets @@ -160,12 +160,12 @@ export default function abilityTest() { // 覆盖配置为仅图片类型 recentPhotoOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; - recentPhotoOptions.period = 0; // 保持为0,表示所有时间 + recentPhotoOptions.period = 7; recentPhotoOptions.photoSource = PhotoSource.CAMERA; // 验证覆盖后的配置 expect(recentPhotoOptions.MIMEType).assertEqual(photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE); - expect(recentPhotoOptions.period).assertEqual(0); + expect(recentPhotoOptions.period).assertEqual(7); expect(recentPhotoOptions.photoSource).assertEqual(PhotoSource.CAMERA); done(); }); @@ -176,14 +176,15 @@ export default function abilityTest() { */ it('test_ui_display_logic', 0, (done: Function) => { // 模拟组件状态 - let showRecentPhoto: boolean = true; // 自动显示组件 + let showRecentPhoto: boolean = false; let photoExists: boolean = false; let selectedPhotoUri: string = ''; // 测试初始状态 - expect(showRecentPhoto).assertTrue(); + expect(showRecentPhoto).assertFalse(); // 测试显示组件但无照片状态 + showRecentPhoto = true; photoExists = false; expect(showRecentPhoto).assertTrue(); expect(photoExists).assertFalse(); diff --git a/Media/Picker/RecentPhotoComponentSample/ohosTest.md b/Media/Picker/RecentPhotoComponentSample/ohosTest.md index 223790789..aba82e143 100644 --- a/Media/Picker/RecentPhotoComponentSample/ohosTest.md +++ b/Media/Picker/RecentPhotoComponentSample/ohosTest.md @@ -4,7 +4,7 @@ | 测试功能 | 预置条件 | 输入 | 预期输出 | 是否自动 | 测试结果 | | ---------------- | ------------------ | -------------------- | ---------------------------- | -------- | -------- | -| 组件初始化配置 | 无 | 进入示例应用,查看组件初始化 | 组件成功初始化,配置选项正确设置(period=0表示所有时间)

配置显示多久时间段内的最近图片,单位为秒(s)。最长可配置时长为1天(86400s)。
当值小于等于0、大于86400或者未配置时,默认按最长时间段1天显示最近图片。当配置时间段内无符合的图片或视频时,组件不显示。 | 是 | Pass | +| 组件初始化配置 | 无 | 进入示例应用,查看组件初始化 | 组件成功初始化,配置选项正确设置(period=0表示一天内所有时间)

配置显示多久时间段内的最近图片,单位为秒(s)。最长可配置时长为1天(86400s)。
当值小于等于0、大于86400或者未配置时,默认按最长时间段1天显示最近图片。当配置时间段内无符合的图片时,组件不显示。 | 是 | Pass | | 检查是否存在最近图片 | 设备相册中存在图片资源 | 进入示例应用,查看RecentPhotoComponent组件 | 组件检测到存在符合条件的最近图片 | 是 | Pass | | 显示最近一张图片 | 设备相册中存在图片资源 | 进入示例应用,查看RecentPhotoComponent组件 | 组件成功显示最近一张图片 | 是 | Pass | | 点击查看图片详情 | 设备相册中存在图片资源 | 进入示例应用,然后点击组件显示的图片 | 页面显示该图片的URI信息 | 是 | Pass | diff --git a/Media/Picker/SmartPicker/entry/src/main/ets/common/utils/SmartPhotoPickerUtils.ets b/Media/Picker/SmartPicker/entry/src/main/ets/common/utils/SmartPhotoPickerUtils.ets index b9f7bb871..72a17c907 100644 --- a/Media/Picker/SmartPicker/entry/src/main/ets/common/utils/SmartPhotoPickerUtils.ets +++ b/Media/Picker/SmartPicker/entry/src/main/ets/common/utils/SmartPhotoPickerUtils.ets @@ -24,7 +24,8 @@ export class SmartPhotoPickerUtils { let option: photoAccessHelper.PhotoSelectOptions = { isPhotoTakingSupported: true, maxSelectNumber: 1, - MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE + MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE, + isSearchSupported: false }; let photoPicker = new photoAccessHelper.PhotoViewPicker(); try { @@ -47,7 +48,8 @@ export class SmartPhotoPickerUtils { recommendationOptions: recommendationOptions, isPhotoTakingSupported: true, maxSelectNumber: 1, - MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE + MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE, + isSearchSupported: false }; try { photoPicker.select(option).then(() => { @@ -72,7 +74,8 @@ export class SmartPhotoPickerUtils { recommendationOptions: recommendationOptions, isPhotoTakingSupported: true, maxSelectNumber: 1, - MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE + MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE, + isSearchSupported: false }; try { photoPicker.select(option).then(() => { diff --git a/Media/Picker/SmartPicker/entry/src/main/ets/pages/Index.ets b/Media/Picker/SmartPicker/entry/src/main/ets/pages/Index.ets index a1c75d9b0..cf3ab7019 100644 --- a/Media/Picker/SmartPicker/entry/src/main/ets/pages/Index.ets +++ b/Media/Picker/SmartPicker/entry/src/main/ets/pages/Index.ets @@ -14,12 +14,16 @@ */ // [Start SmartPicker_full] +// 导入Toast模块 +import promptAction from '@ohos.promptAction'; @Entry @Component struct Index { @State content: string = ''; @State recommendationType: number = 4; + private lastClickTime: number = 0; // 记录最后一次点击时间 + private readonly THROTTLE_INTERVAL: number = 1000; // 点击间隔限制为1秒 // [Start SmartPicker_create] // 创建导航堆栈实例 @@ -37,13 +41,46 @@ struct Index { .width('100%') .margin({ bottom: 8 }) .onClick(() => { + const currentTime = Date.now(); + if (currentTime - this.lastClickTime < this.THROTTLE_INTERVAL) { + // 使用toast提示 + promptAction.showToast({ + message: '操作过于频繁,请稍后再试', + duration: 2000 + }); + // 返回时也需要更新时间 + this.lastClickTime = currentTime; + return false; + } + + // 返回时也需要更新时间 + this.lastClickTime = currentTime; + this.pathStack.pushPathByName('ComponentImplPage', null); + return true; }) Button($r('app.string.Interface_recommendation')) .width('100%') .margin({ bottom: 16 }) .onClick(() => { + const currentTime = Date.now(); + // 添加点击频控逻辑 + if (currentTime - this.lastClickTime < this.THROTTLE_INTERVAL) { + // 使用toast提示 + promptAction.showToast({ + message: '操作过于频繁,请稍后再试', + duration: 2000 + }); + // 返回时也需要更新时间 + this.lastClickTime = currentTime; + return false; + } + + // 返回时也需要更新时间 + this.lastClickTime = currentTime; + this.pathStack.pushPathByName('InterfaceImplPage', null); + return true; }) // [End SmartPicker_callbacks] } diff --git a/Media/Picker/SmartPicker/entry/src/main/ets/view/ComponentImplPage.ets b/Media/Picker/SmartPicker/entry/src/main/ets/view/ComponentImplPage.ets index 826448bdb..12d3e5308 100644 --- a/Media/Picker/SmartPicker/entry/src/main/ets/view/ComponentImplPage.ets +++ b/Media/Picker/SmartPicker/entry/src/main/ets/view/ComponentImplPage.ets @@ -85,7 +85,7 @@ struct ComponentImplPage { .height('40vp') .onClick(() => { this.isShow = true; - this.pickerOptions = {}; + this.pickerOptions = { isSearchSupported: false }; }) .bindSheet($$this.isShow, this.photoPickerBuilder(), { height: '720', @@ -112,8 +112,11 @@ struct ComponentImplPage { .margin({ top: 8 }) .onClick(() => { this.isTypePickerShow = true; - this.pickerOptions.recommendationOptions = { - recommendationType: this.recommendationType + this.pickerOptions = { + isSearchSupported: false, + recommendationOptions: { + recommendationType: this.recommendationType + } }; }) .bindSheet($$this.isTypePickerShow, this.photoPickerBuilder(), { @@ -152,8 +155,11 @@ struct ComponentImplPage { let textInfo: photoAccessHelper.TextContextInfo = { text: this.content }; - this.pickerOptions.recommendationOptions = { - textContextInfo: textInfo + this.pickerOptions = { + isSearchSupported: false, + recommendationOptions: { + textContextInfo: textInfo + } }; }) .bindSheet($$this.isTextPickerShow, this.photoPickerBuilder(), { diff --git a/Media/Picker/SmartPicker/entry/src/main/syscap.json b/Media/Picker/SmartPicker/entry/src/main/syscap.json new file mode 100755 index 000000000..00ccb8376 --- /dev/null +++ b/Media/Picker/SmartPicker/entry/src/main/syscap.json @@ -0,0 +1,13 @@ +{ + "devices": { + "general": ["default"] + }, + "production": { + "removedSysCaps": [ + "SystemCapability.Security.DataTransitManager", + "SystemCapability.Security.DeviceSecurityLevel", + "SystemCapability.Security.DeviceAuth", + "SystemCapability.HiviewDFX.HiDumper" + ] + } +} \ No newline at end of file -- Gitee