Ver código fonte

feat: 转盘页面、食谱查看页面UI 和 部分交互

YuJing 1 mês atrás
pai
commit
d32e4ddba2

+ 5 - 1
commons/basic/src/main/ets/models/YTDiaLogModel.ets

@@ -22,11 +22,15 @@ export enum DiaLogPageEnum{
 
   // 选择性别
   SelectGender,
+  // 食谱
+  RecipePopup,
+  // 添加至食谱
+  AddToRecipe,
 }
 
 export interface DiaLogParam{
   pageEnum: DiaLogPageEnum,
   align?: YTDiaLogModel,
-  param?: BasicType,
+  param?: BasicType<ESObject>,
   params?: Array<BasicType>
 }

+ 1 - 0
commons/basic/src/main/ets/utils/YTRouter.ets

@@ -82,6 +82,7 @@ class YTRouter extends NavPathStack {
     yTRouter.pushPathByName('YTNaviDiaLog', param, back, false)
   }
 
+  // 获取路由弹窗参数
   getNaviDiaLogParam(){
     return yTRouter.getParamByName('YTNaviDiaLog').pop() as DiaLogParam
   }

+ 293 - 0
features/feature/src/main/ets/components/YTDiaLogBuild.ets

@@ -1,5 +1,6 @@
 import { BasicType } from 'basic'
 import { DiaLogPageEnum, DiaLogParam } from 'basic/src/main/ets/models/YTDiaLogModel'
+import { Recipe } from '../model/Index'
 import { NumberKeyBoard, NumberKeyBoardStyle } from './NumberKeyboard'
 import { YtDatePicker } from './YtDatePicker'
 import { _YtHeader } from './_YtHeader'
@@ -15,6 +16,8 @@ export function getBuilder(param: DiaLogParam, onBack: (ans?: string) => void){
 
   /********  额外的   ***********/
   else if (param.pageEnum == DiaLogPageEnum.SelectGender) GenderPickerBuilder(onBack, param.param)
+  else if (param.pageEnum == DiaLogPageEnum.RecipePopup) RecipePopupBuilder(onBack, param.param as BasicType<Recipe>)
+  else if (param.pageEnum == DiaLogPageEnum.AddToRecipe) AddPlanComp({onBack: onBack})
 }
 
 // 底部菜单
@@ -317,3 +320,293 @@ function GenderPickerBuilder(onBack: (ans?:string) => void, param?: BasicType){
   .backgroundColor(Color.White)
   .padding({ bottom: 60, left: 16, right: 16, top: 24 })
 }
+
+// 食谱弹窗
+@Builder
+function RecipePopupBuilder(onBack: (ans?:string) => void, param?: BasicType<Recipe>){
+  Column(){
+    // 关闭按钮
+    Row(){
+      Image($r('app.media.close'))
+        .width(20)
+        .aspectRatio(1)
+        .onClick(() => {
+          onBack()
+        })
+    }
+    .width('100%')
+    .justifyContent(FlexAlign.End)
+    .alignItems(VerticalAlign.Center)
+    .padding({ top: 12, right: 20 })
+
+    // 食谱名
+    Row(){
+      Text(param?.generics?.name)
+        .fontSize(24)
+        .fontWeight(600)
+    }
+    .width("100%")
+    .padding({ left: 32 })
+    .alignItems(VerticalAlign.Center)
+    .justifyContent(FlexAlign.Start)
+
+    Row({space: 13}){
+      // 图片和标签
+      Column({space: 30}){
+        Text(param?.generics?.monthRange)
+          .fontSize(10)
+          .borderRadius(4)
+          .fontColor('#F0A040')
+          .backgroundColor('#F8F0E6')
+          .padding({left: 10, top: 4, right: 10, bottom: 4})
+
+        Row()
+          .width(180)
+          .aspectRatio(1)
+          .offset({x: -30})
+          .backgroundImage(param?.generics?.imageUrl)
+          .backgroundImageSize(ImageSize.Cover)
+      }
+      .width(134)
+      .clip(true)
+      .height('100%')
+      .alignItems(HorizontalAlign.End)
+
+      // 描述
+      List(){
+        ListItem(){
+          Column(){
+            Text(){
+              Span('食材:\n')
+              ForEach(param?.generics?.ingredients, (i: string) => {
+                Span(i + '\n')
+                  .fontSize(12)
+                  .fontWeight(400)
+                  .fontColor('#99161616')
+              })
+            }
+            .fontSize(10)
+            .fontWeight(400)
+            .fontColor('#161616')
+
+            Text(){
+              Span('制作步骤:\n')
+              ForEach(param?.generics?.steps, (i: string) => {
+                Span(i + '\n')
+                  .fontSize(12)
+                  .fontWeight(400)
+                  .fontColor('#99161616')
+              })
+            }
+            .fontSize(10)
+            .fontWeight(400)
+            .fontColor('#161616')
+
+            Text(){
+              Span('营养信息:\n')
+              Span(param?.generics?.description)
+                .fontSize(12)
+                .fontWeight(400)
+                .fontColor('#99161616')
+
+            }
+            .fontSize(10)
+            .fontWeight(400)
+            .fontColor('#161616')
+          }
+          .justifyContent(FlexAlign.Start)
+          .alignItems(HorizontalAlign.Start)
+        }
+      }
+      .height('100%')
+      .layoutWeight(1)
+    }
+    .width("100%")
+    .height(250)
+    .padding({top: 6, right: 16})
+    .justifyContent(FlexAlign.SpaceBetween)
+
+
+    // 确认按钮
+    Row(){
+      Text('添加至本周计划')
+        .fontSize(16)
+        .fontWeight(500)
+        .borderRadius(10)
+        .fontColor(Color.White)
+        .backgroundColor('#95C50A')
+        .padding({ left: 60, top: 10, right: 60, bottom: 10})
+        .onClick(() => {
+          onBack('好嘞')
+        })
+    }
+    .width("100%")
+    .alignItems(VerticalAlign.Center)
+    .justifyContent(FlexAlign.Center)
+    .padding({top: 26, bottom: 29})
+  }
+  .width(320)
+  .height(432)
+  .borderRadius(8)
+  .backgroundColor(Color.White)
+}
+
+// 添加周计划
+@ComponentV2
+struct AddPlanComp{
+  @Local week: number = -1
+  @Local meal: number = -1
+
+  @Event onBack: (ans?:string) => void
+
+  private weekList: string[] = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日']
+  private mealList: string[] = ['早餐', '午餐', '晚餐']
+
+  @Monitor('week', 'meal')
+  checkAns(){
+    if(this.week != -1 && this.meal != -1) {
+      this.onBack('得嘞')
+    }
+  }
+
+  build() {
+    Column(){
+      Row(){
+        Text('添加至周计划')
+          .fontColor(Color.White)
+
+        Image($r('app.media.close'))
+          .width(20)
+          .aspectRatio(1)
+          .onClick(() => { this.onBack() })
+      }
+      .width("100%")
+      .backgroundColor('#95C50A')
+      .padding({ top: 12, bottom: 12, left: 20, right: 20 })
+      .alignItems(VerticalAlign.Center)
+      .justifyContent(FlexAlign.SpaceBetween)
+      .borderRadius({topLeft: 8, topRight: 8})
+
+      Column({space: 20}){
+        Column({space: 8}){
+          Text('选择日期')
+            .fontSize(14)
+            .fontWeight(500)
+            .padding({top: 8, bottom: 8})
+          Row(){
+            Text(this.weekList[this.week])
+              .fontSize(16)
+              .fontWeight(500)
+
+            Image($r('[basic].media.ic_back'))
+              .width(16)
+              .aspectRatio(1)
+              .rotate({angle: 270})
+          }
+          .width("100%")
+          .borderRadius(10)
+          .backgroundColor('#F6F6F6')
+          .padding({ left: 16, right: 16, top: 12, bottom: 12 })
+          .justifyContent(FlexAlign.SpaceBetween)
+          .bindMenu(this.weekMenu, { hapticFeedbackMode: HapticFeedbackMode.ENABLED })
+        }
+        .alignItems(HorizontalAlign.Start)
+
+        Column({space: 8}){
+          Text('选择餐次')
+            .fontSize(14)
+            .fontWeight(500)
+            .padding({top: 8, bottom: 8})
+
+          Row(){
+            Text(this.mealList[this.meal] ?? '')
+              .fontSize(16)
+              .fontWeight(500)
+            Image($r('[basic].media.ic_back'))
+              .width(16)
+              .aspectRatio(1)
+              .rotate({angle: 270})
+          }
+          .width("100%")
+          .borderRadius(10)
+          .backgroundColor('#F6F6F6')
+          .padding({ left: 16, right: 16, top: 12, bottom: 12 })
+          .justifyContent(FlexAlign.SpaceBetween)
+          .bindMenu(this.mealMenu, { hapticFeedbackMode: HapticFeedbackMode.ENABLED })
+        }
+        .alignItems(HorizontalAlign.Start)
+      }
+      .width("100%")
+      .layoutWeight(1)
+      .padding({ left: 16, right: 16, top: 12 })
+      .alignItems(HorizontalAlign.Start)
+      .justifyContent(FlexAlign.Center)
+      .borderRadius({bottomLeft: 8, bottomRight: 8})
+    }
+    .width(320)
+    .height(288)
+    .borderRadius(8)
+    .backgroundColor(Color.White)
+  }
+
+  @Builder
+  weekMenu(){
+    Column({space: 5}){
+      ForEach(this.weekList, (item: string, index) => {
+        Text(item)
+          .fontSize(16)
+          .fontWeight(500)
+          .textAlign(TextAlign.Center)
+          .fontColor(this.week == index ? Color.White : Color.Black)
+          .backgroundColor(this.week == index ? '#FF5F84' : '#F6F6F6')
+          .borderRadius(10)
+          .padding({ left: 16, right: 16, top: 5, bottom: 5 })
+          .onClick(() => { this.week = index })
+      })
+    }
+  }
+
+  @Builder
+  mealMenu(){
+    Column({space: 5}){
+      ForEach(this.mealList, (item: string, index) => {
+        Text(item)
+          .fontSize(16)
+          .fontWeight(500)
+          .textAlign(TextAlign.Center)
+          .fontColor(this.meal == index ? Color.White : Color.Black)
+          .backgroundColor(this.meal == index ? '#FF5F84' : '#F6F6F6')
+          .borderRadius(10)
+          .padding({ left: 16, right: 16, top: 5, bottom: 5 })
+          .onClick(() => { this.meal = index })
+      })
+    }
+  }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ 577 - 0
features/feature/src/main/ets/components/bigwheel/BigWheelView.ets

@@ -0,0 +1,577 @@
+import { IBestToast, YTAvoid, yTRouter, yTToast } from 'basic'
+import { CellStorage } from '.'
+import { ReNameInput } from './ReNameInput'
+import { Cell, Sector } from './Sector'
+
+@Component
+export struct BigWheelView{
+  @StorageProp(YTAvoid.SAFE_TOP_KEY) safeBottom: number = 0
+  //持久化的数组,方便下次打开应用直接承接上一次编辑的数据
+  @StorageLink('bigwheel')
+  DBCells:CellStorage[]=[]
+  //持久化没选中数组id,方便退出应用再次开发还能接着转
+  @StorageLink('unselectbigwheel')
+  DBUnselectCell:number[]=[]
+  //持久化选中数组id,方便退出应用再次开发还能接着转
+  @StorageLink('selectbigwheel')
+  DBSelectCell:number[]=[]
+  //转盘操作数组
+  @State cells: Cell[] = []; // 存储单元格的数组
+  //随着每次切换操作,需要重置触发画布重新画
+  @Watch('drawCircleWithCustomRadii')
+  @State changeCanvas:number=0
+  @State wheelWidth: number = 250; // 转盘的宽度
+  @State currentAngle: number = 0; // 当前转盘的角度
+  @State selectedName: string = ""; // 选中的名称
+  //持久化存储是否重复
+  @StorageLink('isrepeat')
+  isRepeat:boolean=false
+  //需要操作的数组,选中没选中
+  @State selected:Cell[]=[]
+  @State UnSelected:Cell[]=[]
+  @State randomAngle:number=0
+  isAnimating: boolean = false; // 动画状态
+  colorIndex: number = 0; // 颜色索引
+  @StorageLink('duration')
+  spinDuration:number=5000
+  @State spinDurationTime:number=this.spinDuration/1000
+  //是否显示设置界面
+  @State isShow:boolean=false
+  private settings: RenderingContextSettings = new RenderingContextSettings(true)
+  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
+  //转盘选中判断
+  @State currSelectNumber:number=1
+  @State number:number=4 //转盘数量
+  //打开半模态命名模态
+  @State isshowRename:boolean=false
+  /**
+   * 后面迭代保障可以更改转盘的初始的颜色
+   */
+  colorPalette: string[] = [ // 颜色调色板
+    "#fff",
+    "#fff",
+    "#fff",
+    "#fff",
+    "#fff",
+    "#fff",
+  ];
+  filter(){
+    //每次选中的数组有变化,那就要重新过滤,就要移除没选中的数组里面的数据
+    //先获取选中id数组
+    this.DBSelectCell=this.selected.map(item=>item.id) as number[]
+    //再根据选中数组id过滤没选中数组
+    this.UnSelected = this.cells.filter(item => !this.DBSelectCell.includes(item.id));
+    //再根据没选中数组,获取没选中数组id
+    this.DBUnselectCell=this.UnSelected.map(item=>item.id)
+  }
+  // 组件即将出现时调用
+  aboutToAppear() {
+    if(this.DBCells.length==0) {
+      this.cells=[]
+      this.cells.push(new Cell(1, 1, "转盘1",'#fff' ));
+      this.cells.push(new Cell(2, 1, "转盘2", '#fff' ));
+      this.cells.push(new Cell(3, 1, "转盘3",'#fff' ));
+      this.cells.push(new Cell(4, 1, "转盘4", '#fff' ));
+      this.calculateAngles(); // 计算角度
+      //如果没有之前的记录,存一下没选中的数组和数组id
+      this.UnSelected=this.cells
+      this.DBUnselectCell=this.UnSelected.map(item=>item.id)
+    }else{
+      //如果有上次记录,需要先获取上一次得轮盘
+      this.DBCells.forEach((item)=>{
+        this.cells.push(new Cell(item.id,item.proportion,item.title,item.color))
+      })
+      this.calculateAngles(); // 计算角度
+      //轮盘数
+      this.number=this.cells.length
+      //再获取没选中的和选中的
+      this.selected=[]
+      this.UnSelected=[]
+      for (let index = 0; index < this.cells.length; index++) {
+        //遍历扇形,通过选中的数组id,找到选中的数组
+        this.DBSelectCell.forEach((I)=>{
+          if(this.cells[index].id==I){
+            this.selected.push(this.cells[index])
+          }
+        })
+      }
+      //再找没选中数组
+      this.UnSelected=this.cells.filter((item)=>{
+        return !this.DBSelectCell.includes(item.id)
+      })
+      this.currSelectNumber=this.number-3
+    }
+  }
+  //动画
+  private startAnimation(){
+    if (this.isAnimating) { // 如果正在动画中,返回
+      return;
+    }
+    if(this.selected.length==this.cells.length){
+      yTToast.doubleConfirm({
+        message: '当前已经转完所有,是否重置', click: () => {
+            //背景色变成白色
+            this.cells.forEach((item)=>{
+              item.color='#fff'
+            })
+          //重置,所有的转盘重新开始
+          this.selected=[]
+          this.UnSelected=[]
+          this.UnSelected=this.cells
+          this.DBUnselectCell=this.UnSelected.map(item=>item.id)
+          this.DBSelectCell=[]
+          this.DBCells=this.cells.map((item)=>{
+            return {
+              id:item.id,
+              title:item.title,
+              proportion:item.proportion,
+              color:item.color
+            } as CellStorage
+          })
+          yTToast.hide()
+        }
+      })
+      return
+    }
+    //如果都已经被选了,则不能再转了
+    this.calculateNoSelectedAngles()
+    this.selectedName = ""; // 清空选中的名称
+    this.isAnimating = true; // 设置动画状态为正在动画
+    animateTo({ // 开始动画
+      duration: this.spinDuration, // 动画持续时间为5000毫秒
+      curve: Curve.EaseInOut, // 动画曲线为缓入缓出
+      onFinish: () => { // 动画完成后的回调
+        this.currentAngle %= 360; // 保持当前角度在0到360之间
+        for (const cell of this.cells) {
+          // 遍历每个单元格
+          // 检查当前角度是否在单元格的角度范围内
+          if (360 - this.currentAngle >= cell.angleStart && 360 - this.currentAngle <= cell.angleEnd) {
+            if(!this.isRepeat) {
+              this.selected.push(cell)
+              // this.DBSelectCell.push(cell)
+              //这里需要下次点击的时候
+              cell.color = '#e5e7ea'
+              this.filter()
+            }else{
+              // this.selected=[]
+            }
+            this.selectedName = cell.title; // 设置选中的名称为当前单元格的标题
+            //每次转弯要保存
+            break; // 找到后退出循环
+          }
+        }
+        this.DBCells=this.cells.map((item)=>{
+          return {
+            id:item.id,
+            title:item.title,
+            proportion:item.proportion,
+            color:item.color
+          } as CellStorage
+        })
+        // promptAction.showToast({
+        //   message:'选中的数组'+JSON.stringify(this.selected)+'没选中的数组'+JSON.stringify(this.UnSelected)+'选中的数组id'+JSON.stringify(this.DBSelectCell)+'没选中的数组id'+JSON.stringify(this.DBUnselectCell)
+        // })
+        this.isAnimating = false; // 设置动画状态为未动画
+      },
+    }, () => { // 动画进行中的回调
+      //在这里判断
+      // this.randomAngle=Math.floor(Math.random()*360)
+      // this.currentAngle += (360 * this.spinDuration/1000 + Math.floor(Math.random() * 360)); // 更新当前角度,增加随机旋转
+      //在这里算已经选过的,不能在指了
+      // this.currentAngle += (360 * this.spinDuration/1000 + Math.floor(Math.random() * 360)); // 更新当前角度,增加随机旋转
+
+      // promptAction.showToast({
+      //   message:this.randomAngle.toString()
+      // })
+      this.currentAngle += (360 * this.spinDuration/1000)+this.randomAngle
+    });
+
+  }
+  // 计算每个单元格的角度
+  private calculateAngles() {
+    // 根据比例计算总比例,后续迭代需要计算不同比例的角度
+    const totalProportion = this.cells.reduce((sum, cell) => sum + cell.proportion, 0);
+    this.cells.forEach(cell => {
+      cell.angle = (cell.proportion * 360) / totalProportion; // 计算每个单元格的角度
+    });
+    let cumulativeAngle = 0; // 累计角度
+    this.cells.forEach(cell => {
+      cell.angleStart = cumulativeAngle; // 设置起始角度
+      cumulativeAngle += cell.angle; // 更新累计角度
+      cell.angleEnd = cumulativeAngle; // 设置结束角度
+      cell.rotate = cumulativeAngle - (cell.angle / 2); // 计算旋转角度
+    });
+    //手动触发画布,重新画
+    this.changeCanvas++
+  }
+  //不允许重复时,需要在没选中的数组里面选,然后计算角度,使其转到没选中数组的区间当中去
+  private calculateNoSelectedAngles(){
+    if(this.UnSelected.length!=0) {
+      //随机选取一个没有选中的角度范围扇形
+      const currangle=this.currentAngle%360
+      const randomIndex = Math.floor(Math.random() * this.UnSelected.length) as number
+      const ranNumStart = 360-this.UnSelected[randomIndex].angleEnd
+      const ranNumEnd = 360-this.UnSelected[randomIndex].angleStart
+      this.randomAngle =Math.floor(Math.random() * (ranNumEnd - ranNumStart) + ranNumStart)-currangle
+    }
+  }
+  //画线
+  private drawCircleWithCustomRadii() {
+    // this.context.clearRect(0,0,0,0)
+    this.context.clearRect(0, 0, 250, 250); // 清空Canvas的内容
+    const centerX = 125 // 圆心x坐标
+    const centerY = 125 // 圆心y坐标
+    const radius = 125  // 圆半径
+    // 根据自定义角度数组绘制半径线
+    this.cells.forEach(angle => {
+      // 将角度转换为弧度(Canvas使用弧度制)
+      const radians = (angle.angleEnd-90) * Math.PI / 180
+      // 计算半径线终点坐标
+      const endX = centerX + radius * Math.cos(radians)
+      const endY = centerY + radius * Math.sin(radians)
+      // 绘制半径线
+      this.context.beginPath()
+      this.context.moveTo(centerX, centerY)
+      this.context.lineTo(endX, endY)
+      this.context.strokeStyle = '#efd4f9' // 红色半径线
+      this.context.lineWidth = 1.5
+      this.context.stroke()
+    })
+
+  }
+  build() {
+    Stack({alignContent:Alignment.Center}){
+      Stack({alignContent:Alignment.Top}){
+        Column() {
+          // YTHeader({ title: '大转盘', })
+          Row(){
+            Image($r('[basic].media.ic_back'))
+              .width(24)
+              .margin({ left: 16 })
+              .onClick(()=>{
+                yTRouter.routerBack()
+              })
+            Text('大转盘')
+              .fontSize(18)
+              .fontWeight(700)
+              .fontColor(Color.Black)
+            Image($r('app.media.Subtract'))
+              .width(24)
+              .margin({right: 16 })
+              .onClick(()=>{
+                this.isShow=true
+              })
+          }.width('100%')
+          .height(84)
+          .justifyContent(FlexAlign.SpaceBetween)
+          .padding({ top: 44 })
+          .margin({bottom:20})
+          Row(){
+            Column(){}.width(24).height(24)
+            // Image($r('[basic].media.voicemuisc')).width(24)
+            Text('重置').fontColor('rgba(0, 0, 0, 0.65)').onClick(()=>{
+                //背景色变成白色
+                this.cells.forEach((item)=>{
+                  item.color='#fff'
+                })
+              this.selected=[]
+              this.UnSelected=[]
+              this.UnSelected=this.cells
+              this.DBUnselectCell=this.UnSelected.map(item=>item.id)
+              this.DBSelectCell=[]
+              this.DBCells=this.cells.map((item)=>{
+                return {
+                  id:item.id,
+                  title:item.title,
+                  proportion:item.proportion,
+                  color:item.color
+                } as CellStorage
+              })
+            })
+          }.width('100%')
+          .justifyContent(FlexAlign.SpaceBetween)
+          .padding({left:30,right:30})
+          // 显示当前状态
+          Text(this.isAnimating ? '旋转中' : `${this.selectedName}`)
+            .fontSize(20)
+            .fontColor("#0b0e15")
+            .height(40)
+            .margin({top:100})
+          Stack() {
+            Stack() {
+              // 遍历每个单元格并绘制扇形
+              ForEach(this.cells, (cell: Cell) => {
+                Stack() {
+                  Sector({ radius: lpx2px(this.wheelWidth-15), angle: cell.angle, color: cell.color }); // 创建扇形
+                  Text(cell.title).fontColor(Color.Black).fontWeight(700).margin({ bottom: this.wheelWidth / 1.4 }); // 显示单元格标题
+                }.width('100%').height('100%').rotate({ angle: cell.rotate }); // 设置宽度和高度,并旋转
+              });
+            }
+            .borderRadius('50%') // 设置圆角
+            // .backgroundColor(Color.Gray) // 设置背景颜色
+            .width(this.wheelWidth) // 设置转盘宽度
+            .height(this.wheelWidth) // 设置转盘高度
+            .rotate({ angle: this.currentAngle }); // 旋转转盘
+            Column() {
+              Canvas(this.context)
+                .width(250)
+                .height(250)
+                .borderRadius('50%')
+                .backgroundColor(Color.Transparent)
+                .onReady(() => {
+                  this.drawCircleWithCustomRadii()
+                })
+            } .width(this.wheelWidth) // 设置转盘宽度
+            .height(this.wheelWidth) // 设置转盘高度
+            .justifyContent(FlexAlign.Center)
+            .rotate({ angle: this.currentAngle }) // 旋转转盘
+            Image($r('app.media.zhizheng'))
+              .width(63) // 设置按钮宽度
+              .height(79) // 设置按钮高度
+              .objectFit(ImageFit.Contain)
+              .clickEffect({ level: ClickEffectLevel.LIGHT }) // 设置点击效果
+          }
+          .width(this.wheelWidth+15)
+          .height(this.wheelWidth+15)
+          .backgroundImage($r('app.media.xuanzhuankuang'))
+          .backgroundImageSize({width:'100%',height:'100%'})
+          .backgroundImagePosition(Alignment.Center)
+          Button('转一转').fontColor(Color.White)
+            .backgroundColor('#fd54e3').width(246)
+            .height(44).borderRadius(24)
+            .margin({top:99,bottom:48})
+            .onClick(()=>{
+              this.startAnimation()
+            })
+          Row({space:15}) {
+            ForEach([2,3,4,5],(item:number,index:number)=>{
+              Text((item+1).toString())
+                .width(40)
+                .height(40)
+                .textAlign(TextAlign.Center)
+                .border({width:1,color:'#000000'})
+                .borderRadius('50%')
+                .backgroundColor(this.currSelectNumber==index?'#bff2ff':'#f2f2f2')
+                .onClick(()=>{
+                  this.currSelectNumber=index
+                  this.number=item+1
+                  const arr=this.cells
+                  this.cells=[]
+                  //如果选中的长度比之前要长
+                  if(this.number>arr.length) {
+                    for (let i = 0; i < this.number; i++) {
+                      if (i < arr.length) {
+                        this.cells.push(arr[i])
+                      }else{
+                        this.cells.push(new Cell(i + 1, 1, "转盘" + (i + 1), '#fff'));
+                      }
+                    }
+                  }else{
+                    //短
+                    for (let i = 0; i < this.number; i++) {
+                      this.cells.push(arr[i])
+                    }
+                  }
+                  //要继承之前没选中的,选中的数组根据id
+                  this.calculateAngles(); // 重新计算角度
+                  //选中的和没选中的也要重新计算角度
+                  //找到id重新过滤
+                  this.selected=[]
+                  this.UnSelected=[]
+                  //每次选择扇形数量都要重新更新一下
+                  let a=[] as number[]
+                  let b=[] as number[]
+                  //先获取选中数组,没选中数组id
+                  a=this.DBSelectCell
+                  b=this.DBUnselectCell
+                  //遍历扇形,获取选中数组和没选中数组
+                  for (let i = 0; i < this.cells.length; i++) {
+                    for(let j=0;j<a.length;j++){
+                      if(a[j]==this.cells[i].id){
+                        this.selected.push(this.cells[i])
+                        continue
+                      }
+                    }
+                  }
+                  //找到选中的id数组
+                  this.DBSelectCell=this.selected.map(item=>item.id)
+                  this.UnSelected=this.cells.filter((item)=>{
+                    return !this.DBSelectCell.includes(item.id)
+                  })
+                  this.DBUnselectCell=this.UnSelected.map(item=>item.id)
+                  this.DBCells=[]
+                  this.DBCells=this.cells.map((item)=>{
+                    return {
+                      id:item.id,
+                      title:item.title,
+                      proportion:item.proportion,
+                      color:item.color
+                    } as CellStorage
+                  })
+                })
+            })
+            Row(){
+              Image($r('app.media.zidingyi')).width(16)
+              Text('命名').fontSize(12).fontColor(Color.White)
+            }.width(64)
+            .height(40)
+            .borderRadius(20)
+            .backgroundColor('rgba(255, 157, 240, 1)')
+            .justifyContent(FlexAlign.Center)
+            .onClick(()=>{
+              this.isshowRename=true
+            })
+          }
+        }.width('100%').padding({ bottom: this.safeBottom })
+        .justifyContent(FlexAlign.Center).onClick(()=>{
+          this.isShow=false
+        })
+        //是否展示选项设置
+        if(this.isShow) {
+          this.BigWheelManagerBuilder()
+
+        }
+      }.height('100%')
+      .backgroundImage($r('app.media.backimgNumber'))
+      .backgroundImageSize({width:'100%',height:'100%'})
+      //重命名设置
+      if(this.isshowRename) {
+        this.ReNameBuilder()
+
+      }
+    }
+  }
+
+  @Builder
+  ReNameBuilder(){
+    Column() {
+      Column() {
+        Row() {
+          Text('转盘命名').fontSize(20).fontWeight(500).fontColor('#FF1C1C1C').margin({left:110,right:64,top:24})
+          Column() {
+            Image($r('app.media.quxiaocl')).width(10)
+          }.width(24)
+          .height(24)
+          .backgroundColor(Color.White)
+          .justifyContent(FlexAlign.Center)
+          .borderRadius('50%')
+          .onClick(() => {
+            this.isshowRename = false
+          })
+
+        }.width('100%')
+        Column({space:10}){
+          ForEach(this.cells,(item:Cell,index:number)=>{
+            ReNameInput({
+              text:item.title,
+              num:index+1,
+              inputChange:(value:string)=>{
+                item.title=value
+              }
+            })
+          })
+        }
+      }.width(300).height(318)
+      .justifyContent(FlexAlign.Start)
+      .borderRadius(20)
+      .padding({left:16,right:16})
+      .linearGradient({
+        angle:135,
+        colors:[
+          ['rgba(248, 211, 249, 1)',0.2],
+          ['rgba(192, 242, 255, 1)',1]
+        ]
+      })
+
+    }.width('100%').height('100%').justifyContent(FlexAlign.Center).backgroundColor('rgba(30, 30, 30,0.5)')
+  }
+  @Builder
+  BigWheelManagerBuilder(){
+    Column() {
+      //允许结果是否重复
+      Row() {
+        Row({ space: 10 }) {
+          Image($r('app.media.qiehuan')).width(24)
+          Text('允许结果重复').fontWeight(700)
+        }
+        Row() {
+          Toggle({ type: ToggleType.Switch ,isOn:$$this.isRepeat})
+            .width(38)
+            .height(20)
+            .selectedColor('rgba(253, 84, 227, 1)')  //打开状态下的背景颜色
+            .switchStyle({
+              pointRadius: 8, //圆形滑块半径
+              trackBorderRadius: 14, //滑轨的圆角
+              pointColor: Color.White, //圆形滑块颜色  switchPointColor不生效
+              unselectedColor: 'rgba(233, 233, 234, 1)'  //关闭状态的背景颜色
+            })
+            .onClick(() => {
+              this.isRepeat=!this.isRepeat
+            })
+        }
+      }
+      .width('100%')
+      .height(40)
+      .backgroundColor(Color.White)
+      .borderRadius(8)
+      .justifyContent(FlexAlign.SpaceBetween)
+      .padding({ left: 12, right: 12 })
+      .alignItems(VerticalAlign.Center)
+      Row() {
+        Text('每次转动轮盘可能会随机选中相同的选项').fontSize(12).fontColor('rgba(0, 0, 0, 0.45)')
+      }.width('100%')
+      .justifyContent(FlexAlign.Start)
+      .padding({ left: 22 })
+      .margin({ bottom: 25, top: 10 })
+
+      Row() {
+        Row({ space: 10 }) {
+          Image($r('app.media.xuanzhuantime')).width(24)
+          Text('旋转时长').fontWeight(700)
+        }
+
+        Row() {
+
+          Counter() {
+            Text(this.spinDurationTime.toString() + 's').border({ width: 0 })
+          }
+          .onInc(() => {
+            this.spinDurationTime++
+            this.spinDuration = this.spinDurationTime * 1000
+          })
+          .onDec(() => {
+            if(this.spinDurationTime==1){
+              IBestToast.show({
+                message:'秒数最低为1秒'
+              })
+              return
+            }
+            this.spinDurationTime--
+            this.spinDuration = this.spinDurationTime * 1000
+          })
+        }
+
+      }
+      .width('100%')
+      .height(40)
+      .backgroundColor(Color.White)
+      .borderRadius(8)
+      .justifyContent(FlexAlign.SpaceBetween)
+      .padding({ left: 12, right: 12 })
+      .alignItems(VerticalAlign.Center)
+
+    }
+    .width('100%')
+    .height(214)
+    .padding({ left: 22, top: 56, right: 22 })
+    .borderRadius({ bottomLeft: 20, bottomRight: 20 })
+    .linearGradient({
+      angle: 135,
+      colors: [
+        ['rgba(239, 144, 237, 1)', 0.2],
+        ['rgba(191, 242, 255, 1)', 1]
+      ]
+    })
+  }
+
+}

+ 30 - 0
features/feature/src/main/ets/components/bigwheel/ReNameInput.ets

@@ -0,0 +1,30 @@
+import { IBestToast } from "basic"
+
+@Component
+export struct ReNameInput{
+  @Prop text:string=""
+  @Prop num:number=1
+  inputChange=(value:string)=>{
+  }
+  build() {
+    Row({space:10}){
+      Text(`转盘${this.num}`)
+      TextInput({text:$$this.text})
+        .width(194)
+        .height(30)
+        .padding(0)
+        .border({width:{bottom:1,left:0,right:0,top:0},color:'rgba(0, 0, 0, 0.3)'})
+        .borderRadius(0)
+        .backgroundColor(Color.Transparent)
+        .onChange((value:string)=>{
+          if(value.length>5){
+            this.text = value.slice(0, 5)
+            IBestToast.show({message: '名称最大为5位' })
+            return
+          }
+          this.inputChange(value)
+        })
+    }.width('100%')
+
+  }
+}

+ 86 - 0
features/feature/src/main/ets/components/bigwheel/Sector.ets

@@ -0,0 +1,86 @@
+// 定义扇形组件
+@Component
+export struct Sector {
+  @Prop radius: number; // 扇形的半径
+  @Prop angle: number; // 扇形的角度
+  @Prop color: string; // 扇形的颜色
+  // 创建扇形路径的函数
+  createSectorPath(radius: number, angle: number): string {
+    const centerX = radius / 2; // 计算扇形中心的X坐标
+    const centerY = radius / 2; // 计算扇形中心的Y坐标
+    const startX = centerX; // 扇形起始点的X坐标
+    const startY = centerY - radius; // 扇形起始点的Y坐标
+    const halfAngle = angle / 4; // 计算半个角度
+    // 计算扇形结束点1的坐标
+    const endX1 = centerX + radius * Math.cos((halfAngle * Math.PI) / 180);
+    const endY1 = centerY - radius * Math.sin((halfAngle * Math.PI) / 180);
+
+    // 计算扇形结束点2的坐标
+    const endX2 = centerX + radius * Math.cos((-halfAngle * Math.PI) / 180);
+    const endY2 = centerY - radius * Math.sin((-halfAngle * Math.PI) / 180);
+
+    // 判断是否为大弧
+    const largeArcFlag = angle / 2 > 180 ? 1 : 0;
+    const sweepFlag = 1; // 设置弧线方向为顺时针
+
+    const pathCommands =
+      `M${startX} ${startY} A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${endX1} ${endY1} L${centerX} ${centerY} L${endX2} ${endY2} A${radius} ${radius} 0 ${largeArcFlag} ${1 -
+        sweepFlag} ${startX} ${startY} Z`;
+    // 生成SVG路径命令
+    // const pathCommands =
+    //   `M${startX} ${startY} A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${startX} ${startY} A${radius} ${radius} 0 ${largeArcFlag} ${1 -
+    //     sweepFlag} ${endX2} ${endY2} Z`;
+    return pathCommands; // 返回路径命令
+  }
+
+
+  // 构建扇形组件
+  build() {
+    Stack() {
+      // 创建第一个扇形路径
+      Path()
+        .width(`${this.radius}px`) // 设置宽度为半径
+        .height(`${this.radius}px`) // 设置高度为半径
+        .commands(this.createSectorPath(this.radius, this.angle)) // 设置路径命令
+        .fillOpacity(1) // 设置填充透明度
+        .fill(this.color) // 设置填充颜色
+        // .stroke(Color.Black)
+        .strokeWidth(0) // 设置边框宽度为0
+        .rotate({ angle: this.angle / 4 - 90 }); // 旋转扇形
+
+      // 创建第二个扇形路径
+      Path()
+        .width(`${this.radius}px`) // 设置宽度为半径
+        .height(`${this.radius}px`) // 设置高度为半径
+        .commands(this.createSectorPath(this.radius, this.angle)) // 设置路径命令
+        .fillOpacity(1) // 设置填充透明度
+        .fill(this.color) // 设置填充颜色
+        // .stroke('#f8edfa')
+        // .stroke(Color.Black)
+        .strokeWidth(0) // 设置边框宽度为0
+        .rotate({ angle: 180 - (this.angle / 4 - 90) }); // 旋转扇形
+    }
+  }
+}
+
+// 定义单元格类
+@ObservedV2
+export class Cell {
+  @Trace id:number=0
+  @Trace angle: number = 0; // 扇形的角度
+  @Trace title: string; // 当前格子的标题
+  @Trace color: string; // 背景颜色
+  @Trace rotate: number = 0; // 在转盘要旋转的角度
+  angleStart: number = 0; // 轮盘所在区间的起始
+  angleEnd: number = 0; // 轮盘所在区间的结束
+  proportion: number = 0; // 所占比例,方便后续迭代,初始每份给1就行
+
+  // 构造函数
+  constructor(id:number,proportion: number, title: string, color: string) {
+    this.proportion = proportion; // 设置比例
+    this.title = title; // 设置标题
+    this.color = color; // 设置颜色
+    this.id=id
+  }
+}
+

+ 6 - 0
features/feature/src/main/ets/components/bigwheel/index.ets

@@ -0,0 +1,6 @@
+export interface CellStorage{
+  id:number,
+  title: string,
+  color: string,
+  proportion: number
+}

+ 3 - 0
features/feature/src/main/ets/utils/RouterUtils.ets

@@ -12,6 +12,9 @@ class RouterUtils{
   router2BabyInfoPage(back?: Callback<PopInfo>){
     yTRouter.pushPathByName('BabyInfoPage', null, back, true)
   }
+  router2RecipeSearchPage(back?: Callback<PopInfo>){
+    yTRouter.pushPathByName('RecipeSearchPage', null, back, true)
+  }
 }
 
 export const routerUtils = new RouterUtils()

+ 12 - 7
features/feature/src/main/ets/view/MainView.ets

@@ -45,13 +45,18 @@ export struct MainView {
           RelativeContainer(){
             // 文字描述
             Text() {
-              Span("宝宝")
+              if(this.vm.babyInfo.id){
+                Span("宝宝")
 
-              Span(this.vm.babyInfo.days ?? '0天')
-                .fontWeight(700)
-                .fontColor('#95C50A')
+                Span(this.vm.babyInfo.days ?? '0天')
+                  .fontWeight(700)
+                  .fontColor('#95C50A')
 
-              Span("了")
+                Span("了")
+              } else {
+                Span("请先添加宝宝信息")
+                  .fontSize(16)
+              }
             }
             .fontSize(20)
             .fontWeight(400)
@@ -68,7 +73,7 @@ export struct MainView {
             Image($r('app.media.icon_edit'))
               .width(20)
               .aspectRatio(1)
-              .onClick(() => { this.vm.editBabyInfo() })
+              .onClick(() => { this.vm.checkLogin(() => { this.vm.editBabyInfo()}, true) })
               .alignRules({
                 center: { anchor: "__container__", align: VerticalAlign.Center},
                 right: { anchor: "__container__", align: HorizontalAlign.End}
@@ -456,7 +461,7 @@ struct RecipeWidget{
             .textAlign(TextAlign.Center)
             .padding({left: 36, right: 36, top: 15, bottom: 15})
             .backgroundColor(item.id == this.selectCuisine ? '#F3F3F3' : '#FFFFFF')
-            .onClick(() => { this.updateCuisine(item.id!) })
+            .onClick(() => { this.vm.checkLogin(() => { this.updateCuisine(item.id!) }, true) })
         })
       }
     }

+ 106 - 1
features/feature/src/main/ets/view/SecondView.ets

@@ -1,7 +1,112 @@
+import { ytBuildComp } from "../components/ytBuildComp";
+import { Recipe } from "../model/Index";
+import { SecondViewModel } from "../viewModel/SecondViewModel";
 
-@Component
+@ComponentV2
 export struct SecondView {
+  @Local vm: SecondViewModel = new SecondViewModel();
+
+  aboutToAppear(): void {
+    console.log(`this.vm = ${JSON.stringify(this.vm)}`)
+  }
+  
   build() {
+    ytBuildComp(){
+      Column({space: 15}){
+        // 标题 和 年龄选择
+        Row(){
+          Text('辅食食谱')
+            .fontSize(18)
+            .fontWeight(700)
+
+          Row({space: 5}){
+            Text('全部年龄')
+              .fontSize(12)
+              .fontWeight(500)
+              .fontColor('#95C50A')
+
+            Image($r('[basic].media.ic_back'))
+              .width(16)
+              .aspectRatio(1)
+              .rotate({ angle: 270 })
+          }
+          .borderRadius(8)
+          .backgroundColor('#EFF4E1')
+          .padding({ left: 14, right: 14, top: 8, bottom: 8 })
+        }
+        .width("100%")
+        .justifyContent(FlexAlign.SpaceBetween)
+
+        // 搜索框
+        Row({space: 5}){
+          Image($r('app.media.Search'))
+            .width(20)
+            .aspectRatio(1)
+          Text("请输入菜品名称或食材")
+            .fontSize(14)
+            .fontWeight(400)
+            .fontColor('#A6A6AB')
+        }
+        .width("100%")
+        .borderRadius(10)
+        .alignItems(VerticalAlign.Center)
+        .justifyContent(FlexAlign.Start)
+        .border({width: 1.5, color: '#95C50A'})
+        .padding({left: 16, top: 11, bottom: 11, right: 16})
+        .onClick(() => { this.vm.goToSearchPage() })
+
+        // 食谱列表
+        Grid(){
+          ForEach(this.vm.recipe, (item: Recipe, index) => {
+            GridItem(){
+              this.RecipeBuilder(item)
+            }
+          })
+        }
+        .rowsGap(12)
+        .width("100%")
+        .layoutWeight(1)
+        .scrollBar(BarState.Off)
+        .columnsTemplate("1fr 1fr")
+        .padding({ bottom: 25 })
+      }
+      .width('100%')
+      .height('100%')
+      .justifyContent(FlexAlign.Start)
+      .alignItems(HorizontalAlign.Center)
+      .padding({top: this.vm.safeTop, left:28,right: 28 })
+    }
+  }
+
+  @Builder RecipeBuilder(item: Recipe){
+    Column({space: 10}){
+      Image(item.imageUrl)
+        .width('100%')
+        .height(112)
+        .borderRadius({topLeft: 8, topRight: 8})
+
+      Column({space: 4}){
+        Text(item.name)
+          .fontSize(12)
+          .fontWeight(400)
 
+        Text(item.monthRange)
+          .fontSize(10)
+          .borderRadius(10)
+          .fontColor('#F0A040')
+          .backgroundColor('#F8F0E6')
+          .padding({left: 10, right: 10, top: 5, bottom: 5})
+      }
+      .width("100%")
+      .alignItems(HorizontalAlign.Start)
+      .padding({left: 12, right: 10, bottom: 8, top: 8})
+    }
+    .width(154)
+    .borderRadius(8)
+    .backgroundColor(Color.White)
+    .shadow({
+      radius: 15, type: ShadowType.COLOR, color: 'rgba(0, 0, 0, 0.1)', offsetY: 4
+    })
+    .onClick(() => { this.vm.expandRecipe(item) })
   }
 }

+ 3 - 1
features/feature/src/main/ets/view/ThirdView.ets

@@ -1,6 +1,8 @@
+import { BigWheelView } from "../components/bigwheel/BigWheelView";
+
 @Component
 export struct ThirdView {
   build() {
-
+    BigWheelView()
   }
 }

+ 1 - 1
features/feature/src/main/ets/viewModel/MainViewModel.ets

@@ -46,7 +46,7 @@ export class MainViewModel{
   }
 
   // 登录校验 - 宝宝信息校验
-  checkLogin(f: () => void, isCheckBaby: boolean = true) {
+  checkLogin(f: () => void, isCheckBaby: boolean = false) {
     if(this.userInfo.checkLogin()) {
       if(this.babyInfo?.id || !isCheckBaby){
         f()

+ 76 - 0
features/feature/src/main/ets/viewModel/SecondViewModel.ets

@@ -0,0 +1,76 @@
+import { YTAvoid, yTRouter } from "basic"
+import { DiaLogPageEnum, DiaLogParam, YTDiaLogModel } from "basic/src/main/ets/models/YTDiaLogModel"
+import { BabyFoodApi } from "../Apis/BabyFoodApi"
+import { Recipe } from "../model/Index"
+import { routerUtils } from "../utils/RouterUtils"
+
+@ObservedV2
+export class SecondViewModel{
+  safeTop: number = 0
+  // 选择的年龄段
+  @Trace monthRange: string = ''
+  // 搜索关键字
+  @Trace keyword: string = ''
+  @Trace recipe: Recipe[] = []
+
+  constructor() {
+    this.safeTop = AppStorage.get(YTAvoid.SAFE_TOP_KEY) as number
+    this.getRecipeList()
+  }
+
+  // 去搜索页
+  goToSearchPage(){
+    routerUtils.router2RecipeSearchPage()
+  }
+
+  // 展开食谱弹窗
+  async expandRecipe(item: Recipe){
+    item.ingredients = ['南瓜100g', '高铁面粉100g', '高铁面粉100g']
+    item.steps = ['1. 南瓜去皮去籽,切成小块', '2. 上锅蒸15分钟至软烂', '3. 用勺子压成泥状', '4.米粉用温水调成糊状', '5.将南瓜泥与米粉混合均匀']
+    item.description = '富含β-胡萝卜素和膳食纤维,\n' +
+      '有助于宝宝视力发展和消化\n' +
+      '系统健康'
+
+    let param: DiaLogParam = {
+      pageEnum: DiaLogPageEnum.RecipePopup,
+      align: YTDiaLogModel.Center,
+      param: {
+        generics: item,
+      }
+    }
+    yTRouter.router2NaviDiaLog(param, (res) => {
+      setTimeout(() => {
+        this.addToPlan(item)
+      }, 200)
+    })
+  }
+
+  // 打开 添加至食谱计划弹窗
+  async addToPlan(item: Recipe){
+    let param: DiaLogParam = {
+      pageEnum: DiaLogPageEnum.AddToRecipe,
+      align: YTDiaLogModel.Center,
+      param: {
+        generics: item,
+      }
+    }
+    yTRouter.router2NaviDiaLog(param, (res) => {
+    // this.addToPlan(item)
+    })
+  }
+
+  // 获取食谱列表
+  async getRecipeList(){
+    this.recipe = await BabyFoodApi.getAllRecipes(this.keyword, this.monthRange)
+  }
+
+
+  /**
+   * 重写的返回逻辑
+   * @returns
+   */
+  _onBackPressed(){
+    yTRouter.pop('')
+    return true;
+  }
+}

BIN
features/feature/src/main/resources/base/media/Subtract.png


BIN
features/feature/src/main/resources/base/media/backimgNumber.png


BIN
features/feature/src/main/resources/base/media/qiehuan.png


BIN
features/feature/src/main/resources/base/media/quxiaocl.png


BIN
features/feature/src/main/resources/base/media/xuanzhuankuang.png


BIN
features/feature/src/main/resources/base/media/xuanzhuantime.png


BIN
features/feature/src/main/resources/base/media/zhizheng.png


BIN
features/feature/src/main/resources/base/media/zidingyi.png