HarmonyOS NEXT 5.0鸿蒙开发一套影院APP(附带源码)

简介: 本项目基于HarmonyOS NEXT 5.0开发了一款影院应用程序,主要实现了电影和影院信息的展示功能。应用包括首页、电影列表、影院列表等模块。首页包含轮播图与正在热映及即将上映的电影切换显示;电影列表模块通过API获取电影数据并以网格形式展示,用户可以查看电影详情;影院列表则允许用户选择城市后查看对应影院信息,并支持城市选择弹窗。此外,项目中还集成了Axios用于网络请求,并进行了二次封装以简化接口调用流程,同时添加了请求和响应拦截器来处理通用逻辑。整体代码结构清晰,使用了组件化开发方式,便于维护和扩展。该简介概括了提供的内容,但请注意实际开发中还需考虑UI优化、性能提升等方面的工作。

鸿蒙开发HarmonyOS NEXT5.0开发一套影院APP


效果图

电影 影院

创建项目

tabs菜单实现

在Tabs中使用TabContent,对应一个切换页签的内容视图。改写Index.ets,实现tabs菜单的切换

@Entry

@Component

struct Index {

 build() {

   Tabs({ barPosition: BarPosition.End }) {

     TabContent() {

       Text('电影页面')

     }.tabBar('电影')

     TabContent() {

       Text('影院页面')

     }.tabBar('影院')

   }

 }

}

第三方axios引入和基本使用

简介

Axios ,是一个基于 promise 的网络请求库,可以运行 node.js 和浏览器中。本库基于Axios 原库v1.3.4版本进行适配,使其可以运行在 OpenHarmony,并沿用其现有用法和特性。

  • http 请求
  • Promise API
  • request 和 response 拦截器
  • 转换 request 和 response 的 data 数据
  • 自动转换 JSON data 数据

axios三方库封装的意义对axios进行封装的意义在于提供更高层次的抽象,以便简化网络请求的使用和管理。以下是一些具体的理由:

1.统一接口:封装后,可以统一管理所有的网络请求接口,使得在应用中调用网络请求时更加一致,减少重复代码。

2.简化配置:封装可以避免每次请求都需要重复配置相似的参数(例如headers、请求方式等),通过配置对象直接传入更简洁。

3.请求和响应拦截器:封装允许在发送请求之前或收到响应之后,对请求或响应进行处理,比如添加公共的请求头、处理错误、数据格式化等。

4.错误处理:通过自定义的错误处理机制,可以实现统一的错误处理逻辑,比如根据状态码处理特定的错误(例如401未登录、403权限不足等)。

5.增强功能:可以根据项目需求添加额外的功能,例如显示加载状态、处理用户登录状态等。

6.提高可维护性:将网络请求相关的逻辑集中管理,可以让代码更加清晰,降低维护成本。

7.支持特定业务需求:可根据实际的业务需求扩展功能,比如提供缓存机制、重试机制等,增强请求的灵活性。

下载安装

ohpm install @ohos/axios

OpenHarmony ohpm 环境配置等更多内容,请参考如何安装 OpenHarmony ohpm 包

需要权限

ohos.permission.INTERNET

module.json5文件中添加以下内容,开启网络请求权限。

   "requestPermissions": [

     {

       "name": "ohos.permission.INTERNET"

     }

   ]

axios二次封装和类型定义

创建request.ets,实现axios的二次封装

import axios, { InternalAxiosRequestConfig, AxiosResponse, AxiosError } from '@ohos/axios'

import { ResultEnum } from '../enums/ResultEnum';

// 创建 axios 实例

const request = axios.create({

 baseURL: "https://m.maizuo.com",

 timeout: 50000,

 headers: {

   "Content-Type": "application/json;charset=utf-8",

   'x-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.1","e":"1734338281267481973260289","bc":"320800"}'

 }

})

// 添加请求拦截器

request.interceptors.request.use((config: InternalAxiosRequestConfig) => {

 // 对请求数据做点什么 如 添加token

 return config;

}, (error: AxiosError) => {

 // 对请求错误做些什么

 return Promise.reject(error);

});

// 添加响应拦截器

request.interceptors.response.use((response: AxiosResponse) => {

 const result: AxiosResponse<R> = response.data

 console.info(JSON.stringify(response.data))

 if (result.status === ResultEnum.SUCCESS) {

   return response.data.data;

 }

 return Promise.reject(new Error(result.data.msg || "Error"));

}, (error: AxiosError) => {

 // 对响应错误做点什么

 return Promise.reject(error.message);

});

// 统一返回类型

interface R {

 status: number,

 msg?: string,

 data: object

}

export default request;

正在热映和即将上映实现

创建Home.etsCinema.ets组件,以及index.etsapi组件

首页api封装

import request from '../utils/request'

export interface MoveData {

 films?: Films[],

 total?: number

}

export interface Films {

 filmId: number;

 name: string;

 poster: string;

 actors: Array<Actors>;

 director: string;

 category: string;

 synopsis: string;

 filmType: FilmType;

 nation: string;

 language?: string;

 videoId?: string;

 premiereAt: number;

 timeType: number;

 runtime: number;

 grade: string;

 item: Item;

 isPresale: boolean;

 isSale: boolean;

};

export interface Actors {

 name: string;

 role: string;

 avatarAddress: string;

};

export interface FilmType {

 name: string;

 value: number;

};

export interface Item {

 name: string;

 type: number;

};

export const getIndex = (page: number) => {

 return request<string,MoveData>({

   url: `/gateway?cityId=320800&pageNum=${page}&pageSize=10&type=1&k=5393095`,

   method: "get",

   headers:{

     "X-Host": "mall.film-ticket.film.list",

   }

 })

}

首页内容

这里分两个部分,轮播图和电影列表,使用Swiper组件实现轮播图功能,Flex组件实现正在热映和即将上映得功能,配合Scroll组件实现内容滚动、点击返回顶部,并刷新请求接口

import { Films, getIndex, MoveData } from '../api/index'

import MovieList from '../components/MovieList'

@Component

struct Home {

 // 显示正在热映/即将上映

 @State flag: boolean = true

 // 分页参数

 @State page: number = 1

 // 正在热映的电影数据

 @State moveData: MoveData = {}

 @State len: number = 0

 scroll: Scroller = new Scroller()

 aboutToAppear(): void {

   this.getData()

 }

 // 请求电影列表功能单独封装

 getData() {

   getIndex(this.page).then((data:MoveData) => {

     // 替换图片地址

     data.films?.map((item: Films) => {

       item.poster = item.poster.replace("pic.maizuo.com", "static.maizuo.com/pc/v5")

       return item

     }) as Array<Films>

     if (this.page === 1) {

       this.moveData = data

     } else {

       (this.moveData as MoveData).films = (this.moveData as MoveData).films?.concat(data.films as Films[])

     }

   })

 }

 build() {

   Column() {

     Scroll(this.scroll) {

       Column() {

         // 轮播图

         Swiper() {

           Image($r('app.media.h5_01')).width('100%').height(180)

           Image($r('app.media.h5_02')).width('100%').height(180)

           Image($r('app.media.h5_03')).width('100%').height(180)

         }.autoPlay(true).loop(true)

         // 即将上映

         Flex({ justifyContent: FlexAlign.SpaceAround }) {

           Text('正在热映')

             .padding(10)

             .border({ width: { bottom: this.flag ? 3 : 0 } })

             .borderColor(Color.Red)

             .onClick(() => {

               this.flag = true

             })

           Text('即将上映').padding(10)

             .border({ width: { bottom: !this.flag ? 3 : 0 } })

             .borderColor(Color.Red)

             .onClick(() => {

               this.flag = false

             })

         }

         if (this.flag) {

           // Text('正在热映内容')

           MovieList({ moveData: this.moveData, page: this.page })

         } else {

           Text('即将上映内容')

         }

       }

     }.onScrollEdge((size) => {

       if (size === 2) {

         this.page++

         this.getData()

       }

     })

     // 滚动停止时触发

     .onScrollStop(() => {

       //  this.scroll.currentOffset() 返回当前的滚动偏移量 yOffset是y轴的偏移量,赋值给len

       this.len = this.scroll.currentOffset().yOffset

     })

     if (this.len > 250) {

       Text('顶部')

         .fontWeight(FontWeight.Bold)

         .width(40)

         .height(40)

         .backgroundColor(Color.White)

         .position({ x: '80%', y: '80%' })

         .onClick(() => {

           this.scroll.scrollTo({ yOffset: 0, xOffset: 0 })

         })

     }

   }

 }

}

export default Home

电影列表内容和样式问题

将电影列表作为一个组件,写一个MovieList.ets组件

import { Actors, Films, MoveData } from "../api"

@Component

struct MovieList {

 // 定义moveData数据

 @Prop moveData: MoveData

 @Link page: number

 filter_actors(arr: Actors[]) {

   // 如果arr为空

   if (!arr){

     return '暂无主演'

   }

   return arr.map((item: Actors) => {

     return item.name

   }).join(' ') // 通过数组转为字符串,用空格进行拼接

 }

 aboutToAppear(): void {

 }

 build() {

   Column() {

     GridRow({

       gutter: { y: 20 }

     }) {

       ForEach(this.moveData.films, (item: Films) => {

         GridCol({

           span: {

             sm: 12,

             md: 6,

             lg: 3

           }

         }) {

           Flex({ justifyContent: FlexAlign.SpaceBetween }) {

             Image(item.poster).width('20%').height(100).margin({ right: 10 })

             Column() {

               Text(item.name).fontSize(20).fontWeight(FontWeight.Bold).lineHeight(30)

               Text(this.filter_actors(item.actors))

               Row(){

                 Text(item.nation+'|').fontSize(15)

                 Text(`${item.runtime}`).fontSize(15)

               }

             }.alignItems(HorizontalAlign.Start).width('80%')

           }.padding(10)

         }

       })

     }

   }

 }

}

export default MovieList

影院列表

页面内容

这里主要分为两块内容,影院列表和城市选择,城市选择这里我们使用CustomDialogController自定义一个弹窗,并实现影院列表的刷新

import { Cinemas, Cities, getCinema, getCity } from '../api/cinema'


// 创建一个选择城市的弹窗

@CustomDialog

struct CityDialog {

 controller: CustomDialogController = new CustomDialogController({

   builder: CityDialog({})

 })

 @State cities: Cities[] = []

 // 定义父组件传入的通信函数

 updateCity: (item: Cities) => void = () => {

 }


 aboutToAppear(): void {

   getCity().then(data => {

     this.cities = data.cities

   })

 }


 build() {

   Scroll() {

     Column() {

       Text('请选择城市')

         .fontSize(20)

         .fontColor(Color.Red)

         .margin(5)

         .padding(5)

         .border({ width: { bottom: 1 } })


       ForEach(this.cities, (item: Cities) => {

         Text(item.name).width('100%').height(50).textAlign(TextAlign.Center)

           .onClick(() => {

             this.updateCity(item)

           })

       })

     }

   }

 }

}



@Component

struct Cinema {

 @State cityName: string = '上海'

 @State cityId: number = 310100

 @State cinemas: Cinemas[] = []

 // 弹窗属性

 cityDialog: CustomDialogController = new CustomDialogController({

   builder: CityDialog({

     updateCity: (city: Cities) => {

       this.updateCity(city)

     }

   })

 })


 updateCity(city: Cities) {

   console.info(JSON.stringify(city))

   this.cityName = city.name

   this.cityDialog.close()

   this.getData(city.cityId)

 }


 aboutToAppear(): void {

   this.getData(this.cityId)

 }


 getData(cityId: number) {

   getCinema(cityId).then(data => {

     console.info(JSON.stringify(data))

     this.cinemas = data.cinemas

   })

 }


 build() {

   Scroll() {

     Column() {

       Text(this.cityName).fontColor(Color.Red).fontSize(20).fontWeight(FontWeight.Bold)

         .onClick(() => {

           this.cityDialog.open()

         })


       ForEach(this.cinemas, (item: Cinemas) => {

         Column({ space: 10 }) {

           Text(item.name).fontSize(18).width('100%')

           Text(item.address)

             .fontSize(15)

             .fontColor(Color.Gray)

             .width('100%')

         }.margin(10).justifyContent(FlexAlign.Start)

       })

     }

   }

 }

}


export default Cinema

影院api定义

创建cinema.etsapi组件

import request from '../utils/request'



// 获取影院列表

export const getCinema = (cityId: number) => {

 return request<string, CinemasData>({

   url: `/gateway?cityId=${cityId}&ticketFlag=1&k=2500238`,

   method: 'GET',

   headers: {

     "X-Host": "mall.film-ticket.cinema.list",

   }

 })

}


export const getCity = () => {

 return request<string,CitiesData>({

   url: `/gateway?k=9628046`,

   method: 'GET',

   headers: {

     "X-Host": "mall.film-ticket.city.list",

   }

 })

}


// 影院数据

export interface CinemasData {

 cinemas: Array<Cinemas>;

}


export interface Cinemas {

 cinemaId: number;

 name: string;

 address: string;

 longitude: number;

 latitude: number;

 gpsAddress: string;

 cityId: number;

 cityName: string;

 districtId: number;

 districtName: string;

 district: District;

 phone: string;

 telephones: Array<string>;

 isVisited: number;

 lowPrice: number;

 Distance: number;

 eTicketFlag: number;

 seatFlag: number;

}


export interface District {

 districtId: number;

 name: string;

}


// 城市列表数据

export interface CitiesData {

 cities: Array<Cities>;

}


export interface Cities {

 cityId: number;

 name: string;

 pinyin: string;

 isHot: number;

}

到这里,影院的基本功能已经完成,还有很多地方需要优化,你可以根据自己的需求,进行完善。


公众号搜“Harry技术”,关注我,带你看不一样的人间烟火!回复“鸿蒙”获取源码地址。

目录
相关文章
|
3天前
|
API 数据安全/隐私保护 UED
探索鸿蒙的蓝牙A2DP与访问API:从学习到实现的开发之旅
在掌握了鸿蒙系统的开发基础后,我挑战了蓝牙功能的开发。通过Bluetooth A2DP和Access API,实现了蓝牙音频流传输、设备连接和权限管理。具体步骤包括:理解API作用、配置环境与权限、扫描并连接设备、实现音频流控制及动态切换设备。最终,我构建了一个简单的蓝牙音频播放器,具备设备扫描、连接、音频播放与停止、切换输出设备等功能。这次开发让我对蓝牙技术有了更深的理解,也为未来的复杂项目打下了坚实的基础。
85 58
探索鸿蒙的蓝牙A2DP与访问API:从学习到实现的开发之旅
|
2天前
|
安全 API 数据安全/隐私保护
自学记录HarmonyOS Next DRM API 13:构建安全的数字内容保护系统
在完成HarmonyOS Camera API开发后,我深入研究了数字版权管理(DRM)技术。最新DRM API 13提供了强大的工具,用于保护数字内容的安全传输和使用。通过学习该API的核心功能,如获取许可证、解密内容和管理权限,我实现了一个简单的数字视频保护系统。该系统包括初始化DRM模块、获取许可证、解密视频并播放。此外,我还配置了开发环境并实现了界面布局。未来,随着数字版权保护需求的增加,DRM技术将更加重要。如果你对这一领域感兴趣,欢迎一起探索和进步。
44 18
|
1天前
|
编解码 人工智能 开发框架
《鸿蒙HarmonyOS应用开发从入门到精通(第2版)》学习笔记——HarmonyOS技术理念
HarmonyOS在万物智联时代提出了三大技术理念:一次开发,多端部署;可分可合,自由流转;统一生态,原生智能。通过多端开发环境、多端开发能力和多端分发机制,HarmonyOS显著降低了开发成本,提升了开发效率。开发者只需一套工程即可实现多设备应用的高效开发与部署。元服务作为轻量化程序实体,支持跨设备无缝流转,提供便捷服务。同时,HarmonyOS内置强大的AI能力,助力开发者快速实现应用智能化。
42 16
|
1天前
|
存储 API 计算机视觉
自学记录HarmonyOS Next Image API 13:图像处理与传输的开发实践
在完成数字版权管理(DRM)项目后,我决定挑战HarmonyOS Next的图像处理功能,学习Image API和SendableImage API。这两个API支持图像加载、编辑、存储及跨设备发送共享。我计划开发一个简单的图像编辑与发送工具,实现图像裁剪、缩放及跨设备共享功能。通过研究,我深刻体会到HarmonyOS的强大设计,未来这些功能可应用于照片编辑、媒体共享等场景。如果你对图像处理感兴趣,不妨一起探索更多高级特性,共同进步。
29 11
|
1天前
|
开发框架 小程序 IDE
鸿蒙原生开发手记:05-开发之外的那些事
鸿蒙原生开发手记:05-开发之外的那些事
27 9
|
2天前
|
前端开发 算法 安全
一站式搭建相亲交友APP丨交友系统源码丨语音视频聊天社交软件平台系统丨开发流程步骤
本文详细介绍了一站式搭建相亲交友APP的开发流程,涵盖需求分析、技术选型、系统设计、编码实现、测试、部署上线及后期维护等环节。通过市场调研明确平台定位与功能需求,选择适合的技术栈(如React、Node.js、MySQL等),设计系统架构和数据库结构,开发核心功能如用户注册、匹配算法、音视频聊天等,并进行严格的测试和优化,确保系统的稳定性和安全性。最终,通过云服务部署上线,并持续维护和迭代,提供一个功能完善、安全可靠的社交平台。
49 6
|
1天前
|
API 索引
HarmonyOs开发:导航tabs组件封装与使用
主页的底部导航以及页面顶部的切换导航,无论哪个系统,哪个App,都是最常见的功能之一,虽然说在鸿蒙中有现成的组件tabs可以很快速的实现,但是在使用的时候,依然有几个潜在的问题存在,第一,当导航较少时,tabs是默认居中模式,目前无法进行居左,在有这样功能的时候,难以满足需求;第二,导航右侧需要展示按钮的时候,tabs也是无法满足的;除此之外,还有很多人都非常关心的问题,底部的指示器可以跟随页面的滑动而滑动;面对着种种问题的存在,系统的tabs改进之路仍然很艰巨。
HarmonyOs开发:导航tabs组件封装与使用
|
1天前
鸿蒙开发:自定义一个剪辑双滑块组件
既然是一个剪辑截取的功能,音频也好,视频也好,大同小异,无非就是轨道不一,进度不一,但拖拽的滑块都是相似的,除了常见的音视频使用之外,有双向滑动需求的场景也是可以满足的。
鸿蒙开发:自定义一个剪辑双滑块组件
|
2天前
|
前端开发 搜索推荐 PHP
大开眼界!uniapp秀操作,陪玩系统新功能,陪玩app源码,可实时互动随心优化!
多客游戏陪玩系统采用前端uniapp与PHP语言,实现全开源、易改造,RTC传输协议确保低延迟语音连麦,分布式部署应对高并发。功能创新包括游戏约单、多人语音聊天室、动态广场、私信聊天等,提供高端社交和个性化服务,满足各类需求,让玩家畅享游戏乐趣。
|
4天前
【HarmonyOS Next开发】:ListItemGroup使用
通过使用ListItemGroup和AlphabetIndexer两种类型组件,实现带标题分类和右侧导航栏的页面
89 61

热门文章

最新文章