zhenghy
  1 +import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
  2 +import { http } from '@kit.NetworkKit';
  3 +import { BusinessError } from '@kit.BasicServicesKit';
  4 +import { promptAction } from '@kit.ArkUI';
  5 +import { image } from '@kit.ImageKit';
  6 +import { photoAccessHelper } from '@kit.MediaLibraryKit';
  7 +import fs from '@ohos.file.fs';
  8 +
  9 +const PERMISSIONS: Array<Permissions> = [
  10 + 'ohos.permission.READ_MEDIA',
  11 + 'ohos.permission.WRITE_MEDIA'
  12 +];
  13 +
  14 +@Component
  15 +export struct ImageDownloadComponent {
  16 + @State image: PixelMap | undefined = undefined;
  17 + @State photoAccessHelper: photoAccessHelper.PhotoAccessHelper | undefined = undefined; // 相册模块管理实例
  18 + @State imageBuffer: ArrayBuffer | undefined = undefined; // 图片ArrayBuffer
  19 + url: string = ''
  20 +
  21 + build() {
  22 + Column() {
  23 + Image($r('app.media.icon_arrow_left_white'))
  24 + .width(24)
  25 + .height(24)
  26 + .aspectRatio(1)
  27 + .interpolation(ImageInterpolation.High)
  28 + .rotate({ angle: -90 })
  29 + .onClick(async () => {
  30 + console.info(`cj2024 onClick ${this.imageBuffer}`)
  31 + if (this.imageBuffer !== undefined) {
  32 + await this.saveImage(this.imageBuffer);
  33 + promptAction.showToast({
  34 + message: $r('app.string.image_request_success'),
  35 + duration: 2000
  36 + })
  37 + }
  38 + })
  39 + }
  40 +
  41 + }
  42 +
  43 + async aboutToAppear(): Promise<void> {
  44 + console.info(`cj2024 图片下载 ${this.url}`)
  45 + const context = getContext(this) as common.UIAbilityContext;
  46 + const atManager = abilityAccessCtrl.createAtManager();
  47 + await atManager.requestPermissionsFromUser(context, PERMISSIONS);
  48 + this.getPicture();
  49 + }
  50 +
  51 + /**
  52 + * 通过http的request方法从网络下载图片资源
  53 + */
  54 + async getPicture() {
  55 + console.info(`cj2024 getPicture`)
  56 + http.createHttp()
  57 + .request(this.url,
  58 + (error: BusinessError, data: http.HttpResponse) => {
  59 + if (error) {
  60 + // 下载失败时弹窗提示检查网络,不执行后续逻辑
  61 + promptAction.showToast({
  62 + message: $r('app.string.image_request_fail'),
  63 + duration: 2000
  64 + })
  65 + return;
  66 + }
  67 + this.transcodePixelMap(data);
  68 + // 判断网络获取到的资源是否为ArrayBuffer类型
  69 + console.info(`cj2024 getPicture ${data.result}`)
  70 + if (data.result instanceof ArrayBuffer) {
  71 + console.info(`cj2024 getPicture 222`)
  72 + this.imageBuffer = data.result as ArrayBuffer;
  73 + }
  74 + }
  75 + )
  76 + }
  77 +
  78 + /**
  79 + * 使用createPixelMap将ArrayBuffer类型的图片装换为PixelMap类型
  80 + * @param data:网络获取到的资源
  81 + */
  82 + transcodePixelMap(data: http.HttpResponse) {
  83 + console.info(`cj2024 transcodePixelMap ${data.responseCode}`)
  84 + if (http.ResponseCode.OK === data.responseCode) {
  85 + const imageData: ArrayBuffer = data.result as ArrayBuffer;
  86 + // 通过ArrayBuffer创建图片源实例。
  87 + const imageSource: image.ImageSource = image.createImageSource(imageData);
  88 + const options: image.InitializationOptions = {
  89 + 'alphaType': 0, // 透明度
  90 + 'editable': false, // 是否可编辑
  91 + 'pixelFormat': 3, // 像素格式
  92 + 'scaleMode': 1, // 缩略值
  93 + 'size': { height: 100, width: 100 }
  94 + }; // 创建图片大小
  95 +
  96 + // 通过属性创建PixelMap
  97 + imageSource.createPixelMap(options).then((pixelMap: PixelMap) => {
  98 + this.image = pixelMap;
  99 + });
  100 + }
  101 + }
  102 +
  103 + /**
  104 + * 保存ArrayBuffer到图库
  105 + * @param buffer:图片ArrayBuffer
  106 + * @returns
  107 + */
  108 + async saveImage(buffer: ArrayBuffer | string): Promise<void> {
  109 + console.info(`cj2024 saveImage buffer ${buffer}`)
  110 + const context = getContext(this) as common.UIAbilityContext; // 获取getPhotoAccessHelper需要的context
  111 + const helper = photoAccessHelper.getPhotoAccessHelper(context); // 获取相册管理模块的实例
  112 + const uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg'); // 指定待创建的文件类型、后缀和创建选项,创建图片或视频资源
  113 + console.info(`cj2024 saveImage uri ${uri}`)
  114 + const file = await fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
  115 + await fs.write(file.fd, buffer);
  116 + await fs.close(file.fd);
  117 + }
  118 +}
1 import { PhotoListBean } from 'wdBean/Index'; 1 import { PhotoListBean } from 'wdBean/Index';
2 import { Logger } from 'wdKit/Index'; 2 import { Logger } from 'wdKit/Index';
3 import { MultiPictureDetailItemComponent } from './MultiPictureDetailItemComponent'; 3 import { MultiPictureDetailItemComponent } from './MultiPictureDetailItemComponent';
4 -import { display } from '@kit.ArkUI'; 4 +import { display, router } from '@kit.ArkUI';
  5 +import { ImageDownloadComponent } from './ImageDownloadComponent';
5 6
6 const TAG = 'ImageSwiperComponent'; 7 const TAG = 'ImageSwiperComponent';
7 8
@@ -35,6 +36,20 @@ export struct ImageSwiperComponent { @@ -35,6 +36,20 @@ export struct ImageSwiperComponent {
35 36
36 build() { 37 build() {
37 RelativeContainer() { 38 RelativeContainer() {
  39 + Image($r('app.media.icon_arrow_left_white'))
  40 + .width(24)
  41 + .height(24)
  42 + .aspectRatio(1)
  43 + .interpolation(ImageInterpolation.High)
  44 + .alignRules({
  45 + top: { anchor: "__container__", align: VerticalAlign.Top },
  46 + left: { anchor: "__container__", align: HorizontalAlign.Start }
  47 + })
  48 + .onClick(() => {
  49 + router.back();
  50 + })
  51 + .id("backImg")
  52 +
38 if (this.photoList && this.photoList?.length > 0) { 53 if (this.photoList && this.photoList?.length > 0) {
39 Swiper(this.swiperController) { 54 Swiper(this.swiperController) {
40 ForEach(this.photoList, (item: PhotoListBean) => { 55 ForEach(this.photoList, (item: PhotoListBean) => {
@@ -96,6 +111,19 @@ export struct ImageSwiperComponent { @@ -96,6 +111,19 @@ export struct ImageSwiperComponent {
96 middle: { anchor: "__container__", align: HorizontalAlign.Center } 111 middle: { anchor: "__container__", align: HorizontalAlign.Center }
97 }) 112 })
98 } 113 }
  114 +
  115 + ImageDownloadComponent({ url: this.photoList[this.swiperIndex].picPath })
  116 + .alignRules({
  117 + bottom: { anchor: "__container__", align: VerticalAlign.Bottom },
  118 + right: { anchor: "__container__", align: HorizontalAlign.End }
  119 + })
  120 + .margin({
  121 + top: 8,
  122 + left: 18,
  123 + bottom: 24,
  124 + right: 18
  125 + })
  126 + .id("downloadImg")
99 } 127 }
100 .width('100%') 128 .width('100%')
101 .height('100%') 129 .height('100%')
  1 +import { CommonConstants } from 'wdConstant';
  2 +import { Logger } from 'wdKit';
  3 +
  4 +const TAG = 'EmptyComponent';
  5 +
  6 +/**
  7 + * WDViewDefaultType 缺省页
  8 + */
  9 +export const enum WDViewDefaultType {
  10 + ///无网
  11 + WDViewDefaultType_NoNetwork,
  12 + ///网络失败 请稍后重试-倒计时
  13 + WDViewDefaultType_NetworkFailed,
  14 + ///内容获取失败
  15 + WDViewDefaultType_ContentFailed,
  16 +}
  17 +
  18 +/**
  19 + * 空数据/无数据
  20 + */
  21 +@Preview
  22 +@Component
  23 +export struct MultiPictureDetailEmptyComponent {
  24 + // private emptySize: SizeOptions = {};
  25 + @State emptyWidth: string | number = CommonConstants.FULL_PARENT;
  26 + @State emptyHeight: string | number = CommonConstants.FULL_PARENT;
  27 + @State emptyType: number = WDViewDefaultType.WDViewDefaultType_ContentFailed
  28 + /**
  29 + * The empty image width percentage setting.
  30 + */
  31 + readonly EMPTY_IMAGE_WIDTH: string = '15%';
  32 + /**
  33 + * The empty image height percentage setting.
  34 + */
  35 + readonly EMPTY_IMAGE_HEIGHT: string = '15%';
  36 + /**
  37 + * The empty data text component margin top.
  38 + */
  39 + readonly EMPTY_TIP_TEXT_MARGIN_TOP: string = '10';
  40 + /**
  41 + * The empty data text opacity.
  42 + */
  43 + readonly TEXT_OPACITY: number = 1;
  44 +
  45 + build() {
  46 + this.noProgrammeData();
  47 + }
  48 +
  49 + /**
  50 + * 无数据,空白view组件
  51 + */
  52 + @Builder
  53 + noProgrammeData() {
  54 + Column() {
  55 + Image(this.buildNoDataTipImage())
  56 + .width('this.EMPTY_IMAGE_WIDTH')
  57 + .height(this.EMPTY_IMAGE_HEIGHT)
  58 + .objectFit(ImageFit.Contain)
  59 + // .border({ width: 1, color: Color.Red, radius: 6 })
  60 +
  61 + Text(this.buildNoDataTip())
  62 + .fontSize($r('app.float.font_size_14'))
  63 + .fontColor('#999999')
  64 + .fontWeight(FontWeight.Normal)
  65 + .opacity(this.TEXT_OPACITY)
  66 + .margin({ top: this.EMPTY_TIP_TEXT_MARGIN_TOP })
  67 + .onClick((event: ClickEvent) => {
  68 + Logger.info(TAG, `noProgrammeData onClick event?.source: ${event.source}`);
  69 + })
  70 + Button('点击重试', { type: ButtonType.Normal, stateEffect: true })
  71 + .borderRadius(4)
  72 + .margin(16)
  73 + .height(28)
  74 + .fontSize(12)
  75 + .fontColor('#CCCCCC')
  76 + .fontFamily('PingFang SC-Medium')
  77 + .border({ width: 1, color: '#545454' })
  78 + .backgroundColor(Color.Black)
  79 + }
  80 + .justifyContent(FlexAlign.Center)
  81 + .width(this.emptyWidth)
  82 + .height(this.emptyHeight)
  83 + .backgroundColor(Color.Black)
  84 + }
  85 +
  86 + buildNoDataTip(): string {
  87 + Logger.info(TAG, "buildNoDataTip");
  88 + let contentString: string = '获取内容失败请重试'
  89 + if (this.emptyType === WDViewDefaultType.WDViewDefaultType_NoNetwork) {
  90 + contentString = '网络出小差了,请检查网络后重试'
  91 + } else if (this.emptyType === WDViewDefaultType.WDViewDefaultType_ContentFailed) {
  92 + contentString = '获取内容失败请重试'
  93 + }
  94 +
  95 + return contentString
  96 + }
  97 +
  98 + buildNoDataTipImage(): Resource | string {
  99 + Logger.info(TAG, "buildNoDataTip");
  100 + let imageString: Resource | string = $r('app.media.icon_no_content')
  101 + if (this.emptyType === WDViewDefaultType.WDViewDefaultType_NoNetwork) {
  102 + imageString = $r('app.media.icon_no_net')
  103 + } else if (this.emptyType === WDViewDefaultType.WDViewDefaultType_ContentFailed) {
  104 + imageString = $r('app.media.icon_no_content')
  105 + } else if (this.emptyType === WDViewDefaultType.WDViewDefaultType_NetworkFailed) {
  106 + imageString = $r('app.media.icon_no_net')
  107 + }
  108 + return imageString
  109 + }
  110 +}
1 import { Logger } from 'wdKit'; 1 import { Logger } from 'wdKit';
  2 +import { ResponseDTO } from 'wdNetwork';
2 import { 3 import {
3 ContentDetailDTO, 4 ContentDetailDTO,
4 PhotoListBean, 5 PhotoListBean,
@@ -11,6 +12,7 @@ import display from '@ohos.display'; @@ -11,6 +12,7 @@ import display from '@ohos.display';
11 import font from '@ohos.font'; 12 import font from '@ohos.font';
12 import { OperRowListView } from './view/OperRowListView'; 13 import { OperRowListView } from './view/OperRowListView';
13 import { MultiPictureDetailItemComponent } from './MultiPictureDetailItemComponent'; 14 import { MultiPictureDetailItemComponent } from './MultiPictureDetailItemComponent';
  15 +import { MultiPictureDetailEmptyComponent } from './MultiPictureDetailEmptyComponent';
14 import { DateTimeUtils } from 'wdKit/Index'; 16 import { DateTimeUtils } from 'wdKit/Index';
15 import { HttpUrlUtils } from 'wdNetwork/Index'; 17 import { HttpUrlUtils } from 'wdNetwork/Index';
16 import { WDRouterPage, WDRouterRule } from 'wdRouter/Index'; 18 import { WDRouterPage, WDRouterRule } from 'wdRouter/Index';
@@ -34,6 +36,7 @@ export struct MultiPictureDetailPageComponent { @@ -34,6 +36,7 @@ export struct MultiPictureDetailPageComponent {
34 @State swiperIndex: number = 0; 36 @State swiperIndex: number = 0;
35 @Provide followStatus: string = '0' // 关注状态 37 @Provide followStatus: string = '0' // 关注状态
36 private scroller: Scroller = new Scroller() 38 private scroller: Scroller = new Scroller()
  39 + @State netStatus: number = 0 // 存储网络状态用来展示缺省图
37 40
38 //watch监听页码回调 41 //watch监听页码回调
39 onCurrentPageNumUpdated(): void { 42 onCurrentPageNumUpdated(): void {
@@ -56,10 +59,6 @@ export struct MultiPictureDetailPageComponent { @@ -56,10 +59,6 @@ export struct MultiPictureDetailPageComponent {
56 familySrc: $rawfile('font/BebasNeue_Regular.otf') 59 familySrc: $rawfile('font/BebasNeue_Regular.otf')
57 }) 60 })
58 this.getContentDetailData() 61 this.getContentDetailData()
59 - if (HttpUrlUtils.getUserId()) {  
60 - this.getInteractBrowsOperate()  
61 - this.getBatchAttentionStatus()  
62 - }  
63 } 62 }
64 63
65 aboutToDisappear() { 64 aboutToDisappear() {
@@ -238,6 +237,16 @@ export struct MultiPictureDetailPageComponent { @@ -238,6 +237,16 @@ export struct MultiPictureDetailPageComponent {
238 }) 237 })
239 .height(px2vp(this.titleHeight) + 64) 238 .height(px2vp(this.titleHeight) + 64)
240 239
  240 + } else {
  241 + if (this.netStatus === 1) {
  242 + MultiPictureDetailEmptyComponent({ emptyType: 2})
  243 + .id('e_empty_content')
  244 + .alignRules({
  245 + center: { anchor: "__container__", align: VerticalAlign.Center },
  246 + middle: { anchor: "__container__", align: HorizontalAlign.Center }
  247 + })
  248 + }
  249 + }
241 OperRowListView({ 250 OperRowListView({
242 contentDetailData: this.contentDetailData, 251 contentDetailData: this.contentDetailData,
243 }) 252 })
@@ -256,7 +265,6 @@ export struct MultiPictureDetailPageComponent { @@ -256,7 +265,6 @@ export struct MultiPictureDetailPageComponent {
256 .border({ width: { top: 0.5 }, color: '#FFFFFF' }) 265 .border({ width: { top: 0.5 }, color: '#FFFFFF' })
257 .id('e_oper_row') 266 .id('e_oper_row')
258 } 267 }
259 - }  
260 .width('100%') 268 .width('100%')
261 .height('100%') 269 .height('100%')
262 .backgroundColor(Color.Black) 270 .backgroundColor(Color.Black)
@@ -266,11 +274,41 @@ export struct MultiPictureDetailPageComponent { @@ -266,11 +274,41 @@ export struct MultiPictureDetailPageComponent {
266 .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) 274 .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
267 } 275 }
268 276
269 - private async getContentDetailData() { 277 + private getContentDetailData() {
270 try { 278 try {
271 - let data = await MultiPictureDetailViewModel.getDetailData(this.relId, this.contentId, this.relType)  
272 - this.contentDetailData = data?.[0]; 279 + PageRepository.fetchDetailData(this.relId, this.contentId, this.relType).then((resDTO: ResponseDTO<ContentDetailDTO[]>) => {
  280 + // Logger.info(TAG, `fetchDetailData then,navResDTO.timestamp ${resDTO.timestamp}`);
  281 + // Logger.info(TAG, `fetchDetailData then,navResDTO: ${JSON.stringify(resDTO)}`);
  282 + // Logger.info(TAG, `fetchDetailData then,navResDTO.data: ${JSON.stringify(resDTO.data)}`);
  283 + if (!resDTO || !resDTO.data) {
  284 + Logger.error(TAG, 'fetchDetailData is empty');
  285 + return
  286 + }
  287 + if (resDTO.code != 0) {
  288 + Logger.error(TAG, `fetchDetailData then code:${resDTO.code}, message:${resDTO.message}`);
  289 + return
  290 + }
  291 + this.contentDetailData = resDTO.data?.[0];
273 Logger.info(TAG, `contentDetailData:${JSON.stringify(this.contentDetailData)}`) 292 Logger.info(TAG, `contentDetailData:${JSON.stringify(this.contentDetailData)}`)
  293 + if (HttpUrlUtils.getUserId()) {
  294 + this.getInteractBrowsOperate()
  295 + this.getBatchAttentionStatus()
  296 + }
  297 + }).catch((err: Error) => {
  298 + Logger.info(TAG, `fetchDetailData then,err: ${JSON.stringify(err)}`);
  299 + /*// 请求失败处理
  300 + if (err.response) {
  301 + // 请求已发出,但服务器响应的状态码不在2xx范围内
  302 + console.error('请求失败,状态码:', err.response.status);
  303 + console.error('响应数据:', err.response.data);
  304 + } else if (err.request) {
  305 + // 请求已发出,但无响应(例如:网络故障)
  306 + console.error('请求已发出,但无响应:', err.request);
  307 + } else {
  308 + // 发生了其他类型的错误(如配置错误或拒绝权限等)
  309 + console.error('请求发生错误:', err.message);
  310 + }*/
  311 + })
274 } catch (exception) { 312 } catch (exception) {
275 313
276 } 314 }
@@ -42,6 +42,8 @@ export struct BottomNavigationComponent { @@ -42,6 +42,8 @@ export struct BottomNavigationComponent {
42 let bottomNav = await PageViewModel.getBottomNavData(getContext(this)) 42 let bottomNav = await PageViewModel.getBottomNavData(getContext(this))
43 if (bottomNav && bottomNav.bottomNavList != null) { 43 if (bottomNav && bottomNav.bottomNavList != null) {
44 Logger.info(TAG, `aboutToAppear, bottomNav.length: ${bottomNav.bottomNavList.length}`); 44 Logger.info(TAG, `aboutToAppear, bottomNav.length: ${bottomNav.bottomNavList.length}`);
  45 + // 使用filter方法移除name为'服务'的项
  46 + bottomNav.bottomNavList = bottomNav.bottomNavList.filter(item => item.name !== '服务');
45 this.bottomNavList = bottomNav.bottomNavList 47 this.bottomNavList = bottomNav.bottomNavList
46 } 48 }
47 } 49 }