57 changed files with 2591 additions and 410 deletions
@ -1,8 +1,131 @@ |
|||
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({ |
|||
selector: "app-ingredient-preview", |
|||
templateUrl: "./ingredient-preview.component.html", |
|||
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({ |
|||
selector: 'app-data-vis', |
|||
templateUrl: './data-vis.component.html', |
|||
styleUrls: ['./data-vis.component.less'] |
|||
selector: "app-data-vis", |
|||
templateUrl: "./data-vis.component.html", |
|||
styleUrls: ["./data-vis.component.less"], |
|||
}) |
|||
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