57 changed files with 2591 additions and 410 deletions
@ -1,8 +1,131 @@ |
|||||
import { Component } from "@angular/core"; |
import { Component } from "@angular/core"; |
||||
|
import { ActivatedRoute, Router } from "@angular/router"; |
||||
|
import { ApiService } from "@cdk/services"; |
||||
|
import { NzMessageService } from "ng-zorro-antd/message"; |
||||
|
import { DishInterface } from "../ingredient-dish/ingredient-dish.component"; |
||||
|
import { forkJoin, map } from "rxjs"; |
||||
|
|
||||
@Component({ |
@Component({ |
||||
selector: "app-ingredient-preview", |
selector: "app-ingredient-preview", |
||||
templateUrl: "./ingredient-preview.component.html", |
templateUrl: "./ingredient-preview.component.html", |
||||
styleUrls: ["./ingredient-preview.component.less"], |
styleUrls: ["./ingredient-preview.component.less"], |
||||
}) |
}) |
||||
export class IngredientPreviewComponent {} |
export class IngredientPreviewComponent { |
||||
|
constructor( |
||||
|
private route: ActivatedRoute, |
||||
|
private router: Router, |
||||
|
private api: ApiService, |
||||
|
private msg: NzMessageService |
||||
|
) {} |
||||
|
|
||||
|
basic: any | null; |
||||
|
|
||||
|
dishs: DishInterface[] = []; |
||||
|
|
||||
|
totalObj: Record<string, string> | null = null; |
||||
|
|
||||
|
monthText = { |
||||
|
1: "一月", |
||||
|
2: "二月", |
||||
|
3: "三月", |
||||
|
4: "四月", |
||||
|
5: "五月", |
||||
|
6: "六月", |
||||
|
7: "七月", |
||||
|
8: "八月", |
||||
|
9: "九月", |
||||
|
10: "十月", |
||||
|
11: "十一月", |
||||
|
12: "十二月", |
||||
|
} as any; |
||||
|
|
||||
|
days: number[] = []; |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
const id = this.route.snapshot.queryParamMap.get("id"); |
||||
|
const snapshot = this.route.snapshot.queryParamMap.get("snapshot"); |
||||
|
const storage = snapshot ? sessionStorage.getItem(snapshot) : null; |
||||
|
|
||||
|
if (id) { |
||||
|
forkJoin([this.api.getMenuDist(id), this.api.getMenuItem(id)]) |
||||
|
.pipe( |
||||
|
map(([d, b]) => { |
||||
|
return [d.body, b.body]; |
||||
|
}) |
||||
|
) |
||||
|
.subscribe(([dishs, basic]) => { |
||||
|
this.days = Array.from({ length: basic.day }, (_, i) => i + 1); |
||||
|
this.getStandardName(basic.nutrient); |
||||
|
this.basic = basic; |
||||
|
|
||||
|
this.initMenuDish(dishs); |
||||
|
}); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
if (storage) { |
||||
|
try { |
||||
|
const { basic, dishs } = JSON.parse(storage); |
||||
|
this.days = Array.from({ length: basic.day }, (_, i) => i + 1); |
||||
|
this.getStandardName(basic.nutrient); |
||||
|
this.basic = basic; |
||||
|
this.dishs = dishs; |
||||
|
} catch (error) { |
||||
|
this.msg.error("解析食谱数据出错了,请重试!"); |
||||
|
this.router.navigate(["/ingredient/item/list"]); |
||||
|
} |
||||
|
return; |
||||
|
} |
||||
|
this.msg.error("没有找到食谱数据"); |
||||
|
this.router.navigate(["/ingredient/item/list"]); |
||||
|
} |
||||
|
|
||||
|
initMenuDish(menuDishFormServer: any[]) { |
||||
|
const foodIds = new Set<number>(); |
||||
|
menuDishFormServer.forEach((i: any) => { |
||||
|
i.ingredient.map((food: any) => { |
||||
|
// 收集 食材 key 获取 食材名称 & 把 value 对象转换成 groupValues 数组
|
||||
|
foodIds.add(food.key); |
||||
|
food["groupValues"] = Object.entries(food.value).map(([peopleName, value]) => { |
||||
|
return { |
||||
|
peopleName, |
||||
|
value, |
||||
|
}; |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
forkJoin([this.api.getFoodList({ keys: Array.from(foodIds) })]).subscribe(([res]) => { |
||||
|
this.dishs = menuDishFormServer.map((i: any) => { |
||||
|
return { |
||||
|
...i, |
||||
|
dishName: i.name, |
||||
|
mealIndex: this.basic.meals.findIndex((m: string) => m === i.meal), |
||||
|
|
||||
|
items: i.ingredient.map((food: any) => { |
||||
|
const fd = res.body.find((f) => f.key === food.key); |
||||
|
food.foodName = fd.name; |
||||
|
return food; |
||||
|
}), |
||||
|
}; |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
getStandardName(id: string) { |
||||
|
this.api.getStandard({ id }).subscribe((res) => { |
||||
|
this.basic["standardName"] = res.body.name; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
calcTotal(day: number, meal: string, people: string) { |
||||
|
let total = 0; |
||||
|
this.dishs.forEach((d) => { |
||||
|
if (d.day === day && d.meal === meal) { |
||||
|
d.items.forEach((food) => { |
||||
|
total += food.value[people]; |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
return total.toFixed(2); |
||||
|
} |
||||
|
} |
||||
|
|||||
@ -0,0 +1,3 @@ |
|||||
|
<iframe #iframe |
||||
|
style="display: none;"> |
||||
|
</iframe> |
||||
@ -0,0 +1,92 @@ |
|||||
|
import { DomPortalOutlet, PortalOutlet, TemplatePortal } from "@angular/cdk/portal"; |
||||
|
import { |
||||
|
ApplicationRef, |
||||
|
Component, |
||||
|
ComponentFactoryResolver, |
||||
|
ElementRef, |
||||
|
Injector, |
||||
|
Input, |
||||
|
OnInit, |
||||
|
TemplateRef, |
||||
|
ViewChild, |
||||
|
ViewContainerRef, |
||||
|
} from "@angular/core"; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: "app-print", |
||||
|
templateUrl: "./print.component.html", |
||||
|
styleUrls: ["./print.component.less"], |
||||
|
}) |
||||
|
export class PrintComponent implements OnInit { |
||||
|
@Input() content!: TemplateRef<any>; |
||||
|
@ViewChild("iframe") iframe!: ElementRef<HTMLIFrameElement>; |
||||
|
private portalHost?: PortalOutlet; |
||||
|
public printed: boolean = false; |
||||
|
|
||||
|
constructor( |
||||
|
private componentFactoryResolver: ComponentFactoryResolver, |
||||
|
private injector: Injector, |
||||
|
private appRef: ApplicationRef, |
||||
|
private viewContainerRef: ViewContainerRef |
||||
|
) {} |
||||
|
|
||||
|
ngOnInit(): void {} |
||||
|
|
||||
|
testCall() { |
||||
|
alert("testCall"); |
||||
|
} |
||||
|
|
||||
|
private _attachStyles(targetWindow: Window): void { |
||||
|
// Copy styles from parent window
|
||||
|
document.querySelectorAll("style").forEach((htmlElement) => { |
||||
|
targetWindow.document.head.appendChild(htmlElement.cloneNode(true)); |
||||
|
}); |
||||
|
// Copy stylesheet link from parent window
|
||||
|
const styleSheetElement = this._getStyleSheetElement(); |
||||
|
targetWindow.document.head.appendChild(styleSheetElement); |
||||
|
} |
||||
|
|
||||
|
private _getStyleSheetElement() { |
||||
|
const styleSheetElement = document.createElement("link"); |
||||
|
document.querySelectorAll("link").forEach((htmlElement) => { |
||||
|
if (htmlElement.rel === "stylesheet") { |
||||
|
const absoluteUrl = new URL(htmlElement.href).href; |
||||
|
styleSheetElement.rel = "stylesheet"; |
||||
|
styleSheetElement.type = "text/css"; |
||||
|
styleSheetElement.href = absoluteUrl; |
||||
|
} |
||||
|
}); |
||||
|
return styleSheetElement; |
||||
|
} |
||||
|
|
||||
|
print(): void { |
||||
|
const iframe = this.iframe.nativeElement; |
||||
|
const { contentDocument, contentWindow } = iframe; |
||||
|
if (contentDocument && contentWindow) { |
||||
|
this.portalHost = new DomPortalOutlet( |
||||
|
contentDocument.body, |
||||
|
this.componentFactoryResolver, |
||||
|
this.appRef, |
||||
|
this.injector |
||||
|
); |
||||
|
|
||||
|
const portal = new TemplatePortal(this.content, this.viewContainerRef); |
||||
|
|
||||
|
if (!this.printed) { |
||||
|
this._attachStyles(contentWindow); |
||||
|
} |
||||
|
|
||||
|
this.portalHost.attach(portal); |
||||
|
|
||||
|
window.onafterprint = () => { |
||||
|
// Chrome 不会触发
|
||||
|
contentDocument.body.innerHTML = ""; |
||||
|
}; |
||||
|
|
||||
|
setTimeout(() => { |
||||
|
contentWindow.print(); |
||||
|
contentDocument.body.innerHTML = ""; |
||||
|
}, 500); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,136 @@ |
|||||
|
<form nz-form [formGroup]="formGroup" nzLayout="vertical"> |
||||
|
|
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzRequired> |
||||
|
菜品名称 |
||||
|
</nz-form-label> |
||||
|
<nz-form-control [nzErrorTip]="formControlErrorTpl"> |
||||
|
<input placeholder="请输入菜品名称" nz-input formControlName="name" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzRequired> |
||||
|
菜品标签 |
||||
|
</nz-form-label> |
||||
|
<nz-form-control [nzErrorTip]="formControlErrorTpl"> |
||||
|
<nz-select |
||||
|
formControlName="mark" |
||||
|
nzPlaceHolder="请选择菜品标签"> |
||||
|
<nz-option *ngFor="let item of globalEnum.mark" [nzValue]="item.value" [nzLabel]="item.key"></nz-option> |
||||
|
</nz-select> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzRequired> |
||||
|
适用月份 |
||||
|
</nz-form-label> |
||||
|
<nz-form-control [nzErrorTip]="formControlErrorTpl"> |
||||
|
<app-month-select formControlName="month"></app-month-select> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
|
||||
|
<nz-form-item> |
||||
|
<nz-form-label> |
||||
|
菜品图片 |
||||
|
</nz-form-label> |
||||
|
<nz-form-control [nzErrorTip]="formControlErrorTpl"> |
||||
|
<input type="hidden" formControlName="icon" /> |
||||
|
<div class="mb-2" *ngIf="icon"> |
||||
|
<img [src]="icon" class="h-20 w-20" /> |
||||
|
</div> |
||||
|
<button class="upload-btn " nz-button [nzLoading]="uploadLoading"> |
||||
|
<i nz-icon nzType="upload"></i> |
||||
|
上传图片 |
||||
|
<input type="file" (change)="onFileChange($event)" /> |
||||
|
</button> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
|
||||
|
<!-- <nz-form-item> |
||||
|
<nz-form-label nzRequired> |
||||
|
食材名称 |
||||
|
</nz-form-label> |
||||
|
<nz-form-control [nzErrorTip]="formControlErrorTpl"> |
||||
|
<input placeholder="请输入食材名称/编号检索" nz-input formControlName="food" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> --> |
||||
|
|
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzRequired class="block-label"> |
||||
|
<div class="flex justify-between items-center flex-1"> |
||||
|
<span class="flex-1"> |
||||
|
食材名称 |
||||
|
</span> |
||||
|
<!-- <a nz-button nzType="link" (click)="addFoodVisible = true"> |
||||
|
<span nz-icon nzType="plus"></span> |
||||
|
<span> |
||||
|
添加食材 |
||||
|
</span> |
||||
|
</a> --> |
||||
|
</div> |
||||
|
</nz-form-label> |
||||
|
<nz-form-control [nzErrorTip]="formControlErrorTpl"> |
||||
|
|
||||
|
<nz-select |
||||
|
[nzMode]="'multiple'" |
||||
|
nzShowSearch |
||||
|
nzServerSearch |
||||
|
nzPlaceHolder="请输入食材名称检索" |
||||
|
[nzShowArrow]="false" |
||||
|
[nzFilterOption]="nzFilterOption" |
||||
|
[(ngModel)]="foodSelected" |
||||
|
(ngModelChange)="onFoodSelected($event)" |
||||
|
(nzOnSearch)="searchFood($event)" |
||||
|
[ngModelOptions]="{standalone: true}"> |
||||
|
<nz-option *ngFor="let o of foodListOfOption" [nzLabel]="o.text" [nzValue]="o.value"></nz-option> |
||||
|
</nz-select> |
||||
|
<!-- <div *ngIf="addFoodVisible"> |
||||
|
<nz-select nzSize="large" nzShowSearch nzPlaceHolder="请输入食材名称/编号检索"> |
||||
|
|
||||
|
</nz-select> |
||||
|
<div class="flex justify-end my-2"> |
||||
|
<nz-space> |
||||
|
<button *nzSpaceItem nz-button nzType="text" (click)="addFoodVisible = false">取消</button> |
||||
|
<button *nzSpaceItem nz-button nzType="primary" (click)="addFood()">确定</button> |
||||
|
</nz-space> |
||||
|
</div> |
||||
|
</div> --> |
||||
|
<ul class="mt-4"> |
||||
|
<li class="mb-2" *ngFor="let f of foodItemSelected;let i = index"> |
||||
|
<div class="flex items-center"> |
||||
|
<!-- <div class="w-1/2 pr-2"> |
||||
|
<button nz-button nzBlock> |
||||
|
{{f.value}}-{{f.text}}: |
||||
|
</button> |
||||
|
</div> --> |
||||
|
|
||||
|
<div class="flex-1 pr-2"> |
||||
|
<nz-input-group nzAddOnBefore="{{f.value}}-{{f.text}}:" [nzAddOnAfter]="'g'" class="w-full"> |
||||
|
<input |
||||
|
nz-input |
||||
|
type="number" |
||||
|
[(ngModel)]="f.num" |
||||
|
[ngModelOptions]="{standalone: true}" |
||||
|
placeholder="请输入{{f.value}}-{{f.text}}重量" /> |
||||
|
</nz-input-group> |
||||
|
</div> |
||||
|
<div class="pl-2"> |
||||
|
<nz-switch [ngModel]="f.isMain" (ngModelChange)="onMainChange($event,f.value)" |
||||
|
[ngModelOptions]="{standalone: true}"> |
||||
|
</nz-switch> |
||||
|
是否主料 |
||||
|
</div> |
||||
|
<!-- <div> |
||||
|
<button nz-button (click)="removeFood(i)"> |
||||
|
<span nz-icon nzType="delete"></span> |
||||
|
</button> |
||||
|
</div> --> |
||||
|
</div> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</form> |
||||
|
<ng-template #formControlErrorTpl let-control> |
||||
|
<form-error-tips [control]="control"></form-error-tips> |
||||
|
</ng-template> |
||||
@ -0,0 +1,17 @@ |
|||||
|
.month-wrap { |
||||
|
::ng-deep { |
||||
|
.ant-checkbox-wrapper { |
||||
|
margin: 6px 0; |
||||
|
flex-basis: calc(100% / 6); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.block-label { |
||||
|
::ng-deep { |
||||
|
label { |
||||
|
display: inline-flex; |
||||
|
width: 100%; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,230 @@ |
|||||
|
import { Component, Input, OnInit } from "@angular/core"; |
||||
|
import { FormArray, FormBuilder, FormGroup } from "@angular/forms"; |
||||
|
import { ApiService } from "@cdk/services"; |
||||
|
import { Utils } from "@cdk/utils"; |
||||
|
import { FormValidators } from "@cdk/validators"; |
||||
|
import { NzMessageService } from "ng-zorro-antd/message"; |
||||
|
import { Subject, debounceTime, distinctUntilChanged, filter, finalize, switchMap, throttleTime } from "rxjs"; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: "app-dish-form", |
||||
|
templateUrl: "./dish-form.component.html", |
||||
|
styleUrls: ["./dish-form.component.less"], |
||||
|
}) |
||||
|
export class DishFormComponent { |
||||
|
constructor(private fb: FormBuilder, private msg: NzMessageService, private api: ApiService) {} |
||||
|
|
||||
|
@Input() data: any; |
||||
|
|
||||
|
@Input() orgs: any[] = []; |
||||
|
|
||||
|
@Input() foods: any[] = []; |
||||
|
|
||||
|
private orgSearch$ = new Subject<Record<string, string>>(); |
||||
|
|
||||
|
private foodSearch$ = new Subject<Record<string, string>>(); |
||||
|
|
||||
|
formGroup!: FormGroup; |
||||
|
|
||||
|
selectedValue = null; |
||||
|
|
||||
|
orgListOfOption: Array<{ value: string; text: string }> = []; |
||||
|
|
||||
|
foodListOfOption: Array<{ value: string; text: string }> = []; |
||||
|
|
||||
|
searchedFood: Array<{ value: string; text: string }> = []; |
||||
|
|
||||
|
foodSelected: string[] = []; |
||||
|
|
||||
|
foodItemSelected: any[] = []; |
||||
|
|
||||
|
nzFilterOption = (): boolean => true; |
||||
|
|
||||
|
globalEnum = this.api.globalEnum; |
||||
|
|
||||
|
uploadLoading = false; |
||||
|
|
||||
|
addFoodVisible = false; |
||||
|
|
||||
|
get food(): FormArray { |
||||
|
return this.formGroup.get("ingredient") as FormArray; |
||||
|
} |
||||
|
|
||||
|
get icon() { |
||||
|
return this.formGroup.get("icon")?.value; |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.formGroup = this.fb.group({ |
||||
|
id: this.fb.control("", []), |
||||
|
name: this.fb.control("", [FormValidators.required()]), |
||||
|
icon: this.fb.control("", []), |
||||
|
mark: this.fb.control("", [FormValidators.required()]), |
||||
|
month: this.fb.control([], []), |
||||
|
}); |
||||
|
|
||||
|
this.orgSearch$ |
||||
|
.pipe( |
||||
|
debounceTime(500), |
||||
|
distinctUntilChanged(), |
||||
|
switchMap((q) => this.api.getOrgList(q)) |
||||
|
) |
||||
|
.subscribe((data) => { |
||||
|
const listOfOption: Array<{ value: string; text: string }> = []; |
||||
|
data.body.forEach((item) => { |
||||
|
listOfOption.push({ |
||||
|
value: item.id.toString(), |
||||
|
text: item.name, |
||||
|
}); |
||||
|
}); |
||||
|
this.orgListOfOption = listOfOption; |
||||
|
}); |
||||
|
this.foodSearch$ |
||||
|
.pipe( |
||||
|
filter((f) => !!f), |
||||
|
debounceTime(500), |
||||
|
distinctUntilChanged(), |
||||
|
switchMap((q) => this.api.getFoodList(q)) |
||||
|
) |
||||
|
.subscribe((data) => { |
||||
|
const listOfOption: Array<{ value: string; text: string }> = []; |
||||
|
data.body.forEach((item) => { |
||||
|
listOfOption.push({ |
||||
|
value: item.key, |
||||
|
text: item.name, |
||||
|
}); |
||||
|
}); |
||||
|
this.searchedFood = this.searchedFood.concat(listOfOption); |
||||
|
this.foodListOfOption = listOfOption; |
||||
|
}); |
||||
|
this.setValues(); |
||||
|
} |
||||
|
|
||||
|
setValues() { |
||||
|
this.orgListOfOption = this.orgs.map((i) => ({ text: i.name, value: i.id })); |
||||
|
|
||||
|
if (this.data) { |
||||
|
// this.allMonth = this.allMonth.map((i) =>
|
||||
|
// (this.data.month ?? []).includes(i.value) ? { ...i, checked: true } : i
|
||||
|
// );
|
||||
|
this.foods.forEach((f) => { |
||||
|
const item = { text: f.name, value: f.key }; |
||||
|
this.foodListOfOption.push(item); |
||||
|
this.searchedFood.push(item); |
||||
|
const num = this.data.ingredient.find((i: any) => i.key === f.key); |
||||
|
if (num) { |
||||
|
this.foodItemSelected.push({ num: num.value, ...item, isMain: num.isMain }); |
||||
|
this.foodSelected.push(f.key); |
||||
|
} |
||||
|
}); |
||||
|
this.formGroup.patchValue({ |
||||
|
...this.data, |
||||
|
mark: this.data.marks, |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public getValues() { |
||||
|
let values = null; |
||||
|
console.log("this.formGroup.getRawValue()", this.formGroup.getRawValue(), this.foodItemSelected); |
||||
|
if (Utils.validateFormGroup(this.formGroup)) { |
||||
|
const value = this.formGroup.getRawValue(); |
||||
|
// const { _nutrition, key, name, type } = this.formGroup.getRawValue();
|
||||
|
let ingredient: any[] = []; |
||||
|
for (const f of this.foodItemSelected) { |
||||
|
let num = Number(f.num); |
||||
|
if (!num) { |
||||
|
this.msg.error(`请输入${f.value}-${f.text}的重量`); |
||||
|
return; |
||||
|
} |
||||
|
ingredient.push({ |
||||
|
isMain: f.isMain, |
||||
|
key: f.value, |
||||
|
value: num, |
||||
|
}); |
||||
|
} |
||||
|
const month = value.month.join(","); |
||||
|
|
||||
|
values = { |
||||
|
...value, |
||||
|
|
||||
|
month, |
||||
|
ingredient, |
||||
|
}; |
||||
|
} |
||||
|
return values; |
||||
|
} |
||||
|
|
||||
|
onMainChange(e: boolean, key: string) { |
||||
|
this.foodItemSelected.forEach((i) => { |
||||
|
if (e) { |
||||
|
i.isMain = false; |
||||
|
if (i.value === key) { |
||||
|
i.isMain = true; |
||||
|
} |
||||
|
} else { |
||||
|
if (i.value === key) { |
||||
|
i.isMain = false; |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
searchOrg(value: string): void { |
||||
|
if (value) { |
||||
|
this.orgSearch$.next({ keyword: value }); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
searchFood(value: string): void { |
||||
|
if (value) { |
||||
|
this.foodSearch$.next({ keyword: value }); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onFoodSelected(v: string[]) { |
||||
|
this.foodItemSelected = []; |
||||
|
this.searchedFood.forEach((item) => { |
||||
|
if (this.foodItemSelected.some((s) => s.value === item.value)) { |
||||
|
return; |
||||
|
} |
||||
|
if (v.includes(item.value)) { |
||||
|
this.foodItemSelected.push(item); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// this.foodItemSelected = this.searchedFood.filter((f) => {
|
||||
|
// return v.includes(f.value)
|
||||
|
// });
|
||||
|
} |
||||
|
|
||||
|
addFood() { |
||||
|
this.food.push( |
||||
|
this.fb.group({ |
||||
|
name: this.fb.control("", [FormValidators.required()]), |
||||
|
tag: this.fb.control(0, [FormValidators.required()]), |
||||
|
weight: this.fb.control(0, [FormValidators.required()]), |
||||
|
}) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
removeFood(idx: number) { |
||||
|
this.food.removeAt(idx); |
||||
|
} |
||||
|
|
||||
|
onFileChange(e: Event) { |
||||
|
const target = e.target as HTMLInputElement; |
||||
|
const file = target.files![0]; |
||||
|
target.value = ""; |
||||
|
if (file.size / 1024 / 1024 >= 5) { |
||||
|
this.msg.error("图片大小不能超过5M"); |
||||
|
return; |
||||
|
} |
||||
|
const fileReader = new FileReader(); |
||||
|
fileReader.onload = () => { |
||||
|
const base64 = fileReader.result as string; |
||||
|
this.formGroup.get("icon")?.setValue(base64); |
||||
|
}; |
||||
|
fileReader.readAsDataURL(file); |
||||
|
} |
||||
|
} |
||||
@ -1 +1,40 @@ |
|||||
<p>data-vis works!</p> |
<div class="body"> |
||||
|
<div class="head clearfix"> |
||||
|
<div class="logo"> |
||||
|
<img src="/assets/images/jl-logo.png" /> |
||||
|
</div> |
||||
|
<h1 class="title">成都市实验小学西区分校食谱营养报告</h1> |
||||
|
<div class="time">{{showTime}}</div> |
||||
|
</div> |
||||
|
<div class="mainbox flex"> |
||||
|
|
||||
|
<!-- <div class="boxnav mapc"> |
||||
|
|
||||
|
</div> --> |
||||
|
<div class="flex-1 "> |
||||
|
<div class="box"> |
||||
|
<div class="tit">今日带量食谱</div> |
||||
|
<div class="boxnav"> |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="box"> |
||||
|
<div class="tit">今日食材种类</div> |
||||
|
<div class="boxnav"> |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="w-1/3 pl-4"> |
||||
|
<div class="box"> |
||||
|
<div class="tit">今日营养分析</div> |
||||
|
<div class="boxnav"> |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,240 @@ |
|||||
|
@charset "utf-8"; |
||||
|
|
||||
|
/* CSS Document */ |
||||
|
* { |
||||
|
-webkit-box-sizing: border-box; |
||||
|
-moz-box-sizing: border-box; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.body { |
||||
|
width: 100vw; |
||||
|
height: 100vh; |
||||
|
position: relative; |
||||
|
color: #fff; |
||||
|
font-size: 16px; |
||||
|
background-color: #012059; |
||||
|
/* background: radial-gradient(50% 35%, #034f8e, #034987, #02366d, #002353); */ |
||||
|
} |
||||
|
|
||||
|
li { |
||||
|
list-style-type: none; |
||||
|
} |
||||
|
|
||||
|
i { |
||||
|
margin: 0px; |
||||
|
padding: 0px; |
||||
|
text-indent: 0px; |
||||
|
} |
||||
|
|
||||
|
img { |
||||
|
border: none; |
||||
|
max-width: 100%; |
||||
|
} |
||||
|
|
||||
|
a { |
||||
|
text-decoration: none; |
||||
|
color: #fff; |
||||
|
} |
||||
|
|
||||
|
a.active, |
||||
|
a:focus { |
||||
|
outline: none !important; |
||||
|
text-decoration: none; |
||||
|
} |
||||
|
|
||||
|
ol, |
||||
|
ul, |
||||
|
p, |
||||
|
h1, |
||||
|
h2, |
||||
|
h3, |
||||
|
h4, |
||||
|
h5, |
||||
|
h6 { |
||||
|
padding: 0; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
a:hover { |
||||
|
color: #06c; |
||||
|
text-decoration: none !important; |
||||
|
} |
||||
|
|
||||
|
.clearfix:after, |
||||
|
.clearfix:before { |
||||
|
display: table; |
||||
|
content: " "; |
||||
|
} |
||||
|
|
||||
|
.clearfix:after { |
||||
|
clear: both; |
||||
|
} |
||||
|
|
||||
|
.pulll_left { |
||||
|
float: left; |
||||
|
} |
||||
|
|
||||
|
.pulll_right { |
||||
|
float: right; |
||||
|
} |
||||
|
|
||||
|
i { |
||||
|
font-style: normal; |
||||
|
} |
||||
|
|
||||
|
.text-w { |
||||
|
color: #ffe400; |
||||
|
} |
||||
|
|
||||
|
.text-d { |
||||
|
color: #ff6316; |
||||
|
} |
||||
|
|
||||
|
.text-s { |
||||
|
color: #14e144; |
||||
|
} |
||||
|
|
||||
|
.text-b { |
||||
|
color: #07e5ff; |
||||
|
} |
||||
|
|
||||
|
.head { |
||||
|
position: relative; |
||||
|
height: 90px; |
||||
|
margin: 0 15px; |
||||
|
padding-right: 60px; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.head h1 { |
||||
|
font-size: 30px; |
||||
|
letter-spacing: -2px; |
||||
|
text-align: center; |
||||
|
line-height: 90px; |
||||
|
color: #daf9ff; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
} |
||||
|
|
||||
|
.head .menu ul { |
||||
|
font-size: 0; |
||||
|
} |
||||
|
|
||||
|
.head .menu li { |
||||
|
display: inline-block; |
||||
|
position: relative; |
||||
|
margin: 25px 15px; |
||||
|
} |
||||
|
|
||||
|
.head .menu li a { |
||||
|
display: block; |
||||
|
font-size: 18px; |
||||
|
color: #fff; |
||||
|
line-height: 40px; |
||||
|
padding: 0 15px; |
||||
|
} |
||||
|
|
||||
|
.head .time { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
right: 0; |
||||
|
line-height: 90px; |
||||
|
font-family: electronicFont; |
||||
|
font-size: 28px; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.head .menu li a:hover { |
||||
|
color: #f4e925; |
||||
|
} |
||||
|
|
||||
|
.logo { |
||||
|
height: 60px; |
||||
|
|
||||
|
img { |
||||
|
display: block; |
||||
|
|
||||
|
height: 100%; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.mainbox { |
||||
|
padding: 0px 10px 10px; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.nav1 { |
||||
|
margin-left: -6px; |
||||
|
margin-right: -6px; |
||||
|
} |
||||
|
|
||||
|
.nav1>li { |
||||
|
padding: 0 6px; |
||||
|
float: left; |
||||
|
} |
||||
|
|
||||
|
.box { |
||||
|
border: 1px solid rgba(7, 118, 181, 0.5); |
||||
|
box-shadow: inset 0 0 10px rgba(7, 118, 181, 0.4); |
||||
|
margin-bottom: 12px; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.tit { |
||||
|
padding: 10px 10px 10px 25px; |
||||
|
border-bottom: 1px solid rgba(7, 118, 181, 0.7); |
||||
|
font-size: 16px; |
||||
|
font-weight: 500; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.tit:before, |
||||
|
.tit01:before { |
||||
|
position: absolute; |
||||
|
content: ""; |
||||
|
width: 6px; |
||||
|
height: 6px; |
||||
|
background: rgba(22, 214, 255, 0.9); |
||||
|
box-shadow: 0 0 5px rgba(22, 214, 255, 0.9); |
||||
|
border-radius: 10px; |
||||
|
left: 10px; |
||||
|
top: 18px; |
||||
|
} |
||||
|
|
||||
|
.tit:after, |
||||
|
.box:before { |
||||
|
width: 100%; |
||||
|
height: 1px; |
||||
|
content: ""; |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
bottom: -1px; |
||||
|
background: linear-gradient(to right, #076ead, #4ba6e0, #076ead); |
||||
|
box-shadow: 0 0 5px rgba(131, 189, 227, 1); |
||||
|
opacity: 0.6; |
||||
|
} |
||||
|
|
||||
|
.box:before { |
||||
|
top: -1px; |
||||
|
} |
||||
|
|
||||
|
.boxnav { |
||||
|
padding: 10px; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
// .mapc { |
||||
|
// position: absolute; |
||||
|
// top: 10%; |
||||
|
// left: 50%; |
||||
|
// width: 500px; |
||||
|
// height: 500px; |
||||
|
// transform: translateX(-50%); |
||||
|
// background: url(/assets/diz/1/bg3.png) no-repeat center center; |
||||
|
// background-size: cover; |
||||
|
// } |
||||
@ -1,10 +1,33 @@ |
|||||
import { Component } from '@angular/core'; |
import { Component } from "@angular/core"; |
||||
|
import { ApiService } from "@cdk/services"; |
||||
|
import { format } from "date-fns"; |
||||
|
import { Subject, interval, takeUntil } from "rxjs"; |
||||
|
|
||||
@Component({ |
@Component({ |
||||
selector: 'app-data-vis', |
selector: "app-data-vis", |
||||
templateUrl: './data-vis.component.html', |
templateUrl: "./data-vis.component.html", |
||||
styleUrls: ['./data-vis.component.less'] |
styleUrls: ["./data-vis.component.less"], |
||||
}) |
}) |
||||
export class DataVisComponent { |
export class DataVisComponent { |
||||
|
constructor(private api: ApiService) {} |
||||
|
destroy$ = new Subject(); |
||||
|
|
||||
|
showTime: string = ""; |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
interval(1000) |
||||
|
.pipe(takeUntil(this.destroy$)) |
||||
|
.subscribe(() => { |
||||
|
this.showTime = format(new Date(), "yyyy-MM-dd HH:mm:ss"); |
||||
|
}); |
||||
|
|
||||
|
this.api.getMenuDataVis().subscribe((res) => { |
||||
|
console.log("res", res); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy(): void { |
||||
|
this.destroy$.next(null); |
||||
|
this.destroy$.complete(); |
||||
|
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,51 @@ |
|||||
|
<app-page [full]="true"> |
||||
|
|
||||
|
<div class="p-4" *ngIf="step === 0"> |
||||
|
<nz-card nzTitle="录入食谱基础信息"> |
||||
|
<app-ingredient-form-basic [client]="true" [menu]="menuItem" |
||||
|
(onSave)="onStepChange($event)"> |
||||
|
</app-ingredient-form-basic> |
||||
|
</nz-card> |
||||
|
</div> |
||||
|
|
||||
|
<ng-container *ngIf="step === 1"> |
||||
|
|
||||
|
<nz-card nzSize="small" [nzBordered]="false"> |
||||
|
<div class="flex justify-between"> |
||||
|
<div class="flex-1 "> |
||||
|
<button nz-button (click)="step = 0"> |
||||
|
配置 |
||||
|
</button> |
||||
|
</div> |
||||
|
|
||||
|
<nz-space> |
||||
|
<button *nzSpaceItem nz-button> |
||||
|
食谱营养分析 |
||||
|
</button> |
||||
|
<button *nzSpaceItem nz-button (click)="createNewMenu()"> |
||||
|
新建食谱 |
||||
|
</button> |
||||
|
<button *nzSpaceItem nz-button> |
||||
|
导入食谱 |
||||
|
</button> |
||||
|
<button *nzSpaceItem nz-button> |
||||
|
食谱预览 |
||||
|
</button> |
||||
|
<button *nzSpaceItem nz-button nzType="primary" (click)="confirmSave()"> |
||||
|
保存 |
||||
|
</button> |
||||
|
<!-- <button *nzSpaceItem nz-button> |
||||
|
另存为 |
||||
|
</button> --> |
||||
|
</nz-space> |
||||
|
</div> |
||||
|
</nz-card> |
||||
|
|
||||
|
<div class="p-4"> |
||||
|
<app-ingredient-dish #menuDish [menuBaisc]="menuItem" |
||||
|
[client]="true" |
||||
|
[menuDishFormServer]="menuDishFormServer"> |
||||
|
</app-ingredient-dish> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
</app-page> |
||||
@ -0,0 +1,7 @@ |
|||||
|
.day-item { |
||||
|
::ng-deep { |
||||
|
.ant-card-head-title { |
||||
|
overflow: visible; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,162 @@ |
|||||
|
import { Component, OnInit, ViewChild } from "@angular/core"; |
||||
|
import { ActivatedRoute, Router } from "@angular/router"; |
||||
|
import { ConfirmIngredientComponent } from "@cdk/ingredient/confirm-ingredient/confirm-ingredient.component"; |
||||
|
import { IngredientDishComponent } from "@cdk/ingredient/ingredient-dish/ingredient-dish.component"; |
||||
|
import { MealDishInterface } from "@cdk/ingredient/ingredient-meals/ingredient-meals.component"; |
||||
|
import { ApiService } from "@cdk/services"; |
||||
|
import { OptionItemInterface } from "@cdk/types"; |
||||
|
import { NzMessageService } from "ng-zorro-antd/message"; |
||||
|
import { NzModalService } from "ng-zorro-antd/modal"; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: "app-ingredient-form", |
||||
|
templateUrl: "./ingredient-form.component.html", |
||||
|
styleUrls: ["./ingredient-form.component.less"], |
||||
|
}) |
||||
|
export class IngredientFormComponent implements OnInit { |
||||
|
constructor( |
||||
|
private modal: NzModalService, |
||||
|
private msg: NzMessageService, |
||||
|
private router: Router, |
||||
|
private route: ActivatedRoute, |
||||
|
private api: ApiService |
||||
|
) { |
||||
|
this.id = this.route.snapshot.paramMap.get("id"); |
||||
|
} |
||||
|
|
||||
|
@ViewChild("menuDish") menuDish!: IngredientDishComponent; |
||||
|
|
||||
|
step = 0; |
||||
|
|
||||
|
id: string | null = ""; |
||||
|
|
||||
|
menuItem: any = null; |
||||
|
|
||||
|
menuDishFormServer: any = null; |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.step = this.id && this.id !== "create" ? 1 : 0; |
||||
|
this.getDetail(); |
||||
|
} |
||||
|
|
||||
|
getDetail() { |
||||
|
if (this.id && this.id !== "create") { |
||||
|
this.api.getMenuItem(this.id).subscribe((res) => { |
||||
|
if (res.body) { |
||||
|
this.menuItem = res.body; |
||||
|
} |
||||
|
}); |
||||
|
this.api.getMenuDist(this.id).subscribe((res) => { |
||||
|
if (Array.isArray(res.body)) { |
||||
|
res.body.forEach((d) => { |
||||
|
d.ingredient.forEach((f: any) => { |
||||
|
f["groupValues"] = Object.entries(f.value).map(([peopleName, value]) => { |
||||
|
return { |
||||
|
peopleName, |
||||
|
value, |
||||
|
}; |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
this.menuDishFormServer = res.body; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onStepChange(basicInfo: any) { |
||||
|
this.step = 1; |
||||
|
this.menuItem = { |
||||
|
...basicInfo, |
||||
|
menuIds: basicInfo.menuId, |
||||
|
meals: basicInfo.meals.reduce( |
||||
|
(a: string[], c: OptionItemInterface) => (c["checked"] ? a.concat(c.value) : a), |
||||
|
[] as string[] |
||||
|
), |
||||
|
crows: basicInfo.peoples.reduce( |
||||
|
(a: string[], c: OptionItemInterface) => (c["checked"] ? a.concat(c.value) : a), |
||||
|
[] as string[] |
||||
|
), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
createNewMenu() { |
||||
|
this.modal.confirm({ |
||||
|
nzTitle: "警告", |
||||
|
nzContent: "新建食谱将清空本次所有的配餐数据,确认要清空吗?", |
||||
|
nzOnOk: () => { |
||||
|
this.menuDish.mealDishList = []; |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
confirmSave() { |
||||
|
this.modal.create({ |
||||
|
nzTitle: "确认食谱信息", |
||||
|
nzContent: ConfirmIngredientComponent, |
||||
|
nzData: this.menuItem, |
||||
|
nzWidth: 650, |
||||
|
nzOnOk: () => { |
||||
|
const { mealDishList } = this.menuDish; |
||||
|
|
||||
|
mealDishList.forEach((dish) => { |
||||
|
dish["dishId"] = dish.dish; |
||||
|
dish.items = dish.items.map((i) => { |
||||
|
return { |
||||
|
...i, |
||||
|
value: (i["groupValues"] as any[]).reduce((a, c) => { |
||||
|
return { |
||||
|
...a, |
||||
|
[c.peopleName]: c.value, |
||||
|
}; |
||||
|
}, {} as Record<string, number>), |
||||
|
}; |
||||
|
}); |
||||
|
}); |
||||
|
console.log("mealDishList", mealDishList); |
||||
|
this.api |
||||
|
.saveMenuDist({ |
||||
|
menuIds: this.menuItem.menuIds ?? [this.menuItem.id], |
||||
|
dishes: mealDishList, |
||||
|
}) |
||||
|
.subscribe((res) => { |
||||
|
this.msg.success(res.desc); |
||||
|
this.router.navigate(["/ingredient/item/list"]); |
||||
|
}); |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
formatData(menuObject: any) { |
||||
|
let dishes: any[] = []; |
||||
|
Object.entries(menuObject).forEach(([day, v]) => { |
||||
|
Object.entries(v as Record<string, MealDishInterface[]>).forEach(([mealIndex, dishList]) => { |
||||
|
dishList.forEach((dish) => { |
||||
|
dishes.push({ |
||||
|
...dish, |
||||
|
day: Number(day), |
||||
|
meal: this.menuItem.meals[mealIndex], |
||||
|
items: dish.foods.map((food) => { |
||||
|
return { |
||||
|
...food, |
||||
|
value: food.groupValues.reduce((a, c) => { |
||||
|
return { |
||||
|
...a, |
||||
|
[c.peopleName]: c.value, |
||||
|
}; |
||||
|
}, {} as Record<string, number>), |
||||
|
}; |
||||
|
}), |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
const toServer = { |
||||
|
menuIds: this.menuItem.menuIds ?? [this.menuItem.id], |
||||
|
dishes, |
||||
|
}; |
||||
|
|
||||
|
return toServer; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,94 @@ |
|||||
|
<app-page> |
||||
|
<ng-template #pageExtraTpl> |
||||
|
<nz-space> |
||||
|
<button *nzSpaceItem nz-button nzType="primary" [routerLink]="['/','ingredient','item','form','create']"> |
||||
|
<i nz-icon nzType="plus"></i> |
||||
|
创建食谱 |
||||
|
</button> |
||||
|
</nz-space> |
||||
|
</ng-template> |
||||
|
<div class="h-full overflow-hidden bg-white rounded-lg"> |
||||
|
|
||||
|
<nz-card [nzBordered]="false" nzTitle="食谱库"> |
||||
|
<table-list [props]="tableList" |
||||
|
[search]="searchTpl" |
||||
|
[action]="pageExtraTpl" |
||||
|
[formGroup]="queryForm" |
||||
|
[renderColumns]="renderColumnsTpl" |
||||
|
optionWidth="300px"> |
||||
|
|
||||
|
<ng-template #actionTpl> |
||||
|
<button nz-button>批量删除</button> |
||||
|
</ng-template> |
||||
|
<ng-template #searchTpl> |
||||
|
|
||||
|
<nz-form-item class="w-40"> |
||||
|
<nz-form-control> |
||||
|
<nz-select nzPlaceHolder="状态" formControlName="status" [nzAllowClear]="true"> |
||||
|
<nz-option *ngFor="let item of globalEnum.menuStatus" [nzValue]="item.label" |
||||
|
[nzLabel]="item.value"> |
||||
|
</nz-option> |
||||
|
</nz-select> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
<nz-form-item> |
||||
|
<nz-form-control> |
||||
|
<input nz-input placeholder="请输入食谱名称" formControlName="name" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</ng-template> |
||||
|
<ng-template #renderColumnsTpl let-data let-key="key" let-row="row"> |
||||
|
<ng-container [ngSwitch]="key"> |
||||
|
<ng-container *ngSwitchCase="'modify'"> |
||||
|
{{data | date:'yyyy-MM-dd HH:mm:ss'}} |
||||
|
</ng-container> |
||||
|
<ng-container *ngSwitchCase="'vender'"> |
||||
|
{{ tableOrg[data] ? tableOrg[data].name : '-'}} |
||||
|
</ng-container> |
||||
|
|
||||
|
<ng-container *ngSwitchCase="'meals'"> |
||||
|
<nz-tag *ngFor="let item of data">{{item}}</nz-tag> |
||||
|
</ng-container> |
||||
|
<ng-container *ngSwitchCase="'day'"> |
||||
|
{{data}} 天 |
||||
|
</ng-container> |
||||
|
<ng-container *ngSwitchCase="'status'"> |
||||
|
{{statusTextMap[data]}} |
||||
|
</ng-container> |
||||
|
<ng-container *ngSwitchCase="'month'"> |
||||
|
<div class="flex flex-wrap"> |
||||
|
<ng-container *ngIf="data.length === 12"> |
||||
|
<nz-tag> |
||||
|
全年 |
||||
|
</nz-tag> |
||||
|
</ng-container> |
||||
|
<ng-container *ngIf="data.length !== 12"> |
||||
|
<nz-tag *ngFor="let item of data" class="mb-1"> |
||||
|
{{monthText[item]}} |
||||
|
</nz-tag> |
||||
|
</ng-container> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
<ng-container *ngSwitchDefault> |
||||
|
{{data}} |
||||
|
</ng-container> |
||||
|
</ng-container> |
||||
|
</ng-template> |
||||
|
</table-list> |
||||
|
</nz-card> |
||||
|
</div> |
||||
|
</app-page> |
||||
|
|
||||
|
|
||||
|
<ng-template #releaseStartTimeTpl> |
||||
|
<div nz-form> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzSpan="6" [nzRequired]="true"> |
||||
|
发布日期 |
||||
|
</nz-form-label> |
||||
|
<nz-form-control nzSpan="12"> |
||||
|
<nz-date-picker class="w-full" nzPlaceHolder="请选择发布日期" [(ngModel)]="startTime"></nz-date-picker> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</div> |
||||
|
</ng-template> |
||||
@ -0,0 +1,220 @@ |
|||||
|
import { Component, OnInit, TemplateRef, ViewChild } from "@angular/core"; |
||||
|
import { FormControl, FormGroup } from "@angular/forms"; |
||||
|
import { NzDrawerRef, NzDrawerService } from "ng-zorro-antd/drawer"; |
||||
|
import { AnyObject, OrgDTO, TableListOption } from "@cdk/public-api"; |
||||
|
import { ApiService } from "@cdk/services"; |
||||
|
import { NzModalService } from "ng-zorro-antd/modal"; |
||||
|
import { lastValueFrom, tap } from "rxjs"; |
||||
|
import { NzMessageService } from "ng-zorro-antd/message"; |
||||
|
import { MyResponse } from "@cdk/types"; |
||||
|
import { Router } from "@angular/router"; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: "app-ingredient-list", |
||||
|
templateUrl: "./ingredient-list.component.html", |
||||
|
styleUrls: ["./ingredient-list.component.less"], |
||||
|
}) |
||||
|
export class IngredientListComponent { |
||||
|
constructor( |
||||
|
private drawer: NzDrawerService, |
||||
|
private api: ApiService, |
||||
|
private modal: NzModalService, |
||||
|
private msg: NzMessageService, |
||||
|
private router: Router |
||||
|
) {} |
||||
|
|
||||
|
globalEnum = this.api.globalEnum; |
||||
|
|
||||
|
statusTextMap: Record<string, string> = {}; |
||||
|
|
||||
|
@ViewChild("releaseStartTimeTpl") releaseStartTimeTpl!: TemplateRef<{}>; |
||||
|
|
||||
|
private drawerRef?: NzDrawerRef; |
||||
|
|
||||
|
public tableList = new TableListOption(this.fetchData.bind(this), { |
||||
|
frontPagination: false, |
||||
|
}); |
||||
|
|
||||
|
public queryForm = new FormGroup({ |
||||
|
name: new FormControl(""), |
||||
|
vender: new FormControl(""), |
||||
|
status: new FormControl(""), |
||||
|
}); |
||||
|
|
||||
|
startTime: Date | null = null; |
||||
|
|
||||
|
tableOrg: { [k: number]: OrgDTO } = {}; |
||||
|
|
||||
|
monthText = { |
||||
|
1: "一月", |
||||
|
2: "二月", |
||||
|
3: "三月", |
||||
|
4: "四月", |
||||
|
5: "五月", |
||||
|
6: "六月", |
||||
|
7: "七月", |
||||
|
8: "八月", |
||||
|
9: "九月", |
||||
|
10: "十月", |
||||
|
11: "十一月", |
||||
|
12: "十二月", |
||||
|
} as any; |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.statusTextMap = this.globalEnum.menuStatus.reduce((a, c) => { |
||||
|
return { |
||||
|
...a, |
||||
|
[String(c.label)]: c.value, |
||||
|
}; |
||||
|
}, {} as Record<string, string>); |
||||
|
this.initTableList(); |
||||
|
} |
||||
|
|
||||
|
initTableList() { |
||||
|
this.tableList.scroll = { x: null }; |
||||
|
this.tableList = this.tableList.setColumns([ |
||||
|
{ key: "name", title: "食谱名称" }, |
||||
|
|
||||
|
{ key: "meals", title: "包含餐次" }, |
||||
|
{ key: "month", title: "适用月份", width: "300px" }, |
||||
|
{ key: "day", title: "周期" }, |
||||
|
{ key: "status", title: "状态" }, |
||||
|
{ key: "modify", title: "更新时间" }, |
||||
|
{ key: "operate", title: "创建人" }, |
||||
|
]); |
||||
|
|
||||
|
this.tableList = this.tableList.setOptions([ |
||||
|
{ |
||||
|
title: "详情", |
||||
|
premissions: [], |
||||
|
onClick: this.preview.bind(this), |
||||
|
}, |
||||
|
{ |
||||
|
title: "导出", |
||||
|
premissions: [], |
||||
|
|
||||
|
onClick: this.export.bind(this), |
||||
|
}, |
||||
|
{ |
||||
|
title: "审核", |
||||
|
premissions: [], |
||||
|
onClick: this.shenhe.bind(this), |
||||
|
visible(v) { |
||||
|
// 0 提交审核 编辑 删除
|
||||
|
// 1 审核中 删除
|
||||
|
// 2 发布 编辑 删除
|
||||
|
// 3 提交审核 编辑 删除
|
||||
|
// 4 提交审核 编辑 删除
|
||||
|
return [0, 3, 4].includes(v.status); |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
title: "发布", |
||||
|
premissions: [], |
||||
|
onClick: this.release.bind(this), |
||||
|
visible(v) { |
||||
|
return [2].includes(v.status); |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
title: "禁用", |
||||
|
premissions: [], |
||||
|
onClick: this.disableMenu.bind(this), |
||||
|
visible(v) { |
||||
|
return [2].includes(v.status); |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
title: "编辑", |
||||
|
premissions: [], |
||||
|
onClick: (v) => { |
||||
|
this.router.navigate([`/ingredient/item/form/${v["id"]}`]); |
||||
|
}, |
||||
|
visible(v) { |
||||
|
return [0, 3, 4].includes(v.status); |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
title: "删除", |
||||
|
premissions: [], |
||||
|
onClick: this.deleteItem.bind(this), |
||||
|
}, |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
fetchData(query: AnyObject, pager: AnyObject) { |
||||
|
return this.api.getMenuPage(pager, query).pipe(); |
||||
|
} |
||||
|
|
||||
|
preview({ id }: any) { |
||||
|
window.open(`/ingredient/preview?id=${id}`); |
||||
|
} |
||||
|
|
||||
|
export({ id }: any) { |
||||
|
this.msg.loading("导出中..."); |
||||
|
this.api.exportMenu(id).subscribe(() => { |
||||
|
setTimeout(() => { |
||||
|
this.msg.remove(); |
||||
|
}, 1500); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
cancelFoodForm() { |
||||
|
this.drawerRef?.close(); |
||||
|
} |
||||
|
|
||||
|
shenhe({ id }: any) { |
||||
|
this.modal.confirm({ |
||||
|
nzTitle: "警告", |
||||
|
nzContent: `是否要将该食谱提交审核?`, |
||||
|
nzOnOk: async () => { |
||||
|
const res = await lastValueFrom(this.api.submitMenuForReview(id)); |
||||
|
this.msg.success(res.desc); |
||||
|
this.tableList.run(); |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
release({ id, day }: any) { |
||||
|
this.modal.create({ |
||||
|
nzTitle: "发布食谱", |
||||
|
nzContent: this.releaseStartTimeTpl, |
||||
|
nzOnOk: async () => { |
||||
|
if (!this.startTime) { |
||||
|
this.msg.error("请选择发布日期"); |
||||
|
return false; |
||||
|
} |
||||
|
const res = await lastValueFrom(this.api.release(id, this.startTime, day)); |
||||
|
this.msg.success(res.desc); |
||||
|
this.tableList.run(); |
||||
|
return true; |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
deleteItem({ id }: any) { |
||||
|
this.modal.confirm({ |
||||
|
nzTitle: "警告", |
||||
|
nzContent: `是否要删除该食谱?`, |
||||
|
nzOkDanger: true, |
||||
|
nzOnOk: async () => { |
||||
|
const res = await lastValueFrom(this.api.deleteMenu(id)); |
||||
|
this.msg.success(res.desc); |
||||
|
this.tableList.run(); |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
disableMenu({ id }: any) { |
||||
|
this.modal.confirm({ |
||||
|
nzTitle: "警告", |
||||
|
nzContent: `是否要禁用该食谱?`, |
||||
|
nzOkDanger: true, |
||||
|
nzOnOk: async () => { |
||||
|
const res = await lastValueFrom(this.api.disableMenu(id)); |
||||
|
this.msg.success(res.desc); |
||||
|
this.tableList.run(); |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
<app-ingredient-preview></app-ingredient-preview> |
||||
@ -0,0 +1,13 @@ |
|||||
|
import { Component, OnInit } from "@angular/core"; |
||||
|
import { ActivatedRoute, Router } from "@angular/router"; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: "app-ingredient-preview-page", |
||||
|
templateUrl: "./ingredient-preview-page.component.html", |
||||
|
styleUrls: ["./ingredient-preview-page.component.less"], |
||||
|
}) |
||||
|
export class IngredientPreviewPageComponent implements OnInit { |
||||
|
constructor(private route: ActivatedRoute, private router: Router) {} |
||||
|
|
||||
|
ngOnInit(): void {} |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
<app-page> |
||||
|
|
||||
|
<div class="h-full overflow-hidden bg-white rounded-lg"> |
||||
|
|
||||
|
<nz-card [nzBordered]="false" nzTitle="食谱发布计划"> |
||||
|
<table-list [props]="tableList" [search]="searchTpl" [formGroup]="queryForm" |
||||
|
[renderColumns]="renderColumnsTpl"> |
||||
|
|
||||
|
|
||||
|
<ng-template #searchTpl> |
||||
|
<nz-form-item> |
||||
|
<nz-form-control> |
||||
|
<input nz-input placeholder="请输入食谱名称" formControlName="name" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
|
||||
|
<nz-form-item> |
||||
|
<nz-form-control> |
||||
|
<nz-space> |
||||
|
<nz-radio-group *nzSpaceItem nzButtonStyle="solid" |
||||
|
[(ngModel)]="week" |
||||
|
(ngModelChange)="onWeekChange($event)" |
||||
|
[ngModelOptions]="{standalone: true}"> |
||||
|
<label nz-radio-button [nzValue]="0">全部</label> |
||||
|
<label nz-radio-button [nzValue]="1">本周</label> |
||||
|
<label nz-radio-button [nzValue]="-1">上周</label> |
||||
|
</nz-radio-group> |
||||
|
<ng-container *nzSpaceItem> |
||||
|
<nz-range-picker |
||||
|
[(ngModel)]="dateRange" |
||||
|
[ngModelOptions]="{standalone: true}"> |
||||
|
</nz-range-picker> |
||||
|
</ng-container> |
||||
|
</nz-space> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
|
||||
|
</ng-template> |
||||
|
<ng-template #renderColumnsTpl let-data let-key="key" let-row="row"> |
||||
|
<ng-container [ngSwitch]="key"> |
||||
|
<ng-container *ngSwitchCase="'created'"> |
||||
|
{{data | date:'yyyy-MM-dd HH:mm:ss'}} |
||||
|
</ng-container> |
||||
|
|
||||
|
<ng-container *ngSwitchCase="'vender'"> |
||||
|
{{ tableOrg[data] ? tableOrg[data].name : '-'}} |
||||
|
</ng-container> |
||||
|
<ng-container *ngSwitchCase="'meals'"> |
||||
|
<nz-tag *ngFor="let item of data">{{item}}</nz-tag> |
||||
|
</ng-container> |
||||
|
<ng-container *ngSwitchCase="'day'"> |
||||
|
{{data}} 天 |
||||
|
</ng-container> |
||||
|
<ng-container *ngSwitchDefault> |
||||
|
|
||||
|
{{data}} |
||||
|
|
||||
|
</ng-container> |
||||
|
</ng-container> |
||||
|
</ng-template> |
||||
|
</table-list> |
||||
|
</nz-card> |
||||
|
</div> |
||||
|
</app-page> |
||||
@ -0,0 +1,129 @@ |
|||||
|
import { Component, OnInit, TemplateRef, ViewChild } from "@angular/core"; |
||||
|
import { FormControl, FormGroup } from "@angular/forms"; |
||||
|
import { NzDrawerRef, NzDrawerService } from "ng-zorro-antd/drawer"; |
||||
|
import { AnyObject, MyResponse, OrgDTO, TableListOption } from "@cdk/public-api"; |
||||
|
import { ApiService } from "@cdk/services"; |
||||
|
import { lastValueFrom, tap } from "rxjs"; |
||||
|
import { endOfWeek, format, startOfWeek, subWeeks } from "date-fns"; |
||||
|
import { NzModalService } from "ng-zorro-antd/modal"; |
||||
|
import { NzMessageService } from "ng-zorro-antd/message"; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: "app-ingredient-release", |
||||
|
templateUrl: "./ingredient-release.component.html", |
||||
|
styleUrls: ["./ingredient-release.component.less"], |
||||
|
}) |
||||
|
export class IngredientReleaseComponent { |
||||
|
constructor( |
||||
|
private drawer: NzDrawerService, |
||||
|
private api: ApiService, |
||||
|
private modal: NzModalService, |
||||
|
private msg: NzMessageService |
||||
|
) {} |
||||
|
|
||||
|
public tableList = new TableListOption(this.fetchData.bind(this)); |
||||
|
|
||||
|
public queryForm = new FormGroup({ |
||||
|
name: new FormControl(""), |
||||
|
vender: new FormControl(""), |
||||
|
}); |
||||
|
|
||||
|
tableOrg: { [k: number]: OrgDTO } = {}; |
||||
|
|
||||
|
week: number = 0; |
||||
|
|
||||
|
dateRange: Date[] | null = null; |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.initTableList(); |
||||
|
} |
||||
|
|
||||
|
initTableList() { |
||||
|
this.tableList.scroll = { x: null }; |
||||
|
this.tableList = this.tableList.setColumns([ |
||||
|
{ key: "name", title: "食谱名称" }, |
||||
|
|
||||
|
{ key: "meals", title: "包含餐次" }, |
||||
|
{ key: "day", title: "周期" }, |
||||
|
{ key: "created", title: "创建时间" }, |
||||
|
{ key: "dateRange", title: "应用时间" }, |
||||
|
]); |
||||
|
|
||||
|
this.tableList = this.tableList.setOptions([ |
||||
|
{ |
||||
|
title: "详情", |
||||
|
premissions: [], |
||||
|
onClick: this.preview.bind(this), |
||||
|
}, |
||||
|
{ |
||||
|
title: "导出", |
||||
|
premissions: [], |
||||
|
onClick: this.export.bind(this), |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
title: "取消发布", |
||||
|
premissions: [], |
||||
|
onClick: this.cancelRelease.bind(this), |
||||
|
}, |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
fetchData(pager: AnyObject, query: AnyObject) { |
||||
|
if (this.dateRange) { |
||||
|
if (this.dateRange[0]) { |
||||
|
query["startTime"] = format(this.dateRange[0], "yyyy-MM-dd"); |
||||
|
} |
||||
|
if (this.dateRange[1]) { |
||||
|
query["endTime"] = format(this.dateRange[1], "yyyy-MM-dd"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return this.api.getMenuReleasePage(pager, query).pipe(); |
||||
|
} |
||||
|
|
||||
|
preview({ id }: any) { |
||||
|
window.open(`/ingredient/preview?id=${id}`); |
||||
|
} |
||||
|
|
||||
|
onWeekChange(e: -1 | 0 | 1) { |
||||
|
if (e !== 0) { |
||||
|
const range = this.getWeekDates(e); |
||||
|
this.dateRange = range; |
||||
|
} else { |
||||
|
this.dateRange = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
getWeekDates(offset: -1 | 1) { |
||||
|
const now = new Date(); |
||||
|
const startDate = |
||||
|
offset === -1 ? startOfWeek(subWeeks(now, 1), { weekStartsOn: 1 }) : startOfWeek(now, { weekStartsOn: 1 }); |
||||
|
const endDate = |
||||
|
offset === -1 ? endOfWeek(subWeeks(now, 1), { weekStartsOn: 1 }) : endOfWeek(now, { weekStartsOn: 1 }); |
||||
|
|
||||
|
return [startDate, endDate]; |
||||
|
} |
||||
|
|
||||
|
export({ id }: any) { |
||||
|
this.msg.loading("导出中..."); |
||||
|
this.api.exportMenu(id).subscribe(() => { |
||||
|
setTimeout(() => { |
||||
|
this.msg.remove(); |
||||
|
}, 1500); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
cancelRelease({ id }: any) { |
||||
|
this.modal.confirm({ |
||||
|
nzTitle: "警告", |
||||
|
nzContent: "是否要取消发布该食谱?", |
||||
|
nzOkDanger: true, |
||||
|
nzOnOk: async () => { |
||||
|
const res = await lastValueFrom(this.api.cancelRelease(id)); |
||||
|
this.msg.success(res.desc); |
||||
|
this.tableList.run(); |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
After Width: | Height: | Size: 366 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 302 KiB |
Loading…
Reference in new issue