在此组件.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。
这很难查找,为此我想发布一个答案。这就是我们正在做的事情:
您有一系列食物,并且您有一系列含有食物的餐食。 您想在膳食中添加食物:
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
);
}
}