Angular 18拖放部分数据

问题描述 投票:0回答:1

在此组件.html中:

<div cdkDropListGroup>
<div cdkDropList [cdkDropListData]="meals" class="makeMeal" (cdkDropListDropped)="drop($event)">
  <div *ngFor="let meal of meals" class="meal-drop">
    <img src="page_delete.png" (click)="deleteFood()" title="delete" />
    <h4>{{meal.name}}</h4>
    <label id="lblID" class="hidden">{{meal.id}}</label>
  </div>
  Add a meal:
  <input id="mealName" type="text" [(ngModel)]="mealName" name="mealName" required style="margin-left:10px;" />
  <img src="page_white_add.png" (click)="addMeal()" />
</div>
<p>My foods:</p>
<div cdkDropList [cdkDropListData]="foods" class="food-grid" (cdkDropListDropped)="drop($event)">
  <div *ngFor="let food of foods" class="food-item">
    <img src="page_delete.png" (click)="deleteFood()" title="delete" />
    <h4>{{ food.name }}</h4>
    <ul>
      --> bunch of data <--

    </ul>
  </div>
</div>
我想拖动食物项目,然后将项目名称及其 ID 添加到餐食项目中

我已经尝试过这个,但它不起作用,因为我无法克服数据不匹配的问题:

drop(event: CdkDragDrop<Foods[] | Meals[]>) {
  if (event.previousContainer === event.container) {
    moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
  } else {
    if (event.previousContainer.id === 'cdk-drop-list-foods') {
      const foodItem = event.previousContainer.data[event.previousIndex];
      const mealIndex = event.container.data.findIndex(meal => meal.id === event.container.id);
      if (mealIndex !== -1) {
        this.meals[mealIndex].foods.push(foodItem);
      }
    } else {
      const mealIndex = this.meals.findIndex(meal => meal.id === event.previousContainer.id);
      const foodItem = this.meals[mealIndex].foods[event.previousIndex];
      this.meals[mealIndex].foods.splice(event.previousIndex, 1);
      // Add food back to foods array
      this.foods.push(foodItem);
    }
  }
}

export interface Foods {
  name: string,
  calories: number,
  protein: number
  carbs: number,
  potassium: number,
  id: number
}
export interface Meals {
  name: string,
  id: string,
  foods: Foods[]
}

我真的只是想从 Food 中删除名称和 ID。

angular drag-and-drop type-mismatch
1个回答
0
投票

这很难查找,为此我想发布一个答案。这就是我们正在做的事情:

您有一系列食物,并且您有一系列含有食物的餐食。 您想在膳食中添加食物:

export interface Foods {
  name: string,
  calories: number,
  protein: number
  carbs: number,
  potassium: number,
  id: string,
  isActive: boolean
}
export interface Meals {
  name: string,
  id: string,
  calCount: number,
  foods: Foods[]
}

这其中的关键是一开始就将食物集合添加到膳食集合中。 然后,您将在 HTML 组件中设置数组来接受数据。这将让您将一种类型的数据合并到另一种类型中。

private draggedFoodItem: Foods | null = null;

onTaskDragStart(event: any, food: Foods) {
  this.draggedFoodItem = food;
  event.dataTransfer.setData('text/plain', JSON.stringify(food));

}

onTaskDragOver(event: any) {
  event.preventDefault();
}

onTaskDrop(event: any, meal: Meals) {
  event.preventDefault();

  if (this.draggedFoodItem) {
    // Add the dragged food item to the meal's food list
    meal.foods.push(this.draggedFoodItem);

    // Remove the dragged food item from the original foods list
    this.foods = this.foods.filter(food => food.id !== this.draggedFoodItem?.id);

    // Clear the dragged item reference
    this.draggedFoodItem = null;
    const totalCalories = meal.foods.reduce((sum, food) => sum + (food.calories || 0), 0);

    meal.calCount = totalCalories;
  }
}

代码的其余部分(我这里有所有相关组件,除了处理用户的组件之外)在保存数据、离开并返回后将数据重新加载到页面。

这会将数据写入 API,我没有包含该 API,因为这几乎只是关于拖放功能

组件.css:

.container {
  margin-left: auto;
  margin-right: auto;
  max-width: 400px;
  text-align: center;
}
  .container input[type=text] {
    width: 150px;
  }
div.settings {
  display: grid;
  grid-template-columns: max-content max-content;
  grid-gap: 5px;
}
  div.settings label {
    text-align: right;
  }
    div.settings label:after {
      content: ":";
    }
.food-item {
  padding: 5px;
  border: 1px solid #ccc;
  background-color: #f9f9f9;
  text-align: left;
  position: relative;
}
.food-item ul{
    list-style:none;
    padding:0;
}
.food-item ul li{
    padding:0;
    white-space:nowrap;
    font-size:small;
}
.food-item ul li ul li{
    display:inline-block;
    margin-right:10px;
    min-width:70px;
}
  .food-item img {
    width: 25px;
    height: 25px;
    position:absolute;
    top:1px;
    right:1px;
    z-index:5;
  }
  .food-item div{
      padding: 10px;
  }
.meal-item {
  padding: 5px;
  border: 1px solid #ccc;
  background-color: #f9f9f9;
  text-align: left;
  position: relative;
  width: 90%;
  min-height:30px;
}
  .meal-item div {
    border: 1px green solid;
    border-radius: 15px;
    margin-left: 5px;
    margin-top: 2px;
    padding: 5px;
  }
.makeMeal{
}
.makeMeal img{
    width:25px;
    height:25px;
}
.meal-drop{
    border:1px solid red;
    position: relative;
    min-height:25px;
}
.meal-drop img{
    height:15px;
    width:15px;
    position:absolute;
    top: 2px;
    right: 2px;
}
.meal-drop div{
    clear:both;
    width:fit-content;
    height:auto;
}
.meal-drop button{
    position:absolute;
    bottom: 2px;
    right: 6px;
    width: fit-content;
}
.hidden{
    display:none;
}
.target{
    border: 1px blue solid;
    min-width:90%;
    min-height:25px;
}
.plainList{
    list-style:none;
}
  .plainList ul {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 5px;
    padding:10px;
  }
.plainList li{
    padding:10px;
    position:relative;
    display:inline-block;
    margin-left:5px;
    margin-bottom:5px;
    border-radius:15px;
}
  .plainList li img{
      height:20px;
      width:20px;
      position:absolute;
      top:1px;
      right:1px;
      cursor:pointer;
  }
.hideFood{
    display:none;
}
.showFood{
    display:block;
    border: 1px solid purple;
}

组件.html:

<div class="container">
  <h2>Build A Meal:</h2>
  <p>My meals:</p>

  <div cdkDropListGroup>
    <div class="makeMeal">

      <ul class="plainList">
        @for(meal of meals; track meal.id; let i = $index){
        <li class="meal-item">
          {{meal.name}}<img src="page_delete.png" (click)="deleteMeal(meal.id, i)" />
          <div class="meal-drop" [attr.draggable]="true"
               (dragover)="onTaskDragOver($event)"
               (drop)="onTaskDrop($event, meal)">

            @for(droppedFood of meal.foods; track droppedFood.id; let j = $index){
            <div>{{ droppedFood.name }}<img src="page_delete.png" (click)="deleteMealFood(meal.id, i, j)" /></div>
            }
            <button (click)="mealSave(meal)">Save</button>
          </div>
          Total calores: {{meal.calCount}}
        </li>
        }
        @empty{
        <li>No meals found</li>
        }
      </ul>

      Add a meal:
      <input id="mealName" type="text" [(ngModel)]="mealName" name="mealName" required style="margin-left:10px;" />
      <img src="page_white_add.png" (click)="addMeal()" />
    </div>
    
  </div>

  <p>My foods:</p>
  <div class="food-grid">
    <ul class="plainList">
      @for(food of foods; track food.id; let i = $index){
      <li class="food-item" [attr.draggable]="true"
          (dragstart)="onTaskDragStart($event, food)">
        <div (click)="toggleActive(food)">
          <img src="page_delete.png" (click)="deleteFood(food.id, i)"  />
          {{food.name}}
        <div [ngClass]="food.isActive? 'showFood' : 'hideFood'">
          calories: {{food.calories}}<br />
          protein: {{food.protein}}<br />
          carbs: {{food.carbs}}<br />
          potassium: {{food.potassium}}
        </div>
        </div>
        
      </li>
      }
      @empty{
      <li>No food items found</li>
      }
      
    </ul>
  </div>
</div>

组件.ts:

import { Component, OnInit, NgModule, ChangeDetectorRef } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
import { SaveMealService, Foods, Meals } from '../services/save-meal.service';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-build-meal',
  standalone: true,
  imports: [FormsModule, CommonModule],
  templateUrl: './build-meal.component.html',
  styleUrl: './build-meal.component.css'
})

export class BuildMealComponent {
  private isUserLoggedIn: boolean = false;
  private userId: string = '';
  private bmr: number = 0;
  public message: string = '';
  public calCount: number = 0;
  public foods: Foods[] = [];
  public meals: Meals[] = [];
  public mealName: string = '';
  private draggedFoodItem: Foods | null = null;
  public isActive = false;
  constructor(
    private authService: AuthService,
    private foodService: SaveMealService,
    private router: Router,
    private cdRef: ChangeDetectorRef
  ) { }

  ngAfterViewChecked() {
    this.cdRef.detectChanges();
  }
  ngOnInit() {

    if (this.authService.isLoggedIn()) {
      this.isUserLoggedIn = true;
      var logUser = this.authService.getUserInfo();
      this.userId = this.authService.userID;
      this.bmr = this.authService.bmr;
      this.getFood();
      this.getMeals();      
    }
  }

  addMeal() {
    if (this.mealName.trim()) {
      this.foodService.createMeal(this.userId, this.mealName).subscribe({
        next: (response: number) => {
          this.message = 'Meal created successfully';
          this.getMeals(); // Refresh meal list after adding
          this.mealName = ''; // Clear the input field
        },
        error: (err: number) => {
          this.message = 'Error creating meal: ' + err;
        }
      });
    }
  }

  deleteFood(id: string, index: number) {
    this.foodService.removeFood(this.userId, id).subscribe({
      next: (response: any) => {
        console.log(response);
        this.foods.splice(index, 1);
      }
  });
}

  deleteMeal(id: string, meal_index: number) {
    this.foodService.removeMeal(this.userId, id);
    this.meals.splice(meal_index, 1);
  }

  deleteMealFood(meal_id: string, meal_index: number, food_index: number) {
    const id = parseInt(meal_id);
    const foodId = this.meals[id].foods[food_index];
    this.foodService.RemoveMealFood(meal_id, foodId.toString());
  }
  
  getFood() {
    this.foodService.getAllFood(this.userId).subscribe({
      next: (response: Foods[]) => {

        this.foods = response;
      },
      error: (err: any) => {
        this.message = 'Error retrieving food: ' + err;
      }
    });
  }

  getMeals() {
    this.foodService.getAllMeals(this.userId).subscribe({
      next: (response: Meals[]) => {
        // Initialize foods array if not already present
        this.meals = response.map(meal => ({
          ...meal,
          foods: meal.foods || [] // Initialize as an empty array if undefined
        }));
      },
      error: (err: any) => {
        this.message = 'Error retrieving meals: ' + err;
      }
    });
  }

  mealSave(meal: Meals) {
    this.foodService.saveMeal(meal, meal.id).subscribe(results => {
      console.log('All requests completed:', results);
    });
  }

  onTaskDragStart(event: any, food: Foods) {
    this.draggedFoodItem = food;
    event.dataTransfer.setData('text/plain', JSON.stringify(food));

  }

  onTaskDragOver(event: any) {
    event.preventDefault();
  }

  onTaskDrop(event: any, meal: Meals) {
    event.preventDefault();

    if (this.draggedFoodItem) {
      // Add the dragged food item to the meal's food list
      meal.foods.push(this.draggedFoodItem);

      // Remove the dragged food item from the original foods list
      this.foods = this.foods.filter(food => food.id !== this.draggedFoodItem?.id);

      // Clear the dragged item reference
      this.draggedFoodItem = null;
      const totalCalories = meal.foods.reduce((sum, food) => sum + (food.calories || 0), 0);

      meal.calCount = totalCalories;
    }
  }

  toggleActive(food: any) {
    food.isActive = !food.isActive;
  }

  trackById(index: number, item: any): number {
    return item.id;
  }
}

节省餐食.service.ts:

import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { forkJoin, Observable, of, throwError } from 'rxjs';
import { catchError, map, switchMap } from 'rxjs/operators';
import { jwtDecode } from "jwt-decode";
import { tap } from 'rxjs/operators';
export interface Foods {
  name: string,
  calories: number,
  protein: number
  carbs: number,
  potassium: number,
  id: string,
  isActive: boolean
}
export interface Meals {
  name: string,
  id: string,
  calCount: number,
  foods: Foods[]
}

@Injectable({
  providedIn: 'root'
})

export class SaveMealService {
  private apiUrl = 'https://127.0.0.1:7010/api/Food';
  private apiMealUrl = 'https://localhost:7010/api/Meal';
  private apiTemplateUrl = 'https://127.0.0.1:7010/api/MealTemplate'
  constructor(
    private http: HttpClient
  ) { }

  createMeal(
    userId: string,
    mealName: string

  ): Observable<number> {
    const meal = { userId, mealName };
    
    return this.http.post<number>(`${this.apiMealUrl}/CreateMeal`, meal).pipe(
      tap(response => {
      })
    );
    
  }

  removeFood(userid: string, id: string
  ): Observable<any> {
    const food = { id };
    return this.http.post<any>(`${this.apiUrl}/removeFood?id=${id}`,  {
      headers: { 'Content-Type': 'application/json' },
      observe: 'response'  // Observe the full HTTP response
    }).pipe(
      map(response => {
        if (response.status === 200) {  // Check for 'OK' status
          // Perform navigation to login page here (optional)
          console.log(response);
          return response.body as number;  // Return true if the response status is 200 OK
        }
        return -2;
      }),
      catchError(() => of(-3))  // Handle error and return false
    );
  }

  removeMeal(userId: string, id: string) {
    const item = { userId, id };
    return this.http.post<any>(`${this.apiMealUrl}/removeMeal`, item, {
      headers: { 'Content-Type': 'application/json' },
      observe: 'response'  // Observe the full HTTP response
    }).pipe(
      tap(response => {
      })
    );
  }

  saveMeal(meal: Meals, mealId: string) {
  const foods: Foods[] = meal.foods;

  const requests = foods.map(item => {
    const foodId = item.id.toString(); // Ensure foodId is a string
    
    return this.http.post<any>(`${this.apiTemplateUrl}/SaveMealAsync?foodId=${foodId}&mealId=${mealId}`, { // Wrap menuItem in an "item" property
      headers: { 'Content-Type': 'application/json' },
      observe: 'response'
    }).pipe(
      map(response => response.status === 200),
      catchError(err => {
        this.errorHandler(err);
        return [false];  // Return false if an error occurs
      })
    );
  });

  return forkJoin(requests); // Return an observable that completes when all requests are done
}

  errorHandler(error: HttpErrorResponse) {
    console.log(error.message);
    return throwError(error.message || "server error.");
  }

  getAllFood(userID: string): Observable<Foods[]> {

    return this.http.get<Foods[]>(`${this.apiUrl}/getFoods?id=${userID}`).pipe(
      tap(response => {
      })
    );
  }

  getAllMeals(userId: string): Observable<Meals[]> {
    return this.http.get<Meals[]>(`${this.apiMealUrl}/getMeals?userID=${userId}`).pipe(
      switchMap((meals: Meals[]) => {
        // For each meal, get the associated foods
        const mealsWithFoods$ = meals.map(meal =>
          this.http.get<Foods[]>(`https://127.0.0.1:7010/api/MealTemplate/getFoodsByMeal?mealId=${meal.id}`)
            .pipe(
              map(foods => {
                // Calculate the total calorie count for the meal
                const totalCalories = foods.reduce((acc, food) => acc + food.calories, 0);
                return { ...meal, foods, calCount: totalCalories }; // Include calCount in the meal object
              })
            )
        );

        // Combine all observables into one
        return forkJoin(mealsWithFoods$);
      }),
      tap(response => {
        console.log('Meals with foods and calorie counts:', response);
      })
    );
  }

  RemoveMealFood(
    meal_id: string,
    foodId: string): Observable<any> {
    return this.http.post<any>(`${this.apiTemplateUrl}/RemoveFood?mealId=${meal_id}&foodId=${foodId}`, {
      headers: { 'Content-Type': 'application/json' },
      observe: 'response'  // Observe the full HTTP response
    }).pipe(
      map(response => {
        if (response.status === 200) {  // Check for 'OK' status
          // Perform navigation to login page here (optional)
          console.log(response);
          return response;
        }
        return false;
      }),
      catchError(() => of(false))
    );
  }

  saveFood(
    userid: string,
    foodName: string,
    calories: number,
    carbohydrates: number,
    protein: number,
    potassium: number,
    servingSize: number,
    portion: number
  ): Observable<number> {
    const food = { userid, foodName, calories, protein, carbohydrates, potassium, servingSize, portion };
    return this.http.post<any>(`${this.apiUrl}/SaveFood`, food, {
      headers: { 'Content-Type': 'application/json' },
      observe: 'response'  // Observe the full HTTP response
    }).pipe(
      map(response => {
        if (response.status === 200) {  // Check for 'OK' status
          // Perform navigation to login page here (optional)
          console.log(response);
          return response.body as number;  // Return true if the response status is 200 OK
        }
        return -2;
      }),
      catchError(() => of(-3))  // Handle error and return false
    );
  }
 
}
最新问题
© www.soinside.com 2019 - 2024. All rights reserved.