Browse Source

标准设置修改

main
kkerwin 2 years ago
parent
commit
1cbb759944
  1. 16
      .editorconfig
  2. 8
      .prettierrc
  3. 5
      README.md
  4. 90
      projects/admin/src/app/components/dish-form/dish-form.component.html
  5. 193
      projects/admin/src/app/components/dish-form/dish-form.component.ts
  6. 102
      projects/admin/src/app/pages/dish/dish.component.html
  7. 191
      projects/admin/src/app/pages/dish/dish.component.ts
  8. 83
      projects/admin/src/app/pages/standard/standard-form/standard-form.component.html
  9. 116
      projects/admin/src/app/pages/standard/standard-form/standard-form.component.ts
  10. 80
      projects/admin/src/app/pages/standard/standard-list/standard-list.component.ts
  11. 188
      projects/admin/src/app/pages/standard/standard-setting/standard-setting.component.html
  12. 261
      projects/admin/src/app/pages/standard/standard-setting/standard-setting.component.ts
  13. 11
      projects/admin/src/app/pages/standard/standard.service.ts
  14. 195
      projects/cdk/src/ingredient/ingredient-analysis/ingredient-analysis.component.html
  15. 130
      projects/cdk/src/ingredient/ingredient-analysis/ingredient-analysis.component.ts
  16. 123
      projects/cdk/src/ingredient/ingredient-dish/ingredient-dish.component.ts
  17. 82
      projects/cdk/src/ingredient/ingredient-meals/ingredient-meals.component.html
  18. 8
      projects/cdk/src/ingredient/ingredient-meals/ingredient-meals.component.less
  19. 111
      projects/cdk/src/ingredient/ingredient-meals/ingredient-meals.component.ts
  20. 85
      projects/cdk/src/ingredient/ingredient-preview/ingredient-preview.component.html
  21. 449
      projects/cdk/src/services/api.service.ts
  22. 67
      projects/cdk/src/shared/components/dish-select/dish-select.component.ts
  23. 80
      projects/client/src/app/components/dish-form/dish-form.component.html
  24. 58
      projects/client/src/app/pages/data-vis/data-vis.component.html
  25. 174
      projects/client/src/app/pages/data-vis/data-vis.component.ts
  26. 97
      projects/client/src/app/pages/dish/dish.component.html
  27. 177
      projects/client/src/app/pages/dish/dish.component.ts
  28. 104
      projects/client/src/app/pages/system/org-info/org-info.component.html

16
.editorconfig

@ -1,16 +0,0 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

8
.prettierrc

@ -0,0 +1,8 @@
{
"singleQuote": true,
"trailingComma": "all",
"useTabs": true,
"tabWidth": 4,
"semi": false,
"printWidth": 120
}

5
README.md

@ -51,3 +51,8 @@
3. 详情&编辑食谱 -> 显示类型优化 & 分析 3. 详情&编辑食谱 -> 显示类型优化 & 分析
4. 菜品分类 4. 菜品分类
5. 食材批量删除 5. 食材批量删除
# 12/09
1. 新增菜品报错,编辑没有问题
2.

90
projects/admin/src/app/components/dish-form/dish-form.component.html

@ -1,8 +1,27 @@
<form nz-form [formGroup]="formGroup" nzLayout="vertical"> <form nz-form [formGroup]="formGroup" nzLayout="vertical">
<nz-alert class="mb-4" nzType="info" nzMessage="菜品建议" [nzDescription]="nzDescriptionTpl">
<ng-template #nzDescriptionTpl>
<div>每份菜品各类食材建议使用量为:</div>
<ul class="flex justify-between">
<li class="basis-1/4">● 肉类30~50克</li>
<li class="basis-1/4">● 素菜50~100克</li>
<li class="basis-1/4">● 食用油3~5克</li>
<li class="basis-1/4">● 精盐0.5克</li>
</ul>
<div class="mt-2">如需用到除盐外的其他调味品,建议:</div>
<ul class="flex flex-wrap">
<li class="basis-1/4">● 味精、鸡精1克</li>
<li class="basis-1/4">● 豆瓣酱、酱油等3~5克</li>
<li class="w-full">● 醋、料酒、小葱、干辣椒、花椒、五香粉等香料不必填写</li>
</ul>
<div class="mt-2">汤类菜品:</div>
<ul>
<li>● 每份建议添加饮用水200克</li>
</ul>
</ng-template>
</nz-alert>
<nz-form-item> <nz-form-item>
<nz-form-label nzRequired> <nz-form-label nzRequired> 单位 </nz-form-label>
单位
</nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl"> <nz-form-control [nzErrorTip]="formControlErrorTpl">
<!-- <nz-select <!-- <nz-select
formControlName="unit" formControlName="unit"
@ -14,69 +33,52 @@
<nz-select <nz-select
[nzMode]="'multiple'" [nzMode]="'multiple'"
nzPlaceHolder="请选择单位,多选时在多个单位均添加此菜品" nzPlaceHolder="请选择单位,多选时在多个单位均添加此菜品"
[nzShowArrow]="false" [nzShowArrow]="false"
formControlName="vendors"
formControlName="vendors"> >
<nz-option *ngFor="let o of orgListOfOption" [nzLabel]="o.text" [nzValue]="o.value"></nz-option> <nz-option *ngFor="let o of orgListOfOption" [nzLabel]="o.text" [nzValue]="o.value"></nz-option>
</nz-select> </nz-select>
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label nzRequired> <nz-form-label nzRequired> 菜品名称 </nz-form-label>
菜品名称
</nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl"> <nz-form-control [nzErrorTip]="formControlErrorTpl">
<input placeholder="请输入菜品名称" nz-input formControlName="name" /> <input placeholder="请输入菜品名称" nz-input formControlName="name" />
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label nzRequired> <nz-form-label nzRequired> 菜品标签 </nz-form-label>
菜品标签
</nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl"> <nz-form-control [nzErrorTip]="formControlErrorTpl">
<nz-select <nz-select formControlName="mark" nzPlaceHolder="请选择菜品标签">
formControlName="mark"
nzPlaceHolder="请选择菜品标签">
<nz-option *ngFor="let item of globalEnum.mark" [nzValue]="item.value" [nzLabel]="item.key"></nz-option> <nz-option *ngFor="let item of globalEnum.mark" [nzValue]="item.value" [nzLabel]="item.key"></nz-option>
</nz-select> </nz-select>
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label nzRequired> <nz-form-label nzRequired> 烹饪方式 </nz-form-label>
烹饪方式
</nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl"> <nz-form-control [nzErrorTip]="formControlErrorTpl">
<nz-select nzPlaceHolder="请选择烹饪方式" formControlName="poly" nzAllowClear> <nz-select nzPlaceHolder="请选择烹饪方式" formControlName="poly" nzAllowClear>
<nz-option *ngFor="let item of globalEnum.poly" [nzLabel]="item.name" <nz-option *ngFor="let item of globalEnum.poly" [nzLabel]="item.name" [nzValue]="item.key"> </nz-option>
[nzValue]="item.key">
</nz-option>
</nz-select> </nz-select>
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label nzRequired> <nz-form-label nzRequired> 适用月份 </nz-form-label>
适用月份
</nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl"> <nz-form-control [nzErrorTip]="formControlErrorTpl">
<app-month-select formControlName="month"></app-month-select> <app-month-select formControlName="month"></app-month-select>
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label> <nz-form-label> 菜品图片 </nz-form-label>
菜品图片
</nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl" nzExtra="支持.jpg .jpeg .png 格式,小于2M"> <nz-form-control [nzErrorTip]="formControlErrorTpl" nzExtra="支持.jpg .jpeg .png 格式,小于2M">
<input type="hidden" formControlName="icon" /> <input type="hidden" formControlName="icon" />
<div class="mb-2" *ngIf="iconPreview"> <div class="mb-2" *ngIf="iconPreview">
<img [src]="iconPreview" class="h-20 w-20" /> <img [src]="iconPreview" class="h-20 w-20" />
</div> </div>
<button class="upload-btn " nz-button [nzLoading]="uploadLoading"> <button class="upload-btn" nz-button [nzLoading]="uploadLoading">
<i nz-icon nzType="upload"></i> <i nz-icon nzType="upload"></i>
上传图片 上传图片
<input type="file" (change)="onFileChange($event)" accept=".jpg,.jpeg,.png" /> <input type="file" (change)="onFileChange($event)" accept=".jpg,.jpeg,.png" />
@ -96,9 +98,7 @@
<nz-form-item> <nz-form-item>
<nz-form-label nzRequired class="block-label"> <nz-form-label nzRequired class="block-label">
<div class="flex justify-between items-center flex-1"> <div class="flex justify-between items-center flex-1">
<span class="flex-1"> <span class="flex-1"> 食材名称 </span>
食材名称
</span>
<!-- <a nz-button nzType="link" (click)="addFoodVisible = true"> <!-- <a nz-button nzType="link" (click)="addFoodVisible = true">
<span nz-icon nzType="plus"></span> <span nz-icon nzType="plus"></span>
<span> <span>
@ -108,7 +108,6 @@
</div> </div>
</nz-form-label> </nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl"> <nz-form-control [nzErrorTip]="formControlErrorTpl">
<nz-select <nz-select
[nzMode]="'multiple'" [nzMode]="'multiple'"
nzShowSearch nzShowSearch
@ -119,7 +118,8 @@
[(ngModel)]="foodSelected" [(ngModel)]="foodSelected"
(ngModelChange)="onFoodSelected($event)" (ngModelChange)="onFoodSelected($event)"
(nzOnSearch)="searchFood($event)" (nzOnSearch)="searchFood($event)"
[ngModelOptions]="{standalone: true}"> [ngModelOptions]="{ standalone: true }"
>
<nz-option *ngFor="let o of foodListOfOption" [nzLabel]="o.text" [nzValue]="o.value"></nz-option> <nz-option *ngFor="let o of foodListOfOption" [nzLabel]="o.text" [nzValue]="o.value"></nz-option>
</nz-select> </nz-select>
<!-- <div *ngIf="addFoodVisible"> <!-- <div *ngIf="addFoodVisible">
@ -134,7 +134,7 @@
</div> </div>
</div> --> </div> -->
<ul class="mt-4"> <ul class="mt-4">
<li class="mb-2" *ngFor="let f of foodItemSelected;let i = index"> <li class="mb-2" *ngFor="let f of foodItemSelected; let i = index">
<div class="flex items-center"> <div class="flex items-center">
<!-- <div class="w-1/2 pr-2"> <!-- <div class="w-1/2 pr-2">
<button nz-button nzBlock> <button nz-button nzBlock>
@ -143,18 +143,26 @@
</div> --> </div> -->
<div class="flex-1 pr-2"> <div class="flex-1 pr-2">
<nz-input-group nzAddOnBefore="{{f.value}}-{{f.text}}:" [nzAddOnAfter]="'g'" class="w-full"> <nz-input-group
nzAddOnBefore="{{ f.value }}-{{ f.text }}:"
[nzAddOnAfter]="'g'"
class="w-full"
>
<input <input
nz-input nz-input
type="number" type="number"
[(ngModel)]="f.num" [(ngModel)]="f.num"
[ngModelOptions]="{standalone: true}" [ngModelOptions]="{ standalone: true }"
placeholder="请输入{{f.value}}-{{f.text}}重量" /> placeholder="请输入{{ f.value }}-{{ f.text }}重量"
/>
</nz-input-group> </nz-input-group>
</div> </div>
<div class="pl-2"> <div class="pl-2">
<nz-switch [ngModel]="f.isMain" (ngModelChange)="onMainChange($event,f.value)" <nz-switch
[ngModelOptions]="{standalone: true}"> [ngModel]="f.isMain"
(ngModelChange)="onMainChange($event, f.value)"
[ngModelOptions]="{ standalone: true }"
>
</nz-switch> </nz-switch>
是否主料 是否主料
</div> </div>

193
projects/admin/src/app/components/dish-form/dish-form.component.ts

@ -1,75 +1,75 @@
import { Component, Input, OnInit } from "@angular/core"; import { Component, Input, OnInit } from '@angular/core'
import { FormArray, FormBuilder, FormGroup } from "@angular/forms"; import { FormArray, FormBuilder, FormGroup } from '@angular/forms'
import { ApiService } from "@cdk/services"; import { ApiService } from '@cdk/services'
import { Utils } from "@cdk/utils"; import { Utils } from '@cdk/utils'
import { FormValidators } from "@cdk/validators"; import { FormValidators } from '@cdk/validators'
import { NzMessageService } from "ng-zorro-antd/message"; import { NzMessageService } from 'ng-zorro-antd/message'
import { Subject, debounceTime, distinctUntilChanged, filter, finalize, switchMap, throttleTime } from "rxjs"; import { Subject, debounceTime, distinctUntilChanged, filter, finalize, switchMap, throttleTime } from 'rxjs'
@Component({ @Component({
selector: "app-dish-form", selector: 'app-dish-form',
templateUrl: "./dish-form.component.html", templateUrl: './dish-form.component.html',
styleUrls: ["./dish-form.component.less"], styleUrls: ['./dish-form.component.less'],
}) })
export class DishFormComponent { export class DishFormComponent {
constructor(private fb: FormBuilder, private msg: NzMessageService, private api: ApiService) {} constructor(private fb: FormBuilder, private msg: NzMessageService, private api: ApiService) {}
@Input() data: any; @Input() data: any
@Input() orgs: any[] = []; @Input() orgs: any[] = []
@Input() foods: any[] = []; @Input() foods: any[] = []
private orgSearch$ = new Subject<Record<string, string>>(); private orgSearch$ = new Subject<Record<string, string>>()
private foodSearch$ = new Subject<Record<string, string>>(); private foodSearch$ = new Subject<Record<string, string>>()
formGroup!: FormGroup; formGroup!: FormGroup
selectedValue = null; selectedValue = null
orgListOfOption: Array<{ value: string; text: string }> = []; orgListOfOption: Array<{ value: string; text: string }> = []
foodListOfOption: Array<{ value: string; text: string }> = []; foodListOfOption: Array<{ value: string; text: string }> = []
searchedFood: Array<{ value: string; text: string }> = []; searchedFood: Array<{ value: string; text: string }> = []
foodSelected: string[] = []; foodSelected: string[] = []
foodItemSelected: any[] = []; foodItemSelected: any[] = []
nzFilterOption = (): boolean => true; nzFilterOption = (): boolean => true
globalEnum = this.api.globalEnum; globalEnum = this.api.globalEnum
uploadLoading = false; uploadLoading = false
addFoodVisible = false; addFoodVisible = false
iconPreview = ""; iconPreview = ''
get food(): FormArray { get food(): FormArray {
return this.formGroup.get("ingredient") as FormArray; return this.formGroup.get('ingredient') as FormArray
} }
get icon() { get icon() {
return this.formGroup.get("icon")?.value; return this.formGroup.get('icon')?.value
} }
ngOnInit(): void { ngOnInit(): void {
this.formGroup = this.fb.group({ this.formGroup = this.fb.group({
id: this.fb.control("", []), id: this.fb.control('', []),
vendors: this.fb.control([], [FormValidators.required()]), vendors: this.fb.control([], [FormValidators.required()]),
name: this.fb.control("", [FormValidators.required()]), name: this.fb.control('', [FormValidators.required()]),
icon: this.fb.control("", []), icon: this.fb.control('', []),
mark: this.fb.control("", [FormValidators.required()]), mark: this.fb.control('', [FormValidators.required()]),
poly: this.fb.control("", [FormValidators.required()]), poly: this.fb.control('', [FormValidators.required()]),
month: this.fb.control([], []), month: this.fb.control([], []),
}); })
this.api.getOrgList().subscribe((res) => { this.api.getOrgList().subscribe((res) => {
this.orgListOfOption = res; this.orgListOfOption = res
}); })
// this.orgSearch$ // this.orgSearch$
// .pipe( // .pipe(
@ -92,118 +92,119 @@ export class DishFormComponent {
filter((f) => !!f), filter((f) => !!f),
debounceTime(500), debounceTime(500),
distinctUntilChanged(), distinctUntilChanged(),
switchMap((q) => this.api.getFoodList(q)) switchMap((q) => this.api.getFoodList(q)),
) )
.subscribe((data) => { .subscribe((data) => {
const listOfOption: Array<{ value: string; text: string }> = []; const listOfOption: Array<{ value: string; text: string }> = []
data.body.forEach((item) => { data.body.forEach((item) => {
listOfOption.push({ listOfOption.push({
value: item.key, value: item.key,
text: item.name, text: item.name,
}); })
}); })
this.searchedFood = this.searchedFood.concat(listOfOption); this.searchedFood = this.searchedFood.concat(listOfOption)
this.foodListOfOption = listOfOption; this.foodListOfOption = listOfOption
}); })
this.setValues(); this.setValues()
} }
setValues() { setValues() {
this.orgListOfOption = this.orgs.map((i) => ({ text: i.name, value: i.id })); this.orgListOfOption = this.orgs.map((i) => ({ text: i.name, value: i.id }))
if (this.data) { if (this.data) {
// this.allMonth = this.allMonth.map((i) => // this.allMonth = this.allMonth.map((i) =>
// (this.data.month ?? []).includes(i.value) ? { ...i, checked: true } : i // (this.data.month ?? []).includes(i.value) ? { ...i, checked: true } : i
// ); // );
this.formGroup.get('vendors')?.disable()
this.foods.forEach((f) => { this.foods.forEach((f) => {
const item = { text: f.name, value: f.key }; const item = { text: f.name, value: f.key }
this.foodListOfOption.push(item); this.foodListOfOption.push(item)
this.searchedFood.push(item); this.searchedFood.push(item)
const num = this.data.ingredient.find((i: any) => i.key === f.key); const num = this.data.ingredient.find((i: any) => i.key === f.key)
if (num) { if (num) {
this.foodItemSelected.push({ num: num.value, ...item, isMain: num.isMain }); this.foodItemSelected.push({ num: num.value, ...item, isMain: num.isMain })
this.foodSelected.push(f.key); this.foodSelected.push(f.key)
} }
}); })
if (this.data["icon"]) { if (this.data['icon']) {
this.iconPreview = "/api/icon/" + this.data["icon"]; this.iconPreview = '/api/icon/' + this.data['icon']
} }
this.formGroup.patchValue({ this.formGroup.patchValue({
...this.data, ...this.data,
vendors: [this.data.vender], vendors: [this.data.vender],
mark: this.data.marks, mark: this.data.marks,
}); })
} }
} }
public getValues() { public getValues() {
let values = null; let values = null
console.log("this.formGroup.getRawValue()", this.formGroup.getRawValue(), this.foodItemSelected); console.log('this.formGroup.getRawValue()', this.formGroup.getRawValue(), this.foodItemSelected)
if (Utils.validateFormGroup(this.formGroup)) { if (Utils.validateFormGroup(this.formGroup)) {
const value = this.formGroup.getRawValue(); const value = this.formGroup.getRawValue()
// const { _nutrition, key, name, type } = this.formGroup.getRawValue(); // const { _nutrition, key, name, type } = this.formGroup.getRawValue();
let ingredient: any[] = []; let ingredient: any[] = []
for (const f of this.foodItemSelected) { for (const f of this.foodItemSelected) {
let num = Number(f.num); let num = Number(f.num)
if (!num) { if (!num) {
this.msg.error(`请输入${f.value}-${f.text}的重量`); this.msg.error(`请输入${f.value}-${f.text}的重量`)
return; return
} }
ingredient.push({ ingredient.push({
isMain: f.isMain, isMain: f.isMain,
key: f.value, key: f.value,
value: num, value: num,
}); })
} }
const month = value.month.join(","); const month = value.month.join(',')
const vendors = value.vendors?.join(",") ?? ""; const vendors = value.vendors?.join(',') ?? ''
values = { values = {
...value, ...value,
vendors, vendors,
month, month,
ingredient, ingredient,
};
} }
return values; }
return values
} }
onMainChange(e: boolean, key: string) { onMainChange(e: boolean, key: string) {
this.foodItemSelected.forEach((i) => { this.foodItemSelected.forEach((i) => {
if (e) { if (e) {
i.isMain = false; i.isMain = false
if (i.value === key) { if (i.value === key) {
i.isMain = true; i.isMain = true
} }
} else { } else {
if (i.value === key) { if (i.value === key) {
i.isMain = false; i.isMain = false
} }
} }
}); })
} }
searchOrg(value: string): void { searchOrg(value: string): void {
if (value) { if (value) {
this.orgSearch$.next({ keyword: value }); this.orgSearch$.next({ keyword: value })
} }
} }
searchFood(value: string): void { searchFood(value: string): void {
if (value) { if (value) {
this.foodSearch$.next({ keyword: value }); this.foodSearch$.next({ keyword: value })
} }
} }
onFoodSelected(v: string[]) { onFoodSelected(v: string[]) {
this.foodItemSelected = []; this.foodItemSelected = []
this.searchedFood.forEach((item) => { this.searchedFood.forEach((item) => {
if (this.foodItemSelected.some((s) => s.value === item.value)) { if (this.foodItemSelected.some((s) => s.value === item.value)) {
return; return
} }
if (v.includes(item.value)) { if (v.includes(item.value)) {
this.foodItemSelected.push(item); this.foodItemSelected.push(item)
} }
}); })
// this.foodItemSelected = this.searchedFood.filter((f) => { // this.foodItemSelected = this.searchedFood.filter((f) => {
// return v.includes(f.value) // return v.includes(f.value)
@ -213,36 +214,36 @@ export class DishFormComponent {
addFood() { addFood() {
this.food.push( this.food.push(
this.fb.group({ this.fb.group({
name: this.fb.control("", [FormValidators.required()]), name: this.fb.control('', [FormValidators.required()]),
tag: this.fb.control(0, [FormValidators.required()]), tag: this.fb.control(0, [FormValidators.required()]),
weight: this.fb.control(0, [FormValidators.required()]), weight: this.fb.control(0, [FormValidators.required()]),
}) }),
); )
} }
removeFood(idx: number) { removeFood(idx: number) {
this.food.removeAt(idx); this.food.removeAt(idx)
} }
onFileChange(e: Event) { onFileChange(e: Event) {
const target = e.target as HTMLInputElement; const target = e.target as HTMLInputElement
const file = target.files![0]; const file = target.files![0]
target.value = ""; target.value = ''
if (file.size / 1024 / 1024 >= 2) { if (file.size / 1024 / 1024 >= 2) {
this.msg.error("图片大小不能超过2M"); this.msg.error('图片大小不能超过2M')
return; return
} }
const fileReader = new FileReader(); const fileReader = new FileReader()
fileReader.onload = () => { fileReader.onload = () => {
const base64 = fileReader.result as string; const base64 = fileReader.result as string
this.iconPreview = base64; this.iconPreview = base64
// this.formGroup.get("icon")?.setValue(base64); // this.formGroup.get("icon")?.setValue(base64);
}; }
fileReader.readAsDataURL(file); fileReader.readAsDataURL(file)
const formdata = new FormData(); const formdata = new FormData()
formdata.append("file", file); formdata.append('file', file)
this.api.upload(formdata).subscribe((res) => { this.api.upload(formdata).subscribe((res) => {
this.formGroup.get("icon")?.setValue(res.body); this.formGroup.get('icon')?.setValue(res.body)
}); })
} }
} }

102
projects/admin/src/app/pages/dish/dish.component.html

@ -2,8 +2,9 @@
<ng-template #pageExtraTpl> <ng-template #pageExtraTpl>
<nz-space> <nz-space>
<button *nzSpaceItem nz-button [disabled]="!selectedIds.length" (click)="deleteItem()">批量删除</button> <button *nzSpaceItem nz-button [disabled]="!selectedIds.length" (click)="deleteItem()">批量删除</button>
<button *nzSpaceItem nz-button [disabled]="!selectedIds.length" <button *nzSpaceItem nz-button [disabled]="!selectedIds.length" (click)="printTag()">
(click)="printTag()">批量打印营养标签</button> 批量打印营养标签
</button>
<button *nzSpaceItem nz-button nzType="primary" (click)="showFoodForm()"> <button *nzSpaceItem nz-button nzType="primary" (click)="showFoodForm()">
<i nz-icon nzType="plus"></i> <i nz-icon nzType="plus"></i>
新增菜品 新增菜品
@ -11,28 +12,34 @@
</nz-space> </nz-space>
</ng-template> </ng-template>
<div class="h-full overflow-hidden bg-white rounded-lg"> <div class="h-full overflow-hidden bg-white rounded-lg">
<nz-card [nzBordered]="false" nzTitle="菜品管理"> <nz-card [nzBordered]="false" nzTitle="菜品管理">
<table-list [props]="tableList" [search]="searchTpl" [action]="pageExtraTpl" [formGroup]="queryForm" <table-list
[renderColumns]="renderColumnsTpl"> [props]="tableList"
[search]="searchTpl"
[action]="pageExtraTpl"
[formGroup]="queryForm"
[renderColumns]="renderColumnsTpl"
>
<ng-template #searchTpl> <ng-template #searchTpl>
<nz-form-item class="w-60"> <nz-form-item class="w-60">
<nz-form-control> <nz-form-control>
<nz-select <nz-select nzPlaceHolder="请选择单位" formControlName="vendors">
nzPlaceHolder="请选择单位" <nz-option
formControlName="vendors"> *ngFor="let o of listOfOption"
<nz-option *ngFor="let o of listOfOption" [nzLabel]="o.text" [nzLabel]="o.text"
[nzValue]="o.value"></nz-option> [nzValue]="o.value"
></nz-option>
</nz-select> </nz-select>
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item class="w-40"> <nz-form-item class="w-40">
<nz-form-control> <nz-form-control>
<nz-select nzPlaceHolder="菜品标签" formControlName="mark" nzAllowClear> <nz-select nzPlaceHolder="菜品标签" formControlName="mark" nzAllowClear>
<nz-option *ngFor="let item of globalEnum.mark" [nzLabel]="item.key" <nz-option
[nzValue]="item.key"> *ngFor="let item of globalEnum.mark"
[nzLabel]="item.key"
[nzValue]="item.key"
>
</nz-option> </nz-option>
</nz-select> </nz-select>
</nz-form-control> </nz-form-control>
@ -47,25 +54,31 @@
<ng-template #renderColumnsTpl let-data let-key="key" let-row="row"> <ng-template #renderColumnsTpl let-data let-key="key" let-row="row">
<ng-container [ngSwitch]="key"> <ng-container [ngSwitch]="key">
<ng-container *ngSwitchCase="'icon'"> <ng-container *ngSwitchCase="'icon'">
<div class="dish-img overflow-auto" <div
class="dish-img overflow-auto"
*ngIf="data" *ngIf="data"
[ngStyle]="{'background-image':'url(/api/icon/' + data + ')'}"> [ngStyle]="{ 'background-image': 'url(/api/icon/' + data + ')' }"
</div> ></div>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'vender'"> <ng-container *ngSwitchCase="'vender'">
{{ tableOrg[data] ? tableOrg[data].name : '-'}} {{ tableOrg[data] ? tableOrg[data].name : '-' }}
</ng-container>
<ng-container *ngSwitchCase="'label'">
<nz-tag *ngFor="let item of data ?? []">
{{ item }}
</nz-tag>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'ingredient'"> <ng-container *ngSwitchCase="'ingredient'">
<div class=" flex flex-wrap"> <div class="flex flex-wrap">
<ng-container *ngFor="let item of data"> <ng-container *ngFor="let item of data">
<nz-tag *ngIf="tableFoods[item.key]" class="m-1"> <nz-tag *ngIf="tableFoods[item.key]" class="m-1">
{{tableFoods[item.key]['name']}}:{{item.value}} g {{ tableFoods[item.key]['name'] }}:{{ item.value }} g
</nz-tag> </nz-tag>
</ng-container> </ng-container>
</div> </div>
</ng-container> </ng-container>
<ng-container *ngSwitchDefault> <ng-container *ngSwitchDefault>
{{data}} {{ data }}
</ng-container> </ng-container>
</ng-container> </ng-container>
</ng-template> </ng-template>
@ -74,51 +87,31 @@
</div> </div>
</app-page> </app-page>
<ng-template #formFooterTpl> <ng-template #formFooterTpl>
<nz-space> <nz-space>
<button *nzSpaceItem nz-button (click)="cancelForm()" type="button"> <button *nzSpaceItem nz-button (click)="cancelForm()" type="button">取消</button>
取消 <button *nzSpaceItem nz-button nzType="primary" [nzLoading]="submitLoading" (click)="onSubmit()">保存</button>
</button>
<button *nzSpaceItem nz-button nzType="primary" [nzLoading]="submitLoading" (click)="onSubmit()">
保存
</button>
</nz-space> </nz-space>
</ng-template> </ng-template>
<app-print #print [content]="printContent"> </app-print>
<app-print #print
[content]="printContent">
</app-print>
<ng-template #printContent> <ng-template #printContent>
<div class="printContent" *ngFor="let item of printData">
<div class="printContent"
*ngFor="let item of printData">
<table class="print-table"> <table class="print-table">
<tbody> <tbody>
<tr> <tr>
<th colspan="3"> <th colspan="3">
{{item.name}} {{ item.name }}
</th> </th>
</tr> </tr>
<tr> <tr>
<th colspan="3"> <th colspan="3">营养成分表</th>
营养成分表
</th>
</tr> </tr>
<tr> <tr>
<th class="text-left"> <th class="text-left">名称</th>
名称 <th class="text-center">每100克(g)</th>
</th> <th class="text-center">营养参考值%(NVR%)</th>
<th class="text-center">
每100克(g)
</th>
<th class="text-center">
营养参考值%(NVR%)
</th>
</tr> </tr>
</tbody> </tbody>
<tbody> <tbody>
@ -127,14 +120,9 @@
<td class="text-center">{{ th.nutrition }}</td> <td class="text-center">{{ th.nutrition }}</td>
<td class="text-center">{{ th.nvr }}</td> <td class="text-center">{{ th.nvr }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div> <div>主要原料:{{ item.ingredients.join(',') }}</div>
主要原料:{{item.ingredients.join(',')}} <div>1毫克(mg)钠相当于2.5毫克食盐</div>
</div>
<div>
1毫克(mg)钠相当于2.5毫克食盐
</div>
</div> </div>
</ng-template> </ng-template>

191
projects/admin/src/app/pages/dish/dish.component.ts

@ -1,9 +1,9 @@
import { Component, OnInit, TemplateRef, ViewChild } from "@angular/core"; import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'
import { FormControl, FormGroup } from "@angular/forms"; import { FormControl, FormGroup } from '@angular/forms'
import { NzDrawerRef, NzDrawerService } from "ng-zorro-antd/drawer"; import { NzDrawerRef, NzDrawerService } from 'ng-zorro-antd/drawer'
import { AnyObject, OrgDTO, TableListOption } from "@cdk/public-api"; import { AnyObject, OrgDTO, TableListOption } from '@cdk/public-api'
import { DishFormComponent } from "@admin/app/components"; import { DishFormComponent } from '@admin/app/components'
import { ApiService } from "@cdk/services"; import { ApiService } from '@cdk/services'
import { import {
Subject, Subject,
debounceTime, debounceTime,
@ -14,68 +14,68 @@ import {
switchMap, switchMap,
takeUntil, takeUntil,
tap, tap,
} from "rxjs"; } from 'rxjs'
import { NzModalService } from "ng-zorro-antd/modal"; import { NzModalService } from 'ng-zorro-antd/modal'
import { NzMessageService } from "ng-zorro-antd/message"; import { NzMessageService } from 'ng-zorro-antd/message'
import { ResponseType } from "@cdk/types"; import { ResponseType } from '@cdk/types'
import { PrintComponent } from "@cdk/shared/components"; import { PrintComponent } from '@cdk/shared/components'
@Component({ @Component({
selector: "app-dish", selector: 'app-dish',
templateUrl: "./dish.component.html", templateUrl: './dish.component.html',
styleUrls: ["./dish.component.less"], styleUrls: ['./dish.component.less'],
}) })
export class DishComponent { export class DishComponent {
constructor( constructor(
private drawer: NzDrawerService, private drawer: NzDrawerService,
private api: ApiService, private api: ApiService,
private modal: NzModalService, private modal: NzModalService,
private msg: NzMessageService private msg: NzMessageService,
) {} ) {}
@ViewChild("formFooterTpl") formFooterTpl!: TemplateRef<{}>; @ViewChild('formFooterTpl') formFooterTpl!: TemplateRef<{}>
@ViewChild("print") printRef!: PrintComponent; @ViewChild('print') printRef!: PrintComponent
private drawerRef?: NzDrawerRef; private drawerRef?: NzDrawerRef
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>()
private orgSearch$ = new Subject<string>(); private orgSearch$ = new Subject<string>()
public globalEnum = this.api.globalEnum; public globalEnum = this.api.globalEnum
public printData: any | null; public printData: any | null
public tableList = new TableListOption(this.fetchData.bind(this), { public tableList = new TableListOption(this.fetchData.bind(this), {
selectable: true, selectable: true,
frontPagination: false, frontPagination: false,
}); })
public queryForm = new FormGroup({ public queryForm = new FormGroup({
keyword: new FormControl(""), keyword: new FormControl(''),
mark: new FormControl(""), mark: new FormControl(''),
vendors: new FormControl(""), vendors: new FormControl(''),
}); })
public selectedIds: string[] = []; public selectedIds: string[] = []
tableOrg: { [k: number]: OrgDTO } = {}; tableOrg: { [k: number]: OrgDTO } = {}
tableFoods: { [k: string]: any } = {}; tableFoods: { [k: string]: any } = {}
listOfOption: Array<{ value: number | string; text: string }> = []; listOfOption: Array<{ value: number | string; text: string }> = []
nzFilterOption = (): boolean => true; nzFilterOption = (): boolean => true
submitLoading = false; submitLoading = false
ngOnInit(): void { ngOnInit(): void {
this.initTableList(); this.initTableList()
this.api.getOrgList().subscribe((res) => { this.api.getOrgList().subscribe((res) => {
this.listOfOption = res; this.listOfOption = res
}); })
// this.orgSearch$ // this.orgSearch$
// .pipe( // .pipe(
// filter((f) => !!f), // filter((f) => !!f),
@ -96,85 +96,86 @@ export class DishComponent {
// }); // });
this.tableList.getState$.pipe(takeUntil(this.destroy$)).subscribe((res) => { this.tableList.getState$.pipe(takeUntil(this.destroy$)).subscribe((res) => {
this.selectedIds = res.selectedKeys as Array<string>; this.selectedIds = res.selectedKeys as Array<string>
}); })
} }
searchOrg = (k: string) => { searchOrg = (k: string) => {
this.orgSearch$.next(k); this.orgSearch$.next(k)
}; }
ngOnDestroy(): void { ngOnDestroy(): void {
this.destroy$.next(); this.destroy$.next()
this.destroy$.complete(); this.destroy$.complete()
} }
initTableList() { initTableList() {
this.tableList.scroll = { x: null }; this.tableList.scroll = { x: null }
this.tableList = this.tableList.setColumns([ this.tableList = this.tableList.setColumns([
{ key: "icon", title: "菜品图片", width: "66px" }, { key: 'icon', title: '菜品图片', width: '66px' },
{ key: "name", title: "菜品名称" }, { key: 'name', title: '菜品名称' },
{ key: "marks", title: "菜品标签" }, { key: 'marks', title: '菜品标签' },
{ key: "poly", title: "烹饪方式" }, { key: 'label', title: '三低菜品标识' },
{ key: "ingredient", title: "食材及含量", width: "30%" }, { key: 'poly', title: '烹饪方式' },
{ key: "vender", title: "单位" }, { key: 'ingredient', title: '食材及含量', width: '30%' },
]); { key: 'vender', title: '单位' },
])
this.tableList = this.tableList.setOptions([ this.tableList = this.tableList.setOptions([
{ {
title: "打印营养标签", title: '打印营养标签',
premissions: [], premissions: [],
onClick: this.printTag.bind(this), onClick: this.printTag.bind(this),
}, },
{ {
title: "编辑", title: '编辑',
premissions: [], premissions: [],
onClick: this.showFoodForm.bind(this), onClick: this.showFoodForm.bind(this),
}, },
{ {
title: "删除", title: '删除',
premissions: [], premissions: [],
onClick: this.deleteItem.bind(this), onClick: this.deleteItem.bind(this),
}, },
]); ])
} }
printTag(v?: any) { printTag(v?: any) {
this.msg.loading("数据请求中,请不要刷新页面", { this.msg.loading('数据请求中,请不要刷新页面', {
nzDuration: 0, nzDuration: 0,
}); })
const ids = v ? [v.id] : this.selectedIds; const ids = v ? [v.id] : this.selectedIds
this.api this.api
.getDishLabel(ids) .getDishLabel(ids)
.pipe( .pipe(
finalize(() => { finalize(() => {
setTimeout(() => { setTimeout(() => {
this.msg.remove(); this.msg.remove()
}, 1000); }, 1000)
}) }),
) )
.subscribe((res) => { .subscribe((res) => {
this.printData = res.body; this.printData = res.body
this.printRef.print(); this.printRef.print()
}); })
} }
fetchData(query: AnyObject, pager: AnyObject) { fetchData(query: AnyObject, pager: AnyObject) {
return this.api.getDishPage(pager, query).pipe( return this.api.getDishPage(pager, query).pipe(
tap((res) => { tap((res) => {
this.getTableColumData(res); this.getTableColumData(res)
}) }),
); )
} }
getTableColumData(res: ResponseType) { getTableColumData(res: ResponseType) {
if (Array.isArray(res.body.content)) { if (Array.isArray(res.body.content)) {
const vendors = res.body.content.map((i: any) => i.vender); const vendors = res.body.content.map((i: any) => i.vender)
const foodKeys = new Set( const foodKeys = new Set(
res.body.content.reduce((a: string[], c: any) => { res.body.content.reduce((a: string[], c: any) => {
return a.concat(c.ingredient.map((i: any) => i.key)); return a.concat(c.ingredient.map((i: any) => i.key))
}, [] as string[]) }, [] as string[]),
); )
if (vendors.length > 0) { if (vendors.length > 0) {
this.api.getOrgList({ vendors }).subscribe((org) => { this.api.getOrgList({ vendors }).subscribe((org) => {
@ -183,10 +184,10 @@ export class DishComponent {
return { return {
...a, ...a,
[c.id]: c, [c.id]: c,
};
}, {} as AnyObject);
} }
}); }, {} as AnyObject)
}
})
} }
if (foodKeys.size > 0) { if (foodKeys.size > 0) {
this.api.getFoodList({ keys: Array.from(foodKeys) }).subscribe((foods) => { this.api.getFoodList({ keys: Array.from(foodKeys) }).subscribe((foods) => {
@ -195,18 +196,18 @@ export class DishComponent {
return { return {
...a, ...a,
[c.key]: c, [c.key]: c,
};
}, {} as AnyObject);
console.log("this.tableFoods", this.tableFoods);
} }
}); }, {} as AnyObject)
console.log('this.tableFoods', this.tableFoods)
}
})
} }
} }
} }
showFoodForm(data?: any) { showFoodForm(data?: any) {
this.drawerRef = this.drawer.create({ this.drawerRef = this.drawer.create({
nzTitle: data ? "编辑菜品" : "新增菜品", nzTitle: data ? '编辑菜品' : '新增菜品',
nzWidth: 700, nzWidth: 700,
nzContent: DishFormComponent, nzContent: DishFormComponent,
nzContentParams: { nzContentParams: {
@ -215,46 +216,46 @@ export class DishComponent {
foods: Object.values(this.tableFoods), foods: Object.values(this.tableFoods),
}, },
nzFooter: this.formFooterTpl, nzFooter: this.formFooterTpl,
}); })
} }
cancelForm() { cancelForm() {
this.drawerRef?.close(); this.drawerRef?.close()
} }
onSubmit() { onSubmit() {
if (this.drawerRef) { if (this.drawerRef) {
const com = this.drawerRef.getContentComponent() as DishFormComponent; const com = this.drawerRef.getContentComponent() as DishFormComponent
const val = com.getValues(); const val = com.getValues()
if (val) { if (val) {
this.submitLoading = true; this.submitLoading = true
this.api this.api
.saveDish(val) .saveDish(val)
.pipe( .pipe(
finalize(() => { finalize(() => {
this.submitLoading = false; this.submitLoading = false
}) }),
) )
.subscribe((res) => { .subscribe((res) => {
this.msg.success(res.desc); this.msg.success(res.desc)
this.tableList.run(); this.tableList.run()
this.cancelForm(); this.cancelForm()
}); })
} }
} }
} }
deleteItem(v?: any) { deleteItem(v?: any) {
const ids = v ? [v.id] : this.selectedIds; const ids = v ? [v.id] : this.selectedIds
this.modal.confirm({ this.modal.confirm({
nzTitle: "警告", nzTitle: '警告',
nzContent: `是否要删除${ids.length}个菜品?`, nzContent: `是否要删除${ids.length}个菜品?`,
nzOkDanger: true, nzOkDanger: true,
nzOnOk: async () => { nzOnOk: async () => {
const res = await lastValueFrom(this.api.deleteDish(ids)); const res = await lastValueFrom(this.api.deleteDish(ids))
this.msg.success(res.desc); this.msg.success(res.desc)
this.tableList.run(); this.tableList.run()
}, },
}); })
} }
} }

83
projects/admin/src/app/pages/standard/standard-form/standard-form.component.html

@ -1,20 +1,15 @@
<app-page> <app-page>
<form nz-form [formGroup]="formGroup" nzLayout="vertical"> <form nz-form [formGroup]="formGroup" nzLayout="vertical">
<nz-card nzTitle="新增营养标准"> <nz-card nzTitle="新增营养标准">
<nz-form-item> <nz-form-item>
<nz-form-label nzRequired> <nz-form-label nzRequired> 营养标准名称 </nz-form-label>
营养标准名称
</nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl" nzSpan="12"> <nz-form-control [nzErrorTip]="formControlErrorTpl" nzSpan="12">
<input nz-input placeholder="请输入营养标准名称" formControlName="name" /> <input nz-input placeholder="请输入营养标准名称" formControlName="name" />
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label nzRequired> <nz-form-label nzRequired> 默认溢出范围 </nz-form-label>
溢出范围
</nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl" nzSpan="12"> <nz-form-control [nzErrorTip]="formControlErrorTpl" nzSpan="12">
<nz-input-group nzAddOnBefore="±" nzAddOnAfter="%"> <nz-input-group nzAddOnBefore="±" nzAddOnAfter="%">
<input nz-input formControlName="overflow" /> <input nz-input formControlName="overflow" />
@ -22,6 +17,37 @@
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item>
<nz-form-label nzRequired> 营养素溢出范围 </nz-form-label>
<nz-form-control nzSpan="12">
<nz-table nzTemplateMode nzBordered nzSize="small">
<thead>
<tr>
<th>营养素</th>
<th>溢出范围</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of api.globalEnum.nutrient">
<td>
{{ item.value }}
</td>
<td>
<nz-input-group nzAddOnBefore="±" nzAddOnAfter="%">
<input
nz-input
min="0"
[(ngModel)]="overflows[item.key]"
[ngModelOptions]="{ standalone: true }"
/>
</nz-input-group>
</td>
</tr>
</tbody>
</nz-table>
</nz-form-control>
</nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label nzRequired> <nz-form-label nzRequired>
<div class="flex justify-between items-center w-full"> <div class="flex justify-between items-center w-full">
@ -46,10 +72,11 @@
</div> </div>
</nz-form-label> </nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl" nzSpan="12"> <nz-form-control [nzErrorTip]="formControlErrorTpl" nzSpan="12">
<nz-checkbox-group class="org-list" <nz-checkbox-group
class="org-list"
[(ngModel)]="listOfOption" [(ngModel)]="listOfOption"
[ngModelOptions]="{standalone: true}"> [ngModelOptions]="{ standalone: true }"
>
</nz-checkbox-group> </nz-checkbox-group>
<!-- <nz-select <!-- <nz-select
[nzMode]="'multiple'" [nzMode]="'multiple'"
@ -75,31 +102,25 @@
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-control [nzErrorTip]="formControlErrorTpl" nzSpan="12"> <nz-form-control [nzErrorTip]="formControlErrorTpl" nzSpan="12"> </nz-form-control>
<nz-space>
<button *nzSpaceItem nz-button nzType="primary" (click)="onSubmit(true)"
[nzLoading]="submitLoading">
保存并设置营养标准
</button>
<button *nzSpaceItem nz-button nzType="primary" (click)="onSubmit()"
[nzLoading]="submitLoading">
保存
</button>
<button *nzSpaceItem nz-button type="button" [routerLink]="['/standard/list']">
取消
</button>
</nz-space>
</nz-form-control>
</nz-form-item> </nz-form-item>
</nz-card> </nz-card>
</form> </form>
<ng-template #formControlErrorTpl let-control> <ng-template #formControlErrorTpl let-control>
<form-error-tips [control]="control"></form-error-tips> <form-error-tips [control]="control"></form-error-tips>
</ng-template> </ng-template>
<div class="fixed-footter left-[218px] fixed bottom-0 right-0 bg-white z-10 py-2">
<div class="flex pl-12">
<nz-space>
<button *nzSpaceItem nz-button nzType="primary" (click)="onSubmit(true)" [nzLoading]="submitLoading">
保存并设置营养标准
</button>
<button *nzSpaceItem nz-button nzType="primary" (click)="onSubmit()" [nzLoading]="submitLoading">
保存
</button>
<button *nzSpaceItem nz-button type="button" [routerLink]="['/standard/list']">取消</button>
</nz-space>
</div>
</div>
</app-page> </app-page>

116
projects/admin/src/app/pages/standard/standard-form/standard-form.component.ts

@ -1,93 +1,84 @@
import { Component } from "@angular/core"; import { Component } from '@angular/core'
import { FormBuilder, FormGroup } from "@angular/forms"; import { FormBuilder, FormGroup } from '@angular/forms'
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from '@angular/router'
import { OrgDTO } from "@cdk/dtos"; import { OrgDTO } from '@cdk/dtos'
import { ApiService } from "@cdk/services"; import { ApiService } from '@cdk/services'
import { OptionItemInterface } from "@cdk/types"; import { OptionItemInterface } from '@cdk/types'
import { Utils } from "@cdk/utils"; import { Utils } from '@cdk/utils'
import { FormValidators } from "@cdk/validators"; import { FormValidators } from '@cdk/validators'
import { NzMessageService } from "ng-zorro-antd/message"; import { NzMessageService } from 'ng-zorro-antd/message'
import { Subject, debounceTime, distinctUntilChanged, filter, finalize, map, switchMap } from "rxjs"; import { Subject, debounceTime, distinctUntilChanged, filter, finalize, map, switchMap } from 'rxjs'
interface CheckboxItem { interface CheckboxItem {
value: number | string; value: number | string
label: string; label: string
checked?: boolean; checked?: boolean
} }
@Component({ @Component({
selector: "app-standard-form", selector: 'app-standard-form',
templateUrl: "./standard-form.component.html", templateUrl: './standard-form.component.html',
styleUrls: ["./standard-form.component.less"], styleUrls: ['./standard-form.component.less'],
}) })
export class StandardFormComponent { export class StandardFormComponent {
constructor( constructor(
private fb: FormBuilder, private fb: FormBuilder,
private router: Router, private router: Router,
private route: ActivatedRoute, private route: ActivatedRoute,
private api: ApiService, public api: ApiService,
private msg: NzMessageService private msg: NzMessageService,
) { ) {
const id = this.route.snapshot.paramMap.get("id"); const id = this.route.snapshot.paramMap.get('id')
if (id !== "create") { if (id !== 'create') {
const data = this.router.getCurrentNavigation()?.extras; const data = this.router.getCurrentNavigation()?.extras
if (data) { if (data) {
this.state = data.state; this.state = data.state
} else { } else {
this.router.navigate(["/standard/list"]); this.router.navigate(['/standard/list'])
} }
} }
} }
private orgSearch$ = new Subject<string>(); private orgSearch$ = new Subject<string>()
formGroup!: FormGroup; formGroup!: FormGroup
submitLoading = false; submitLoading = false
orgList: OrgDTO[] = []; orgList: OrgDTO[] = []
state: any; state: any
vendors: OrgDTO[] = []; vendors: OrgDTO[] = []
// nzPopoverVisible = false; // nzPopoverVisible = false;
listOfOption: (OrgDTO & CheckboxItem)[] = []; listOfOption: (OrgDTO & CheckboxItem)[] = []
nzFilterOption = (): boolean => true; nzFilterOption = (): boolean => true
overflows: Record<string, number> = {}
ngOnInit(): void { ngOnInit(): void {
console.log('this.api.globalEnum.nutrient', this.api.globalEnum.nutrient)
this.formGroup = this.fb.group({ this.formGroup = this.fb.group({
id: this.fb.control("", []), id: this.fb.control('', []),
name: this.fb.control("", [FormValidators.required()]), name: this.fb.control('', [FormValidators.required()]),
// vendors: this.fb.control([], [FormValidators.required()]), // vendors: this.fb.control([], [FormValidators.required()]),
overflow: this.fb.control("0", [FormValidators.required()]), overflow: this.fb.control('0', [FormValidators.required()]),
}); })
if (this.state) { if (this.state) {
this.formGroup.patchValue(this.state); this.formGroup.patchValue(this.state)
// if (Array.isArray(this.state.vendors)) { this.overflows = this.state?.overflows ?? {}
// this.api.getOrgList({ vendors: this.state.vendors }).subscribe((data) => {
// const listOfOption: Array<{ value: number; label: string }> = [];
// data.forEach((item) => {
// listOfOption.push({
// value: item.id,
// label: item.name,
// });
// });
// this.listOfOption = listOfOption;
// this.formGroup.patchValue(this.state);
// });
// }
} }
this.api.getOrgList().subscribe((res) => { this.api.getOrgList().subscribe((res) => {
this.listOfOption = res as unknown as (OrgDTO & CheckboxItem)[]; this.listOfOption = res as unknown as (OrgDTO & CheckboxItem)[]
if (Array.isArray(this.state?.vendors)) { if (Array.isArray(this.state?.vendors)) {
this.listOfOption = this.listOfOption.map((i) => { this.listOfOption = this.listOfOption.map((i) => {
return { ...i, checked: this.state.vendors.includes(i.id) }; return { ...i, checked: this.state.vendors.includes(i.id) }
}); })
} }
}); })
// this.orgSearch$ // this.orgSearch$
// .pipe( // .pipe(
// filter((f) => !!f), // filter((f) => !!f),
@ -108,8 +99,8 @@ export class StandardFormComponent {
} }
searchOrg = (k: string) => { searchOrg = (k: string) => {
this.orgSearch$.next(k); this.orgSearch$.next(k)
}; }
// onSelectOrg(v: OptionItemInterface[]) { // onSelectOrg(v: OptionItemInterface[]) {
// v.forEach((i) => { // v.forEach((i) => {
@ -128,27 +119,28 @@ export class StandardFormComponent {
onSubmit(gotoSetting?: boolean) { onSubmit(gotoSetting?: boolean) {
if (Utils.validateFormGroup(this.formGroup)) { if (Utils.validateFormGroup(this.formGroup)) {
this.submitLoading = true; this.submitLoading = true
this.api this.api
.saveStandard({ .saveStandard({
foodCategoryDay: this.state?.foodCategoryDay, foodCategoryDay: this.state?.foodCategoryDay,
foodCategoryWeek: this.state?.foodCategoryWeek, foodCategoryWeek: this.state?.foodCategoryWeek,
ingredient: this.state?.ingredient, ingredient: this.state?.ingredient,
overflows: this.overflows,
...this.formGroup.value, ...this.formGroup.value,
vendors: this.listOfOption.filter((f) => f.checked).map((i) => i.id), vendors: this.listOfOption.filter((f) => f.checked).map((i) => i.id),
}) })
.pipe( .pipe(
finalize(() => { finalize(() => {
this.submitLoading = false; this.submitLoading = false
}) }),
) )
.subscribe((res) => { .subscribe((res) => {
this.msg.success(res.desc); this.msg.success(res.desc)
const redirectTo = gotoSetting ? ["/", "standard", "setting", res.body.id] : ["/standard/list"]; const redirectTo = gotoSetting ? ['/', 'standard', 'setting', res.body.id] : ['/standard/list']
this.router.navigate(redirectTo, { this.router.navigate(redirectTo, {
state: res.body, state: res.body,
}); })
}); })
} }
} }
} }

80
projects/admin/src/app/pages/standard/standard-list/standard-list.component.ts

@ -1,93 +1,97 @@
import { Component, OnInit, TemplateRef, ViewChild } from "@angular/core"; import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'
import { FormControl, FormGroup } from "@angular/forms"; import { FormControl, FormGroup } from '@angular/forms'
import { NzDrawerRef, NzDrawerService } from "ng-zorro-antd/drawer"; import { NzDrawerRef, NzDrawerService } from 'ng-zorro-antd/drawer'
import { AnyObject, TableListOption } from "@cdk/public-api"; import { AnyObject, TableListOption } from '@cdk/public-api'
import { DishFormComponent } from "@admin/app/components"; import { DishFormComponent } from '@admin/app/components'
import { ApiService } from "@cdk/services"; import { ApiService } from '@cdk/services'
import { NzModalService } from "ng-zorro-antd/modal"; import { NzModalService } from 'ng-zorro-antd/modal'
import { lastValueFrom } from "rxjs"; import { lastValueFrom } from 'rxjs'
import { NzMessageService } from "ng-zorro-antd/message"; import { NzMessageService } from 'ng-zorro-antd/message'
import { Router } from "@angular/router"; import { Router } from '@angular/router'
import { StandardService } from '../standard.service'
@Component({ @Component({
selector: "app-standard-list", selector: 'app-standard-list',
templateUrl: "./standard-list.component.html", templateUrl: './standard-list.component.html',
styleUrls: ["./standard-list.component.less"], styleUrls: ['./standard-list.component.less'],
}) })
export class StandardListComponent { export class StandardListComponent {
constructor( constructor(
private api: ApiService, private api: ApiService,
private modal: NzModalService, private modal: NzModalService,
private msg: NzMessageService, private msg: NzMessageService,
private router: Router private router: Router,
private standard: StandardService,
) {} ) {}
public tableList = new TableListOption(this.fetchData.bind(this), { public tableList = new TableListOption(this.fetchData.bind(this), {
frontPagination: false, frontPagination: false,
}); })
public queryForm = new FormGroup({ public queryForm = new FormGroup({
keyword: new FormControl(""), keyword: new FormControl(''),
}); })
ngOnInit(): void { ngOnInit(): void {
this.initTableList(); this.initTableList()
} }
initTableList() { initTableList() {
this.tableList.scroll = { x: null }; this.tableList.scroll = { x: null }
this.tableList = this.tableList.setColumns([ this.tableList = this.tableList.setColumns([
{ key: "name", title: "营养标准名称" }, { key: 'name', title: '营养标准名称' },
{ key: "people", title: "人群细分" }, { key: 'people', title: '人群细分' },
{ key: "vendors", title: "适用单位" }, { key: 'vendors', title: '适用单位' },
{ key: "modify", title: "更新时间" }, { key: 'modify', title: '更新时间' },
]); ])
this.tableList = this.tableList.setOptions([ this.tableList = this.tableList.setOptions([
{ {
title: "标准设置", title: '标准设置',
premissions: [], premissions: [],
onClick: this.toSetting.bind(this), onClick: this.toSetting.bind(this),
}, },
{ {
title: "编辑", title: '编辑',
premissions: [], premissions: [],
onClick: this.toEdit.bind(this), onClick: this.toEdit.bind(this),
}, },
{ {
title: "删除", title: '删除',
premissions: [], premissions: [],
onClick: this.deleteItem.bind(this), onClick: this.deleteItem.bind(this),
}, },
]); ])
} }
fetchData(query: AnyObject, pager: AnyObject) { fetchData(query: AnyObject, pager: AnyObject) {
return this.api.getStandardPage(pager, query); return this.api.getStandardPage(pager, query)
} }
toEdit(d: AnyObject) { toEdit(d: AnyObject) {
this.router.navigate([`/standard/form/${d["id"]}`], { // this.standard.settingData$.next(d)
this.router.navigate([`/standard/form/${d['id']}`], {
state: d, state: d,
}); })
} }
toSetting(d: AnyObject) { toSetting(d: AnyObject) {
this.router.navigate([`/standard/setting/${d["id"]}`], { // this.standard.settingData$.next(d)
this.router.navigate([`/standard/setting/${d['id']}`], {
state: d, state: d,
}); })
} }
deleteItem(v: any) { deleteItem(v: any) {
this.modal.confirm({ this.modal.confirm({
nzTitle: "警告", nzTitle: '警告',
nzContent: `是否要删除该营养标准?`, nzContent: `是否要删除该营养标准?`,
nzOkDanger: true, nzOkDanger: true,
nzOnOk: async () => { nzOnOk: async () => {
const res = await lastValueFrom(this.api.deleteStandard(v.id)); const res = await lastValueFrom(this.api.deleteStandard(v.id))
this.msg.success(res.desc); this.msg.success(res.desc)
this.tableList.run(); this.tableList.run()
}, },
}); })
} }
} }

188
projects/admin/src/app/pages/standard/standard-setting/standard-setting.component.html

@ -199,17 +199,13 @@
<app-page> <app-page>
<div nz-form nzLayout="vertical"> <div nz-form nzLayout="vertical">
<ng-template #calcTypeTpl> <ng-template #calcTypeTpl>
<nz-space> <nz-space>
<div *nzSpaceItem> <div *nzSpaceItem>计算单位:</div>
计算单位:
</div>
<div *nzSpaceItem> <div *nzSpaceItem>
<nz-radio-group [(ngModel)]="calcType" (ngModelChange)="calcTypeChange()"> <nz-radio-group [(ngModel)]="calcType" (ngModelChange)="calcTypeChange()">
<label nz-radio [nzValue]="item.key" <label nz-radio [nzValue]="item.key" *ngFor="let item of globalEnum.measurementType">
*ngFor="let item of globalEnum.measurementType"> {{ item.value }}
{{item.value}}
</label> </label>
</nz-radio-group> </nz-radio-group>
</div> </div>
@ -220,26 +216,34 @@
<div nz-row [nzGutter]="12"> <div nz-row [nzGutter]="12">
<div nz-col nzSpan="24"> <div nz-col nzSpan="24">
<ng-template #dayBtnTpl> <ng-template #dayBtnTpl>
<button nz-button nzType="link" (click)="addFoodType('day')"> <button nz-button nzType="link" (click)="addFoodType('day')">添加食物种类</button>
添加食物种类
</button>
</ng-template> </ng-template>
<nz-card nzType="inner" nzTitle="食物种类及数量标准(日)" [nzExtra]="dayBtnTpl"> <nz-card nzType="inner" nzTitle="食物种类及数量标准(日)" [nzExtra]="dayBtnTpl">
<nz-empty *ngIf="foodCategoryDay.length === 0"></nz-empty> <nz-empty *ngIf="foodCategoryDay.length === 0"></nz-empty>
<div *ngIf="foodCategoryDay.length > 0"> <div *ngIf="foodCategoryDay.length > 0">
<div class=""> <div class="">
<div nz-row [nzGutter]="[48,12]"> <div nz-row [nzGutter]="[48, 12]">
<div nz-col nzSpan="24" *ngFor="let d of foodCategoryDay;let i = index"> <div nz-col nzSpan="24" *ngFor="let d of foodCategoryDay; let i = index">
<div class="flex items-center"> <div class="flex items-center">
<div class="w-50"> <div class="w-50">
<input nz-input [(ngModel)]="d.name" placeholder="请输入自定义规则名称" /> <input
nz-input
[(ngModel)]="d.name"
placeholder="请输入自定义规则名称"
/>
</div> </div>
<div class="flex-1 pl-2"> <div class="flex-1 pl-2">
<nz-select nzPlaceHolder="请选择包含食物种类(多选时数量合并计算)" nzMode="multiple" <nz-select
class="w-full" [(ngModel)]="d.category"> nzPlaceHolder="请选择包含食物种类(多选时数量合并计算)"
<nz-option *ngFor="let cate of globalEnum.category" nzMode="multiple"
class="w-full"
[(ngModel)]="d.category"
>
<nz-option
*ngFor="let cate of globalEnum.category"
[nzLabel]="cate.key" [nzLabel]="cate.key"
[nzValue]="cate.value"> [nzValue]="cate.value"
>
</nz-option> </nz-option>
</nz-select> </nz-select>
</div> </div>
@ -248,26 +252,28 @@
<div class="flex-1"> <div class="flex-1">
<input type="number" min="0" nz-input [(ngModel)]="d.min" /> <input type="number" min="0" nz-input [(ngModel)]="d.min" />
</div> </div>
<div class="flex items-center justify-center px-2"> <div class="flex items-center justify-center px-2">~</div>
~
</div>
<div class="flex-1"> <div class="flex-1">
<input type="number" placeholder="不填即不限" min="0" nz-input <input
[(ngModel)]="d.max" /> type="number"
placeholder="不填即不限"
min="0"
nz-input
[(ngModel)]="d.max"
/>
</div> </div>
</div> </div>
</div> </div>
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
{{calcTypeText}} {{ calcTypeText }}
</div> </div>
<div> <div>
<button nz-button nzType="link" (click)="removeFoodType('day',i)"> <button nz-button nzType="link" (click)="removeFoodType('day', i)">
<i nz-icon nzType="delete"></i> <i nz-icon nzType="delete"></i>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@ -275,26 +281,34 @@
</div> </div>
<div nz-col nzSpan="24" class="mt-4"> <div nz-col nzSpan="24" class="mt-4">
<ng-template #weekBtnTpl> <ng-template #weekBtnTpl>
<button nz-button nzType="link" (click)="addFoodType('week')"> <button nz-button nzType="link" (click)="addFoodType('week')">添加食物种类</button>
添加食物种类
</button>
</ng-template> </ng-template>
<nz-card nzType="inner" nzTitle="食物种类及数量标准(周)" [nzExtra]="weekBtnTpl"> <nz-card nzType="inner" nzTitle="食物种类及数量标准(周)" [nzExtra]="weekBtnTpl">
<nz-empty *ngIf="foodCategoryWeek.length === 0"></nz-empty> <nz-empty *ngIf="foodCategoryWeek.length === 0"></nz-empty>
<div *ngIf="foodCategoryWeek.length > 0"> <div *ngIf="foodCategoryWeek.length > 0">
<div class=""> <div class="">
<div nz-row [nzGutter]="[48,12]"> <div nz-row [nzGutter]="[48, 12]">
<div nz-col nzSpan="24" *ngFor="let w of foodCategoryWeek;let i = index"> <div nz-col nzSpan="24" *ngFor="let w of foodCategoryWeek; let i = index">
<div class="flex items-center"> <div class="flex items-center">
<div class="w-50"> <div class="w-50">
<input nz-input [(ngModel)]="w.name" placeholder="请输入自定义规则名称" /> <input
nz-input
[(ngModel)]="w.name"
placeholder="请输入自定义规则名称"
/>
</div> </div>
<div class="flex-1 pl-2"> <div class="flex-1 pl-2">
<nz-select nzPlaceHolder="请选择包含食物种类(多选时数量合并计算)" nzMode="multiple" <nz-select
class="w-full" [(ngModel)]="w.category"> nzPlaceHolder="请选择包含食物种类(多选时数量合并计算)"
<nz-option *ngFor="let cate of globalEnum.category" nzMode="multiple"
class="w-full"
[(ngModel)]="w.category"
>
<nz-option
*ngFor="let cate of globalEnum.category"
[nzLabel]="cate.key" [nzLabel]="cate.key"
[nzValue]="cate.value"> [nzValue]="cate.value"
>
</nz-option> </nz-option>
</nz-select> </nz-select>
</div> </div>
@ -303,26 +317,28 @@
<div class="flex-1"> <div class="flex-1">
<input type="number" min="0" nz-input [(ngModel)]="w.min" /> <input type="number" min="0" nz-input [(ngModel)]="w.min" />
</div> </div>
<div class="flex items-center justify-center px-2"> <div class="flex items-center justify-center px-2">~</div>
~
</div>
<div class="flex-1"> <div class="flex-1">
<input type="number" placeholder="不填即不限" min="0" nz-input <input
[(ngModel)]="w.max" /> type="number"
placeholder="不填即不限"
min="0"
nz-input
[(ngModel)]="w.max"
/>
</div> </div>
</div> </div>
</div> </div>
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
{{calcTypeText}} {{ calcTypeText }}
</div> </div>
<div> <div>
<button nz-button nzType="link" (click)="removeFoodType('week',i)"> <button nz-button nzType="link" (click)="removeFoodType('week', i)">
<i nz-icon nzType="delete"></i> <i nz-icon nzType="delete"></i>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@ -332,113 +348,121 @@
</nz-card> </nz-card>
<nz-card nzTitle="营养标准人群设置" class="mt-4 mb-20" [nzExtra]="peopleExtraTpl"> <nz-card nzTitle="营养标准人群设置" class="mt-4 mb-20" [nzExtra]="peopleExtraTpl">
<div #intersectionTpl></div>
<nz-empty *ngIf="ingredient.length === 0"></nz-empty> <nz-empty *ngIf="ingredient.length === 0"></nz-empty>
<ng-container *ngIf="ingredient.length > 0"> <ng-container *ngIf="ingredient.length > 0">
<nz-card <nz-card
nzType="inner" nzType="inner"
[nzTitle]="peopleGroupNamTpl" [nzTitle]="peopleGroupNamTpl"
[nzExtra]="peopleActionTpl" [nzExtra]="peopleActionTpl"
*ngFor="let p of ingredient;let i = index" [nzBodyStyle]="{ padding: 0 }"
class="mt-4"> *ngFor="let p of ingredient; let i = index; let first = first; let last = last"
class="mt-4"
>
<ng-template #peopleGroupNamTpl> <ng-template #peopleGroupNamTpl>
<div class="flex items-center"> <div class="flex items-center">
<span class="mr-2"> <span class="mr-2"> 人群名称: </span>
人群名称: <div class="w-80">
</span>
<div class=" w-80">
<input nz-input placeholder="请输入人群名称" [(ngModel)]="p.name" /> <input nz-input placeholder="请输入人群名称" [(ngModel)]="p.name" />
</div> </div>
</div> </div>
</ng-template> </ng-template>
<ng-template #peopleActionTpl> <ng-template #peopleActionTpl>
<nz-space> <nz-space>
<button *nzSpaceItem nz-button nzType="link" (click)="addNutrition(i)"> <button *nzSpaceItem nz-button (click)="addNutrition(i)">添加营养素</button>
添加营养素 <button *nzSpaceItem nz-button (click)="removePeopleGroup(i)" nzDanger>删除人群</button>
</button> <button *nzSpaceItem nz-button (click)="moveUp(i)" [disabled]="first">上移</button>
<button *nzSpaceItem nz-button nzType="link" (click)="removePeopleGroup(i)" nzDanger> <button *nzSpaceItem nz-button (click)="moveDown(i)" [disabled]="last">下移</button>
删除人群 <button *nzSpaceItem nz-button (click)="expand(i)">
{{ expanded.has(i) ? '收起' : '展开' }}
</button> </button>
</nz-space> </nz-space>
</ng-template> </ng-template>
<div class="p-4" *ngIf="expanded.has(i)">
<nz-empty *ngIf="p.nutritions.length === 0"></nz-empty> <nz-empty *ngIf="p.nutritions.length === 0"></nz-empty>
<ng-container *ngIf="p.nutritions.length > 0"> <ng-container *ngIf="p.nutritions.length > 0">
<h3 class="mb-2">每人每天能量和营养素供给量标准</h3> <h3 class="mb-2">每人每天能量和营养素供给量标准</h3>
<div nz-row [nzGutter]="[48,12]"> <div nz-row [nzGutter]="[48, 12]">
<div nz-col nzSpan="24" nzXXl="12" *ngFor="let n of p.nutritions"> <div nz-col nzSpan="24" nzXXl="12" *ngFor="let n of p.nutritions">
<div class="flex items-center"> <div class="flex items-center">
<div class="w-40"> <div class="w-40">
<nz-select nzPlaceHolder="请选择" class="w-full" [(ngModel)]="n.nutrition"> <nz-select nzPlaceHolder="请选择" class="w-full" [(ngModel)]="n.nutrition">
<nz-option *ngFor="let cate of globalEnum.nutrient " <nz-option
*ngFor="let cate of globalEnum.nutrient"
[nzLabel]="cate.value + '(' + cate.measurement + ')'" [nzLabel]="cate.value + '(' + cate.measurement + ')'"
[nzValue]="cate.key"> [nzValue]="cate.key"
>
</nz-option> </nz-option>
</nz-select> </nz-select>
</div> </div>
<div class="flex-1 ml-2"> <div class="flex-1 ml-2">
<div class="flex"> <div class="flex">
<input nz-input placeholder="最小值" type="number" <input
[(ngModel)]="n.min" /> nz-input
<div class="px-2 w-10"> placeholder="最小值"
~ type="number"
</div> [(ngModel)]="n.min"
<input nz-input placeholder="最大值" type="number" />
[(ngModel)]="n.max" /> <div class="px-2 w-10">~</div>
<input
nz-input
placeholder="最大值"
type="number"
[(ngModel)]="n.max"
/>
</div> </div>
</div> </div>
<div class="ml-2"> <div class="ml-2">
<nz-space nzAlign="center"> <nz-space nzAlign="center">
<div class="pr-3" *nzSpaceItem> <div class="pr-3" *nzSpaceItem>
<label nz-checkbox [(ngModel)]="n.hasUl" <label
(ngModelChange)="ulChange(i,n.nutrition,$event)"> nz-checkbox
[(ngModel)]="n.hasUl"
(ngModelChange)="ulChange(i, n.nutrition, $event)"
>
ul值 ul值
</label> </label>
</div> </div>
<div *nzSpaceItem class="w-20"> <div *nzSpaceItem class="w-20">
<ng-container> <ng-container>
<input [disabled]="!n.hasUl" <input
[disabled]="!n.hasUl"
nz-input nz-input
placeholder=" ul值" placeholder=" ul值"
type="number" type="number"
[(ngModel)]="n.ul" /> [(ngModel)]="n.ul"
/>
</ng-container> </ng-container>
</div> </div>
</nz-space> </nz-space>
</div> </div>
<div class="ml-2"> <div class="ml-2">
<button nz-button nzType="link" (click)="removeNutrition(i,n.nutrition)"> <button nz-button nzType="link" (click)="removeNutrition(i, n.nutrition)">
<i nz-icon nzType="delete"></i> <i nz-icon nzType="delete"></i>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</ng-container> </ng-container>
</div>
</nz-card> </nz-card>
</ng-container> </ng-container>
</nz-card> </nz-card>
<ng-template #peopleExtraTpl> <ng-template #peopleExtraTpl>
<button nz-button (click)="addPeopleGroup()"> <button nz-button (click)="addPeopleGroup()">添加人群</button>
添加人群
</button>
</ng-template> </ng-template>
</div> </div>
<ng-template #formControlErrorTpl let-control> <ng-template #formControlErrorTpl let-control>
<form-error-tips [control]="control"></form-error-tips> <form-error-tips [control]="control"></form-error-tips>
</ng-template> </ng-template>
<div class="fixed-footter left-[218px] fixed bottom-0 right-0 bg-white z-10 py-2 "> <div class="fixed-footter left-[218px] fixed bottom-0 right-0 bg-white z-10 py-2">
<div class="flex justify-center"> <div class="flex justify-center">
<nz-space> <nz-space>
<button *nzSpaceItem nz-button nzType="primary" (click)="onSubmit()"> <button *nzSpaceItem nz-button nzType="primary" (click)="onSubmit()">确定</button>
确定 <button *nzSpaceItem nz-button routerLink="/standard/list">取消</button>
</button>
<button *nzSpaceItem nz-button routerLink="/standard/list">
取消
</button>
</nz-space> </nz-space>
</div> </div>
</div> </div>

261
projects/admin/src/app/pages/standard/standard-setting/standard-setting.component.ts

@ -1,204 +1,283 @@
import { Component } from "@angular/core"; import { AfterViewInit, Component, ElementRef, OnDestroy, ViewChild } from '@angular/core'
import { FormBuilder, FormGroup } from "@angular/forms"; import { FormBuilder, FormGroup } from '@angular/forms'
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from '@angular/router'
import { ApiService } from "@cdk/services"; import { ApiService } from '@cdk/services'
import { AnyObject } from "@cdk/types"; import { AnyObject } from '@cdk/types'
import { FormValidators } from "@cdk/validators"; import { FormValidators } from '@cdk/validators'
import { NzMessageService } from "ng-zorro-antd/message"; import { NzMessageService } from 'ng-zorro-antd/message'
import { StandardService } from '../standard.service'
import { Observable, Subscription } from 'rxjs'
// infer
export type StandardItemInterface = { export type StandardItemInterface = {
min: number; min: number
max: number | null; max: number | null
name: string; name: string
type: string; type: string
category: string[]; category: string[]
}; }
export type StandardPeopleInterface = { export type StandardPeopleInterface = {
ul?: number; ul?: number
hasUl: boolean; hasUl: boolean
max: number; max: number
min: number; min: number
nutrition: string; nutrition: string
}; }
@Component({ @Component({
selector: "app-standard-setting", selector: 'app-standard-setting',
templateUrl: "./standard-setting.component.html", templateUrl: './standard-setting.component.html',
styleUrls: ["./standard-setting.component.less"], styleUrls: ['./standard-setting.component.less'],
}) })
export class StandardSettingComponent { export class StandardSettingComponent implements AfterViewInit, OnDestroy {
constructor( constructor(
private fb: FormBuilder, private fb: FormBuilder,
private router: Router, private router: Router,
private route: ActivatedRoute, private route: ActivatedRoute,
private api: ApiService, private api: ApiService,
private msg: NzMessageService private msg: NzMessageService,
private standard: StandardService,
) { ) {
const id = this.route.snapshot.paramMap.get("id"); const id = this.route.snapshot.paramMap.get('id')
if (id !== "create") { if (id !== 'create') {
const data = this.router.getCurrentNavigation()?.extras; const data = this.router.getCurrentNavigation()?.extras
if (data) { if (data) {
this.state = data.state; this.state = data.state
} else { } else {
this.router.navigate(["/standard/list"]); this.router.navigate(['/standard/list'])
} }
} }
} }
public globalEnum = this.api.globalEnum; public globalEnum = this.api.globalEnum
state: any; state: any
calcType = this.globalEnum.measurementType?.[0]?.key; calcType = this.globalEnum.measurementType?.[0]?.key
get calcTypeText() { get calcTypeText() {
return this.globalEnum.measurementType.find((f) => f.key === this.calcType)?.value; return this.globalEnum.measurementType.find((f) => f.key === this.calcType)?.value
} }
uploadLoading = false; uploadLoading = false
foodCategoryDay: StandardItemInterface[] = []
foodCategoryWeek: StandardItemInterface[] = []
foodCategoryDay: StandardItemInterface[] = []; ingredient: { name: string; nutritions: StandardPeopleInterface[] }[] = []
foodCategoryWeek: StandardItemInterface[] = []; intersection$ = new Subscription()
ingredient: { name: string; nutritions: StandardPeopleInterface[] }[] = []; renderIngredient = false
expanded = new Set<number>()
@ViewChild('intersectionTpl') intersectionTpl!: ElementRef<HTMLDivElement>
ngOnInit(): void { ngOnInit(): void {
this.foodCategoryDay = this.parseFoodCategory(this.state?.foodCategoryDay); this.foodCategoryDay = this.parseFoodCategory(this.state?.foodCategoryDay)
this.foodCategoryWeek = this.parseFoodCategory(this.state?.foodCategoryWeek); this.foodCategoryWeek = this.parseFoodCategory(this.state?.foodCategoryWeek)
this.ingredient = this.parseIngredient(this.state?.ingredient);
this.ingredient = this.parseIngredient(this.state)
}
ngOnDestroy(): void {
// this.intersection$.unsubscribe()
}
ngAfterViewInit(): void {
// const el = this.intersectionTpl.nativeElement
// if (el) {
// this.intersection$.add(
// this.observeElement(el).subscribe((isIn) => {
// if (isIn) {
// this.renderIngredient = true
// this.intersection$.unsubscribe()
// }
// }),
// )
// }
}
observeElement = (element: HTMLElement) => {
return new Observable((observer) => {
const intersectionObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
observer.next(entry.isIntersecting)
})
})
intersectionObserver.observe(element)
return () => {
intersectionObserver.disconnect()
}
})
} }
parseIngredient(data: any): { name: string; nutritions: StandardPeopleInterface[] }[] { parseIngredient(data: any): { name: string; nutritions: StandardPeopleInterface[] }[] {
if (!data) { if (!data?.ingredient) {
return []; return []
} }
return Object.entries(data).map(([k, v]) => { console.log('data', data)
const sorts = data.crows ?? Object.keys(data.ingredient)
return sorts.map((peopleName: string) => {
const v: any = data.ingredient[peopleName]
return { return {
name: k, name: peopleName,
nutritions: !v nutritions: !v
? [] ? []
: Object.entries(v).map(([kn, vn]) => { : Object.entries(v).map(([kn, vn]: [string, any]) => {
return { return {
ul: vn?.ul ?? void 0, ul: vn?.ul ?? void 0,
hasUl: !!vn?.ul, hasUl: !!vn?.ul,
max: vn?.max ?? 0, max: vn?.max ?? 0,
min: vn?.min ?? 0, min: vn?.min ?? 0,
nutrition: kn, nutrition: kn,
}; }
}), }),
}; }
}); })
} }
parseFoodCategory(data: any): StandardItemInterface[] { parseFoodCategory(data: any): StandardItemInterface[] {
if (!data) { if (!data) {
return []; return []
} }
if (Array.isArray(data) && data.length > 0) { if (Array.isArray(data) && data.length > 0) {
this.calcType = data[0]?.type; this.calcType = data[0]?.type
} }
return data; return data
} }
onSubmit() { onSubmit() {
if (this.foodCategoryDay.some((s) => !s.name || typeof s.min !== "number" || s.category.length === 0)) { if (this.foodCategoryDay.some((s) => !s.name || typeof s.min !== 'number' || s.category.length === 0)) {
this.msg.error("请设置正确的食物种类及数量标准(日)"); this.msg.error('请设置正确的食物种类及数量标准(日)')
return; return
} }
if (this.foodCategoryWeek.some((s) => !s.name || typeof s.min !== "number" || s.category.length === 0)) { if (this.foodCategoryWeek.some((s) => !s.name || typeof s.min !== 'number' || s.category.length === 0)) {
this.msg.error("请设置正确的食物种类及数量标准(周)"); this.msg.error('请设置正确的食物种类及数量标准(周)')
return; return
} }
if (this.ingredient.some((s) => !s.name || !s.nutritions.some((sn) => sn.nutrition))) { if (this.ingredient.some((s) => !s.name || !s.nutritions.some((sn) => sn.nutrition))) {
this.msg.error("请设置正确的营养标准人群"); this.msg.error('请设置正确的营养标准人群')
return; return
} }
const foodCategoryDay = this.foodCategoryDay; const foodCategoryDay = this.foodCategoryDay
const foodCategoryWeek = this.foodCategoryWeek;
const ingredient = this.ingredient.reduce((a, c) => { const foodCategoryWeek = this.foodCategoryWeek
let crows: string = ''
const ingredient = this.ingredient.reduce((a, c, idx) => {
const crowsName = (idx === 0 ? '' : ',') + c.name
crows += crowsName
return { return {
...a, ...a,
[c.name]: c.nutritions.reduce((an, cn) => { [c.name]: c.nutritions.reduce((an, cn) => {
const ul = { ul: cn.hasUl ? cn.ul : void 0 }; const ul = { ul: cn.hasUl ? cn.ul : void 0 }
return { return {
...an, ...an,
// idx,
[cn.nutrition]: { [cn.nutrition]: {
min: cn.min, min: cn.min,
max: cn.max, max: cn.max,
...ul, ...ul,
}, },
}; }
}, {} as AnyObject), }, {} as AnyObject),
}; }
}, {} as AnyObject); }, {} as AnyObject)
this.api.saveStandard({ ...this.state, foodCategoryDay, foodCategoryWeek, ingredient }, true).subscribe((res) => { this.api
this.msg.success(res.desc); .saveStandard({ ...this.state, foodCategoryDay, foodCategoryWeek, ingredient, crows }, true)
this.router.navigate(["/standard/list"]); .subscribe((res) => {
}); this.msg.success(res.desc)
this.router.navigate(['/standard/list'])
})
} }
addFoodType(type: string) { addFoodType(type: string) {
const item = type === "day" ? this.foodCategoryDay : this.foodCategoryWeek; const item = type === 'day' ? this.foodCategoryDay : this.foodCategoryWeek
// const withoutSelectType = this.globalEnum.category.find((f) => !item.some((s) => s.type === f.key)); // const withoutSelectType = this.globalEnum.category.find((f) => !item.some((s) => s.type === f.key));
item.push({ item.push({
category: [], category: [],
min: 0, min: 0,
max: null, max: null,
name: "", name: '',
type: this.calcType, type: this.calcType,
}); })
} }
removeFoodType(type: string, idx: number) { removeFoodType(type: string, idx: number) {
if (type === "day") { if (type === 'day') {
this.foodCategoryDay = this.foodCategoryDay.filter((f, i) => idx !== i); this.foodCategoryDay = this.foodCategoryDay.filter((f, i) => idx !== i)
} else { } else {
this.foodCategoryWeek = this.foodCategoryWeek.filter((f, i) => idx !== i); this.foodCategoryWeek = this.foodCategoryWeek.filter((f, i) => idx !== i)
} }
} }
addPeopleGroup() { addPeopleGroup() {
this.ingredient.push({ this.ingredient.push({
name: "", name: '',
nutritions: [], nutritions: [],
}); })
} }
removePeopleGroup(idx: number) { removePeopleGroup(idx: number) {
this.ingredient = this.ingredient.filter((_, i) => i !== idx); this.ingredient = this.ingredient.filter((_, i) => i !== idx)
} }
calcTypeChange() { calcTypeChange() {
console.log("this.calcType", this.calcType); console.log('this.calcType', this.calcType)
this.foodCategoryDay = this.foodCategoryDay.map((i) => ({ ...i, type: this.calcType })); this.foodCategoryDay = this.foodCategoryDay.map((i) => ({ ...i, type: this.calcType }))
this.foodCategoryWeek = this.foodCategoryWeek.map((i) => ({ ...i, type: this.calcType })); this.foodCategoryWeek = this.foodCategoryWeek.map((i) => ({ ...i, type: this.calcType }))
}
moveUp(idx: number) {
const arr = this.ingredient
if (idx > 0 && idx < arr.length) {
;[arr[idx], arr[idx - 1]] = [arr[idx - 1], arr[idx]]
}
}
moveDown(idx: number) {
const arr = this.ingredient
if (idx >= 0 && idx < arr.length - 1) {
;[arr[idx], arr[idx + 1]] = [arr[idx + 1], arr[idx]]
}
}
expand(idx: number) {
if (this.expanded.has(idx)) {
this.expanded.delete(idx)
} else {
this.expanded.add(idx)
}
} }
addNutrition(idx: number) { addNutrition(idx: number) {
const current = this.ingredient[idx]; const current = this.ingredient[idx]
this.expanded.add(idx)
const withoutSelectNutritions = this.globalEnum.nutrient.find( const withoutSelectNutritions = this.globalEnum.nutrient.find(
(f) => !current.nutritions.some((s) => s.nutrition === f.key) (f) => !current.nutritions.some((s) => s.nutrition === f.key),
); )
current.nutritions.push({ current.nutritions.push({
nutrition: withoutSelectNutritions?.key ?? "", nutrition: withoutSelectNutritions?.key ?? '',
min: 0, min: 0,
max: 0, max: 0,
hasUl: false, hasUl: false,
}); })
} }
removeNutrition(idx: number, nutrition: string) { removeNutrition(idx: number, nutrition: string) {
this.ingredient[idx].nutritions = this.ingredient[idx].nutritions.filter((f) => f.nutrition !== nutrition); this.ingredient[idx].nutritions = this.ingredient[idx].nutritions.filter((f) => f.nutrition !== nutrition)
} }
ulChange(idx: number, nutrition: string, checked: boolean) { ulChange(idx: number, nutrition: string, checked: boolean) {
this.ingredient[idx].nutritions = this.ingredient[idx].nutritions.map((i) => { this.ingredient[idx].nutritions = this.ingredient[idx].nutritions.map((i) => {
return i.nutrition === nutrition ? { ...i, ul: checked ? 1 : void 0 } : i; return i.nutrition === nutrition ? { ...i, ul: checked ? 1 : void 0 } : i
}); })
} }
} }

11
projects/admin/src/app/pages/standard/standard.service.ts

@ -0,0 +1,11 @@
import { Injectable } from '@angular/core'
import { BehaviorSubject } from 'rxjs'
@Injectable({
providedIn: 'root',
})
export class StandardService {
constructor() {}
settingData$ = new BehaviorSubject<any>(null)
}

195
projects/cdk/src/ingredient/ingredient-analysis/ingredient-analysis.component.html

@ -3,25 +3,21 @@
<div nz-row [nzGutter]="12"> <div nz-row [nzGutter]="12">
<div nz-col nzSpan="8" *ngIf="menu.days.length"> <div nz-col nzSpan="8" *ngIf="menu.days.length">
<nz-select class="w-full" [(ngModel)]="currentDay" (ngModelChange)="getAnalysis()"> <nz-select class="w-full" [(ngModel)]="currentDay" (ngModelChange)="getAnalysis()">
<nz-option [nzLabel]="'日平均'" [nzValue]="0"> </nz-option>
<nz-option *ngFor="let item of menu.days" [nzLabel]="weekdayMap[item]" [nzValue]="item"> <nz-option *ngFor="let item of menu.days" [nzLabel]="weekdayMap[item]" [nzValue]="item">
</nz-option> </nz-option>
</nz-select> </nz-select>
</div> </div>
<div nz-col nzSpan="16"> <div nz-col nzSpan="16">
<nz-select class="w-full" [(ngModel)]="currentPeople" (ngModelChange)="getAnalysis()"> <nz-select class="w-full" [(ngModel)]="currentPeople" (ngModelChange)="getAnalysis()">
<nz-option *ngFor="let item of menu.crows" [nzLabel]="item" nzValue="{{item}}"> <nz-option *ngFor="let item of menu.crows" [nzLabel]="item" nzValue="{{ item }}"> </nz-option>
</nz-option>
</nz-select> </nz-select>
</div> </div>
</div> </div>
<div class="flex mt-4"> <div class="flex mt-4">
<span> <span> 餐次: </span>
餐次:
</span>
<div class="flex-1"> <div class="flex-1">
<nz-tag *ngFor="let item of menu.meals">{{item}}</nz-tag> <nz-tag *ngFor="let item of menu.meals">{{ item }}</nz-tag>
</div> </div>
</div> </div>
<nz-divider nzDashed></nz-divider> <nz-divider nzDashed></nz-divider>
@ -36,52 +32,44 @@
<nz-table nzTemplateMode nzSize="small" nzBordered *ngIf="energy?.energy"> <nz-table nzTemplateMode nzSize="small" nzBordered *ngIf="energy?.energy">
<thead> <thead>
<tr> <tr>
<th> <th>能量占比</th>
能量占比 <th>要求(%)</th>
</th> <th>实际摄入(%)</th>
<th> <th>评价</th>
要求(%)
</th>
<th>
实际摄入(%)
</th>
<th>
评价
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let e of energy.energy"> <tr *ngFor="let e of energy.energy">
<td> <td>
{{e.name}} {{ e.name }}
</td> </td>
<td> <td>
{{e.standard}} {{ e.standard }}
</td> </td>
<td> <td>
{{e.value}} {{ e.value }}
</td> </td>
<td> <td>
<ng-container [ngSwitch]="e.conclusion"> <ng-container [ngSwitch]="e.conclusion">
<ng-container *ngSwitchCase="'合适'"> <ng-container *ngSwitchCase="'合适'">
<div class=" text-green-500"> <div class="text-green-500">
<span nz-icon nzType="smile" nzTheme="outline"></span> <span nz-icon nzType="smile" nzTheme="outline"></span>
{{e.conclusion}} {{ e.conclusion }}
</div> </div>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'略低'"> <ng-container *ngSwitchCase="'略低'">
<div class=" text-gray-500"> <div class="text-gray-500">
<span nz-icon nzType="frown" nzTheme="outline"></span> <span nz-icon nzType="frown" nzTheme="outline"></span>
{{e.conclusion}} {{ e.conclusion }}
</div> </div>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'略高'"> <ng-container *ngSwitchCase="'略高'">
<div class=" text-red-500"> <div class="text-red-500">
<span nz-icon nzType="frown" nzTheme="outline"></span> <span nz-icon nzType="frown" nzTheme="outline"></span>
{{e.conclusion}} {{ e.conclusion }}
</div> </div>
</ng-container> </ng-container>
<ng-container *ngSwitchDefault>{{e.conclusion}}</ng-container> <ng-container *ngSwitchDefault>{{ e.conclusion }}</ng-container>
</ng-container> </ng-container>
</td> </td>
</tr> </tr>
@ -89,129 +77,98 @@
</nz-table> </nz-table>
</nz-tab> </nz-tab>
</nz-tabset> </nz-tabset>
</div> </div>
</nz-tab> </nz-tab>
<nz-tab nzTitle="食材种类"> <nz-tab nzTitle="食材种类">
<div nz-row [nzGutter]="12"> <!-- <div nz-row [nzGutter]="12">
<div nz-col nzSpan="16"> <div nz-col nzSpan="16">
<nz-select class="w-full" [(ngModel)]="currentPeople" (ngModelChange)="getAnalysis()"> <nz-select class="w-full" [(ngModel)]="currentPeople" (ngModelChange)="getAnalysis()">
<nz-option *ngFor="let item of menu.crows" [nzLabel]="item" nzValue="{{item}}"> <nz-option *ngFor="let item of menu.crows" [nzLabel]="item" nzValue="{{ item }}"> </nz-option>
</nz-option>
</nz-select> </nz-select>
</div> </div>
</div> </div> -->
<ng-container *ngIf="rules"> <ng-container *ngIf="rules">
<h4 class="my-4"> <h4 class="my-4">周规则</h4>
周规则
</h4>
<div> <div>
<nz-table nzTemplateMode nzSize="small" nzBordered> <nz-table nzTemplateMode nzSize="small" nzBordered>
<thead> <thead>
<tr> <tr>
<th> <th>种类名称</th>
种类名称 <th>至少需要</th>
</th> <th>当前含有</th>
<th> <th>还需</th>
至少需要
</th>
<th>
当前含有
</th>
<th>
还需
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let w of rules.weekRule"> <tr *ngFor="let w of rules.weekRule">
<td> <td>
{{w.name}} {{ w.name }}
</td> </td>
<td> <td>
{{w.standard}} {{ w.standard }}
</td> </td>
<td> <td>
{{w.supplied}} {{ w.supplied }}
</td> </td>
<td> <td>
<ng-container> <ng-container>
<ng-container *ngIf="w.lack === 0 else elseTpl"> <ng-container *ngIf="w.lack === 0; else elseTpl">
<div class=" text-green-500"> <div class="text-green-500">
<span nz-icon nzType="smile" nzTheme="outline"></span> <span nz-icon nzType="smile" nzTheme="outline"></span>
</div> </div>
</ng-container> </ng-container>
<ng-template #elseTpl> <ng-template #elseTpl>
<ng-container *ngIf="w.lack < 0 else defaultTpl"> <ng-container *ngIf="w.lack < 0; else defaultTpl">
<div class=" text-red-500"> <div class="text-red-500">超量</div>
超量
</div>
</ng-container> </ng-container>
<ng-template #defaultTpl> <ng-template #defaultTpl>
<ng-container>{{w.lack}}</ng-container> <ng-container>{{ w.lack }}</ng-container>
</ng-template> </ng-template>
</ng-template> </ng-template>
</ng-container> </ng-container>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</nz-table> </nz-table>
</div> </div>
<h4 class="my-4"> <h4 class="my-4">日规则</h4>
日规则
</h4>
<div> <div>
<div *ngFor="let day of rules.dayRule|keyvalue" class="mb-3"> <div *ngFor="let day of rules.dayRule | keyvalue" class="mb-3">
<nz-table nzTemplateMode nzSize="small" nzBordered> <nz-table nzTemplateMode nzSize="small" nzBordered>
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
<th> <th>种类名称</th>
种类名称 <th>至少需要</th>
</th> <th>当前含有</th>
<th> <th>还需</th>
至少需要
</th>
<th>
当前含有
</th>
<th>
还需
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let d of $any(day.value);let first = first"> <tr *ngFor="let d of $any(day.value); let first = first">
<td *ngIf="first" [attr.rowSpan]="$any(day.value).length"> <td *ngIf="first" [attr.rowSpan]="$any(day.value).length">周{{ d.day }}</td>
周{{d.day}}
</td>
<td> <td>
{{d.name}} {{ d.name }}
</td> </td>
<td> <td>
{{d.standard}} {{ d.standard }}
</td> </td>
<td> <td>
{{d.supplied}} {{ d.supplied }}
</td> </td>
<td> <td>
<ng-container> <ng-container>
<ng-container *ngIf="d.lack === 0 else elseTpl"> <ng-container *ngIf="d.lack === 0; else elseTpl">
<div class=" text-green-500"> <div class="text-green-500">
<span nz-icon nzType="smile" nzTheme="outline"></span> <span nz-icon nzType="smile" nzTheme="outline"></span>
</div> </div>
</ng-container> </ng-container>
<ng-template #elseTpl> <ng-template #elseTpl>
<ng-container *ngIf="d.lack < 0 else defaultTpl"> <ng-container *ngIf="d.lack < 0; else defaultTpl">
<div class=" text-red-500"> <div class="text-red-500">超量</div>
超量
</div>
</ng-container> </ng-container>
<ng-template #defaultTpl> <ng-template #defaultTpl>
<ng-container>{{d.lack}}</ng-container> <ng-container>{{ d.lack }}</ng-container>
</ng-template> </ng-template>
</ng-template> </ng-template>
</ng-container> </ng-container>
@ -219,12 +176,11 @@
</tr> </tr>
</tbody> </tbody>
</nz-table> </nz-table>
</div> </div>
</div> </div>
</ng-container> </ng-container>
</nz-tab> </nz-tab>
<nz-tab nzTitle="盐油糖趋势"> <!-- <nz-tab nzTitle="盐油糖趋势">
<div nz-row [nzGutter]="12"> <div nz-row [nzGutter]="12">
<div nz-col nzSpan="16"> <div nz-col nzSpan="16">
<nz-select class="w-full" [(ngModel)]="currentPeople" (ngModelChange)="getAnalysis()"> <nz-select class="w-full" [(ngModel)]="currentPeople" (ngModelChange)="getAnalysis()">
@ -242,72 +198,57 @@
</div> </div>
</div> </div>
</nz-tab> </nz-tab> -->
<nz-tab nzTitle="烹饪方式"> <nz-tab nzTitle="烹饪方式">
<div nz-row [nzGutter]="12"> <!-- <div nz-row [nzGutter]="12">
<div nz-col nzSpan="16"> <div nz-col nzSpan="16">
<nz-select class="w-full" [(ngModel)]="currentPeople" (ngModelChange)="getAnalysis()"> <nz-select class="w-full" [(ngModel)]="currentPeople" (ngModelChange)="getAnalysis()">
<nz-option *ngFor="let item of menu.crows" [nzLabel]="item" nzValue="{{item}}"> <nz-option *ngFor="let item of menu.crows" [nzLabel]="item" nzValue="{{ item }}"> </nz-option>
</nz-option>
</nz-select> </nz-select>
</div> </div>
</div> </div> -->
<div class="mt-2"> <div class="mt-2">
<h4 class="my-4"> <h4 class="my-4">总计</h4>
总计
</h4>
<div> <div>
<nz-table nzTemplateMode nzSize="small" nzBordered> <nz-table nzTemplateMode nzSize="small" nzBordered>
<thead> <thead>
<tr> <tr>
<th> <th>烹饪方式</th>
烹饪方式 <th>菜品数量</th>
</th>
<th>
菜品数量
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let w of poly.total"> <tr *ngFor="let w of poly.total">
<td> <td>
{{w.name}} {{ w.name }}
</td> </td>
<td> <td>
{{w.value}} {{ w.value }}
</td> </td>
</tr> </tr>
</tbody> </tbody>
</nz-table> </nz-table>
<h4 class="my-4"> <h4 class="my-4">每日统计</h4>
每日统计 <div *ngFor="let p of poly.days | keyvalue" class="mb-2">
</h4>
<div *ngFor="let p of poly.days|keyvalue" class="mb-2">
<nz-table nzTemplateMode nzSize="small" nzBordered> <nz-table nzTemplateMode nzSize="small" nzBordered>
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
<th> <th>烹饪方式</th>
烹饪方式 <th>菜品数量</th>
</th>
<th>
菜品数量
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<ng-container *ngFor="let w of $any(p.value);let first = first"> <ng-container *ngFor="let w of $any(p.value); let first = first">
<tr> <tr>
<td [attr.rowSpan]="$any(p.value).length" *ngIf="first"> <td [attr.rowSpan]="$any(p.value).length" *ngIf="first">
{{weekdayMap[$any(p.key)]}} {{ weekdayMap[$any(p.key)] }}
</td> </td>
<td> <td>
{{w.name}} {{ w.name }}
</td> </td>
<td> <td>
{{w.value}} {{ w.value }}
</td> </td>
</tr> </tr>
</ng-container> </ng-container>

130
projects/cdk/src/ingredient/ingredient-analysis/ingredient-analysis.component.ts

@ -1,98 +1,98 @@
import { Component, ElementRef, Input, OnInit, ViewChild } from "@angular/core"; import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from '@angular/router'
import { ApiService } from "@cdk/services"; import { ApiService } from '@cdk/services'
import { finalize } from "rxjs"; import { finalize } from 'rxjs'
import { weekdayMap } from "../ingredient-form-basic/ingredient-form-basic.component"; import { weekdayMap } from '../ingredient-form-basic/ingredient-form-basic.component'
import { EChartsType, init } from "echarts"; import { EChartsType, init } from 'echarts'
const sugerMap = new Map([ const sugerMap = new Map([
["salt", "盐"], ['salt', '盐'],
["sugar", "糖"], ['sugar', '糖'],
["oil", "油"], ['oil', '油'],
]); ])
@Component({ @Component({
selector: "lib-ingredient-analysis", selector: 'lib-ingredient-analysis',
templateUrl: "./ingredient-analysis.component.html", templateUrl: './ingredient-analysis.component.html',
styleUrls: ["./ingredient-analysis.component.less"], styleUrls: ['./ingredient-analysis.component.less'],
}) })
export class IngredientAnalysisComponent implements OnInit { export class IngredientAnalysisComponent implements OnInit {
constructor(private api: ApiService, private route: ActivatedRoute) {} constructor(private api: ApiService, private route: ActivatedRoute) {}
@Input() menu: any; @Input() menu: any
@Input() current: any; @Input() current: any
@ViewChild("sugerTpl", { static: true }) sugerEl!: ElementRef<HTMLElement>; @ViewChild('sugerTpl', { static: true }) sugerEl!: ElementRef<HTMLElement>
sugerRef?: EChartsType; sugerRef?: EChartsType
currentDay: number = 1; currentDay: number = 0
currentPeople!: string; currentPeople!: string
id!: string; id!: string
analysis: any; analysis: any
rules: any; rules: any
energy: any; energy: any
poly = { poly = {
total: [] as any[], total: [] as any[],
days: {} as any, days: {} as any,
}; }
suger: any; suger: any
analysisLoading = false; analysisLoading = false
weekdayMap = weekdayMap; weekdayMap = weekdayMap
ngOnInit(): void { ngOnInit(): void {
this.currentDay = this.current?.day ?? this.menu.day[0]; // this.currentDay = this.current?.day ?? this.menu.day[0]
this.currentPeople = this.menu.crows[0]; this.currentPeople = this.menu.crows[0]
this.id = this.route.snapshot.queryParamMap.get("newId") ?? this.menu.id; this.id = this.route.snapshot.queryParamMap.get('newId') ?? this.menu.id
this.getAnalysis(); this.getAnalysis()
} }
getAnalysis() { getAnalysis() {
this.analysisLoading = true; this.analysisLoading = true
this.api this.api
.getAnalysis(this.id, this.currentDay, this.currentPeople) .getAnalysis(this.id, this.currentDay, this.currentPeople)
.pipe( .pipe(
finalize(() => { finalize(() => {
this.analysisLoading = false; this.analysisLoading = false
}) }),
) )
.subscribe((res) => { .subscribe((res) => {
this.analysis = res.body; this.analysis = res.body
}); })
this.getEnergy(); this.getEnergy()
this.getRule(); this.getRule()
this.getSugar(); this.getSugar()
this.getPoly(); this.getPoly()
} }
getEnergy() { getEnergy() {
this.api.getAnalysisEnergy(this.id, this.currentDay, this.currentPeople).subscribe((res) => { this.api.getAnalysisEnergy(this.id, this.currentDay, this.currentPeople).subscribe((res) => {
this.energy = res.body; this.energy = res.body
}); })
} }
getRule() { getRule() {
this.api.getAnalysisRule(this.id, this.currentDay, this.currentPeople).subscribe((res) => { this.api.getAnalysisRule(this.id, this.currentDay, this.currentPeople).subscribe((res) => {
this.rules = res.body; this.rules = res.body
}); })
} }
getSugar() { getSugar() {
this.api.getAnalysisSugar(this.id, this.currentPeople).subscribe((res) => { this.api.getAnalysisSugar(this.id, this.currentPeople).subscribe((res) => {
this.suger = res.body; this.suger = res.body
}); })
} }
getPoly() { getPoly() {
@ -100,45 +100,45 @@ export class IngredientAnalysisComponent implements OnInit {
const poly = { const poly = {
total: [] as any[], total: [] as any[],
days: {} as any, days: {} as any,
}; }
Object.entries(res.body).forEach(([k, v]) => { Object.entries(res.body).forEach(([k, v]) => {
if (k === "0") { if (k === '0') {
poly.total = Object.entries(v as any).map((o) => ({ name: o[0], value: o[1] })); poly.total = Object.entries(v as any).map((o) => ({ name: o[0], value: o[1] }))
} else if (k !== "crow") { } else if (k !== 'crow') {
poly.days[k] = Object.entries(v as any).map((o) => ({ name: o[0], value: o[1] })); poly.days[k] = Object.entries(v as any).map((o) => ({ name: o[0], value: o[1] }))
} }
}); })
this.poly = poly; this.poly = poly
}); })
} }
nzSelectedIndexChange(d: any) { nzSelectedIndexChange(d: any) {
if (d === 2) { if (d === 2) {
const xAxis: string[] = Object.keys(this.suger["oil"]).map((i: any) => weekdayMap[i]); const xAxis: string[] = Object.keys(this.suger['oil']).map((i: any) => weekdayMap[i])
const series: any[] = []; const series: any[] = []
Object.entries(this.suger).forEach(([k, v]) => { Object.entries(this.suger).forEach(([k, v]) => {
if (k !== "crow") { if (k !== 'crow') {
series.push({ series.push({
type: "line", type: 'line',
name: sugerMap.get(k), name: sugerMap.get(k),
data: Object.values(v as any), data: Object.values(v as any),
}); })
} }
}); })
const option = { const option = {
legend: {}, legend: {},
tooltip: { trigger: "axis" }, tooltip: { trigger: 'axis' },
xAxis: { type: "category", data: xAxis }, xAxis: { type: 'category', data: xAxis },
yAxis: {}, yAxis: {},
series, series,
}; }
if (!this.sugerRef) { if (!this.sugerRef) {
this.sugerRef = init(this.sugerEl.nativeElement); this.sugerRef = init(this.sugerEl.nativeElement)
} }
this.sugerRef.setOption(option); this.sugerRef.setOption(option)
} }
} }
} }

123
projects/cdk/src/ingredient/ingredient-dish/ingredient-dish.component.ts

@ -1,13 +1,13 @@
import { Component, Input, OnChanges, SimpleChanges, TemplateRef } from "@angular/core"; import { Component, Input, OnChanges, SimpleChanges, TemplateRef } from '@angular/core'
// import { MealDishInterface } from "../ingredient-meals/ingredient-meals.component"; // import { MealDishInterface } from "../ingredient-meals/ingredient-meals.component";
import { NzModalService } from "ng-zorro-antd/modal"; import { NzModalService } from 'ng-zorro-antd/modal'
import { Augmented, OptionItemInterface } from "@cdk/types"; import { Augmented, OptionItemInterface } from '@cdk/types'
import { NzMessageService } from "ng-zorro-antd/message"; import { NzMessageService } from 'ng-zorro-antd/message'
import { forkJoin } from "rxjs"; import { forkJoin } from 'rxjs'
import { ApiService } from "@cdk/services"; import { ApiService } from '@cdk/services'
import { NzDrawerService } from "ng-zorro-antd/drawer"; import { NzDrawerService } from 'ng-zorro-antd/drawer'
import { IngredientAnalysisComponent } from "../ingredient-analysis/ingredient-analysis.component"; import { IngredientAnalysisComponent } from '../ingredient-analysis/ingredient-analysis.component'
import { weekdayMap } from "../ingredient-form-basic/ingredient-form-basic.component"; import { weekdayMap } from '../ingredient-form-basic/ingredient-form-basic.component'
// export interface MenuObjectInterface { // export interface MenuObjectInterface {
// [Day: number]: { // [Day: number]: {
@ -16,47 +16,47 @@ import { weekdayMap } from "../ingredient-form-basic/ingredient-form-basic.compo
// } // }
export type DishInterface = Augmented<{ export type DishInterface = Augmented<{
dish: number; dish: number
day: number; day: number
meal: string; meal: string
mark: string; mark: string
items: Array< items: Array<
Augmented<{ Augmented<{
key: string; key: string
isMain: boolean; isMain: boolean
value: Record<string, number>; value: Record<string, number>
}> }>
>; >
}>; }>
@Component({ @Component({
selector: "app-ingredient-dish", selector: 'app-ingredient-dish',
templateUrl: "./ingredient-dish.component.html", templateUrl: './ingredient-dish.component.html',
styleUrls: ["./ingredient-dish.component.less"], styleUrls: ['./ingredient-dish.component.less'],
}) })
export class IngredientDishComponent implements OnChanges { export class IngredientDishComponent implements OnChanges {
constructor( constructor(
private modal: NzModalService, private modal: NzModalService,
private msg: NzMessageService, private msg: NzMessageService,
private api: ApiService, private api: ApiService,
private drawer: NzDrawerService private drawer: NzDrawerService,
) {} ) {}
@Input() menuBaisc: any | null; @Input() menuBaisc: any | null
@Input() client = false; @Input() client = false
@Input() menuDishFormServer: any | null; @Input() menuDishFormServer: any | null
expanded = new Set<number>(); expanded = new Set<number>()
days: number[] = []; days: number[] = []
selectDay: OptionItemInterface[] = []; selectDay: OptionItemInterface[] = []
weekdayMap = weekdayMap; weekdayMap = weekdayMap
mealCurrentIndex: Record<number, number> = {}; mealCurrentIndex: Record<number, number> = {}
// d = { // d = {
// 1:{ // 1:{
@ -67,19 +67,19 @@ export class IngredientDishComponent implements OnChanges {
// } // }
// menuObject!: MenuObjectInterface; // menuObject!: MenuObjectInterface;
mealDishList: DishInterface[] = []; mealDishList: DishInterface[] = []
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
if (changes["menuBaisc"]?.currentValue) { if (changes['menuBaisc']?.currentValue) {
this.initMenuBasic(); this.initMenuBasic()
} }
if (changes["menuDishFormServer"]?.currentValue) { if (changes['menuDishFormServer']?.currentValue) {
this.initMenuDish(); this.initMenuDish()
} }
} }
initMenuDish() { initMenuDish() {
this.mealDishList = this.menuDishFormServer; this.mealDishList = this.menuDishFormServer
// const foodIds = new Set<number>(); // const foodIds = new Set<number>();
// this.menuDishFormServer.forEach((i: any) => { // this.menuDishFormServer.forEach((i: any) => {
// i.ingredient.map((food: any) => { // i.ingredient.map((food: any) => {
@ -104,17 +104,17 @@ export class IngredientDishComponent implements OnChanges {
initMenuBasic() { initMenuBasic() {
if (this.menuBaisc) { if (this.menuBaisc) {
const meals = this.menuBaisc.meals as string[]; const meals = this.menuBaisc.meals as string[]
console.log("this.menuBaisc", this.menuBaisc); console.log('this.menuBaisc', this.menuBaisc)
const day = this.menuBaisc.day as number[]; const day = this.menuBaisc.day as number[]
this.days = day.map((i) => { this.days = day.map((i) => {
const d = weekdayMap[i]; const d = weekdayMap[i]
this.selectDay.push({ this.selectDay.push({
label: d, label: d,
value: String(i), value: String(i),
}); })
this.mealCurrentIndex[i] = 0; this.mealCurrentIndex[i] = 0
this.expanded.add(i); this.expanded.add(i)
// if (!this.menuObject) { // if (!this.menuObject) {
// this.menuObject = {}; // this.menuObject = {};
// } // }
@ -124,50 +124,49 @@ export class IngredientDishComponent implements OnChanges {
// [idx]: [], // [idx]: [],
// }; // };
// }, {} as Record<number, MealDishInterface[]>); // }, {} as Record<number, MealDishInterface[]>);
return i; return i
}); })
} }
} }
expandChange(i: number) { expandChange(i: number) {
if (this.expanded.has(i)) { if (this.expanded.has(i)) {
this.expanded.delete(i); this.expanded.delete(i)
} else { } else {
this.expanded.add(i); this.expanded.add(i)
} }
} }
onSaveDish(v: DishInterface[], day: number, mealIndex: number) { onSaveDish(v: DishInterface[], day: number, mealIndex: number) {
this.mealDishList = JSON.parse(JSON.stringify(v)); this.mealDishList = JSON.parse(JSON.stringify(v))
// this.menuObject[day][mealIndex] = JSON.parse(JSON.stringify(v)); // this.menuObject[day][mealIndex] = JSON.parse(JSON.stringify(v));
} }
reuse(day: number, nzContent: TemplateRef<{}>) { reuse(day: number, nzContent: TemplateRef<{}>) {
const thisDayDishs = this.mealDishList.filter((f) => f.day === day); const thisDayDishs = this.mealDishList.filter((f) => f.day === day)
console.log("dayDishs", day, this.mealDishList, thisDayDishs, this.selectDay); console.log('dayDishs', day, this.mealDishList, thisDayDishs, this.selectDay)
this.modal.create({ this.modal.create({
nzTitle: "请选择应用到的日期", nzTitle: '请选择应用到的日期',
nzContent, nzContent,
nzOnOk: () => { nzOnOk: () => {
this.selectDay.forEach((i) => { this.selectDay.forEach((i) => {
if (i["checked"]) { if (i['checked']) {
thisDayDishs.forEach((reused) => { thisDayDishs.forEach((reused) => {
this.mealDishList.push(JSON.parse(JSON.stringify({ ...reused, day: Number(i.value) }))); this.mealDishList.push(JSON.parse(JSON.stringify({ ...reused, day: Number(i.value) })))
}); })
} }
i["checked"] = false; i['checked'] = false
}); })
this.mealDishList = JSON.parse(JSON.stringify(this.mealDishList)); this.mealDishList = JSON.parse(JSON.stringify(this.mealDishList))
this.msg.success("操作成功"); this.msg.success('操作成功')
}, },
}); })
} }
analysis(day: number) { analysis(day: number) {
console.log("this.days", this.days);
this.drawer.create({ this.drawer.create({
nzWidth: 720, nzWidth: 720,
nzWrapClassName: "analysis-drawer", nzWrapClassName: 'analysis-drawer',
nzContent: IngredientAnalysisComponent, nzContent: IngredientAnalysisComponent,
nzContentParams: { nzContentParams: {
menu: { menu: {
@ -178,6 +177,6 @@ export class IngredientDishComponent implements OnChanges {
day, day,
}, },
}, },
}); })
} }
} }

82
projects/cdk/src/ingredient/ingredient-meals/ingredient-meals.component.html

@ -1,15 +1,11 @@
<div> <div>
<div class="mb-3"> <div class="mb-3">
<nz-space> <nz-space>
<button *nzSpaceItem nz-button nzType="primary" (click)="shopDishForm()"> <button *nzSpaceItem nz-button nzType="primary" (click)="shopDishForm()">添加菜品</button>
添加菜品 <button *nzSpaceItem nz-button (click)="clearThisMeal()">清空本餐</button>
</button>
<button *nzSpaceItem nz-button (click)="clearThisMeal()">
清空本餐
</button>
</nz-space> </nz-space>
</div> </div>
<nz-table nzTemplateMode [nzBordered]="true" nzSize="small" [nzScroll]="{ x: '1200px' }" class=" relative"> <nz-table nzTemplateMode [nzBordered]="true" nzSize="small" [nzScroll]="{ x: '1200px' }" class="relative">
<thead> <thead>
<!-- <tr> <!-- <tr>
<th nzWidth="300px"></th> <th nzWidth="300px"></th>
@ -21,43 +17,43 @@
</tr> --> </tr> -->
<tr> <tr>
<th nzWidth="300px" class="placeholder-th"> <th nzWidth="300px" class="placeholder-th">&nbsp;</th>
&nbsp;
</th>
<th nzWidth="200px"> <th nzWidth="200px">
<span class=" absolute top-2 left-[500px] z-10 w-[150px] h-[40px]">重量/克</span> <span class="absolute top-2 left-[500px] z-10 w-[150px] h-[40px]">重量/克</span>
</th>
<th *ngFor="let p of peopleGroups; let last = last" [ngClass]="{'placeholder-th':!last}"
nzWidth="150px">
</th> </th>
<th
*ngFor="let p of peopleGroups; let last = last"
[ngClass]="{ 'placeholder-th': !last }"
nzWidth="150px"
></th>
</tr> </tr>
<tr> <tr>
<th nzWidth="300px"> <th nzWidth="300px">菜品</th>
菜品 <th nzWidth="200px">食材</th>
</th>
<th nzWidth="200px">
食材
</th>
<th *ngFor="let p of peopleGroups"> <th *ngFor="let p of peopleGroups">
{{p}} {{ p }}
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<ng-container *ngFor="let dish of mealDishs;let dishIndex = index"> <ng-container *ngFor="let dish of mealDishs; let dishIndex = index">
<ng-container *ngIf="dish['mealIndex'] === mealIndex && dish['day'] === day"> <ng-container *ngIf="dish['mealIndex'] === mealIndex && dish['day'] === day">
<ng-container *ngFor="let food of dish['items'];let first = first;"> <ng-container *ngFor="let food of dish['items']; let first = first">
<tr> <tr>
<td *ngIf="first" [rowSpan]="dish['items'].length "> <td *ngIf="first" [rowSpan]="dish['items'].length">
<div class="flex justify-between"> <div class="flex justify-between">
<span> <div class="flex-1">
{{dish['dishName']}} <div>
</span> {{ dish['dishName'] }}
<button nz-button nzType="text" (click)="onRemoveDish(dish,dishIndex)"> </div>
<div>
<nz-tag nzColor="blue" *ngFor="let lb of dish['dishLabel'] ?? []">
{{ lb }}
</nz-tag>
</div>
</div>
<button nz-button nzType="text" (click)="onRemoveDish(dish, dishIndex)">
<i nz-icon nzType="delete"></i> <i nz-icon nzType="delete"></i>
</button> </button>
<!-- nz-dropdown [nzDropdownMenu]="dishOptions" --> <!-- nz-dropdown [nzDropdownMenu]="dishOptions" -->
@ -70,10 +66,9 @@
</div> </div>
</td> </td>
<td> <td>
<div class="flex justify-between"> <div class="flex justify-between">
<span> <span>
{{food['foodName']}} {{ food['foodName'] }}
</span> </span>
<!-- <button nz-button nzType="text" nz-dropdown [nzDropdownMenu]="foodOptions"> <!-- <button nz-button nzType="text" nz-dropdown [nzDropdownMenu]="foodOptions">
<i nz-icon nzType="more"></i> <i nz-icon nzType="more"></i>
@ -86,39 +81,28 @@
</nz-dropdown-menu> --> </nz-dropdown-menu> -->
</div> </div>
</td> </td>
<td *ngFor="let g of food['groupValues'] "> <td *ngFor="let g of food['groupValues']">
<input nz-input type="number" [(ngModel)]="g.value" (ngModelChange)="onValueChange()" /> <input nz-input type="number" [(ngModel)]="g.value" (ngModelChange)="onValueChange()" />
</td> </td>
</tr> </tr>
</ng-container> </ng-container>
</ng-container> </ng-container>
</ng-container> </ng-container>
<tr class="total"> <tr class="total">
<td colSpan="2" class="text-center"> <td colSpan="2" class="text-center">本餐生重总量</td>
本餐生重总量
</td>
<td *ngFor="let p of peopleGroups"> <td *ngFor="let p of peopleGroups">
{{$any(totalObj[p])?.toFixed(2)}} {{ $any(totalObj[p])?.toFixed(2) }}
</td> </td>
</tr> </tr>
</tbody> </tbody>
</nz-table> </nz-table>
<!-- <nz-empty *ngIf="currentDishs.length === 0" class="empty"></nz-empty> --> <!-- <nz-empty *ngIf="currentDishs.length === 0" class="empty"></nz-empty> -->
</div> </div>
<ng-template #addDishFooter> <ng-template #addDishFooter>
<nz-space> <nz-space>
<button *nzSpaceItem nz-button (click)="cancelForm()" type="button"> <button *nzSpaceItem nz-button (click)="cancelForm()" type="button">取消</button>
取消 <button *nzSpaceItem nz-button nzType="primary" [nzLoading]="submitLoading" (click)="onSubmit()">保存</button>
</button>
<button *nzSpaceItem nz-button nzType="primary" [nzLoading]="submitLoading" (click)="onSubmit()">
保存
</button>
</nz-space> </nz-space>
</ng-template> </ng-template>

8
projects/cdk/src/ingredient/ingredient-meals/ingredient-meals.component.less

@ -14,3 +14,11 @@
.placeholder-th { .placeholder-th {
border-right: none !important; border-right: none !important;
} }
.dish-table {
::ng-deep {
*[nzLeft] {
z-index: 100;
}
}
}

111
projects/cdk/src/ingredient/ingredient-meals/ingredient-meals.component.ts

@ -8,122 +8,125 @@ import {
SimpleChanges, SimpleChanges,
TemplateRef, TemplateRef,
ViewChild, ViewChild,
} from "@angular/core"; } from '@angular/core'
import { NzModalService } from "ng-zorro-antd/modal"; import { NzModalService } from 'ng-zorro-antd/modal'
import { import {
AddDishToIngredientComponent, AddDishToIngredientComponent,
FoodInDishInterface, FoodInDishInterface,
} from "../add-dish-to-ingredient/add-dish-to-ingredient.component"; } from '../add-dish-to-ingredient/add-dish-to-ingredient.component'
import { NzDrawerRef, NzDrawerService } from "ng-zorro-antd/drawer"; import { NzDrawerRef, NzDrawerService } from 'ng-zorro-antd/drawer'
import { NzMessageService } from "ng-zorro-antd/message"; import { NzMessageService } from 'ng-zorro-antd/message'
import { Augmented } from "@cdk/types"; import { Augmented } from '@cdk/types'
import { DishInterface } from "../ingredient-dish/ingredient-dish.component"; import { DishInterface } from '../ingredient-dish/ingredient-dish.component'
export type MealDishInterface = Augmented<{ export type MealDishInterface = Augmented<{
dishId: number; dishId: number
dishName: string; dishName: string
mark: string; mark: string
foods: FoodInDishInterface[]; foods: FoodInDishInterface[]
}>; }>
@Component({ @Component({
selector: "app-ingredient-meals", selector: 'app-ingredient-meals',
templateUrl: "./ingredient-meals.component.html", templateUrl: './ingredient-meals.component.html',
styleUrls: ["./ingredient-meals.component.less"], styleUrls: ['./ingredient-meals.component.less'],
}) })
export class IngredientMealsComponent implements OnChanges, OnInit { export class IngredientMealsComponent implements OnChanges, OnInit {
constructor(private modal: NzModalService, private msg: NzMessageService, private drawer: NzDrawerService) {} constructor(private modal: NzModalService, private msg: NzMessageService, private drawer: NzDrawerService) {}
@Input() day!: number; @Input() day!: number
@Input() mealIndex!: number; @Input() mealIndex!: number
@Input() peopleGroups: string[] = []; @Input() peopleGroups: string[] = []
@Input() meals: string[] = []; @Input() meals: string[] = []
@Input() mealDishs: DishInterface[] = []; @Input() mealDishs: DishInterface[] = []
@Output() onSaveDish = new EventEmitter<DishInterface[]>(); @Output() onSaveDish = new EventEmitter<DishInterface[]>()
@ViewChild("addDishFooter") addDishFooter!: TemplateRef<{}>; @ViewChild('addDishFooter') addDishFooter!: TemplateRef<{}>
drawerRef?: NzDrawerRef; drawerRef?: NzDrawerRef
submitLoading = false; submitLoading = false
// currentDishs: DishInterface[] = []; // currentDishs: DishInterface[] = [];
totalObj: Record<string, number> = {}; totalObj: Record<string, number> = {}
ngOnInit(): void { ngOnInit(): void {
this.init(); this.init()
} }
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
if (changes["mealDishs"]?.currentValue) { if (changes['mealDishs']?.currentValue) {
this.init(); this.init()
} }
} }
init() { init() {
this.calcTotal(); this.calcTotal()
// console.log(" init", this.mealDishs, this.day, this.mealIndex); // console.log(" init", this.mealDishs, this.day, this.mealIndex);
// this.currentDishs = this.mealDishs.filter((f) => f.day === this.day && this.mealIndex === f["mealIndex"]); // this.currentDishs = this.mealDishs.filter((f) => f.day === this.day && this.mealIndex === f["mealIndex"]);
} }
calcTotal() { calcTotal() {
this.totalObj = {}; this.totalObj = {}
this.mealDishs.forEach((dish) => { this.mealDishs.forEach((dish) => {
if (dish.day === this.day && this.mealIndex === dish["mealIndex"]) { if (dish.day === this.day && this.mealIndex === dish['mealIndex']) {
dish.items.forEach((food) => { dish.items.forEach((food) => {
food["groupValues"].forEach((p: any) => { food['groupValues'].forEach((p: any) => {
if (!this.totalObj[p.peopleName]) { if (!this.totalObj[p.peopleName]) {
this.totalObj[p.peopleName] = p.value; this.totalObj[p.peopleName] = p.value
} else { } else {
this.totalObj[p.peopleName] = this.totalObj[p.peopleName] + p.value; this.totalObj[p.peopleName] = this.totalObj[p.peopleName] + p.value
} }
}); })
}); })
} }
}); })
} }
shopDishForm() { shopDishForm() {
this.drawerRef = this.drawer.create({ this.drawerRef = this.drawer.create({
nzTitle: "添加菜品", nzTitle: '添加菜品',
nzWidth: 1200, nzWidth: 1200,
nzContent: AddDishToIngredientComponent, nzContent: AddDishToIngredientComponent,
nzFooter: this.addDishFooter, nzFooter: this.addDishFooter,
nzContentParams: { nzContentParams: {
peopleGroups: this.peopleGroups, peopleGroups: this.peopleGroups,
}, },
}); })
} }
clearThisMeal() { clearThisMeal() {
this.modal.confirm({ this.modal.confirm({
nzTitle: "警告", nzTitle: '警告',
nzContent: "是否要清空本餐?", nzContent: '是否要清空本餐?',
nzOkDanger: true, nzOkDanger: true,
nzOnOk: () => { nzOnOk: () => {
this.mealDishs = this.mealDishs.filter((f) => !(f.day === this.day && f["mealIndex"] === this.mealIndex)); this.mealDishs = this.mealDishs.filter(
this.onSaveDish.emit(this.mealDishs); (f) => !(f.day === this.day && f['mealIndex'] === this.mealIndex),
this.calcTotal(); )
return true; this.onSaveDish.emit(this.mealDishs)
this.calcTotal()
return true
}, },
}); })
} }
cancelForm() { cancelForm() {
this.drawerRef?.close(); this.drawerRef?.close()
} }
onSubmit() { onSubmit() {
if (this.drawerRef) { if (this.drawerRef) {
const { dish, foods } = this.drawerRef.getContentComponent() as AddDishToIngredientComponent; const { dish, foods } = this.drawerRef.getContentComponent() as AddDishToIngredientComponent
this.mealDishs.push({ this.mealDishs.push({
dishLabel: dish.dishLabel,
day: this.day, day: this.day,
mealIndex: this.mealIndex, mealIndex: this.mealIndex,
meal: this.meals[this.mealIndex], meal: this.meals[this.mealIndex],
@ -131,24 +134,24 @@ export class IngredientMealsComponent implements OnChanges, OnInit {
dishName: dish.name, dishName: dish.name,
mark: dish.marks, mark: dish.marks,
items: foods, items: foods,
}); })
this.onSaveDish.emit(this.mealDishs); this.onSaveDish.emit(this.mealDishs)
} }
this.cancelForm(); this.cancelForm()
} }
onRemoveDish(d: DishInterface, idx: number) { onRemoveDish(d: DishInterface, idx: number) {
// this.mealDishs = this.mealDishs.filter( // this.mealDishs = this.mealDishs.filter(
// (f) => !(f.dish === d.dish && f.day === this.day && f["mealIndex"] === this.mealIndex) // (f) => !(f.dish === d.dish && f.day === this.day && f["mealIndex"] === this.mealIndex)
// ); // );
this.mealDishs = this.mealDishs.filter((f, i) => i !== idx); this.mealDishs = this.mealDishs.filter((f, i) => i !== idx)
this.onSaveDish.emit(this.mealDishs); this.onSaveDish.emit(this.mealDishs)
} }
onValueChange() { onValueChange() {
// this.onSaveDish.emit(this.mealDishs); // this.onSaveDish.emit(this.mealDishs);
// console.log("this.currentDishs", this.currentDishs); // console.log("this.currentDishs", this.currentDishs);
this.calcTotal(); this.calcTotal()
} }
} }

85
projects/cdk/src/ingredient/ingredient-preview/ingredient-preview.component.html

@ -3,44 +3,47 @@
<ng-container *ngIf="basic"> <ng-container *ngIf="basic">
<header class="header text-center text-base relative"> <header class="header text-center text-base relative">
<button nz-button routerLink="/ingredient/item/list" class=" absolute left-4 top-[6px]"> <button nz-button routerLink="/ingredient/item/list" class="absolute left-4 top-[6px]">
<span nz-icon nzType="ordered-list" nzTheme="outline"></span> <span nz-icon nzType="ordered-list" nzTheme="outline"></span>
</button> </button>
<span> <span>
{{basic.name}} {{ basic.name }}
</span> </span>
</header> </header>
<div class="p-4"> <div class="p-4">
<nz-descriptions> <nz-descriptions>
<nz-descriptions-item nzTitle="采用标准">{{basic.standardName ?? '-'}}</nz-descriptions-item> <nz-descriptions-item nzTitle="采用标准">{{ basic.standardName ?? '-' }}</nz-descriptions-item>
<nz-descriptions-item nzTitle="食谱周期"> <nz-descriptions-item nzTitle="食谱周期">
<nz-tag *ngFor="let item of basic.day" class="mb-1"> <nz-tag *ngFor="let item of basic.day" class="mb-1">
{{weekdayMap[item]}} {{ weekdayMap[item] }}
</nz-tag> </nz-tag>
</nz-descriptions-item> </nz-descriptions-item>
<nz-descriptions-item nzTitle="适用月份"> <nz-descriptions-item nzTitle="适用月份">
<div class="flex flex-wrap" *ngIf="basic.month as data"> <div class="flex flex-wrap" *ngIf="basic.month as data">
<ng-container *ngIf="data.length === 12"> <ng-container *ngIf="data.length === 12">
<nz-tag> <nz-tag> 全年 </nz-tag>
全年
</nz-tag>
</ng-container> </ng-container>
<ng-container *ngIf="data.length !== 12"> <ng-container *ngIf="data.length !== 12">
<nz-tag *ngFor="let item of data" class="mb-1"> <nz-tag *ngFor="let item of data" class="mb-1">
{{monthText[item]}} {{ monthText[item] }}
</nz-tag> </nz-tag>
</ng-container> </ng-container>
</div> </div>
</nz-descriptions-item> </nz-descriptions-item>
<nz-descriptions-item nzTitle="人群显示"> <nz-descriptions-item nzTitle="人群显示">
<nz-tag *ngFor="let p of basic.crows" class="mb-1"> <a nz-popover [nzPopoverContent]="crowsTpl">{{ basic.crows.length }}</a>
{{p}} <ng-template #crowsTpl>
<div class="w-80">
<nz-tag *ngFor="let p of basic.crows" class="mb-2">
{{ p }}
</nz-tag> </nz-tag>
</div>
</ng-template>
</nz-descriptions-item> </nz-descriptions-item>
<nz-descriptions-item nzTitle="餐次"> <nz-descriptions-item nzTitle="餐次">
<nz-tag *ngFor="let c of basic.meals" class="mb-1"> <nz-tag *ngFor="let c of basic.meals" class="mb-1">
{{c}} {{ c }}
</nz-tag> </nz-tag>
</nz-descriptions-item> </nz-descriptions-item>
<nz-descriptions-item nzTitle="营养分析"> <nz-descriptions-item nzTitle="营养分析">
@ -56,65 +59,67 @@
<thead> <thead>
<tr> <tr>
<th> <th>
<div class="w-24"> <div class="w-24"></div>
</div>
</th> </th>
<th *ngFor="let day of days"> <th *ngFor="let day of days">
<div> <div>
<div class="table-day-row"> <div class="table-day-row">
<div class="td"> {{weekdayMap[day]}} </div> <div class="td">{{ weekdayMap[day] }}</div>
<div class="td">重量/克</div> <div class="td">重量/克</div>
</div> </div>
<div class="table-menu-row flex "> <div class="table-menu-row flex">
<div class="td dish-name text-left"> <div class="td dish-name text-left">菜品</div>
菜品 <div class="td food-name text-left">食材</div>
</div>
<div class="td food-name text-left">
食材
</div>
<div class="table-herder-ages"> <div class="table-herder-ages">
<div class="td age-range" *ngFor="let people of basic.crows">{{people}}</div> <div class="td age-range" *ngFor="let people of basic.crows">{{ people }}</div>
</div> </div>
</div> </div>
</div> </div>
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let meal of basic.meals"> <tr *ngFor="let meal of basic.meals">
<td class="text-center"> <td class="text-center">
<b> <b>
{{meal}} {{ meal }}
</b> </b>
</td> </td>
<td *ngFor="let day of days"> <td *ngFor="let day of days">
<div class="meal"> <div class="meal">
<ng-container *ngFor="let d of dishs"> <ng-container *ngFor="let d of dishs">
<ng-container *ngIf="d.day === day "> <ng-container *ngIf="d.day === day">
<div class="flex" *ngIf="d.meal === meal;"> <div class="flex" *ngIf="d.meal === meal">
<div class="menu-dish dish-name"> <div class="menu-dish dish-name">
<div class="text-left td"> <div class="text-left td">
<div>
<b> <b>
{{d['dishName']}} {{ d['dishName'] }}
</b> </b>
</div> </div>
<div>
<nz-tag nzColor="blue" *ngFor="let l of d['label'] ?? []">
{{ l }}
</nz-tag>
</div>
</div>
</div> </div>
<div class="menu-food"> <div class="menu-food">
<ul> <ul>
<ng-container *ngFor="let f of d.items;let last = last"> <ng-container *ngFor="let f of d.items; let last = last">
<li class="flex food-item"> <li class="flex food-item">
<div class="td food-name text-left" <div
[attr.title]="f['foodName']"> class="td food-name text-left"
{{f['foodName']}} [attr.title]="f['foodName']"
>
{{ f['foodName'] }}
</div> </div>
<div <div
class="td age-range text-right" class="td age-range text-right"
*ngFor="let p of basic.crows"> *ngFor="let p of basic.crows"
{{f.value[p].toFixed(2)}} >
{{ f.value[p].toFixed(2) }}
</div> </div>
</li> </li>
<!-- <li class="flex food-item placeholder" *ngIf="last"> <!-- <li class="flex food-item placeholder" *ngIf="last">
@ -128,8 +133,6 @@
</div> </div>
</li> --> </li> -->
</ng-container> </ng-container>
</ul> </ul>
</div> </div>
</div> </div>
@ -141,20 +144,17 @@
</div> </div>
<div class="menu-food"> <div class="menu-food">
<ul> <ul>
<li class="flex "> <li class="flex">
<div class="td food-name text-left">&nbsp;</div> <div class="td food-name text-left">&nbsp;</div>
<div class="td age-range text-right" *ngFor="let p of basic.crows"> <div class="td age-range text-right" *ngFor="let p of basic.crows">
{{calcTotal(day,meal,p)}} {{ calcTotal(day, meal, p) }}
</div> </div>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
</td> </td>
</tr> </tr>
<!-- <ng-container *ngFor="let dish of dishs"> <!-- <ng-container *ngFor="let dish of dishs">
@ -164,6 +164,5 @@
</ng-container> --> </ng-container> -->
</tbody> </tbody>
</table> </table>
</div> </div>
</nz-spin> </nz-spin>

449
projects/cdk/src/services/api.service.ts

@ -1,9 +1,9 @@
import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http"; import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http'
import { Inject, Injectable, InjectionToken } from "@angular/core"; import { Inject, Injectable, InjectionToken } from '@angular/core'
import { AnyObject, Augmented, OptionItemInterface, PageResult, ResponseType } from "@cdk/types"; import { AnyObject, Augmented, OptionItemInterface, PageResult, ResponseType } from '@cdk/types'
import { format, addDays } from "date-fns"; import { format, addDays } from 'date-fns'
import { Utils } from "@cdk/utils"; import { Utils } from '@cdk/utils'
import { Observable, map, of, tap } from "rxjs"; import { Observable, map, of, tap } from 'rxjs'
import { import {
PermItemDTO, PermItemDTO,
UserDTO, UserDTO,
@ -15,185 +15,187 @@ import {
OrgConfigDTO, OrgConfigDTO,
CategoryDTO, CategoryDTO,
NutrientDTO, NutrientDTO,
} from "@cdk/public-api"; } from '@cdk/public-api'
export const PROJECT_NAME = new InjectionToken<string>("projectName"); export const PROJECT_NAME = new InjectionToken<string>('projectName')
@Injectable({ @Injectable({
providedIn: "root", providedIn: 'root',
}) })
export class ApiService { export class ApiService {
constructor(private http: HttpClient, @Inject(PROJECT_NAME) public project: string) { constructor(private http: HttpClient, @Inject(PROJECT_NAME) public project: string) {
this.initAccount(); this.initAccount()
} }
public account!: ClientAccountDTO; public account!: ClientAccountDTO
public adminAccount!: AdminAccountDTO; public adminAccount!: AdminAccountDTO
public accountKey = this.project === "admin" ? "CATERING_ADMIN_ACCOUNT" : "CATERING_CLIENT_ACCOUNT"; public accountKey = this.project === 'admin' ? 'CATERING_ADMIN_ACCOUNT' : 'CATERING_CLIENT_ACCOUNT'
globalEnum!: GlobalEnum; globalEnum!: GlobalEnum
// 能量、蛋白质、脂肪、碳水化合物、糖、钠/食盐、钙、铁、锌、维生素A、维生素B1、维生素B2、维生素C、膳食纤维 // 能量、蛋白质、脂肪、碳水化合物、糖、钠/食盐、钙、铁、锌、维生素A、维生素B1、维生素B2、维生素C、膳食纤维
nutrientSort = new Map([ nutrientSort = new Map([
["energy", { sort: 0 }], ['energy', { sort: 0 }],
["protein", { sort: 1 }], ['protein', { sort: 1 }],
["fat", { sort: 2 }], ['fat', { sort: 2 }],
["carbs", { sort: 3 }], ['carbs', { sort: 3 }],
["sugar", { sort: 4 }], ['sugar', { sort: 4 }],
["sodium", { sort: 5 }], ['sodium', { sort: 5 }],
["calcium", { sort: 6 }], ['calcium', { sort: 6 }],
["iron", { sort: 7 }], ['iron', { sort: 7 }],
["zinc", { sort: 8 }], ['zinc', { sort: 8 }],
["va", { sort: 9 }], ['va', { sort: 9 }],
["vb1", { sort: 10 }], ['vb1', { sort: 10 }],
["vb2", { sort: 11 }], ['vb2', { sort: 11 }],
["vc", { sort: 12 }], ['vc', { sort: 12 }],
["fiber", { sort: 13 }], ['fiber', { sort: 13 }],
]); ])
initAccount() { initAccount() {
try { try {
const stragedAccount = localStorage.getItem(this.accountKey); const stragedAccount = localStorage.getItem(this.accountKey)
if (stragedAccount) { if (stragedAccount) {
if (this.project === "admin") { if (this.project === 'admin') {
this.adminAccount = JSON.parse(stragedAccount); this.adminAccount = JSON.parse(stragedAccount)
} else { } else {
this.account = JSON.parse(stragedAccount); this.account = JSON.parse(stragedAccount)
} }
} }
} catch (error) { } catch (error) {
console.error("获取用户信息失败", error); console.error('获取用户信息失败', error)
} }
} }
upload(data: FormData) { upload(data: FormData) {
return this.http.put<ResponseType>("/api/icon", data); return this.http.put<ResponseType>('/api/icon', data)
} }
private _formatEnum(v: CategoryDTO[]): OptionItemInterface[] { private _formatEnum(v: CategoryDTO[]): OptionItemInterface[] {
return v.map((i) => ({ label: i.key, value: i.value })); return v.map((i) => ({ label: i.key, value: i.value }))
} }
private _sortNutrient(n: NutrientDTO[]) { private _sortNutrient(n: NutrientDTO[]) {
return n.map((i) => { return n
return { ...i, ...this.nutrientSort.get(i.key) }; .map((i) => {
}); return { ...i, ...(this.nutrientSort.get(i.key) ?? { sort: 100 }) }
})
.sort((a, b) => a.sort - b.sort)
} }
getAllEnum(force?: boolean): Observable<GlobalEnum> { getAllEnum(force?: boolean): Observable<GlobalEnum> {
if (this.globalEnum && !force) { if (this.globalEnum && !force) {
return of(this.globalEnum); return of(this.globalEnum)
} }
return this.http.get<ResponseType<any>>("/api/enum").pipe( return this.http.get<ResponseType<any>>('/api/enum').pipe(
map((res) => { map((res) => {
return { return {
...res.body, ...res.body,
mealType: this._formatEnum(res.body.mealType), mealType: this._formatEnum(res.body.mealType),
menuStatus: this._formatEnum(res.body.menuStatus), menuStatus: this._formatEnum(res.body.menuStatus),
nutrient: this._sortNutrient(res.body.nutrient), nutrient: this._sortNutrient(res.body.nutrient),
}; }
}), }),
tap((r) => { tap((r) => {
this.globalEnum = r; this.globalEnum = r
}) }),
); )
} }
login(v: AnyObject) { login(v: AnyObject) {
v["admin"] = this.project === "admin"; v['admin'] = this.project === 'admin'
const params = Utils.objectToHttpParams(v); const params = Utils.objectToHttpParams(v)
return this.http.get<ResponseType>("/api/login", { params }).pipe( return this.http.get<ResponseType>('/api/login', { params }).pipe(
tap((res) => { tap((res) => {
localStorage.setItem(this.accountKey, JSON.stringify(res.body)); localStorage.setItem(this.accountKey, JSON.stringify(res.body))
this.initAccount(); this.initAccount()
}) }),
); )
} }
logout() { logout() {
return this.http.get<ResponseType>("/api/logout").pipe( return this.http.get<ResponseType>('/api/logout').pipe(
tap((res) => { tap((res) => {
if (res.success) { if (res.success) {
localStorage.removeItem(this.accountKey); localStorage.removeItem(this.accountKey)
} }
}) }),
); )
} }
getOrgInfo() { getOrgInfo() {
return this.http.get<ResponseType>("/api/vender/info").pipe( return this.http.get<ResponseType>('/api/vender/info').pipe(
tap((res) => { tap((res) => {
this.account["vender"] = res.body; this.account['vender'] = res.body
}) }),
); )
} }
getOrgConfig() { getOrgConfig() {
return this.http.get<ResponseType<OrgConfigDTO>>("/api/vender/config"); return this.http.get<ResponseType<OrgConfigDTO>>('/api/vender/config')
} }
saveOrgConfig(config: AnyObject) { saveOrgConfig(config: AnyObject) {
const body = Utils.objectToFormData(config); const body = Utils.objectToFormData(config)
return this.http.post<ResponseType>("/api/vender/config", body); return this.http.post<ResponseType>('/api/vender/config', body)
} }
updatePassword(v: {}) { updatePassword(v: {}) {
const body = Utils.objectToFormData(v); const body = Utils.objectToFormData(v)
return this.http.post<ResponseType>("/api/password", body); return this.http.post<ResponseType>('/api/password', body)
} }
getRoleList() { getRoleList() {
return this.http.get<ResponseType<UserRoleDTO[]>>("/api/role"); return this.http.get<ResponseType<UserRoleDTO[]>>('/api/role')
} }
deleteRole(roleId: string) { deleteRole(roleId: string) {
const params = new HttpParams().set("roleId", roleId); const params = new HttpParams().set('roleId', roleId)
return this.http.delete<ResponseType>("/api/role", { return this.http.delete<ResponseType>('/api/role', {
params, params,
}); })
} }
updateRole(rule: AnyObject) { updateRole(rule: AnyObject) {
const body = Utils.objectToFormData(rule, true); const body = Utils.objectToFormData(rule, true)
const method = rule["roleId"] ? "post" : "put"; const method = rule['roleId'] ? 'post' : 'put'
return this.http[method]<ResponseType>("/api/role", body); return this.http[method]<ResponseType>('/api/role', body)
} }
getRolePerms() { getRolePerms() {
return this.http.get<ResponseType<PermItemDTO[]>>("/api/role/item"); return this.http.get<ResponseType<PermItemDTO[]>>('/api/role/item')
} }
getUserList() { getUserList() {
return this.http.get<ResponseType<UserDTO[]>>("/api/user"); return this.http.get<ResponseType<UserDTO[]>>('/api/user')
} }
checkUid(uid: string) { checkUid(uid: string) {
const params = new HttpParams().set("uid", uid); const params = new HttpParams().set('uid', uid)
return this.http.delete<ResponseType>("/api/user/check", { return this.http.delete<ResponseType>('/api/user/check', {
params, params,
}); })
} }
saveUser(user: AnyObject, edit: boolean) { saveUser(user: AnyObject, edit: boolean) {
const body = Utils.objectToFormData(user); const body = Utils.objectToFormData(user)
const method = edit ? "post" : "put"; const method = edit ? 'post' : 'put'
return this.http[method]<ResponseType>("/api/user", body); return this.http[method]<ResponseType>('/api/user', body)
} }
deleteUser(uid: string) { deleteUser(uid: string) {
const params = new HttpParams().set("uid", uid); const params = new HttpParams().set('uid', uid)
return this.http.delete<ResponseType>("/api/user", { return this.http.delete<ResponseType>('/api/user', {
params, params,
}); })
} }
orgList: (OrgDTO & { value: string; text: string })[] = []; orgList: (OrgDTO & { value: string; text: string })[] = []
getOrgList(query?: { vendors?: number[]; keyword?: string }, force?: boolean) { getOrgList(query?: { vendors?: number[]; keyword?: string }, force?: boolean) {
if (this.orgList.length && !force) { if (this.orgList.length && !force) {
return of(this.orgList); return of(this.orgList)
} }
// const q = Utils.objectStringify(query); // const q = Utils.objectStringify(query);
return this.http.get<ResponseType<OrgDTO[]>>(`/api/vender/select?keyword= `).pipe( return this.http.get<ResponseType<OrgDTO[]>>(`/api/vender/select?keyword= `).pipe(
@ -203,45 +205,45 @@ export class ApiService {
value: i.id.toString(), value: i.id.toString(),
text: i.name, text: i.name,
label: i.name, label: i.name,
})); }))
}), }),
tap((r) => { tap((r) => {
this.orgList = r; this.orgList = r
}) }),
); )
} }
getOrgPage(p: {}, q: {}) { getOrgPage(p: {}, q: {}) {
let params = Utils.objectStringify({ ...p, ...q }, { skipEmptyString: false, skipNull: false }); let params = Utils.objectStringify({ ...p, ...q }, { skipEmptyString: false, skipNull: false })
return this.http.get<ResponseType<PageResult<OrgDTO>>>(`/api/vender?${params}`); return this.http.get<ResponseType<PageResult<OrgDTO>>>(`/api/vender?${params}`)
} }
checkOrgAccount(account: string) { checkOrgAccount(account: string) {
const params = new HttpParams().set("account", account); const params = new HttpParams().set('account', account)
return this.http.get<ResponseType<boolean>>("/api/vender/check/account", { return this.http.get<ResponseType<boolean>>('/api/vender/check/account', {
params, params,
}); })
} }
checkOrgName(name: string) { checkOrgName(name: string) {
const params = new HttpParams().set("name", name); const params = new HttpParams().set('name', name)
return this.http.get<ResponseType<boolean>>("/api/vender/check/name", { return this.http.get<ResponseType<boolean>>('/api/vender/check/name', {
params, params,
}); })
} }
deleteOrg(id: number) { deleteOrg(id: number) {
const params = Utils.objectToFormData({ venderId: id }); const params = Utils.objectToFormData({ venderId: id })
return this.http.delete<ResponseType>("/api/vender", { return this.http.delete<ResponseType>('/api/vender', {
body: params, body: params,
}); })
} }
saveOrg(org: AnyObject) { saveOrg(org: AnyObject) {
const body = Utils.objectToFormData(org); const body = Utils.objectToFormData(org)
const method = org["id"] ? "post" : "put"; const method = org['id'] ? 'post' : 'put'
return this.http[method]<ResponseType>("/api/vender", body); return this.http[method]<ResponseType>('/api/vender', body)
} }
// getOrgConfig() { // getOrgConfig() {
@ -249,7 +251,7 @@ export class ApiService {
// } // }
getFoodPage(p: {}, q: {}) { getFoodPage(p: {}, q: {}) {
const params = Utils.objectStringify({ ...p, ...q }); const params = Utils.objectStringify({ ...p, ...q })
return this.http.get<ResponseType<PageResult>>(`/api/ingredient?${params}`).pipe( return this.http.get<ResponseType<PageResult>>(`/api/ingredient?${params}`).pipe(
map((r) => { map((r) => {
if (Array.isArray(r.body.content)) { if (Array.isArray(r.body.content)) {
@ -257,86 +259,86 @@ export class ApiService {
return { return {
...o, ...o,
nutrientArr: Object.entries(o.nutrient).map(([k, v]) => { nutrientArr: Object.entries(o.nutrient).map(([k, v]) => {
const nutrient = this.globalEnum.nutrient.find((f) => f.key === k); const nutrient = this.globalEnum.nutrient.find((f) => f.key === k)
return { return {
key: k, key: k,
value: v, value: v,
label: nutrient?.value, label: nutrient?.value,
measurement: nutrient?.measurement, measurement: nutrient?.measurement,
}; }
}), }),
};
});
} }
return r;
}) })
); }
return r
}),
)
} }
getFoodList(query: {}) { getFoodList(query: {}) {
const q = Utils.objectStringify(query); const q = Utils.objectStringify(query)
return this.http.get<ResponseType<any[]>>(`/api/ingredient/select?${q}`); return this.http.get<ResponseType<any[]>>(`/api/ingredient/select?${q}`)
} }
getFoodExcelTemplate() { getFoodExcelTemplate() {
return this.http.get("/api/ingredient/excel", { observe: "response", responseType: "blob" as "json" }).pipe( return this.http.get('/api/ingredient/excel', { observe: 'response', responseType: 'blob' as 'json' }).pipe(
tap((res) => { tap((res) => {
this.downLoadFile(res); this.downLoadFile(res)
}) }),
); )
} }
downLoadFile(response: HttpResponse<Object>, defaultName = `${Date.now()}.xlsx`) { downLoadFile(response: HttpResponse<Object>, defaultName = `${Date.now()}.xlsx`) {
const fileNameFromHeader = response.headers.get("Content-Disposition"); const fileNameFromHeader = response.headers.get('Content-Disposition')
if (fileNameFromHeader) { if (fileNameFromHeader) {
const fileName = fileNameFromHeader.trim()?.split("''")?.[1]?.replace(/"/g, "") ?? defaultName; const fileName = fileNameFromHeader.trim()?.split("''")?.[1]?.replace(/"/g, '') ?? defaultName
const blob = new Blob([response.body as any]); const blob = new Blob([response.body as any])
const downloadLink = document.createElement("a"); const downloadLink = document.createElement('a')
downloadLink.href = URL.createObjectURL(blob); downloadLink.href = URL.createObjectURL(blob)
downloadLink.download = decodeURIComponent(fileName); downloadLink.download = decodeURIComponent(fileName)
document.body.appendChild(downloadLink); document.body.appendChild(downloadLink)
downloadLink.click(); downloadLink.click()
document.body.removeChild(downloadLink); document.body.removeChild(downloadLink)
} }
} }
saveFood(food: AnyObject, isEdit?: boolean) { saveFood(food: AnyObject, isEdit?: boolean) {
const body = Utils.objectToFormData(food); const body = Utils.objectToFormData(food)
const method = isEdit ? "post" : "put"; const method = isEdit ? 'post' : 'put'
return this.http[method]<ResponseType>("/api/ingredient", body); return this.http[method]<ResponseType>('/api/ingredient', body)
} }
deleteFoods(ids: number[]) { deleteFoods(ids: number[]) {
const params = Utils.objectToFormData({ keys: ids.join(",") }); const params = Utils.objectToFormData({ keys: ids.join(',') })
return this.http.delete<ResponseType>(`/api/ingredient`, { body: params }); return this.http.delete<ResponseType>(`/api/ingredient`, { body: params })
} }
importFood(f: FormData) { importFood(f: FormData) {
// return this.http.put<ResponseType>("/api/ingredient/upload", f); // return this.http.put<ResponseType>("/api/ingredient/upload", f);
return this.http return this.http
.put<ResponseType>("/api/ingredient/excel", f, { .put<ResponseType>('/api/ingredient/excel', f, {
observe: "response", observe: 'response',
responseType: "blob" as "json", responseType: 'blob' as 'json',
}) })
.pipe .pipe
// tap((res) => { // tap((res) => {
// this.downLoadFile(res); // this.downLoadFile(res);
// }) // })
(); ()
} }
markFood(mark: string, key: string) { markFood(mark: string, key: string) {
const params = Utils.objectToFormData({ mark, key }); const params = Utils.objectToFormData({ mark, key })
return this.http.put<ResponseType>("/api/ingredient/mark", params); return this.http.put<ResponseType>('/api/ingredient/mark', params)
} }
removeFoodMark(key: string) { removeFoodMark(key: string) {
const params = Utils.objectToFormData({ key }); const params = Utils.objectToFormData({ key })
return this.http.delete<ResponseType>("/api/ingredient/mark", { body: params }); return this.http.delete<ResponseType>('/api/ingredient/mark', { body: params })
} }
getStandardPage(p: {}, q: {}) { getStandardPage(p: {}, q: {}) {
const params = Utils.objectStringify({ ...p, ...q }); const params = Utils.objectStringify({ ...p, ...q })
return this.http.get<ResponseType<PageResult>>(`/api/nutrition?${params}`).pipe( return this.http.get<ResponseType<PageResult>>(`/api/nutrition?${params}`).pipe(
map((r) => { map((r) => {
if (Array.isArray(r.body.content)) { if (Array.isArray(r.body.content)) {
@ -347,54 +349,54 @@ export class ApiService {
return { return {
peopleGroupName: k, peopleGroupName: k,
...((v as any) ?? {}), ...((v as any) ?? {}),
}; }
}), }),
};
});
} }
return r;
}) })
); }
return r
}),
)
} }
standardList: (AnyObject & OptionItemInterface)[] = []; standardList: (AnyObject & OptionItemInterface)[] = []
getStandard(q?: { id?: string; name?: string }, force?: boolean) { getStandard(q?: { id?: string; name?: string }, force?: boolean) {
// if (this.standardList.length && !force) { // if (this.standardList.length && !force) {
// return of(this.standardList); // return of(this.standardList);
// } // }
const query = q ? Utils.objectStringify(q) : "keyword=&name="; const query = q ? Utils.objectStringify(q) : 'keyword=&name='
return this.http.get<ResponseType<any>>(`/api/nutrition/select?${query}`).pipe( return this.http.get<ResponseType<any>>(`/api/nutrition/select?${query}`).pipe(
map((r) => { map((r) => {
return r.body.map((i: any) => ({ return r.body.map((i: any) => ({
...i, ...i,
value: i.id.toString(), value: i.id.toString(),
label: i.name, label: i.name,
})); }))
}), }),
tap((r) => { tap((r) => {
this.standardList = r; this.standardList = r
}) }),
); )
} }
getStandardById(q: { id?: string; name?: string }) { getStandardById(q: { id?: string; name?: string }) {
const query = Utils.objectStringify(q); const query = Utils.objectStringify(q)
return this.http.get<ResponseType<any>>(`/api/nutrition/select?${query}`); return this.http.get<ResponseType<any>>(`/api/nutrition/select?${query}`)
} }
saveStandard(v: AnyObject, isEdit?: boolean) { saveStandard(v: AnyObject, isEdit?: boolean) {
const body = Utils.objectToFormData({ ...v, vendors: v["vendors"]?.join(",") }); const body = Utils.objectToFormData({ ...v, vendors: v['vendors']?.join(',') })
const method = v["id"] ? "post" : "put"; const method = v['id'] ? 'post' : 'put'
return this.http[method]<ResponseType>("/api/nutrition", body); return this.http[method]<ResponseType>('/api/nutrition', body)
} }
deleteStandard(id: string) { deleteStandard(id: string) {
const params = Utils.objectToFormData({ id }); const params = Utils.objectToFormData({ id })
return this.http.delete<ResponseType>(`/api/nutrition`, { body: params }); return this.http.delete<ResponseType>(`/api/nutrition`, { body: params })
} }
getDishPage(p: {}, q: {}) { getDishPage(p: {}, q: {}) {
const params = Utils.objectStringify({ ...p, ...q }); const params = Utils.objectStringify({ ...p, ...q })
return this.http.get<ResponseType<PageResult>>(`/api/dish?${params}`).pipe( return this.http.get<ResponseType<PageResult>>(`/api/dish?${params}`).pipe(
map((r) => { map((r) => {
if (Array.isArray(r.body.content)) { if (Array.isArray(r.body.content)) {
@ -411,13 +413,13 @@ export class ApiService {
// }; // };
// }); // });
} }
return r; return r
}) }),
); )
} }
getDishLabel(ids: (string | number)[]) { getDishLabel(ids: (string | number)[]) {
const query = Utils.objectStringify({ ids }); const query = Utils.objectStringify({ ids })
return this.http.get<ResponseType<any[]>>(`/api/dish/label?${query}`).pipe( return this.http.get<ResponseType<any[]>>(`/api/dish/label?${query}`).pipe(
map((res) => { map((res) => {
return { return {
@ -429,156 +431,159 @@ export class ApiService {
.map((c) => { .map((c) => {
return { return {
...c, ...c,
...this.nutrientSort.get(c["key"]), ...this.nutrientSort.get(c['key']),
}; }
}) })
.sort((a, b) => a["sort"]! - b["sort"]!), .sort((a, b) => a['sort']! - b['sort']!),
}; }
}), }),
}; }
}) }),
); )
} }
saveDish(v: AnyObject) { saveDish(v: AnyObject) {
const body = Utils.objectToFormData(v); const body = Utils.objectToFormData(v)
const method = v["id"] ? "post" : "put"; const method = v['id'] ? 'post' : 'put'
return this.http[method]<ResponseType>("/api/dish", body); return this.http[method]<ResponseType>('/api/dish', body)
} }
deleteDish(ids: string[]) { deleteDish(ids: string[]) {
const params = Utils.objectToFormData({ ids: ids.join(",") }); const params = Utils.objectToFormData({ ids: ids.join(',') })
return this.http.delete<ResponseType>(`/api/dish`, { body: params }); return this.http.delete<ResponseType>(`/api/dish`, { body: params })
} }
getMenuStatusPage(p: {}, q: {}) { getMenuStatusPage(p: {}, q: {}) {
const params = Utils.objectStringify({ ...p, ...q }); const params = Utils.objectStringify({ ...p, ...q })
return this.http.get<ResponseType<PageResult>>(`/api/menu/review?${params}`); return this.http.get<ResponseType<PageResult>>(`/api/menu/review?${params}`)
} }
getMenuPage(p: {}, q: {}) { getMenuPage(p: {}, q: {}) {
const params = Utils.objectStringify({ ...p, ...q }); const params = Utils.objectStringify({ ...p, ...q })
return this.http.get<ResponseType<PageResult>>(`/api/menu?${params}`); return this.http.get<ResponseType<PageResult>>(`/api/menu?${params}`)
} }
getMenuItem(id: string | number) { getMenuItem(id: string | number) {
const params = Utils.objectStringify({ id }); const params = Utils.objectStringify({ id })
return this.http.get<ResponseType>(`/api/menu?${params}`); return this.http.get<ResponseType>(`/api/menu?${params}`)
} }
getCountByStatus() { getCountByStatus() {
return this.http.get<ResponseType>(`/api/menu/review/count`); return this.http.get<ResponseType>(`/api/menu/review/count`)
} }
saveMenu(v: AnyObject) { saveMenu(v: AnyObject) {
const body = Utils.objectToFormData(v); const body = Utils.objectToFormData(v)
const method = v["id"] ? "post" : "put"; const method = v['id'] ? 'post' : 'put'
return this.http[method]<ResponseType>("/api/menu", body); return this.http[method]<ResponseType>('/api/menu', body)
} }
// 提交审核 // 提交审核
submitMenuForReview(id: string | number) { submitMenuForReview(id: string | number) {
const params = Utils.objectToFormData({ id }); const params = Utils.objectToFormData({ id })
return this.http.put<ResponseType>(`/api/menu/review`, params); return this.http.put<ResponseType>(`/api/menu/review`, params)
} }
disableMenu(id: string | number) { disableMenu(id: string | number) {
const params = Utils.objectToFormData({ id }); const params = Utils.objectToFormData({ id })
return this.http.delete<ResponseType>(`/api/menu/review`, { body: params }); return this.http.delete<ResponseType>(`/api/menu/review`, { body: params })
} }
reviewMenu(id: number, pass: boolean, reason?: string) { reviewMenu(id: number, pass: boolean, reason?: string) {
const params = Utils.objectToFormData({ id, pass, reason }); const params = Utils.objectToFormData({ id, pass, reason })
return this.http.post<ResponseType>(`/api/menu/review`, params); return this.http.post<ResponseType>(`/api/menu/review`, params)
} }
release(id: string | number, startTime: Date, amount: number) { release(id: string | number, startTime: Date, amount: number) {
// const endTime = addDays(startTime, amount); // const endTime = addDays(startTime, amount);
const params = Utils.objectToFormData({ const params = Utils.objectToFormData({
id, id,
startTime: format(startTime, "yyyy-MM-dd"), startTime: format(startTime, 'yyyy-MM-dd'),
// endTime: format(endTime, "yyyy-MM-dd"), // endTime: format(endTime, "yyyy-MM-dd"),
}); })
return this.http.put<ResponseType>(`/api/menu/release`, params); return this.http.put<ResponseType>(`/api/menu/release`, params)
} }
cancelRelease(id: string | number) { cancelRelease(id: string | number) {
const params = Utils.objectToFormData({ id }); const params = Utils.objectToFormData({ id })
return this.http.delete<ResponseType>(`/api/menu/release`, { body: params }); return this.http.delete<ResponseType>(`/api/menu/release`, { body: params })
} }
deleteMenu(id: string | number) { deleteMenu(id: string | number) {
const params = Utils.objectToFormData({ id }); const params = Utils.objectToFormData({ id })
return this.http.delete<ResponseType>(`/api/menu`, { body: params }); return this.http.delete<ResponseType>(`/api/menu`, { body: params })
} }
getMenuDist(menuId: number | string) { getMenuDist(menuId: number | string) {
return this.http.get<ResponseType>(`/api/menu/dish?menuId=${menuId}`); return this.http.get<ResponseType>(`/api/menu/dish?menuId=${menuId}`)
} }
saveMenuDist(d: {}) { saveMenuDist(d: {}) {
return this.http.put<ResponseType>(`/api/menu/dish/batch`, d); return this.http.put<ResponseType>(`/api/menu/dish/batch`, d)
} }
getMenuReleasePage(p: {}, q: {}) { getMenuReleasePage(p: {}, q: {}) {
const params = Utils.objectStringify({ ...p, ...q }); const params = Utils.objectStringify({ ...p, ...q })
return this.http.get<ResponseType<PageResult>>(`/api/menu/release?${params}`).pipe( return this.http.get<ResponseType<PageResult>>(`/api/menu/release?${params}`).pipe(
map((res) => { map((res) => {
if (Array.isArray(res.body.content)) { if (Array.isArray(res.body.content)) {
res.body.content = res.body.content.map((i) => ({ res.body.content = res.body.content.map((i) => ({
...i, ...i,
dateRange: `${format(new Date(i.startTime), "yyyy-MM-dd")}${format(new Date(i.endTime), "yyyy-MM-dd")}`, dateRange: `${format(new Date(i.startTime), 'yyyy-MM-dd')}${format(
})); new Date(i.endTime),
'yyyy-MM-dd',
)}`,
}))
} }
return res; return res
}) }),
); )
} }
exportMenu(id: number | string) { exportMenu(id: number | string) {
return this.http return this.http
.get<ResponseType>(`/api/menu/dish/export?id=${id}`, { .get<ResponseType>(`/api/menu/dish/export?id=${id}`, {
observe: "response", observe: 'response',
responseType: "blob" as "json", responseType: 'blob' as 'json',
}) })
.pipe( .pipe(
tap((res) => { tap((res) => {
this.downLoadFile(res); this.downLoadFile(res)
}) }),
); )
} }
getAnalysis(id: string, day?: number, crow?: string) { getAnalysis(id: string, day?: number, crow?: string) {
const params = Utils.objectStringify({ id, day, crow }); const params = Utils.objectStringify({ id, day, crow })
return this.http.get<ResponseType>(`/api/menu/analysis?${params}`); return this.http.get<ResponseType>(`/api/menu/analysis?${params}`)
} }
getAnalysisEnergy(id: string, day?: number, crow?: string) { getAnalysisEnergy(id: string, day?: number, crow?: string) {
const params = Utils.objectStringify({ id, day, crow }); const params = Utils.objectStringify({ id, day, crow })
return this.http.get<ResponseType>(`/api/menu/analysis/energy?${params}`); return this.http.get<ResponseType>(`/api/menu/analysis/energy?${params}`)
} }
getAnalysisSugar(id: string, crow?: string) { getAnalysisSugar(id: string, crow?: string) {
const params = Utils.objectStringify({ id, crow }); const params = Utils.objectStringify({ id, crow })
return this.http.get<ResponseType>(`/api/menu/analysis/sugar?${params}`); return this.http.get<ResponseType>(`/api/menu/analysis/sugar?${params}`)
} }
getAnalysisPoly(id: string, crow?: string) { getAnalysisPoly(id: string, crow?: string) {
const params = Utils.objectStringify({ id, crow }); const params = Utils.objectStringify({ id, crow })
return this.http.get<ResponseType>(`/api/menu/analysis/poly?${params}`); return this.http.get<ResponseType>(`/api/menu/analysis/poly?${params}`)
} }
getAnalysisRule(id: string, day?: number, crow?: string) { getAnalysisRule(id: string, day?: number, crow?: string) {
const params = Utils.objectStringify({ id, day, crow }); const params = Utils.objectStringify({ id, day, crow })
return this.http.get<ResponseType>(`/api/menu/analysis/types?${params}`); return this.http.get<ResponseType>(`/api/menu/analysis/types?${params}`)
} }
getCurrentDayDataVisList() { getCurrentDayDataVisList() {
return this.http.get<ResponseType>(`/api/menu/display`); return this.http.get<ResponseType>(`/api/menu/display`)
} }
getMenuDataVis(menuId: number) { getMenuDataVis(menuId: number) {
// return this.http.get<ResponseType>(`/api/menu/dish`); // return this.http.get<ResponseType>(`/api/menu/dish`);
return this.http.get<ResponseType>(`/api/menu/display?menuId=${menuId}`); return this.http.get<ResponseType>(`/api/menu/display?menuId=${menuId}`)
} }
} }

67
projects/cdk/src/shared/components/dish-select/dish-select.component.ts

@ -1,13 +1,13 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from '@angular/core'
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
import { ApiService } from "@cdk/services"; import { ApiService } from '@cdk/services'
import { OptionItemInterface } from "@cdk/types"; import { OptionItemInterface } from '@cdk/types'
import { Subject, debounceTime, distinctUntilChanged, filter, finalize, switchMap, takeUntil, tap } from "rxjs"; import { Subject, debounceTime, distinctUntilChanged, filter, finalize, switchMap, takeUntil, tap } from 'rxjs'
@Component({ @Component({
selector: "app-dish-select", selector: 'app-dish-select',
templateUrl: "./dish-select.component.html", templateUrl: './dish-select.component.html',
styleUrls: ["./dish-select.component.less"], styleUrls: ['./dish-select.component.less'],
providers: [ providers: [
{ {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
@ -19,17 +19,17 @@ import { Subject, debounceTime, distinctUntilChanged, filter, finalize, switchMa
export class DishSelectComponent implements OnInit, ControlValueAccessor { export class DishSelectComponent implements OnInit, ControlValueAccessor {
constructor(private api: ApiService) {} constructor(private api: ApiService) {}
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>()
private dishSearch$ = new Subject<string>(); private dishSearch$ = new Subject<string>()
nzFilterOption = (): boolean => true; nzFilterOption = (): boolean => true
value?: string | string[]; value?: string | string[]
listOfOption: OptionItemInterface[] = []; listOfOption: OptionItemInterface[] = []
loading = false; loading = false
ngOnInit(): void { ngOnInit(): void {
this.dishSearch$ this.dishSearch$
@ -39,59 +39,60 @@ export class DishSelectComponent implements OnInit, ControlValueAccessor {
distinctUntilChanged(), distinctUntilChanged(),
takeUntil(this.destroy$), takeUntil(this.destroy$),
tap(() => { tap(() => {
this.loading = true; this.loading = true
}), }),
switchMap((term: string) => switchMap((term: string) =>
this.api.getDishPage({ pageNo: 0, pageSize: 5 }, { keyword: term }).pipe( this.api.getDishPage({ pageNo: 0, pageSize: 5 }, { keyword: term }).pipe(
finalize(() => { finalize(() => {
this.loading = false; this.loading = false
}) }),
) ),
) ),
) )
.subscribe((data) => { .subscribe((data) => {
const listOfOption: Array<OptionItemInterface> = []; const listOfOption: Array<OptionItemInterface> = []
data.body.content.forEach((item) => { data.body.content.forEach((item) => {
listOfOption.push({ listOfOption.push({
...item, ...item,
dishLabel: item.label,
value: String(item.id), value: String(item.id),
label: item.name, label: item.name,
}); })
}); })
this.listOfOption = listOfOption; this.listOfOption = listOfOption
}); })
} }
ngOnDestroy(): void { ngOnDestroy(): void {
this.destroy$.next(); this.destroy$.next()
this.destroy$.complete(); this.destroy$.complete()
} }
onSelectChange(v: any) { onSelectChange(v: any) {
const item = this.listOfOption.find((f) => f.value === v); const item = this.listOfOption.find((f) => f.value === v)
// console.log("v", item); // console.log("v", item);
if (item) { if (item) {
this.onChange(item); this.onChange(item)
} }
} }
search = (k: string) => { search = (k: string) => {
this.dishSearch$.next(k); this.dishSearch$.next(k)
}; }
onChange(v: OptionItemInterface) {} onChange(v: OptionItemInterface) {}
ontouch(v: any) {} ontouch(v: any) {}
writeValue(v?: string[] | string): void { writeValue(v?: string[] | string): void {
this.value = v; this.value = v
} }
registerOnChange(fn: any): void { registerOnChange(fn: any): void {
this.onChange = fn; this.onChange = fn
} }
registerOnTouched(fn: any): void { registerOnTouched(fn: any): void {
this.ontouch = fn; this.ontouch = fn
} }
} }

80
projects/client/src/app/components/dish-form/dish-form.component.html

@ -1,56 +1,62 @@
<form nz-form [formGroup]="formGroup" nzLayout="vertical"> <form nz-form [formGroup]="formGroup" nzLayout="vertical">
<nz-alert class="mb-4" nzType="info" nzMessage="菜品建议" [nzDescription]="nzDescriptionTpl">
<ng-template #nzDescriptionTpl>
<div>每份菜品各类食材建议使用量为:</div>
<ul class="flex justify-between">
<li class="basis-1/4">● 肉类30~50克</li>
<li class="basis-1/4">● 素菜50~100克</li>
<li class="basis-1/4">● 食用油3~5克</li>
<li class="basis-1/4">● 精盐0.5克</li>
</ul>
<div class="mt-2">如需用到除盐外的其他调味品,建议:</div>
<ul class="flex flex-wrap">
<li class="basis-1/4">● 味精、鸡精1克</li>
<li class="basis-1/4">● 豆瓣酱、酱油等3~5克</li>
<li class="w-full">● 醋、料酒、小葱、干辣椒、花椒、五香粉等香料不必填写</li>
</ul>
<div class="mt-2">汤类菜品:</div>
<ul>
<li>● 每份建议添加饮用水200克</li>
</ul>
</ng-template>
</nz-alert>
<nz-form-item> <nz-form-item>
<nz-form-label nzRequired> <nz-form-label nzRequired> 菜品名称 </nz-form-label>
菜品名称
</nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl"> <nz-form-control [nzErrorTip]="formControlErrorTpl">
<input placeholder="请输入菜品名称" nz-input formControlName="name" /> <input placeholder="请输入菜品名称" nz-input formControlName="name" />
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label nzRequired> <nz-form-label nzRequired> 菜品标签 </nz-form-label>
菜品标签
</nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl"> <nz-form-control [nzErrorTip]="formControlErrorTpl">
<nz-select <nz-select formControlName="mark" nzPlaceHolder="请选择菜品标签">
formControlName="mark"
nzPlaceHolder="请选择菜品标签">
<nz-option *ngFor="let item of globalEnum.mark" [nzValue]="item.value" [nzLabel]="item.key"></nz-option> <nz-option *ngFor="let item of globalEnum.mark" [nzValue]="item.value" [nzLabel]="item.key"></nz-option>
</nz-select> </nz-select>
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label nzRequired> <nz-form-label nzRequired> 烹饪方式 </nz-form-label>
烹饪方式
</nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl"> <nz-form-control [nzErrorTip]="formControlErrorTpl">
<nz-select nzPlaceHolder="请选择烹饪方式" formControlName="poly" nzAllowClear> <nz-select nzPlaceHolder="请选择烹饪方式" formControlName="poly" nzAllowClear>
<nz-option *ngFor="let item of globalEnum.poly" [nzLabel]="item.name" <nz-option *ngFor="let item of globalEnum.poly" [nzLabel]="item.name" [nzValue]="item.key"> </nz-option>
[nzValue]="item.key">
</nz-option>
</nz-select> </nz-select>
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label nzRequired> <nz-form-label nzRequired> 适用月份 </nz-form-label>
适用月份
</nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl"> <nz-form-control [nzErrorTip]="formControlErrorTpl">
<app-month-select formControlName="month"></app-month-select> <app-month-select formControlName="month"></app-month-select>
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label> <nz-form-label> 菜品图片 </nz-form-label>
菜品图片
</nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl" nzExtra="支持.jpg .jpeg .png 格式,小于2M"> <nz-form-control [nzErrorTip]="formControlErrorTpl" nzExtra="支持.jpg .jpeg .png 格式,小于2M">
<input type="hidden" formControlName="icon" /> <input type="hidden" formControlName="icon" />
<div class="mb-2" *ngIf="iconPreview"> <div class="mb-2" *ngIf="iconPreview">
<img [src]="iconPreview" class="h-20 w-20" /> <img [src]="iconPreview" class="h-20 w-20" />
</div> </div>
<button class="upload-btn " nz-button [nzLoading]="uploadLoading"> <button class="upload-btn" nz-button [nzLoading]="uploadLoading">
<i nz-icon nzType="upload"></i> <i nz-icon nzType="upload"></i>
上传图片 上传图片
<input type="file" (change)="onFileChange($event)" accept=".jpg,.jpeg,.png" /> <input type="file" (change)="onFileChange($event)" accept=".jpg,.jpeg,.png" />
@ -70,9 +76,7 @@
<nz-form-item> <nz-form-item>
<nz-form-label nzRequired class="block-label"> <nz-form-label nzRequired class="block-label">
<div class="flex justify-between items-center flex-1"> <div class="flex justify-between items-center flex-1">
<span class="flex-1"> <span class="flex-1"> 食材名称 </span>
食材名称
</span>
<!-- <a nz-button nzType="link" (click)="addFoodVisible = true"> <!-- <a nz-button nzType="link" (click)="addFoodVisible = true">
<span nz-icon nzType="plus"></span> <span nz-icon nzType="plus"></span>
<span> <span>
@ -82,7 +86,6 @@
</div> </div>
</nz-form-label> </nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl"> <nz-form-control [nzErrorTip]="formControlErrorTpl">
<nz-select <nz-select
[nzMode]="'multiple'" [nzMode]="'multiple'"
nzShowSearch nzShowSearch
@ -93,7 +96,8 @@
[(ngModel)]="foodSelected" [(ngModel)]="foodSelected"
(ngModelChange)="onFoodSelected($event)" (ngModelChange)="onFoodSelected($event)"
(nzOnSearch)="searchFood($event)" (nzOnSearch)="searchFood($event)"
[ngModelOptions]="{standalone: true}"> [ngModelOptions]="{ standalone: true }"
>
<nz-option *ngFor="let o of foodListOfOption" [nzLabel]="o.text" [nzValue]="o.value"></nz-option> <nz-option *ngFor="let o of foodListOfOption" [nzLabel]="o.text" [nzValue]="o.value"></nz-option>
</nz-select> </nz-select>
<!-- <div *ngIf="addFoodVisible"> <!-- <div *ngIf="addFoodVisible">
@ -108,7 +112,7 @@
</div> </div>
</div> --> </div> -->
<ul class="mt-4"> <ul class="mt-4">
<li class="mb-2" *ngFor="let f of foodItemSelected;let i = index"> <li class="mb-2" *ngFor="let f of foodItemSelected; let i = index">
<div class="flex items-center"> <div class="flex items-center">
<!-- <div class="w-1/2 pr-2"> <!-- <div class="w-1/2 pr-2">
<button nz-button nzBlock> <button nz-button nzBlock>
@ -117,18 +121,26 @@
</div> --> </div> -->
<div class="flex-1 pr-2"> <div class="flex-1 pr-2">
<nz-input-group nzAddOnBefore="{{f.value}}-{{f.text}}:" [nzAddOnAfter]="'g'" class="w-full"> <nz-input-group
nzAddOnBefore="{{ f.value }}-{{ f.text }}:"
[nzAddOnAfter]="'g'"
class="w-full"
>
<input <input
nz-input nz-input
type="number" type="number"
[(ngModel)]="f.num" [(ngModel)]="f.num"
[ngModelOptions]="{standalone: true}" [ngModelOptions]="{ standalone: true }"
placeholder="请输入{{f.value}}-{{f.text}}重量" /> placeholder="请输入{{ f.value }}-{{ f.text }}重量"
/>
</nz-input-group> </nz-input-group>
</div> </div>
<div class="pl-2"> <div class="pl-2">
<nz-switch [ngModel]="f.isMain" (ngModelChange)="onMainChange($event,f.value)" <nz-switch
[ngModelOptions]="{standalone: true}"> [ngModel]="f.isMain"
(ngModelChange)="onMainChange($event, f.value)"
[ngModelOptions]="{ standalone: true }"
>
</nz-switch> </nz-switch>
是否主料 是否主料
</div> </div>

58
projects/client/src/app/pages/data-vis/data-vis.component.html

@ -3,11 +3,10 @@
<div class="logo"> <div class="logo">
<img *ngIf="logo" [attr.src]="'/api/icon/' + logo" /> <img *ngIf="logo" [attr.src]="'/api/icon/' + logo" />
</div> </div>
<h1 class="title">{{orgName}}食谱营养报告</h1> <h1 class="title">{{ orgName }}食谱营养报告</h1>
<div class="time">{{showTime}}</div> <div class="time">{{ showTime }}</div>
</div> </div>
<div class="mainbox flex flex-1"> <div class="mainbox flex flex-1">
<!-- <div class="boxnav mapc"> <!-- <div class="boxnav mapc">
</div> --> </div> -->
@ -16,23 +15,18 @@
<div class="box"> <div class="box">
<div class="tit"> <div class="tit">
今日带量食谱 今日带量食谱
<ng-container *ngIf="currentMenu"> <ng-container *ngIf="currentMenu"> 【{{ currentMenu.name }}】 </ng-container>
【{{currentMenu.name}}】
</ng-container>
</div> </div>
<div class="boxnav overflow-hidden" #tableEl> <div class="boxnav overflow-hidden" #tableEl>
<table class="w-full"> <table class="w-full">
<thead> <thead>
<tr> <tr>
<th width="70px"> <th width="70px"></th>
<th>菜品名称</th>
</th> <th>三低标识</th>
<th>
菜品名称
</th>
<th *ngFor="let p of peoples"> <th *ngFor="let p of peoples">
<div class="td"> <div class="td">
{{p}} {{ p }}
</div> </div>
</th> </th>
</tr> </tr>
@ -40,25 +34,22 @@
<tbody> <tbody>
<ng-container *ngFor="let meal of globalEnum.mealType"> <ng-container *ngFor="let meal of globalEnum.mealType">
<ng-container *ngIf="dishs[meal.value] as mealDish"> <ng-container *ngIf="dishs[meal.value] as mealDish">
<tr *ngFor="let item of mealDish;let first = first"> <tr *ngFor="let item of mealDish; let first = first">
<td *ngIf="first" [attr.rowspan]="mealDish.length"> <td *ngIf="first" [attr.rowspan]="mealDish.length">
{{meal.label}} {{ meal.label }}
</td> </td>
<th> <td>
<div class="td">
{{ item.name}}
</div>
</th>
<th *ngFor="let p of peoples">
<div class="td"> <div class="td">
{{item.people[p]}}g {{ item.name }}
</div> </div>
</th> </td>
<td>123</td>
<td *ngFor="let p of peoples">
<div class="td">{{ item.people[p] }}g</div>
</td>
</tr> </tr>
</ng-container> </ng-container>
</ng-container> </ng-container>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -69,9 +60,9 @@
<div class="tit">今日食材种类</div> <div class="tit">今日食材种类</div>
<div class="boxnav"> <div class="boxnav">
<div class="p-4"> <div class="p-4">
<div nz-row [nzGutter]="[12,12]"> <div nz-row [nzGutter]="[12, 12]">
<div nz-col nzSpan="6" *ngFor="let type of analysis?.types | keyvalue"> <div nz-col nzSpan="6" *ngFor="let type of analysis?.types | keyvalue">
{{type.key}} : <b>{{type.value}}</b> {{ type.key }} : <b>{{ type.value }}</b>
</div> </div>
</div> </div>
</div> </div>
@ -82,13 +73,11 @@
<div class="w-1/3 pl-4"> <div class="w-1/3 pl-4">
<div class="box"> <div class="box">
<div class="tit flex justify-between items-center"> <div class="tit flex justify-between items-center">
<span> <span> 今日营养分析 </span>
今日营养分析
</span>
<span *ngIf="peoples.length"> <span *ngIf="peoples.length">
<select [(ngModel)]="people" class="select" (ngModelChange)="getAnalysis()"> <select [(ngModel)]="people" class="select" (ngModelChange)="getAnalysis()">
<option *ngFor="let p of peoples" [value]="p"> <option *ngFor="let p of peoples" [value]="p">
{{p}} {{ p }}
</option> </option>
</select> </select>
</span> </span>
@ -96,17 +85,12 @@
<div class="boxnav overflow-hidden" #nutritionEl> <div class="boxnav overflow-hidden" #nutritionEl>
<div class="p-4"> <div class="p-4">
<nz-spin [nzSpinning]="analysisLoading"> <nz-spin [nzSpinning]="analysisLoading">
<lib-nutrition-table *ngIf="analysis" <lib-nutrition-table *ngIf="analysis" [dark]="true" [nutritions]="analysis.ingredient">
[dark]="true"
[nutritions]="analysis.ingredient">
</lib-nutrition-table> </lib-nutrition-table>
</nz-spin> </nz-spin>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

174
projects/client/src/app/pages/data-vis/data-vis.component.ts

@ -1,184 +1,186 @@
import { AfterViewInit, Component, ElementRef, Renderer2, ViewChild } from "@angular/core"; import { AfterViewInit, Component, ElementRef, Renderer2, ViewChild } from '@angular/core'
import { ApiService } from "@cdk/services"; import { ApiService } from '@cdk/services'
import { format } from "date-fns"; import { format } from 'date-fns'
import { Subject, Subscription, finalize, interval, takeUntil } from "rxjs"; import { Subject, Subscription, finalize, interval, takeUntil } from 'rxjs'
interface MenuDisplayItem { interface MenuDisplayItem {
name: string; name: string
id: number; id: number
} }
const changeTime = 1000 * 60 * 3; const changeTime = 1000 * 60 * 3
@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 implements AfterViewInit { export class DataVisComponent implements AfterViewInit {
constructor(private api: ApiService, private rd2: Renderer2) {} constructor(private api: ApiService, private rd2: Renderer2) {}
@ViewChild("tableEl") tableEl!: ElementRef<HTMLElement>; @ViewChild('tableEl') tableEl!: ElementRef<HTMLElement>
@ViewChild("nutritionEl") nutritionEl!: ElementRef<HTMLElement>; @ViewChild('nutritionEl') nutritionEl!: ElementRef<HTMLElement>
destroy$ = new Subject(); destroy$ = new Subject()
showTime: string = ""; showTime: string = ''
orgName = ""; orgName = ''
dishs: { dishs: {
[K: string]: any[]; [K: string]: any[]
} = {}; } = {}
peoples: string[] = []; peoples: string[] = []
globalEnum = this.api.globalEnum; globalEnum = this.api.globalEnum
analysisLoading = false; analysisLoading = false
analysis: any; analysis: any
scroll: Record<string, number> = {}; scroll: Record<string, number> = {}
people = ""; people = ''
menuId?: string; menuId?: string
logo = ""; logo = ''
menus: MenuDisplayItem[] = []; menus: MenuDisplayItem[] = []
currentMenu: MenuDisplayItem | null = null; currentMenu: MenuDisplayItem | null = null
lastTime: number = 0; lastTime: number = 0
ngOnInit(): void { ngOnInit(): void {
this.api.getOrgInfo().subscribe((res) => { this.api.getOrgInfo().subscribe((res) => {
const account = this.api.account; const account = this.api.account
this.orgName = account?.vender?.name ?? ""; this.orgName = account?.vender?.name ?? ''
this.logo = account?.vender?.icon ?? ""; this.logo = account?.vender?.icon ?? ''
}); })
interval(1000) interval(1000)
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe(() => { .subscribe(() => {
const now = new Date(); const now = new Date()
this.showTime = format(new Date(), "yyyy-MM-dd HH:mm:ss"); this.showTime = format(new Date(), 'yyyy-MM-dd HH:mm:ss')
if (now.getTime() - this.lastTime > changeTime) { if (now.getTime() - this.lastTime > changeTime) {
const currentIndex = this.menus.findIndex((f) => f.id === this.currentMenu?.id); const currentIndex = this.menus.findIndex((f) => f.id === this.currentMenu?.id)
let idx = currentIndex + 1; let idx = currentIndex + 1
if (idx > this.menus.length - 1) { if (idx > this.menus.length - 1) {
idx = 0; idx = 0
}
this.currentMenu = this.menus[idx]
if (this.currentMenu) {
this.getDataVisData(this.currentMenu.id)
this.lastTime = now.getTime()
} }
this.currentMenu = this.menus[idx];
this.getDataVisData(this.currentMenu.id);
this.lastTime = now.getTime();
} }
}); })
this.api.getCurrentDayDataVisList().subscribe((r) => { this.api.getCurrentDayDataVisList().subscribe((r) => {
if (Array.isArray(r.body)) { if (Array.isArray(r.body)) {
this.menus = r.body; this.menus = r.body
} }
}); })
} }
ngAfterViewInit(): void {} ngAfterViewInit(): void {}
ngOnDestroy(): void { ngOnDestroy(): void {
this.destroy$.next(null); this.destroy$.next(null)
this.destroy$.complete(); this.destroy$.complete()
} }
getDataVisData(id: number) { getDataVisData(id: number) {
this.analysis = null; this.analysis = null
this.peoples = []; this.peoples = []
this.people = ""; this.people = ''
this.dishs = {}; this.dishs = {}
this.scroll = {}; this.scroll = {}
this.api.getMenuDataVis(id).subscribe((res) => { this.api.getMenuDataVis(id).subscribe((res) => {
const dishs = res.body; const dishs = res.body
if (Array.isArray(dishs)) { if (Array.isArray(dishs)) {
this.peoples = Object.keys(dishs?.[0]?.ingredient?.[0]?.value); this.peoples = Object.keys(dishs?.[0]?.ingredient?.[0]?.value)
this.people = this.peoples[0]; this.people = this.peoples[0]
if (!this.peoples) { if (!this.peoples) {
console.error("dishs?.[0]?.ingredient?.[0]?.value 数据错误:", dishs); console.error('dishs?.[0]?.ingredient?.[0]?.value 数据错误:', dishs)
return; return
} }
this.menuId = dishs?.[0]?.menu; this.menuId = dishs?.[0]?.menu
this.getAnalysis(); this.getAnalysis()
dishs.forEach((i: any) => { dishs.forEach((i: any) => {
// 把每个食材按照不同的人群将重量加起来 // 把每个食材按照不同的人群将重量加起来
this.peoples.forEach((people) => { this.peoples.forEach((people) => {
const foods = i.ingredient as any[]; const foods = i.ingredient as any[]
const c = foods.reduce((a, c) => { const c = foods.reduce((a, c) => {
return a + c.value[people]; return a + c.value[people]
}, 0); }, 0)
if (!i.people) { if (!i.people) {
i.people = {}; i.people = {}
} }
i.people[people] = c; i.people[people] = c
}); })
if (Array.isArray(this.dishs[i.meal])) { if (Array.isArray(this.dishs[i.meal])) {
this.dishs[i.meal].push(i); this.dishs[i.meal].push(i)
} else { } else {
this.dishs[i.meal] = [i]; this.dishs[i.meal] = [i]
} }
}); })
setTimeout(() => { setTimeout(() => {
this.autoScroll(this.tableEl.nativeElement, "1"); this.autoScroll(this.tableEl.nativeElement, '1')
}, 1000); }, 1000)
} }
}); })
} }
scrollSubs$?: Subscription; scrollSubs$?: Subscription
autoScroll(el: HTMLElement, scroll: string) { autoScroll(el: HTMLElement, scroll: string) {
this.scrollSubs$?.unsubscribe(); this.scrollSubs$?.unsubscribe()
const child = el.children[0]; const child = el.children[0]
if (!child) { if (!child) {
return; return
} }
const elHeight = el.clientHeight; const elHeight = el.clientHeight
const childHeight = child.clientHeight; const childHeight = child.clientHeight
if (childHeight <= elHeight) { if (childHeight <= elHeight) {
return; return
} }
this.scrollSubs$ = interval(60) this.scrollSubs$ = interval(60)
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe(() => { .subscribe(() => {
this.scroll[scroll] = (this.scroll[scroll] ?? 0) + 1; this.scroll[scroll] = (this.scroll[scroll] ?? 0) + 1
const paddingBottom = 100; const paddingBottom = 100
if (this.scroll[scroll] - paddingBottom > childHeight - el.clientHeight) { if (this.scroll[scroll] - paddingBottom > childHeight - el.clientHeight) {
this.scroll[scroll] = 0; this.scroll[scroll] = 0
} }
this.rd2.setStyle(child, "transform", `translateY(-${this.scroll[scroll]}px)`); this.rd2.setStyle(child, 'transform', `translateY(-${this.scroll[scroll]}px)`)
}); })
} }
getAnalysis() { getAnalysis() {
if (!this.menuId) { if (!this.menuId) {
return; return
} }
this.api this.api
.getAnalysis(this.menuId, void 0, this.people) .getAnalysis(this.menuId, void 0, this.people)
.pipe( .pipe(
finalize(() => { finalize(() => {
this.analysisLoading = false; this.analysisLoading = false
}) }),
) )
.subscribe((res) => { .subscribe((res) => {
this.analysis = res.body; this.analysis = res.body
// setTimeout(() => { // setTimeout(() => {
// this.autoScroll(this.nutritionEl.nativeElement, "2"); // this.autoScroll(this.nutritionEl.nativeElement, "2");
// }, 1000); // }, 1000);
}); })
} }
} }

97
projects/client/src/app/pages/dish/dish.component.html

@ -1,5 +1,4 @@
<app-page [scroll]="false"> <app-page [scroll]="false">
<div nz-row class="h-full overflow-hidden bg-white rounded-lg"> <div nz-row class="h-full overflow-hidden bg-white rounded-lg">
<!-- <div nz-col nzFlex="220px" class="food-type"> <!-- <div nz-col nzFlex="220px" class="food-type">
<nz-card class="h-full" [nzBordered]="false" nzTitle="菜品分类" [nzBodyStyle]="{padding:'1px 0 0 0'}"> <nz-card class="h-full" [nzBordered]="false" nzTitle="菜品分类" [nzBodyStyle]="{padding:'1px 0 0 0'}">
@ -19,32 +18,38 @@
</nz-card> </nz-card>
</div> --> </div> -->
<div nz-col nzFlex="1" class="flex-1 overflow-hidden bg-white h-full"> <div nz-col nzFlex="1" class="flex-1 overflow-hidden bg-white h-full">
<nz-card [nzBordered]="false" nzTitle="菜品列表" class="scroll-card-body"> <nz-card [nzBordered]="false" nzTitle="菜品列表" class="scroll-card-body">
<div class="m-4"> <div class="m-4">
<ng-template #pageExtraTpl> <ng-template #pageExtraTpl>
<nz-space> <nz-space>
<button *nzSpaceItem nz-button [disabled]="!selectedIds.length" <button *nzSpaceItem nz-button [disabled]="!selectedIds.length" (click)="deleteItem()">
(click)="deleteItem()">批量删除</button> 批量删除
<button *nzSpaceItem nz-button [disabled]="!selectedIds.length" </button>
(click)="printTag()">批量打印营养标签</button> <button *nzSpaceItem nz-button [disabled]="!selectedIds.length" (click)="printTag()">
批量打印营养标签
</button>
<button *nzSpaceItem nz-button nzType="primary" (click)="showFoodForm()"> <button *nzSpaceItem nz-button nzType="primary" (click)="showFoodForm()">
<i nz-icon nzType="plus"></i> <i nz-icon nzType="plus"></i>
新增菜品 新增菜品
</button> </button>
</nz-space> </nz-space>
</ng-template> </ng-template>
<table-list [props]="tableList" [search]="searchTpl" [action]="pageExtraTpl" [formGroup]="queryForm" <table-list
[renderColumns]="renderColumnsTpl"> [props]="tableList"
[search]="searchTpl"
[action]="pageExtraTpl"
[formGroup]="queryForm"
[renderColumns]="renderColumnsTpl"
>
<ng-template #searchTpl> <ng-template #searchTpl>
<nz-form-item class="w-40"> <nz-form-item class="w-40">
<nz-form-control> <nz-form-control>
<nz-select nzPlaceHolder="菜品标签" formControlName="mark" nzAllowClear> <nz-select nzPlaceHolder="菜品标签" formControlName="mark" nzAllowClear>
<nz-option *ngFor="let item of globalEnum.mark" [nzLabel]="item.key" <nz-option
[nzValue]="item.key"> *ngFor="let item of globalEnum.mark"
[nzLabel]="item.key"
[nzValue]="item.key"
>
</nz-option> </nz-option>
</nz-select> </nz-select>
</nz-form-control> </nz-form-control>
@ -58,79 +63,66 @@
<ng-template #renderColumnsTpl let-data let-key="key" let-row="row"> <ng-template #renderColumnsTpl let-data let-key="key" let-row="row">
<ng-container [ngSwitch]="key"> <ng-container [ngSwitch]="key">
<ng-container *ngSwitchCase="'icon'"> <ng-container *ngSwitchCase="'icon'">
<div class="dish-img overflow-auto" <div
class="dish-img overflow-auto"
*ngIf="data" *ngIf="data"
[ngStyle]="{'background-image':'url(/api/icon/' + data + ')'}"> [ngStyle]="{ 'background-image': 'url(/api/icon/' + data + ')' }"
</div> ></div>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'vender'"> <ng-container *ngSwitchCase="'vender'">
{{ tableOrg[data] ? tableOrg[data].name : '-'}} {{ tableOrg[data] ? tableOrg[data].name : '-' }}
</ng-container>
<ng-container *ngSwitchCase="'label'">
<nz-tag *ngFor="let item of data ?? []">
{{ item }}
</nz-tag>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'ingredient'"> <ng-container *ngSwitchCase="'ingredient'">
<div class=" flex flex-wrap"> <div class="flex flex-wrap">
<ng-container *ngFor="let item of data"> <ng-container *ngFor="let item of data">
<nz-tag *ngIf="tableFoods[item.key]" class="m-1"> <nz-tag *ngIf="tableFoods[item.key]" class="m-1">
{{tableFoods[item.key]['name']}}:{{item.value}} g {{ tableFoods[item.key]['name'] }}:{{ item.value }} g
</nz-tag> </nz-tag>
</ng-container> </ng-container>
</div> </div>
</ng-container> </ng-container>
<ng-container *ngSwitchDefault> <ng-container *ngSwitchDefault>
{{data}} {{ data }}
</ng-container> </ng-container>
</ng-container> </ng-container>
</ng-template> </ng-template>
</table-list> </table-list>
</div> </div>
</nz-card> </nz-card>
</div> </div>
</div> </div>
</app-page> </app-page>
<ng-template #formFooterTpl> <ng-template #formFooterTpl>
<nz-space> <nz-space>
<button *nzSpaceItem nz-button (click)="cancelForm()" type="button"> <button *nzSpaceItem nz-button (click)="cancelForm()" type="button">取消</button>
取消 <button *nzSpaceItem nz-button nzType="primary" [nzLoading]="submitLoading" (click)="onSubmit()">保存</button>
</button>
<button *nzSpaceItem nz-button nzType="primary" [nzLoading]="submitLoading" (click)="onSubmit()">
保存
</button>
</nz-space> </nz-space>
</ng-template> </ng-template>
<app-print #print <app-print #print [content]="printContent"> </app-print>
[content]="printContent">
</app-print>
<ng-template #printContent> <ng-template #printContent>
<div class="printContent" *ngFor="let item of printData">
<div class="printContent"
*ngFor="let item of printData">
<table class="print-table"> <table class="print-table">
<tbody> <tbody>
<tr> <tr>
<th colspan="3"> <th colspan="3">
{{item.name}} {{ item.name }}
</th> </th>
</tr> </tr>
<tr> <tr>
<th colspan="3"> <th colspan="3">营养成分表</th>
营养成分表
</th>
</tr> </tr>
<tr> <tr>
<th class="text-left"> <th class="text-left">名称</th>
名称 <th class="text-center">每100克(g)</th>
</th> <th class="text-center">营养参考值%(NVR%)</th>
<th class="text-center">
每100克(g)
</th>
<th class="text-center">
营养参考值%(NVR%)
</th>
</tr> </tr>
</tbody> </tbody>
<tbody> <tbody>
@ -139,14 +131,9 @@
<td class="text-center">{{ th.nutrition }}</td> <td class="text-center">{{ th.nutrition }}</td>
<td class="text-center">{{ th.nvr }}</td> <td class="text-center">{{ th.nvr }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div> <div>主要原料:{{ item.ingredients.join(',') }}</div>
主要原料:{{item.ingredients.join(',')}} <div>1毫克(mg)钠相当于2.5毫克食盐</div>
</div>
<div>
1毫克(mg)钠相当于2.5毫克食盐
</div>
</div> </div>
</ng-template> </ng-template>

177
projects/client/src/app/pages/dish/dish.component.ts

@ -1,9 +1,9 @@
import { Component, OnInit, TemplateRef, ViewChild } from "@angular/core"; import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'
import { FormControl, FormGroup } from "@angular/forms"; import { FormControl, FormGroup } from '@angular/forms'
import { NzDrawerRef, NzDrawerService } from "ng-zorro-antd/drawer"; import { NzDrawerRef, NzDrawerService } from 'ng-zorro-antd/drawer'
import { AnyObject, OrgDTO, TableListOption } from "@cdk/public-api"; import { AnyObject, OrgDTO, TableListOption } from '@cdk/public-api'
import { DishFormComponent } from "@client/app/components"; import { DishFormComponent } from '@client/app/components'
import { ApiService } from "@cdk/services"; import { ApiService } from '@cdk/services'
import { import {
Subject, Subject,
debounceTime, debounceTime,
@ -14,124 +14,125 @@ import {
switchMap, switchMap,
takeUntil, takeUntil,
tap, tap,
} from "rxjs"; } from 'rxjs'
import { NzModalService } from "ng-zorro-antd/modal"; import { NzModalService } from 'ng-zorro-antd/modal'
import { NzMessageService } from "ng-zorro-antd/message"; import { NzMessageService } from 'ng-zorro-antd/message'
import { ResponseType } from "@cdk/types"; import { ResponseType } from '@cdk/types'
import { PrintComponent } from "@cdk/shared/components"; import { PrintComponent } from '@cdk/shared/components'
@Component({ @Component({
selector: "app-dish", selector: 'app-dish',
templateUrl: "./dish.component.html", templateUrl: './dish.component.html',
styleUrls: ["./dish.component.less"], styleUrls: ['./dish.component.less'],
}) })
export class DishComponent { export class DishComponent {
constructor( constructor(
private drawer: NzDrawerService, private drawer: NzDrawerService,
private api: ApiService, private api: ApiService,
private modal: NzModalService, private modal: NzModalService,
private msg: NzMessageService private msg: NzMessageService,
) {} ) {}
@ViewChild("formFooterTpl") formFooterTpl!: TemplateRef<{}>; @ViewChild('formFooterTpl') formFooterTpl!: TemplateRef<{}>
@ViewChild("print") printRef!: PrintComponent; @ViewChild('print') printRef!: PrintComponent
private drawerRef?: NzDrawerRef; private drawerRef?: NzDrawerRef
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>()
private orgSearch$ = new Subject<string>(); private orgSearch$ = new Subject<string>()
public globalEnum = this.api.globalEnum; public globalEnum = this.api.globalEnum
public printData: any | null; public printData: any | null
public tableList = new TableListOption(this.fetchData.bind(this), { public tableList = new TableListOption(this.fetchData.bind(this), {
selectable: true, selectable: true,
frontPagination: false, frontPagination: false,
}); })
public queryForm = new FormGroup({ public queryForm = new FormGroup({
keyword: new FormControl(""), keyword: new FormControl(''),
mark: new FormControl(""), mark: new FormControl(''),
vendors: new FormControl(""), vendors: new FormControl(''),
}); })
public selectedIds: string[] = []; public selectedIds: string[] = []
tableOrg: { [k: number]: OrgDTO } = {}; tableOrg: { [k: number]: OrgDTO } = {}
tableFoods: { [k: string]: any } = {}; tableFoods: { [k: string]: any } = {}
listOfOption: Array<{ value: number; text: string }> = []; listOfOption: Array<{ value: number; text: string }> = []
nzFilterOption = (): boolean => true; nzFilterOption = (): boolean => true
submitLoading = false; submitLoading = false
ngOnInit(): void { ngOnInit(): void {
this.initTableList(); this.initTableList()
this.tableList.getState$.pipe(takeUntil(this.destroy$)).subscribe((res) => { this.tableList.getState$.pipe(takeUntil(this.destroy$)).subscribe((res) => {
this.selectedIds = res.selectedKeys as Array<string>; this.selectedIds = res.selectedKeys as Array<string>
}); })
} }
searchOrg = (k: string) => { searchOrg = (k: string) => {
this.orgSearch$.next(k); this.orgSearch$.next(k)
}; }
ngOnDestroy(): void { ngOnDestroy(): void {
this.destroy$.next(); this.destroy$.next()
this.destroy$.complete(); this.destroy$.complete()
} }
initTableList() { initTableList() {
this.tableList.scroll = { x: null }; this.tableList.scroll = { x: null }
this.tableList = this.tableList.setColumns([ this.tableList = this.tableList.setColumns([
{ key: "icon", title: "菜品图片", width: "66px" }, { key: 'icon', title: '菜品图片', width: '66px' },
{ key: "name", title: "菜品名称" }, { key: 'name', title: '菜品名称' },
{ key: "marks", title: "菜品标签" }, { key: 'marks', title: '菜品标签' },
{ key: "poly", title: "烹饪方式" }, { key: 'label', title: '三低菜品标识' },
{ key: "ingredient", title: "食材及含量", width: "30%" }, { key: 'poly', title: '烹饪方式' },
]); { key: 'ingredient', title: '食材及含量', width: '30%' },
])
this.tableList = this.tableList.setOptions([ this.tableList = this.tableList.setOptions([
{ {
title: "打印营养标签", title: '打印营养标签',
premissions: [], premissions: [],
onClick: this.printTag.bind(this), onClick: this.printTag.bind(this),
}, },
{ {
title: "编辑", title: '编辑',
premissions: [], premissions: [],
onClick: this.showFoodForm.bind(this), onClick: this.showFoodForm.bind(this),
}, },
{ {
title: "删除", title: '删除',
premissions: [], premissions: [],
onClick: this.deleteItem.bind(this), onClick: this.deleteItem.bind(this),
}, },
]); ])
} }
fetchData(query: AnyObject, pager: AnyObject) { fetchData(query: AnyObject, pager: AnyObject) {
return this.api.getDishPage(pager, query).pipe( return this.api.getDishPage(pager, query).pipe(
tap((res) => { tap((res) => {
this.getTableColumData(res); this.getTableColumData(res)
}) }),
); )
} }
getTableColumData(res: ResponseType) { getTableColumData(res: ResponseType) {
if (Array.isArray(res.body.content)) { if (Array.isArray(res.body.content)) {
const vendors = res.body.content.map((i: any) => i.vender); const vendors = res.body.content.map((i: any) => i.vender)
const foodKeys = new Set( const foodKeys = new Set(
res.body.content.reduce((a: string[], c: any) => { res.body.content.reduce((a: string[], c: any) => {
return a.concat(c.ingredient.map((i: any) => i.key)); return a.concat(c.ingredient.map((i: any) => i.key))
}, [] as string[]) }, [] as string[]),
); )
if (foodKeys.size > 0) { if (foodKeys.size > 0) {
this.api.getFoodList({ keys: Array.from(foodKeys) }).subscribe((foods) => { this.api.getFoodList({ keys: Array.from(foodKeys) }).subscribe((foods) => {
@ -140,37 +141,37 @@ export class DishComponent {
return { return {
...a, ...a,
[c.key]: c, [c.key]: c,
};
}, {} as AnyObject);
} }
}); }, {} as AnyObject)
}
})
} }
} }
} }
printTag(v?: any) { printTag(v?: any) {
this.msg.loading("数据请求中,请不要刷新页面", { this.msg.loading('数据请求中,请不要刷新页面', {
nzDuration: 0, nzDuration: 0,
}); })
const ids = v ? [v.id] : this.selectedIds; const ids = v ? [v.id] : this.selectedIds
this.api this.api
.getDishLabel(ids) .getDishLabel(ids)
.pipe( .pipe(
finalize(() => { finalize(() => {
setTimeout(() => { setTimeout(() => {
this.msg.remove(); this.msg.remove()
}, 1000); }, 1000)
}) }),
) )
.subscribe((res) => { .subscribe((res) => {
this.printData = res.body; this.printData = res.body
this.printRef.print(); this.printRef.print()
}); })
} }
showFoodForm(data?: any) { showFoodForm(data?: any) {
this.drawerRef = this.drawer.create({ this.drawerRef = this.drawer.create({
nzTitle: data ? "编辑菜品" : "新增菜品", nzTitle: data ? '编辑菜品' : '新增菜品',
nzWidth: 700, nzWidth: 700,
nzContent: DishFormComponent, nzContent: DishFormComponent,
nzContentParams: { nzContentParams: {
@ -179,46 +180,46 @@ export class DishComponent {
foods: Object.values(this.tableFoods), foods: Object.values(this.tableFoods),
}, },
nzFooter: this.formFooterTpl, nzFooter: this.formFooterTpl,
}); })
} }
cancelForm() { cancelForm() {
this.drawerRef?.close(); this.drawerRef?.close()
} }
onSubmit() { onSubmit() {
if (this.drawerRef) { if (this.drawerRef) {
const com = this.drawerRef.getContentComponent() as DishFormComponent; const com = this.drawerRef.getContentComponent() as DishFormComponent
const val = com.getValues(); const val = com.getValues()
if (val) { if (val) {
this.submitLoading = true; this.submitLoading = true
this.api this.api
.saveDish(val) .saveDish(val)
.pipe( .pipe(
finalize(() => { finalize(() => {
this.submitLoading = false; this.submitLoading = false
}) }),
) )
.subscribe((res) => { .subscribe((res) => {
this.msg.success(res.desc); this.msg.success(res.desc)
this.tableList.run(); this.tableList.run()
this.cancelForm(); this.cancelForm()
}); })
} }
} }
} }
deleteItem(v?: any) { deleteItem(v?: any) {
const ids = v ? [v.id] : this.selectedIds; const ids = v ? [v.id] : this.selectedIds
this.modal.confirm({ this.modal.confirm({
nzTitle: "警告", nzTitle: '警告',
nzContent: `是否要删除${ids.length}个菜品?`, nzContent: `是否要删除${ids.length}个菜品?`,
nzOkDanger: true, nzOkDanger: true,
nzOnOk: async () => { nzOnOk: async () => {
const res = await lastValueFrom(this.api.deleteDish(ids)); const res = await lastValueFrom(this.api.deleteDish(ids))
this.msg.success(res.desc); this.msg.success(res.desc)
this.tableList.run(); this.tableList.run()
}, },
}); })
} }
} }

104
projects/client/src/app/pages/system/org-info/org-info.component.html

@ -1,64 +1,48 @@
<app-page> <app-page>
<nz-card [nzTitle]="orgInfoTpl" *ngIf="account?.vender as org"> <nz-card [nzTitle]="orgInfoTpl" *ngIf="account?.vender as org">
<ng-template #orgInfoTpl> <ng-template #orgInfoTpl>
<span> <span> 单位基础信息 </span>
单位基础信息
</span>
<small class="ml-2"> <small class="ml-2">
<a (click)="updateOrg()"> <a (click)="updateOrg()"> 编辑 </a>
编辑
</a>
</small> </small>
</ng-template> </ng-template>
<div nz-form> <div nz-form>
<nz-form-item> <nz-form-item>
<nz-form-label> <nz-form-label> 单位名称 </nz-form-label>
单位名称
</nz-form-label>
<nz-form-control> <nz-form-control>
{{org.name}} {{ org.name }}
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label> <nz-form-label> 单位Logo </nz-form-label>
单位Logo
</nz-form-label>
<nz-form-control> <nz-form-control>
<div class="w-20 h-20" *ngIf="org.icon"> <div class="w-20 h-20" *ngIf="org.icon">
<img [src]="'/api/icon/' + org.icon" class=" w-full h-full" /> <img [src]="'/api/icon/' + org.icon" class="w-full h-full" />
</div> </div>
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label> <nz-form-label> 地址 </nz-form-label>
地址
</nz-form-label>
<nz-form-control> <nz-form-control>
{{org.address ?? '-'}} {{ org.address ?? '-' }}
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label> <nz-form-label> 联系人 </nz-form-label>
联系人
</nz-form-label>
<nz-form-control> <nz-form-control>
{{org.contacts ?? '-'}} {{ org.contacts ?? '-' }}
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label> <nz-form-label> 联系电话 </nz-form-label>
联系电话
</nz-form-label>
<nz-form-control> <nz-form-control>
{{org.phone ?? '-'}} {{ org.phone ?? '-' }}
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label> <nz-form-label> 邮箱 </nz-form-label>
邮箱
</nz-form-label>
<nz-form-control> <nz-form-control>
{{org.email ?? '-'}} {{ org.email ?? '-' }}
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
</div> </div>
@ -66,85 +50,60 @@
<nz-card [nzTitle]="orgInfoTpl" *ngIf="account?.vender as org" class="mt-4"> <nz-card [nzTitle]="orgInfoTpl" *ngIf="account?.vender as org" class="mt-4">
<ng-template #orgInfoTpl> <ng-template #orgInfoTpl>
<span> <span> 账号信息 </span>
账号信息 <small class="ml-2 text-slate-500"> 仅支持主账号操作 </small>
</span>
<small class="ml-2 text-slate-500">
仅支持主账号操作
</small>
</ng-template> </ng-template>
<div nz-form> <div nz-form>
<nz-form-item> <nz-form-item>
<nz-form-label> <nz-form-label> 账号 </nz-form-label>
账号
</nz-form-label>
<nz-form-control> <nz-form-control>
{{account?.uid}} {{ account?.uid }}
<a class="ml-4" (click)="updateAccount('uid',updateAccountInfoTpl)"> <a class="ml-4" (click)="updateAccount('uid', updateAccountInfoTpl)"> 修改账号 </a>
修改账号
</a>
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label> <nz-form-label> 密码 </nz-form-label>
密码
</nz-form-label>
<nz-form-control> <nz-form-control>
<a (click)="changePassword(changePasswordTpl)"> <a (click)="changePassword(changePasswordTpl)"> 修改密码 </a>
修改密码
</a>
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label> <nz-form-label> 账号到期时间 </nz-form-label>
账号到期时间
</nz-form-label>
<nz-form-control> <nz-form-control>
{{org?.expire | date:'yyyy-MM-dd'}} {{ org?.expire | date : 'yyyy-MM-dd' }}
<a class="ml-4" (click)="updateAccount(org?.expire,updateAccountInfoTpl)"> <a class="ml-4" (click)="updateAccount(org?.expire, updateAccountInfoTpl)"> 续费 </a>
续费
</a>
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
</div> </div>
</nz-card> </nz-card>
</app-page> </app-page>
<ng-template #updateAccountInfoTpl> <ng-template #updateAccountInfoTpl>
<div *ngIf="account?.vender as org" class=" text-base text-blue-500"> <div *ngIf="account?.vender as org" class="text-base text-blue-500">
<span> <!-- <span>
{{org.contacts ?? '-'}} {{org.contacts ?? '-'}}
</span> </span> -->
<span> <span> 028-85463212 </span>
{{org.phone ?? '-'}}
</span>
</div> </div>
</ng-template> </ng-template>
<ng-template #changePasswordTpl> <ng-template #changePasswordTpl>
<form nz-form [formGroup]="pwdForm"> <form nz-form [formGroup]="pwdForm">
<nz-form-item> <nz-form-item>
<nz-form-label nzSpan="6" [nzRequired]="true"> <nz-form-label nzSpan="6" [nzRequired]="true"> 原密码 </nz-form-label>
原密码
</nz-form-label>
<nz-form-control [nzErrorTip]="errTipTpl" nzSpan="12"> <nz-form-control [nzErrorTip]="errTipTpl" nzSpan="12">
<input nz-input type="password" placeholder="请输入原密码" formControlName="oldPwd" /> <input nz-input type="password" placeholder="请输入原密码" formControlName="oldPwd" />
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label nzSpan="6" [nzRequired]="true"> <nz-form-label nzSpan="6" [nzRequired]="true"> 新密码 </nz-form-label>
新密码
</nz-form-label>
<nz-form-control [nzErrorTip]="errTipTpl" nzSpan="12"> <nz-form-control [nzErrorTip]="errTipTpl" nzSpan="12">
<input nz-input type="password" placeholder="请输入新密码" formControlName="newPwd" /> <input nz-input type="password" placeholder="请输入新密码" formControlName="newPwd" />
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-label nzSpan="6" [nzRequired]="true"> <nz-form-label nzSpan="6" [nzRequired]="true"> 确认密码 </nz-form-label>
确认密码
</nz-form-label>
<nz-form-control [nzErrorTip]="errTipTpl" nzSpan="12"> <nz-form-control [nzErrorTip]="errTipTpl" nzSpan="12">
<input nz-input type="password" placeholder="请再次输入新密码" formControlName="rePwd" /> <input nz-input type="password" placeholder="请再次输入新密码" formControlName="rePwd" />
</nz-form-control> </nz-form-control>
@ -152,7 +111,6 @@
</form> </form>
</ng-template> </ng-template>
<ng-template #errTipTpl let-control> <ng-template #errTipTpl let-control>
<form-error-tips [control]="control"></form-error-tips> <form-error-tips [control]="control"></form-error-tips>
</ng-template> </ng-template>
Loading…
Cancel
Save