第三天
前言
综合运用本学期所学内容及个人自学知识,使用HarmonyOS 4.0及以上版本开发一款具有实用性和创新性的移动应用软件。
项目要求
- 技术栈:HarmonyOS 4.0,DevEco Studio。
- 实用性与创意:软件需解决具体问题,具备用户友好界面,创新性需体现在功能或用户体验上。
- 代码质量:遵守HarmonyOS编码规范,代码需模块化,易于维护。
食物列表页
代码:
import router from '@ohos.router'
import { CommonConstants } from '../common/constants/CommonConstants'
import { RecordTypeEnum, RecordTypes } from '../model/RecordTypeModel'
import RecordService from '../service/RecordService'
import itemCard from '../view/item/itemCard'
import ItemList from '../view/item/ItemList'
import itemPanelHeader from '../view/item/itemPanelHeader'
import NumberKeyboard from '../view/item/NumberKeyboard'
import RecordItem from '../viewmodel/RecordItem'
import RecordType from '../viewmodel/RecordType'
@Entry
@Component
struct ItemIndex {
@State amount:number = 1
@State showPanel:boolean = false
@State value:string = ''
@State item:RecordItem = null
@State type:RecordType = RecordTypes[0]
@State isFood : boolean = true
onPanelShow(item:RecordItem){
this.showPanel = true
this.amount = 1
this.item = item
this.value = ''
}
onPageShow(){
//获取跳转参数
let params : any = router.getParams()
//获取点击记录类型
this.type = params.type
//获取isFood
this.isFood = this.type.id !== RecordTypeEnum.WORKOUT
}
build() {
Column() {
//头部标题
this.Header()
//列表
ItemList({showPanel:this.onPanelShow.bind(this),isFood : this.isFood })
.layoutWeight(1)//高度固定,底部就能弹出
//底部面板
/*可滑动面板*/
Panel(this.showPanel){
//顶部日期弹窗
itemPanelHeader()
//记录详细信息,数量
if(this.item){
itemCard({ amount: this.amount,item: $item})//对象只能link传
}
//键盘
NumberKeyboard({amount:$amount,value:$value})
//按钮
Row({space:CommonConstants.SPACE_6}){
Button('提交')
.width(120)
.backgroundColor($r('app.color.primary_color'))
.type(ButtonType.Normal)
.borderRadius(6)
.onClick(()=> {
//持久化
RecordService.insert(this.type.id,this.item.id,this.amount)
.then(() => {
//关闭弹窗
this.showPanel = false
})
})
Button('关闭')
.width(120)
.backgroundColor($r('app.color.light_gray'))
.type(ButtonType.Normal)
.borderRadius(6)
.onClick(()=>this.showPanel=false)
}
.padding({top:10})
}
.mode(PanelMode.Full)
.dragBar(false)//调整高度
.backgroundMask($r('app.color.light_gray'))
.backgroundColor(Color.White)
}
.width('100%')
.height('100%')
}
@Builder Header(){
Row(){
Image($r('app.media.ic_public_back'))
.width(24)
.onClick(()=> router.back())
Blank()
Text(this.type.name).fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_600)
}
.width(CommonConstants.THOUSANDTH_940)
.height(32)
}
}
import { CommonConstants } from '../../common/constants/CommonConstants'
import ItemModel from '../../model/ItemModel'
import GroupInfo from '../../viewmodel/GroupInfo'
import ItemCategory from '../../viewmodel/ItemCategory'
import RecordItem from '../../viewmodel/RecordItem'
@Component
export default struct ItemList {
showPanel:(item:RecordItem)=>void
@Prop isFood:boolean
build() {
Tabs(){
TabContent(){
this.TabsList(ItemModel.list(this.isFood))
}
.tabBar('全部')
ForEach(ItemModel.listItemGroupByCategory(this.isFood),
(group:GroupInfo<ItemCategory,RecordItem>) =>
{
TabContent(){
this.TabsList(ItemModel.list(this.isFood))
}
.tabBar(group.type.name)
})
}
.width(CommonConstants.THOUSANDTH_940)
.height('100%')
.barMode(BarMode.Scrollable)//超出可以滚动
}
@Builder TabsList(items:RecordItem[]){
List({space:CommonConstants.SPACE_10}){
ForEach(items,
(item:RecordItem)=>{
ListItem(){
Row(){
Image(item.image)
.width(50)
Column(){
Text(item.name)
.fontWeight(CommonConstants.FONT_WEIGHT_500)
Text(`${item.calorie}千卡/${item.unit}`)
.fontSize(14)
.fontColor($r('app.color.light_gray'))
}
Blank()
Image($r('app.media.ic_public_add_norm_filled'))
.width(18)
.fillColor($r('app.color.primary_color'))
}
.width('100%')
.padding(CommonConstants.SPACE_6)
}
.onClick(()=>this.showPanel(item))
}
)
}
.width('100%')
.height('100%')
}
}
该代码实现了食物列表页面的布局和样式
底部Panel弹窗
顶部日期弹窗
代码:
import { CommonConstants } from '../../common/constants/CommonConstants'
@CustomDialog
export default struct DatePickDialog{
controller:CustomDialogController//控制器
selectedDate:Date = new Date()
build(){
Column(){
//1、日期选择
DatePicker({
start: new Date('2024-01-17'),
end: new Date(),
selected: this.selectedDate
})
.onChange((value: DatePickerResult) => {
this.selectedDate.setFullYear(value.year, value.month, value.day)
})//日期选择
//2、按钮
Row({space:CommonConstants.SPACE_12}){
Button('确定')
.width(120)
.backgroundColor($r('app.color.primary_color'))
.onClick(()=>{
//保存到全局
AppStorage.SetOrCreate('selectedDate',this.selectedDate.getTime())//全局保存
this.controller.close()
})
Button('取消')
.width(120)
.backgroundColor($r('app.color.light_gray'))
.onClick(()=>this.controller.close())
}
}
.padding(CommonConstants.SPACE_12)
}
}
import { CommonConstants } from '../../common/constants/CommonConstants'
import DateUtil from '../../common/utils/DateUtil'
import itemPanelDialog from './itemPanelDialog'
@Component
export default struct itemPanelHeader {
@StorageProp('selectedDate') selectedDate:number = DateUtil.beginTimeOfDay(new Date())
controller:CustomDialogController = new CustomDialogController({
builder:itemPanelDialog({selectedDate: new Date(this.selectedDate)})
})
build() {
Row(){
Text(`${DateUtil.formatDate(this.selectedDate)} 早餐`)
.fontSize(18)
.fontWeight(CommonConstants.FONT_WEIGHT_600)
Image($r('app.media.ic_public_spinner'))
.width(20)
.fillColor(Color.Black)
}
.width(CommonConstants.THOUSANDTH_940)
.justifyContent(FlexAlign.Center)
.onClick(()=>this.controller.open())
}
}
实现了底部Panel滑动面板中的日期选择功能
Panel内容
代码:
import { CommonConstants } from '../../common/constants/CommonConstants'
import RecordItem from '../../viewmodel/RecordItem'
@Component
export default struct itemCard {
@Prop amount:number
@Link item :RecordItem
build() {
Column({space:CommonConstants.SPACE_8}){
Image(this.item.image)
.width(150)
Row()
{
Text(this.item.name)
.fontWeight(CommonConstants.FONT_WEIGHT_700)
}
.backgroundColor($r('app.color.lightest_primary_color'))
.padding({top:5,bottom:5,left:12,right:12})
/*下划线*/
Divider().width(CommonConstants.THOUSANDTH_940).opacity(0.6)
Row({space:CommonConstants.SPACE_8}){
this.NutrientInfo({
label:'热量(千卡)',
value:this.item.calorie
})
if(this.item.id<10000)
{
this.NutrientInfo({
label:'碳水(克)',
value:this.item.carbon
})
this.NutrientInfo({
label:'蛋白(克)',
value:this.item.protein
})
this.NutrientInfo({
label:'脂肪(克)',
value:this.item.fat
})
}
}
Divider().width(CommonConstants.THOUSANDTH_940).opacity(0.6)
Row(){
Column({space:CommonConstants.SPACE_4}){
Text(this.amount.toFixed(1))
.fontSize(50)
.fontColor($r('app.color.primary_color'))
.fontWeight(CommonConstants.FONT_WEIGHT_600)
Divider().color($r('app.color.primary_color'))
}
.width(150)
Text(this.item.unit)
.fontColor($r('app.color.light_gray'))
.fontWeight(CommonConstants.FONT_WEIGHT_600)
}
}
}
@Builder NutrientInfo($$:{label:string,value:number}){
Column({space:CommonConstants.SPACE_8}){
Text($$.label)
.fontSize(14)
.fontColor($r('app.color.light_gray'))
Text(($$.value*this.amount).toFixed(1))//1位小数
.fontWeight(CommonConstants.FONT_WEIGHT_700)
}
}
}
该代码实现了Panel中热量,碳水等信息内容
数字键盘
代码:
import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct NumberKeyboard {
numbers:string[] = ['1','2','3','4','5','6','7','8','9','0','.']
@Link amount:number
@Link value:string
@Styles keyBoxStyle(){
.backgroundColor(Color.White)
.height(60)
.borderRadius(8)
}
build() {
Grid(){
ForEach(this.numbers,num =>{
GridItem(){
Text(num)
.fontSize(20)
.fontWeight(CommonConstants.FONT_WEIGHT_900)
}
.keyBoxStyle()
.onClick(()=>this.clickNumber(num))
})
GridItem(){
Text('删除')
.fontSize(20)
.fontWeight(CommonConstants.FONT_WEIGHT_900)
}
.keyBoxStyle()
.onClick(()=>this.clickDelete())
}
.width('100%')
.height(300)
.backgroundColor($r('app.color.index_page_background'))
.columnsTemplate('1fr 1fr 1fr')//列数
.columnsGap(8)//列间距
.rowsGap(8)
.padding(8)
.margin({top:10})
}
clickNumber(num:string){
//拼接
let val = this.value + num
//处理
let firstIndex = val.indexOf('.')
let lastIndex = val.lastIndexOf('.')
if(firstIndex!==lastIndex||(lastIndex !=-1 && lastIndex<val.length-2)){
return
}
//转数值
let amount = this.parseFloat(val)
if(amount>=999.9){
this.amount = 999
this.value = '999'
}else{
this.amount = amount
this.value = val
}
}
clickDelete(){
if(this.value.length<=0){
this.value = ''
this.amount = 0
return
}
this.value = this.value.substring(0,this.value.length-1)
this.amount = this.parseFloat(this.value)
}
parseFloat(str:string){
if(!str)
{
return 0
}
if(str.endsWith('.')){
str = str.substring(0,str.length-1)
}
return parseFloat(str)
}
}
该代码实现了,Panel中数字键盘,输入的数传到信息内容和统计信息中
总结
我了解并学会了如何使用Panel滑动面板,Divider()下划线,Grid()网格容器,将网格内的变量设置成字符串,进行ForEach循环输出。
在拼接,处理,转换数字时,我知道了,通过indexOf和lastIndexOf来判断 ‘ . ’的首位和尾位是否是相同的下标,不能出现两个‘ . ’,或有小数点的情况下,判断小数点后的位数不能超过两位。最后判断格式,不符合的通过substring函数来删除最后一位。