Browse Source

添加菜品

main
kkerwin 2 years ago
parent
commit
7349084228
  1. 6
      README.md
  2. 60
      projects/admin/src/app/components/dish-form/dish-form.component.html
  3. 164
      projects/admin/src/app/components/dish-form/dish-form.component.ts
  4. 40
      projects/admin/src/app/pages/dish/dish.component.html
  5. 72
      projects/admin/src/app/pages/dish/dish.component.ts
  6. 55
      projects/admin/src/app/pages/food/food.component.html
  7. 47
      projects/admin/src/app/pages/food/food.component.ts
  8. 24
      projects/admin/src/app/pages/standard/standard-form/standard-form.component.html
  9. 81
      projects/admin/src/app/pages/standard/standard-form/standard-form.component.ts
  10. 46
      projects/cdk/src/services/api.service.ts

6
README.md

@ -51,3 +51,9 @@ To get more help on the Angular CLI use `ng help` or go check out the [Angular C
1. 食材也需要一个 类似 /api/vender/select 的接口 1. 食材也需要一个 类似 /api/vender/select 的接口
2. 新增菜品 -> 食材名称 -> 食材标签 的枚举值 有哪些? 2. 新增菜品 -> 食材名称 -> 食材标签 的枚举值 有哪些?
3. 单位修改 status 我调用的是编辑接口报错 3. 单位修改 status 我调用的是编辑接口报错
# 09/18
1. /api/basic/enum 中 markType 和 venderType 重复
2. /api/ingredient/select 需要 支持关键字

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

@ -16,11 +16,11 @@
nzShowSearch nzShowSearch
nzServerSearch nzServerSearch
nzPlaceHolder="请选择单位,多选时在多个单位均添加此菜品" nzPlaceHolder="请选择单位,多选时在多个单位均添加此菜品"
[nzShowArrow]="false" [nzShowArrow]="false"
[nzFilterOption]="nzFilterOption" [nzFilterOption]="nzFilterOption"
(nzOnSearch)="search($event)"> formControlName="vendors"
<nz-option *ngFor="let o of listOfOption" [nzLabel]="o.text" [nzValue]="o.value"></nz-option> (nzOnSearch)="searchOrg($event)">
<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>
@ -39,7 +39,6 @@
<nz-form-control [nzErrorTip]="formControlErrorTpl"> <nz-form-control [nzErrorTip]="formControlErrorTpl">
<nz-select <nz-select
formControlName="mark" formControlName="mark"
nzPlaceHolder="请选择菜品标签"> 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>
@ -61,11 +60,15 @@
</label> </label>
</div> </div>
<nz-divider nzDashed class="my-1"></nz-divider> <nz-divider nzDashed class="my-1"></nz-divider>
<nz-checkbox-group [ngModel]="allMonth" <div class="flex flex-wrap month-wrap">
<label *ngFor="let m of allMonth" nz-checkbox [(nzChecked)]="m.checked"
(nzCheckedChange)="monthChecked($event,m.value)">{{m.label}}</label>
</div>
<!-- <nz-checkbox-group [(ngModel)]="allMonth"
class="flex flex-wrap month-wrap" class="flex flex-wrap month-wrap"
formControlName="month" [ngModelOptions]="{standalone: true}"
(ngModelChange)="monthChecked()"> (ngModelChange)="monthChecked()">
</nz-checkbox-group> </nz-checkbox-group> -->
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
@ -101,16 +104,30 @@
<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>
添加食材 添加食材
</span> </span>
</a> </a> -->
</div> </div>
</nz-form-label> </nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl"> <nz-form-control [nzErrorTip]="formControlErrorTpl">
<div *ngIf="addFoodVisible">
<nz-select
[nzMode]="'multiple'"
nzShowSearch
nzServerSearch
nzPlaceHolder="请输入食材名称检索"
[nzShowArrow]="false"
[nzFilterOption]="nzFilterOption"
[(ngModel)]="foodSelected"
(ngModelChange)="onFoodSelected($event)"
(nzOnSearch)="searchFood($event)"
[ngModelOptions]="{standalone: true}">
<nz-option *ngFor="let o of foodListOfOption" [nzLabel]="o.text" [nzValue]="o.value"></nz-option>
</nz-select>
<!-- <div *ngIf="addFoodVisible">
<nz-select nzSize="large" nzShowSearch nzPlaceHolder="请输入食材名称/编号检索"> <nz-select nzSize="large" nzShowSearch nzPlaceHolder="请输入食材名称/编号检索">
</nz-select> </nz-select>
@ -120,21 +137,28 @@
<button *nzSpaceItem nz-button nzType="primary" (click)="addFood()">确定</button> <button *nzSpaceItem nz-button nzType="primary" (click)="addFood()">确定</button>
</nz-space> </nz-space>
</div> </div>
</div> </div> -->
<ul formArrayName="food"> <ul class="mt-4">
<li class="mb-2" *ngFor="let n of food.controls;let i = index" [formGroupName]="i"> <li class="mb-2" *ngFor="let f of foodItemSelected;let i = index">
<div class="flex items-center"> <div class="flex items-center">
<div class="pr-2"> <div class="w-40 pr-2">
食材名称: <button nz-button nzBlock>
{{f.value}}-{{f.text}}:
</button>
</div> </div>
<div class="pr-2"> <!-- <div class="pr-2">
<nz-select class="!w-[200px]" nzPlaceHolder="食材标签" <nz-select class="!w-[200px]" nzPlaceHolder="食材标签"
formControlName="tag"> formControlName="tag">
</nz-select> </nz-select>
</div> </div> -->
<div class="flex-1 pr-2"> <div class="flex-1 pr-2">
<nz-input-group [nzAddOnAfter]="'g'" class="w-full"> <nz-input-group [nzAddOnAfter]="'g'" class="w-full">
<input nz-input formControlName="weight" [placeholder]="'请输入xxx重量'" /> <input
nz-input
type="number"
[(ngModel)]="f.num"
[ngModelOptions]="{standalone: true}"
placeholder="请输入{{f.value}}-{{f.text}}重量" />
</nz-input-group> </nz-input-group>
</div> </div>
<div> <div>

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

@ -1,9 +1,10 @@
import { Component, 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 { FormValidators } from "@cdk/validators"; import { FormValidators } from "@cdk/validators";
import { NzMessageService } from "ng-zorro-antd/message"; import { NzMessageService } from "ng-zorro-antd/message";
import { finalize } from "rxjs"; import { Subject, debounceTime, distinctUntilChanged, filter, finalize, switchMap, throttleTime } from "rxjs";
@Component({ @Component({
selector: "app-dish-form", selector: "app-dish-form",
@ -13,29 +14,47 @@ import { finalize } from "rxjs";
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() orgs: any[] = [];
@Input() foods: any[] = [];
private orgSearch$ = new Subject<Record<string, string>>();
private foodSearch$ = new Subject<Record<string, string>>();
formGroup!: FormGroup; formGroup!: FormGroup;
selectedValue = null; selectedValue = null;
listOfOption: Array<{ value: string; text: string }> = []; orgListOfOption: Array<{ value: string; text: string }> = [];
foodListOfOption: Array<{ value: string; text: string }> = [];
searchedFood: Array<{ value: string; text: string }> = [];
foodSelected: string[] = [];
foodItemSelected: any[] = [];
nzFilterOption = (): boolean => true; nzFilterOption = (): boolean => true;
globalEnum = this.api.globalEnum; globalEnum = this.api.globalEnum;
allMonth = [ allMonth = [
{ value: "1", label: "一月", checked: false }, { value: 1, label: "一月", checked: false },
{ value: "2", label: "二月", checked: false }, { value: 2, label: "二月", checked: false },
{ value: "3", label: "三月", checked: false }, { value: 3, label: "三月", checked: false },
{ value: "4", label: "四月", checked: false }, { value: 4, label: "四月", checked: false },
{ value: "5", label: "五月", checked: false }, { value: 5, label: "五月", checked: false },
{ value: "6", label: "六月", checked: false }, { value: 6, label: "六月", checked: false },
{ value: "7", label: "七月", checked: false }, { value: 7, label: "七月", checked: false },
{ value: "8", label: "八月", checked: false }, { value: 8, label: "八月", checked: false },
{ value: "9", label: "九月", checked: false }, { value: 9, label: "九月", checked: false },
{ value: "10", label: "十月", checked: false }, { value: 10, label: "十月", checked: false },
{ value: "11", label: "十一月", checked: false }, { value: 11, label: "十一月", checked: false },
{ value: "12", label: "十二月", checked: false }, { value: 12, label: "十二月", checked: false },
]; ];
allMonthChecked = false; allMonthChecked = false;
@ -47,7 +66,7 @@ export class DishFormComponent {
addFoodVisible = false; addFoodVisible = false;
get food(): FormArray { get food(): FormArray {
return this.formGroup.get("food") as FormArray; return this.formGroup.get("ingredient") as FormArray;
} }
get icon() { get icon() {
@ -56,30 +75,116 @@ export class DishFormComponent {
ngOnInit(): void { ngOnInit(): void {
this.formGroup = this.fb.group({ this.formGroup = this.fb.group({
id: this.fb.control("", [FormValidators.required()]), 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([], []), mark: this.fb.control("", [FormValidators.required()]),
food: this.fb.array([], [FormValidators.required()]),
month: this.fb.control([], []), month: this.fb.control([], []),
}); });
}
search(value: string): void {
this.api
.getOrgList({ keyword: value })
this.orgSearch$
.pipe(
debounceTime(500),
distinctUntilChanged(),
switchMap((q) => this.api.getOrgList(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.id.toString(), value: item.id.toString(),
text: item.account, text: item.name,
});
});
this.orgListOfOption = listOfOption;
});
this.foodSearch$
.pipe(
filter((f) => !!f),
debounceTime(500),
distinctUntilChanged(),
switchMap((q) => this.api.getFoodList(q))
)
.subscribe((data) => {
const listOfOption: Array<{ value: string; text: string }> = [];
data.body.forEach((item) => {
listOfOption.push({
value: item.key,
text: item.name,
}); });
}); });
this.listOfOption = listOfOption; this.searchedFood = this.searchedFood.concat(listOfOption);
this.foodListOfOption = listOfOption;
}); });
this.setValues();
}
setValues() {
console.log("this.orgs", this.orgs, this.data);
this.orgListOfOption = this.orgs.map((i) => ({ text: i.name, value: i.id }));
if (this.data) {
this.allMonth = this.allMonth.map((i) =>
(this.data.month ?? []).includes(i.value) ? { ...i, checked: true } : i
);
this.monthCheckEffect();
// console.log("allMonth", allMonth);
this.formGroup.patchValue({
...this.data,
vendors: [this.data.vender],
mark: this.data.marks,
});
}
}
public getValues() {
let values = null;
console.log("this.formGroup.getRawValue()", this.formGroup.getRawValue());
if (Utils.validateFormGroup(this.formGroup)) {
const value = this.formGroup.getRawValue();
// const { _nutrition, key, name, type } = this.formGroup.getRawValue();
let ingredient = Object.create(null);
for (const f of this.foodItemSelected) {
let num = Number(f.num);
if (!num) {
this.msg.error(`请输入${f.value}-${f.text}的重量`);
return;
}
ingredient[f.value] = num;
}
const month = this.allMonth
.reduce((a: any, c: any) => {
if (c.checked) {
return a.concat(Number(c.value));
}
return a;
}, [] as number[])
.join(",");
const vendors = value.vendors?.join(",") ?? "";
values = {
...value,
vendors,
month,
ingredient,
};
}
return values;
}
searchOrg(value: string): void {
if (value) {
this.orgSearch$.next({ keyword: value });
}
}
searchFood(value: string): void {
if (value) {
this.foodSearch$.next({ keyword: value });
}
}
onFoodSelected(v: string[]) {
this.foodItemSelected = this.searchedFood.filter((f) => v.includes(f.value));
} }
addFood() { addFood() {
@ -111,7 +216,12 @@ export class DishFormComponent {
} }
} }
monthChecked() { monthChecked(checked: boolean, value: number) {
this.allMonth = this.allMonth.map((i) => (i.value === value ? { ...i, checked } : i));
this.monthCheckEffect();
}
monthCheckEffect() {
if (this.allMonth.every((item) => !item.checked)) { if (this.allMonth.every((item) => !item.checked)) {
this.allMonthChecked = false; this.allMonthChecked = false;
this.indeterminate = false; this.indeterminate = false;

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

@ -17,9 +17,19 @@
<ng-template #searchTpl> <ng-template #searchTpl>
<nz-form-item class="w-40"> <nz-form-item class="w-60">
<nz-form-control> <nz-form-control>
<nz-select nzPlaceHolder="单位" [nzOptions]="[]"></nz-select> <nz-select
nzShowSearch
nzServerSearch
nzPlaceHolder="请选择单位"
[nzShowArrow]="false"
formControlName="vendors"
[nzFilterOption]="nzFilterOption"
(nzOnSearch)="searchOrg($event)">
<nz-option *ngFor="let o of listOfOption" [nzLabel]="o.text"
[nzValue]="o.value"></nz-option>
</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">
@ -33,7 +43,7 @@
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-control> <nz-form-control>
<input nz-input placeholder="请输入菜品名称" formControlName="name" /> <input nz-input placeholder="请输入菜品名称" formControlName="keyword" />
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
</ng-template> </ng-template>
@ -41,25 +51,19 @@
<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"
[ngStyle]="{'background-image':'url(' + data + ')'}"> [ngStyle]="{'background-image':'url(' + data + ')'}">
</div> </div>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'vender'"> <ng-container *ngSwitchCase="'vender'">
{{tableOrg[data] || '-'}} {{tableOrg[data]?.name || '-'}}
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'foodArr'"> <ng-container *ngSwitchCase="'foodArr'">
<a nz-popover <div class=" flex flex-wrap">
[nzPopoverContent]="popoverTpl" <nz-tag *ngFor="let item of data" class="m-1">
nzPopoverTitle="食材及含量"> {{item.label}}:{{item.value}}{{item.measurement}}
<b>{{data.length}}</b>中营养素 </nz-tag>
</a> </div>
<ng-template #popoverTpl>
<div class=" max-w-sm max-h-60 overflow-auto">
<nz-tag *ngFor="let item of data" class="m-1">
{{item.label}}:{{item.value}}{{item.measurement}}
</nz-tag>
</div>
</ng-template>
</ng-container> </ng-container>
<ng-container *ngSwitchDefault> <ng-container *ngSwitchDefault>
@ -76,10 +80,10 @@
<ng-template #formFooterTpl> <ng-template #formFooterTpl>
<nz-space> <nz-space>
<button *nzSpaceItem nz-button (click)="cancelFoodForm()"> <button *nzSpaceItem nz-button (click)="cancelForm()" type="button">
取消 取消
</button> </button>
<button *nzSpaceItem nz-button nzType="primary"> <button *nzSpaceItem nz-button nzType="primary" (click)="onSubmit()">
保存 保存
</button> </button>
</nz-space> </nz-space>

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

@ -1,10 +1,10 @@
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, 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 { Subject, lastValueFrom, tap } from "rxjs"; import { Subject, debounceTime, distinctUntilChanged, filter, lastValueFrom, switchMap, takeUntil, tap } 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";
@ -35,18 +35,53 @@ export class DishComponent {
}); });
public queryForm = new FormGroup({ public queryForm = new FormGroup({
name: new FormControl(""), keyword: new FormControl(""),
mark: new FormControl(""), mark: new FormControl(""),
vendors: new FormControl(""),
}); });
private orgSearch$ = new Subject<string>();
public selectedIds: string[] = []; public selectedIds: string[] = [];
tableOrg: { [k: number]: string } = {}; tableOrg: { [k: number]: OrgDTO } = {};
listOfOption: Array<{ value: number; text: string }> = [];
nzFilterOption = (): boolean => true;
submitLoading = false;
ngOnInit(): void { ngOnInit(): void {
this.initTableList(); this.initTableList();
this.orgSearch$
.pipe(
filter((f) => !!f),
debounceTime(500),
distinctUntilChanged(),
takeUntil(this.destroy$),
switchMap((term: string) => this.api.getOrgList({ keyword: term }))
)
.subscribe((data) => {
const listOfOption: Array<{ value: number; text: string }> = [];
data.body.forEach((item) => {
listOfOption.push({
value: item.id,
text: item.name,
});
});
this.listOfOption = listOfOption;
});
this.tableList.getState$.pipe(takeUntil(this.destroy$)).subscribe((res) => {
this.selectedIds = res.selectedKeys as Array<string>;
});
} }
searchOrg = (k: string) => {
this.orgSearch$.next(k);
};
ngOnDestroy(): void { ngOnDestroy(): void {
this.destroy$.next(); this.destroy$.next();
this.destroy$.complete(); this.destroy$.complete();
@ -90,7 +125,7 @@ export class DishComponent {
this.tableOrg = org.body.reduce((a, c) => { this.tableOrg = org.body.reduce((a, c) => {
return { return {
...a, ...a,
[c.id]: c.account, [c.id]: c,
}; };
}, {} as AnyObject); }, {} as AnyObject);
} }
@ -100,21 +135,40 @@ export class DishComponent {
); );
} }
showFoodForm(food?: any) { showFoodForm(data?: any) {
this.drawerRef = this.drawer.create({ this.drawerRef = this.drawer.create({
nzTitle: food ? "编辑菜品" : "新增菜品", nzTitle: data ? "编辑菜品" : "新增菜品",
nzWidth: 700, nzWidth: 700,
nzContent: DishFormComponent, nzContent: DishFormComponent,
nzContentParams: {
data,
orgs: Object.values(this.tableOrg),
},
nzFooter: this.formFooterTpl, nzFooter: this.formFooterTpl,
}); });
} }
cancelFoodForm() { cancelForm() {
this.drawerRef?.close(); this.drawerRef?.close();
} }
onSubmit() {
if (this.drawerRef) {
const com = this.drawerRef.getContentComponent() as DishFormComponent;
const val = com.getValues();
if (val) {
this.submitLoading = true;
this.api.saveDish(val).subscribe((res) => {
this.msg.success(res.desc);
this.tableList.run();
this.cancelForm();
});
}
}
}
deleteItem(v?: any) { deleteItem(v?: any) {
const ids = v ? [v.key] : this.selectedIds; const ids = v ? [v.id] : this.selectedIds;
this.modal.confirm({ this.modal.confirm({
nzTitle: "警告", nzTitle: "警告",
nzContent: `是否要删除${ids.length}个菜品?`, nzContent: `是否要删除${ids.length}个菜品?`,

55
projects/admin/src/app/pages/food/food.component.html

@ -50,20 +50,13 @@
<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="'nutrientArr'"> <ng-container *ngSwitchCase="'nutrientArr'">
<a nz-popover <div class=" flex flex-wrap">
[nzPopoverContent]="popoverTpl" <nz-tag *ngFor="let item of data" class="m-1">
nzPopoverTitle="营养素"> {{item.label}}:{{item.value}}{{item.measurement}}
<b>{{data.length}}</b>中营养素 </nz-tag>
</a> </div>
<ng-template #popoverTpl>
<div class=" max-w-sm max-h-60 overflow-auto">
<nz-tag *ngFor="let item of data" class="m-1">
{{item.label}}:{{item.value}}{{item.measurement}}
</nz-tag>
</div>
</ng-template>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'time'"> <ng-container *ngSwitchCase="'modify'">
{{data | date:'yyyy-MM-dd HH:mm:ss'}} {{data | date:'yyyy-MM-dd HH:mm:ss'}}
</ng-container> </ng-container>
<ng-container *ngSwitchDefault> <ng-container *ngSwitchDefault>
@ -83,26 +76,28 @@
<ng-template #importFormTpl> <ng-template #importFormTpl>
<p class=" text-slate-400"> <p class=" text-slate-400">
提示:<a>下载示例模版</a>,进行编辑,导入Excel文件,批量导入食材 提示:<a (click)="downloadTemplate()">下载示例模版</a>,进行编辑,导入Excel文件,批量导入食材
</p> </p>
<div> <div>
<div class="upload-area flex-col" <nz-spin [nzSpinning]="uploadLoading">
(drop)="handleDrop($event)" <div class="upload-area flex-col"
(dragenter)="suppress($event)" (drop)="handleDrop($event)"
(dragover)="suppress($event)"> (dragenter)="suppress($event)"
<input type="file" id="file" (change)="onFileChange($any($event.target).files)" accept=".xlsx,.xls" /> (dragover)="suppress($event)">
<div class="text-center"> <input type="file" id="file" (change)="onFileChange($event)" accept=".xlsx,.xls" />
<p class="text-4xl mb-1"> <div class="text-center">
<a nz-icon nzType="upload" nzTheme="outline"></a> <p class="text-4xl mb-1">
</p> <a nz-icon nzType="upload" nzTheme="outline"></a>
<p class="mb-1"> </p>
点击或将文件拖拽到这里上传 <p class="mb-1">
</p> 点击或将文件拖拽到这里上传
<p class="text-slate-400"> </p>
支持扩展名:.xls .xlsx <p class="text-slate-400">
</p> 支持扩展名:.xls .xlsx
</p>
</div>
</div> </div>
</div> </nz-spin>
</div> </div>
</ng-template> </ng-template>

47
projects/admin/src/app/pages/food/food.component.ts

@ -46,6 +46,8 @@ export class FoodComponent implements OnInit, OnDestroy {
submitLoading = false; submitLoading = false;
uploadLoading = false;
editItem = null; editItem = null;
ngOnInit(): void { ngOnInit(): void {
@ -66,8 +68,8 @@ export class FoodComponent implements OnInit, OnDestroy {
{ key: "key", title: "食材编号" }, { key: "key", title: "食材编号" },
{ key: "name", title: "食材名称" }, { key: "name", title: "食材名称" },
{ key: "type", title: "食材类型" }, { key: "type", title: "食材类型" },
{ key: "nutrientArr", title: "营养素(每100g可食部)" }, { key: "nutrientArr", title: "营养素(每100g可食部)", width: "40%" },
{ key: "time", title: "更新日期" }, { key: "modify", title: "更新日期" },
]); ]);
this.tableList = this.tableList.setOptions([ this.tableList = this.tableList.setOptions([
@ -145,6 +147,12 @@ export class FoodComponent implements OnInit, OnDestroy {
}); });
} }
downloadTemplate() {
this.api.getFoodExcelTemplate().subscribe((res) => {
console.log("res", res);
});
}
showImportForm(nzContent: TemplateRef<{}>) { showImportForm(nzContent: TemplateRef<{}>) {
this.importModalRef = this.modal.create({ this.importModalRef = this.modal.create({
nzTitle: "导入食材清单", nzTitle: "导入食材清单",
@ -169,15 +177,36 @@ export class FoodComponent implements OnInit, OnDestroy {
} }
}; };
onFileChange(files: FileList) { onFileChange(e: FileList | Event) {
let files: FileList;
console.log(e instanceof FileList, e);
if (e instanceof FileList) {
files = e;
} else {
const target = e.target as HTMLInputElement;
console.log(e instanceof FileList, target.files);
files = target.files!;
}
if (files?.length > 0) { if (files?.length > 0) {
const formData = new FormData(); const formData = new FormData();
formData.append("files", files[0]); formData.append("file", files[0]);
this.api.importFood(formData).subscribe((res) => { this.uploadLoading = true;
this.msg.success(res.desc); this.api
this.tableList.run(); .importFood(formData)
this.importModalRef?.close(); .pipe(
}); finalize(() => {
this.uploadLoading = false;
})
)
.subscribe(() => {
this.msg.success("导入成功");
this.tableList.run();
this.importModalRef?.close();
});
}
if (e instanceof Event) {
const target = e.target as HTMLInputElement;
target.value = "";
} }
} }
} }

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

@ -26,14 +26,14 @@
<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">
<span class="flex-1">适用单位</span> <span class="flex-1">适用单位</span>
<ng-template #searchAndSelectTpl> <!-- <ng-template #searchAndSelectTpl>
<div class=" w-96"> <div class=" w-96">
<search-and-select [handleSearh]="searchOrg" <search-and-select [handleSearh]="searchOrg"
(onSelect)="onSelectOrg($event)"> (onSelect)="onSelectOrg($event)">
</search-and-select> </search-and-select>
</div> </div>
</ng-template> </ng-template> -->
<button type="button" nz-button nzType="link" <!-- <button type="button" nz-button nzType="link"
nz-popover nz-popover
[nzPopoverVisible]="nzPopoverVisible" [nzPopoverVisible]="nzPopoverVisible"
(click)="nzPopoverVisible = !nzPopoverVisible" (click)="nzPopoverVisible = !nzPopoverVisible"
@ -42,11 +42,23 @@
[nzPopoverContent]="searchAndSelectTpl"> [nzPopoverContent]="searchAndSelectTpl">
<i nz-icon nzType="plus"></i> <i nz-icon nzType="plus"></i>
添加单位 添加单位
</button> </button> -->
</div> </div>
</nz-form-label> </nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl" nzSpan="12"> <nz-form-control [nzErrorTip]="formControlErrorTpl" nzSpan="12">
<div> <nz-select
[nzMode]="'multiple'"
nzShowSearch
nzServerSearch
nzPlaceHolder="请选择单位"
[nzShowArrow]="false"
formControlName="vendors"
[nzFilterOption]="nzFilterOption"
(nzOnSearch)="searchOrg($event)">
<nz-option *ngFor="let o of listOfOption" [nzLabel]="o.text"
[nzValue]="o.value"></nz-option>
</nz-select>
<!-- <div>
<nz-tag class="px-3 py-1" *ngFor="let v of vendors"> <nz-tag class="px-3 py-1" *ngFor="let v of vendors">
<span>{{v.label}}</span> <span>{{v.label}}</span>
<a (click)="removeOrg(v.value)"> <a (click)="removeOrg(v.value)">
@ -54,7 +66,7 @@
</a> </a>
</nz-tag> </nz-tag>
</div> </div>
<input type="hidden" formControlName="vendors" /> <input type="hidden" formControlName="vendors" /> -->
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>

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

@ -7,7 +7,7 @@ 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 { finalize, map } from "rxjs"; import { Subject, debounceTime, distinctUntilChanged, filter, finalize, map, switchMap } from "rxjs";
@Component({ @Component({
selector: "app-standard-form", selector: "app-standard-form",
@ -33,6 +33,8 @@ export class StandardFormComponent {
} }
} }
private orgSearch$ = new Subject<string>();
formGroup!: FormGroup; formGroup!: FormGroup;
submitLoading = false; submitLoading = false;
@ -41,9 +43,13 @@ export class StandardFormComponent {
state: any; state: any;
nzPopoverVisible = false; // nzPopoverVisible = false;
// vendors: OptionItemInterface[] = [];
listOfOption: Array<{ value: number; text: string }> = [];
vendors: OptionItemInterface[] = []; nzFilterOption = (): boolean => true;
ngOnInit(): void { ngOnInit(): void {
this.formGroup = this.fb.group({ this.formGroup = this.fb.group({
@ -52,34 +58,59 @@ export class StandardFormComponent {
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()]),
}); });
this.formGroup.patchValue(this.state); if (this.state) {
this.formGroup.patchValue(this.state);
if (Array.isArray(this.state.vendors)) {
this.api.getOrgList({ vendors: this.state.vendors }).subscribe((data) => {
const listOfOption: Array<{ value: number; text: string }> = [];
data.body.forEach((item) => {
listOfOption.push({
value: item.id,
text: item.name,
});
});
this.listOfOption = listOfOption;
this.formGroup.patchValue(this.state);
});
}
}
this.orgSearch$
.pipe(
filter((f) => !!f),
debounceTime(500),
distinctUntilChanged(),
switchMap((term: string) => this.api.getOrgList({ keyword: term }))
)
.subscribe((data) => {
const listOfOption: Array<{ value: number; text: string }> = [];
data.body.forEach((item) => {
listOfOption.push({
value: item.id,
text: item.name,
});
});
this.listOfOption = listOfOption;
});
} }
searchOrg = (k: string) => { searchOrg = (k: string) => {
return this.api.getOrgList({ keyword: k }).pipe( this.orgSearch$.next(k);
map((res) => {
return res.body.map((i) => ({
label: i.name,
value: i.id + "",
}));
})
);
}; };
onSelectOrg(v: OptionItemInterface[]) { // onSelectOrg(v: OptionItemInterface[]) {
v.forEach((i) => { // v.forEach((i) => {
if (!this.vendors.some((s) => s.value === i.value)) { // if (!this.vendors.some((s) => s.value === i.value)) {
this.vendors.push(i); // this.vendors.push(i);
} // }
}); // });
this.formGroup.get("vendors")?.setValue(this.vendors.map((i) => i.value)); // this.formGroup.get("vendors")?.setValue(this.vendors.map((i) => i.value));
this.nzPopoverVisible = false; // this.nzPopoverVisible = false;
} // }
removeOrg(v: string) { // removeOrg(v: string) {
this.vendors = this.vendors.filter((f) => f.value !== v); // this.vendors = this.vendors.filter((f) => f.value !== v);
this.formGroup.get("vendors")?.setValue(this.vendors.map((i) => i.value)); // this.formGroup.get("vendors")?.setValue(this.vendors.map((i) => i.value));
} // }
onSubmit(gotoSetting?: boolean) { onSubmit(gotoSetting?: boolean) {
if (Utils.validateFormGroup(this.formGroup)) { if (Utils.validateFormGroup(this.formGroup)) {

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

@ -1,4 +1,4 @@
import { HttpClient, HttpParams } 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, PageResult, ResponseType } from "@cdk/types"; import { AnyObject, PageResult, ResponseType } from "@cdk/types";
import { Utils } from "@cdk/utils"; import { Utils } from "@cdk/utils";
@ -220,7 +220,29 @@ export class ApiService {
getFoodList(query: {}) { getFoodList(query: {}) {
const q = Utils.objectStringify(query); const q = Utils.objectStringify(query);
return this.http.get<ResponseType<OrgDTO[]>>(`/api/vender/select?${q}`); return this.http.get<ResponseType<any[]>>(`/api/ingredient/select?${q}`);
}
getFoodExcelTemplate() {
return this.http.get("/api/ingredient/excel", { observe: "response", responseType: "blob" as "json" }).pipe(
tap((res) => {
this.downLoadFile(res);
})
);
}
downLoadFile(response: HttpResponse<Object>) {
const fileNameFromHeader = response.headers.get("Content-Disposition");
if (fileNameFromHeader) {
const fileName = fileNameFromHeader.trim()?.split("''")?.[1]?.replace(/"/g, "") ?? `模板_${Date.now()}.xlsx`;
const blob = new Blob([response.body as any]);
const downloadLink = document.createElement("a");
downloadLink.href = URL.createObjectURL(blob);
downloadLink.download = decodeURIComponent(fileName);
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
}
} }
saveFood(food: AnyObject, isEdit?: boolean) { saveFood(food: AnyObject, isEdit?: boolean) {
@ -235,7 +257,17 @@ export class ApiService {
} }
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
.put<ResponseType>("/api/ingredient/excel", f, {
observe: "response",
responseType: "blob" as "json",
})
.pipe(
tap((res) => {
this.downLoadFile(res);
})
);
} }
markFood(mark: string, ingredient: string) { markFood(mark: string, ingredient: string) {
@ -304,8 +336,14 @@ export class ApiService {
); );
} }
saveDish(v: AnyObject) {
const body = Utils.objectToFormData(v);
const method = v["id"] ? "post" : "put";
return this.http[method]<ResponseType>("/api/dish", body);
}
deleteDish(ids: string[]) { deleteDish(ids: string[]) {
const params = Utils.objectToFormData({ id: 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 });
} }
} }

Loading…
Cancel
Save