Browse Source

营养标准列表 & 菜品

main
kkerwin 2 years ago
parent
commit
d8eec9736e
  1. 15
      README.md
  2. 24
      projects/admin/src/app/components/dish-form/dish-form.component.html
  3. 59
      projects/admin/src/app/components/dish-form/dish-form.component.ts
  4. 3
      projects/admin/src/app/components/food-form/food-form.component.ts
  5. 33
      projects/admin/src/app/pages/dish/dish.component.html
  6. 58
      projects/admin/src/app/pages/dish/dish.component.ts
  7. 16
      projects/admin/src/app/pages/organization/organization-form/organization-form.component.html
  8. 7
      projects/admin/src/app/pages/organization/organization-form/organization-form.component.ts
  9. 104
      projects/admin/src/app/pages/organization/organization-list/organization-list.component.html
  10. 62
      projects/admin/src/app/pages/organization/organization-list/organization-list.component.ts
  11. 35
      projects/admin/src/app/pages/standard/standard-form/standard-form.component.html
  12. 40
      projects/admin/src/app/pages/standard/standard-form/standard-form.component.ts
  13. 183
      projects/admin/src/app/pages/standard/standard-setting/standard-setting.component.html
  14. 162
      projects/admin/src/app/pages/standard/standard-setting/standard-setting.component.ts
  15. 1
      projects/cdk/src/dtos/enum.dto.ts
  16. 53
      projects/cdk/src/services/api.service.ts
  17. 1
      projects/cdk/src/shared/components/index.ts
  18. 47
      projects/cdk/src/shared/components/search-and-select/search-and-select.component.html
  19. 17
      projects/cdk/src/shared/components/search-and-select/search-and-select.component.less
  20. 58
      projects/cdk/src/shared/components/search-and-select/search-and-select.component.ts
  21. 13
      projects/cdk/src/shared/shared.module.ts
  22. 1
      projects/cdk/src/table-list/table-list/table-list.component.ts
  23. 5
      projects/cdk/src/types/index.ts
  24. 12
      projects/cdk/src/utils/index.ts

15
README.md

@ -37,4 +37,17 @@ To get more help on the Angular CLI use `ng help` or go check out the [Angular C
5. 删除 食材 系统异常
6. 管理端 食材列表 没有返回 更新日期
7. 营养标准列表 列表 没有 更新时间
8. 营养标准添加成功 需要 返回他的 id
8. 营养标准添加成功 需要 返回他的 id
------------------
# 09/17 下午
1. 获取企业列表 原型上 还有个 单位类型
2. 营养标准 需要 返回 单位名称和id vendors:[{label:"单位名称",id:vendorId}]
3. 根据关键字搜索单位 报错或者 返回空 list
----------------
1. 食材也需要一个 类似 /api/vender/select 的接口
2. 新增菜品 -> 食材名称 -> 食材标签 的枚举值 有哪些?
3. 单位修改 status 我调用的是编辑接口报错

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

@ -4,12 +4,23 @@
单位
</nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl">
<nz-select
<!-- <nz-select
formControlName="unit"
[nzOptions]="[]"
nzMode="multiple"
nzPlaceHolder="请选择单位,多选时在多个单位均添加此菜品">
</nz-select> -->
<nz-select
[nzMode]="'multiple'"
nzShowSearch
nzServerSearch
nzPlaceHolder="请选择单位,多选时在多个单位均添加此菜品"
[nzShowArrow]="false"
[nzFilterOption]="nzFilterOption"
(nzOnSearch)="search($event)">
<nz-option *ngFor="let o of listOfOption" [nzLabel]="o.text" [nzValue]="o.value"></nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
@ -27,11 +38,10 @@
</nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl">
<nz-select
formControlName="tag"
[nzOptions]="[]"
formControlName="mark"
nzPlaceHolder="请选择菜品标签">
<nz-option *ngFor="let item of globalEnum.mark" [nzValue]="item.value" [nzLabel]="item.key"></nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
@ -64,10 +74,14 @@
菜品图片
</nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl">
<input type="hidden" formControlName="icon" />
<div class="mb-2" *ngIf="icon">
<img [src]="icon" class="h-20 w-20" />
</div>
<button class="upload-btn " nz-button [nzLoading]="uploadLoading">
<i nz-icon nzType="upload"></i>
上传图片
<input type="file" formControlName="img" (change)="onFileChange($event)" />
<input type="file" (change)="onFileChange($event)" />
</button>
</nz-form-control>
</nz-form-item>

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

@ -1,5 +1,6 @@
import { Component, OnInit } from "@angular/core";
import { FormArray, FormBuilder, FormGroup } from "@angular/forms";
import { ApiService } from "@cdk/services";
import { FormValidators } from "@cdk/validators";
import { NzMessageService } from "ng-zorro-antd/message";
import { finalize } from "rxjs";
@ -10,10 +11,18 @@ import { finalize } from "rxjs";
styleUrls: ["./dish-form.component.less"],
})
export class DishFormComponent {
constructor(private fb: FormBuilder, private msg: NzMessageService) {}
constructor(private fb: FormBuilder, private msg: NzMessageService, private api: ApiService) {}
formGroup!: FormGroup;
selectedValue = null;
listOfOption: Array<{ value: string; text: string }> = [];
nzFilterOption = (): boolean => true;
globalEnum = this.api.globalEnum;
allMonth = [
{ value: "1", label: "一月", checked: false },
{ value: "2", label: "二月", checked: false },
@ -41,18 +50,38 @@ export class DishFormComponent {
return this.formGroup.get("food") as FormArray;
}
get icon() {
return this.formGroup.get("icon")?.value;
}
ngOnInit(): void {
this.formGroup = this.fb.group({
id: this.fb.control("", [FormValidators.required()]),
unit: this.fb.control([], [FormValidators.required()]),
vendors: this.fb.control([], [FormValidators.required()]),
name: this.fb.control("", [FormValidators.required()]),
img: this.fb.control("", []),
tag: this.fb.control([], []),
icon: this.fb.control("", []),
mark: this.fb.control([], []),
food: this.fb.array([], [FormValidators.required()]),
month: this.fb.control([], []),
});
}
search(value: string): void {
this.api
.getOrgList({ keyword: value })
.subscribe((data) => {
const listOfOption: Array<{ value: string; text: string }> = [];
data.body.forEach((item) => {
listOfOption.push({
value: item.id.toString(),
text: item.account,
});
});
this.listOfOption = listOfOption;
});
}
addFood() {
this.food.push(
this.fb.group({
@ -98,25 +127,15 @@ export class DishFormComponent {
const target = e.target as HTMLInputElement;
const file = target.files![0];
target.value = "";
const formData = new FormData();
if (file.size / 1024 / 1024 >= 5) {
this.msg.error("图片大小不能超过5M");
return;
}
const fileReader = new FileReader();
fileReader.onload = () => {
const base64 = fileReader.result as string;
const v = base64.split("base64,")[1];
this.formGroup.get("icon")?.setValue(base64);
};
formData.append("file", file);
this.uploadLoading = true;
// this.api
// .uploadLogo(formData)
// .pipe(
// finalize(() => {
// this.uploadLoading = false;
// })
// )
// .subscribe((r) => {
// this.msg.success(r.desc);
// fileReader.readAsDataURL(file);
// });
fileReader.readAsDataURL(file);
}
}

3
projects/admin/src/app/components/food-form/food-form.component.ts

@ -33,6 +33,7 @@ export class FoodFormComponent implements OnInit {
});
if (this.food) {
this.formGroup.patchValue(this.food);
this.formGroup.get("key")?.disable();
this.food.nutrientArr?.forEach((n: any) => {
this.createNutrition(n);
});
@ -78,7 +79,7 @@ export class FoodFormComponent implements OnInit {
public getValues() {
let values = null;
if (Utils.validateFormGroup(this.formGroup)) {
const { _nutrition, key, name, type } = this.formGroup.value;
const { _nutrition, key, name, type } = this.formGroup.getRawValue();
let nutrient = Object.create(null);
if (Array.isArray(_nutrition)) {
for (const n of _nutrition) {

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

@ -1,7 +1,7 @@
<app-page>
<ng-template #pageExtraTpl>
<nz-space>
<button *nzSpaceItem nz-button>批量删除</button>
<button *nzSpaceItem nz-button [disabled]="!selectedIds.length" (click)="deleteItem()">批量删除</button>
<button *nzSpaceItem nz-button>批量打印营养标签</button>
<button *nzSpaceItem nz-button nzType="primary" (click)="showFoodForm()">
<i nz-icon nzType="plus"></i>
@ -15,9 +15,7 @@
<table-list [props]="tableList" [search]="searchTpl" [action]="pageExtraTpl" [formGroup]="queryForm"
[renderColumns]="renderColumnsTpl">
<ng-template #actionTpl>
<button nz-button>批量删除</button>
</ng-template>
<ng-template #searchTpl>
<nz-form-item class="w-40">
<nz-form-control>
@ -26,7 +24,11 @@
</nz-form-item>
<nz-form-item class="w-40">
<nz-form-control>
<nz-select nzPlaceHolder="菜品标签" [nzOptions]="[]"></nz-select>
<nz-select nzPlaceHolder="菜品标签" formControlName="mark" nzAllowClear>
<nz-option *ngFor="let item of globalEnum.mark" [nzLabel]="item.key"
[nzValue]="item.key">
</nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
<nz-form-item>
@ -37,11 +39,28 @@
</ng-template>
<ng-template #renderColumnsTpl let-data let-key="key" let-row="row">
<ng-container [ngSwitch]="key">
<ng-container *ngSwitchCase="'img'">
<ng-container *ngSwitchCase="'icon'">
<div class="dish-img overflow-auto"
[ngStyle]="{'background-image':'url(' + tempImg + ')'}">
[ngStyle]="{'background-image':'url(' + data + ')'}">
</div>
</ng-container>
<ng-container *ngSwitchCase="'vender'">
{{tableOrg[data] || '-'}}
</ng-container>
<ng-container *ngSwitchCase="'foodArr'">
<a nz-popover
[nzPopoverContent]="popoverTpl"
nzPopoverTitle="食材及含量">
<b>{{data.length}}</b>中营养素
</a>
<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 *ngSwitchDefault>
{{data}}

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

@ -4,7 +4,9 @@ import { NzDrawerRef, NzDrawerService } from "ng-zorro-antd/drawer";
import { AnyObject, TableListOption } from "@cdk/public-api";
import { DishFormComponent } from "@admin/app/components";
import { ApiService } from "@cdk/services";
import { Subject } from "rxjs";
import { Subject, lastValueFrom, tap } from "rxjs";
import { NzModalService } from "ng-zorro-antd/modal";
import { NzMessageService } from "ng-zorro-antd/message";
@Component({
selector: "app-dish",
@ -12,7 +14,12 @@ import { Subject } from "rxjs";
styleUrls: ["./dish.component.less"],
})
export class DishComponent {
constructor(private drawer: NzDrawerService, private api: ApiService) {}
constructor(
private drawer: NzDrawerService,
private api: ApiService,
private modal: NzModalService,
private msg: NzMessageService
) {}
@ViewChild("formFooterTpl") formFooterTpl!: TemplateRef<{}>;
@ -20,16 +27,22 @@ export class DishComponent {
private destroy$ = new Subject<void>();
tempImg = "https://cdn.pixabay.com/photo/2023/08/08/18/01/butterfly-8177925_1280.jpg";
public globalEnum = this.api.globalEnum;
public tableList = new TableListOption(this.fetchData.bind(this), {
selectable: true,
frontPagination: false,
});
public queryForm = new FormGroup({
name: new FormControl(""),
mark: new FormControl(""),
});
public selectedIds: string[] = [];
tableOrg: { [k: number]: string } = {};
ngOnInit(): void {
this.initTableList();
}
@ -42,11 +55,11 @@ export class DishComponent {
initTableList() {
this.tableList.scroll = { x: null };
this.tableList = this.tableList.setColumns([
{ key: "img", title: "菜品图片", width: "66px" },
{ key: "icon", title: "菜品图片", width: "66px" },
{ key: "name", title: "菜品名称" },
{ key: "name", title: "菜品标签" },
{ key: "name", title: "食材及含量", width: "30%" },
{ key: "name", title: "单位" },
{ key: "marks", title: "菜品标签" },
{ key: "foodArr", title: "食材及含量", width: "30%" },
{ key: "vender", title: "单位" },
]);
this.tableList = this.tableList.setOptions([
@ -69,7 +82,22 @@ export class DishComponent {
}
fetchData(query: AnyObject, pager: AnyObject) {
return this.api.page(pager, query);
return this.api.getDishPage(pager, query).pipe(
tap((res) => {
if (Array.isArray(res.body.content)) {
this.api.getOrgList({ vendors: res.body.content.map((i) => i.vender) }).subscribe((org) => {
if (Array.isArray(org.body)) {
this.tableOrg = org.body.reduce((a, c) => {
return {
...a,
[c.id]: c.account,
};
}, {} as AnyObject);
}
});
}
})
);
}
showFoodForm(food?: any) {
@ -85,5 +113,17 @@ export class DishComponent {
this.drawerRef?.close();
}
deleteItem() {}
deleteItem(v?: any) {
const ids = v ? [v.key] : this.selectedIds;
this.modal.confirm({
nzTitle: "警告",
nzContent: `是否要删除${ids.length}个菜品?`,
nzOkDanger: true,
nzOnOk: async () => {
const res = await lastValueFrom(this.api.deleteDish(ids));
this.msg.success(res.desc);
this.tableList.run();
},
});
}
}

16
projects/admin/src/app/pages/organization/organization-form/organization-form.component.html

@ -10,6 +10,18 @@
<input nz-input placeholder="请输入单位名称" formControlName="name" />
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzRequired>
单位类型
</nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl" nzSpan="12">
<nz-select nzPlaceHolder="单位类型" formControlName="category">
<nz-option *ngFor="let item of globalEnum.venderType" [nzValue]="item.value"
[nzLabel]="item.key">
</nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label>
单位Logo
@ -71,7 +83,7 @@
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzRequired>
<nz-form-label [nzRequired]="!state">
初始密码
</nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl" nzSpan="12">
@ -96,7 +108,7 @@
<button *nzSpaceItem nz-button nzType="primary" (click)="onSubmit()">
确定
</button>
<button *nzSpaceItem nz-button>
<button *nzSpaceItem nz-button [routerLink]="['/organization/list']">
取消
</button>
</nz-space>

7
projects/admin/src/app/pages/organization/organization-form/organization-form.component.ts

@ -39,6 +39,8 @@ export class OrganizationFormComponent {
uploadLoading = false;
globalEnum = this.api.globalEnum;
get icon() {
return this.formGroup.get("icon")?.value;
}
@ -47,8 +49,9 @@ export class OrganizationFormComponent {
this.formGroup = this.fb.group({
id: this.fb.control(""),
account: this.fb.control("", [FormValidators.required("账号不能为空")]),
password: this.fb.control("", [FormValidators.required("密码不能为空")]),
password: this.fb.control("", this.state ? [] : [FormValidators.required("密码不能为空")]),
name: this.fb.control("", [FormValidators.required("单位名称不能为空")]),
category: this.fb.control("", [FormValidators.required("单位类型不能为空")]),
expire: this.fb.control("", [FormValidators.required("账号到期时间不能为空")]),
icon: this.fb.control("", []),
address: this.fb.control("", []),
@ -62,7 +65,7 @@ export class OrganizationFormComponent {
async onSubmit() {
if (Utils.validateFormGroup(this.formGroup)) {
const org = { ...(this.state ?? {}), ...this.formGroup.value };
org["password"] = MD5(org.password!).toString().substring(16).toUpperCase();
org["password"] = org.password ? MD5(org.password!).toString().substring(16).toUpperCase() : void 0;
org["expire"] = format(org["expire"], "yyyy-MM-dd");
org["venderId"] = org.id;
// const account = await lastValueFrom(this.api.checkOrgAccount(org.account!));

104
projects/admin/src/app/pages/organization/organization-list/organization-list.component.html

@ -5,70 +5,62 @@
<nz-card [nzBordered]="false" nzTitle="单位列表">
<ng-template #pageExtraTpl>
<nz-space>
<button *nzSpaceItem nz-button nzType="primary" [routerLink]="['/','organization','form','create']">
<i nz-icon nzType="plus"></i>
新增单位
</button>
</nz-space>
</ng-template>
<div class="flex items-center justify-between ">
<div nz-row [nzGutter]="12">
<nz-form-item class="w-52 mr-2">
<table-list [props]="tableList"
[search]="searchTpl"
[action]="pageExtraTpl"
[formGroup]="queryForm"
[renderColumns]="renderColumnsTpl">
<ng-template #searchTpl>
<nz-form-item class="w-52 ">
<nz-form-control>
<nz-select nzPlaceHolder="单位类型" [nzOptions]="[]"></nz-select>
<nz-select nzPlaceHolder="单位类型" formControlName="category">
<nz-option *ngFor="let item of globalEnum.venderType" [nzValue]="item.value"
[nzLabel]="item.key">
</nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
<nz-form-item class="w-60">
<nz-form-control>
<input nz-input placeholder="请输入单位名称" />
<input nz-input placeholder="请输入单位名称" formControlName="keyword" />
</nz-form-control>
</nz-form-item>
</div>
<nz-space>
<button *nzSpaceItem nz-button nzType="primary" [routerLink]="['/','organization','form','create']">
<i nz-icon nzType="plus"></i>
新增单位
</button>
</nz-space>
</div>
<nz-table #table [nzData]="orgList">
<thead>
<tr>
<th>账号</th>
<th>单位名称</th>
<th>单位类型</th>
<th>地址</th>
<th>联系人</th>
<th>联系电话</th>
<th>账号使用状态</th>
<th>账号到期时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let data of table.data">
<td>{{data.account}}</td>
<td>{{data.name}}</td>
<td>{{data.category}}</td>
<td>{{data.province}}{{data.area}}{{data.address}}</td>
<td>{{data.contacts}}</td>
<td>{{data.phone}}</td>
<td>
<nz-switch
nzCheckedChildren="开"
nzUnCheckedChildren="关"
[(ngModel)]="data.status">
</nz-switch>
</td>
<td>{{data.expire | date:'yyyy-MM-dd'}}</td>
<td>
<a (click)="toEdit(data)">编辑</a>
<nz-divider nzType="vertical"></nz-divider>
<a (click)="deleteItem(data.id)">删除</a>
</td>
</tr>
</tbody>
</nz-table>
</ng-template>
<ng-template #renderColumnsTpl let-data let-key="key" let-row="row">
<ng-container [ngSwitch]="key">
<ng-container *ngSwitchCase="'expire'">
{{ data|date:'yyyy-MM-dd HH:mm:ss'}}
</ng-container>
<ng-container *ngSwitchCase="'status'">
<div (click)="toggleStatus(row)">
<div class="pointer-events-none">
<nz-switch
nzCheckedChildren="开"
nzUnCheckedChildren="关"
[ngModel]="data"
[ngModelOptions]="{standalone: true}">
</nz-switch>
</div>
</div>
</ng-container>
<ng-container *ngSwitchCase="'address'">
<td>{{row.province}}{{row.area}}{{data}}</td>
</ng-container>
<ng-container *ngSwitchDefault>
{{data}}
</ng-container>
</ng-container>
</ng-template>
</table-list>
</nz-card>
</div>
</app-page>

62
projects/admin/src/app/pages/organization/organization-list/organization-list.component.ts

@ -23,19 +23,51 @@ export class OrganizationListComponent {
private msg: NzMessageService
) {}
originalOrgList: OrgDTO[] = [];
public tableList = new TableListOption(this.fetchData.bind(this), {
frontPagination: false,
});
orgList: OrgDTO[] = [];
globalEnum = this.api.globalEnum;
public queryForm = new FormGroup({
category: new FormControl(""),
keyword: new FormControl(""),
});
ngOnInit(): void {
this.getList();
this.initTableList();
}
getList() {
this.api.getOrgList().subscribe((res) => {
this.originalOrgList = res.body ?? [];
this.orgList = this.originalOrgList;
});
initTableList() {
this.tableList.scroll = { x: null };
this.tableList = this.tableList.setColumns([
{ key: "account", title: "账号" },
{ key: "name", title: "单位名称" },
{ key: "category", title: "单位类型" },
{ key: "address", title: "地址" },
{ key: "contacts", title: "联系人" },
{ key: "phone", title: "联系电话" },
{ key: "status", title: "账号使用状态" },
{ key: "expire", title: "账号到期时间" },
]);
this.tableList = this.tableList.setOptions([
{
title: "编辑",
premissions: [],
onClick: this.toEdit.bind(this),
},
{
title: "删除",
premissions: [],
onClick: this.deleteItem.bind(this),
},
]);
}
fetchData(p: {}, q: {}) {
return this.api.getOrgPage(p, q);
}
toEdit(org: OrgDTO) {
@ -44,17 +76,23 @@ export class OrganizationListComponent {
});
}
deleteItem(id: number) {
toggleStatus(v: any) {
this.api.saveOrg({ ...v, venderId: v.id, status: !v.status }).subscribe((res) => {
this.msg.success(res.desc);
this.tableList.run();
});
}
deleteItem(v: any) {
this.modal.confirm({
nzTitle: "警告",
nzContent: "是否要删除该单位?",
nzOkDanger: true,
nzOnOk: async () => {
const res = await lastValueFrom(this.api.deleteOrg(id));
const res = await lastValueFrom(this.api.deleteOrg(v.id));
if (res.success) {
this.msg.success(res.desc);
this.originalOrgList = this.originalOrgList.filter((f) => f.id !== id);
this.orgList = this.orgList.filter((f) => f.id !== id);
this.tableList.run();
return true;
}
return false;

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

@ -24,12 +24,37 @@
<nz-form-item>
<nz-form-label nzRequired>
适用单位
<div class="flex justify-between items-center w-full">
<span class="flex-1">适用单位</span>
<ng-template #searchAndSelectTpl>
<div class=" w-96">
<search-and-select [handleSearh]="searchOrg"
(onSelect)="onSelectOrg($event)">
</search-and-select>
</div>
</ng-template>
<button type="button" nz-button nzType="link"
nz-popover
[nzPopoverVisible]="nzPopoverVisible"
(click)="nzPopoverVisible = !nzPopoverVisible"
nzPopoverPlacement="right"
nzPopoverTrigger="click"
[nzPopoverContent]="searchAndSelectTpl">
<i nz-icon nzType="plus"></i>
添加单位
</button>
</div>
</nz-form-label>
<nz-form-control [nzErrorTip]="formControlErrorTpl" nzSpan="12">
<nz-select nzMode="multiple" nzPlaceHolder="请选择适用单位" formControlName="vendors">
<nz-option *ngFor="let org of orgList" [nzValue]="org.id" [nzLabel]="org.name"></nz-option>
</nz-select>
<div>
<nz-tag class="px-3 py-1" *ngFor="let v of vendors">
<span>{{v.label}}</span>
<a (click)="removeOrg(v.value)">
<i nz-icon nzType="close"></i>
</a>
</nz-tag>
</div>
<input type="hidden" formControlName="vendors" />
</nz-form-control>
</nz-form-item>
<nz-form-item>
@ -41,7 +66,7 @@
</button>
<button *nzSpaceItem nz-button nzType="primary" (click)="onSubmit()"
[nzLoading]="submitLoading">
确定
保存
</button>
<button *nzSpaceItem nz-button type="button" [routerLink]="['/standard/list']">
取消

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

@ -3,10 +3,11 @@ import { FormBuilder, FormGroup } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { OrgDTO } from "@cdk/dtos";
import { ApiService } from "@cdk/services";
import { OptionItemInterface } from "@cdk/types";
import { Utils } from "@cdk/utils";
import { FormValidators } from "@cdk/validators";
import { NzMessageService } from "ng-zorro-antd/message";
import { finalize } from "rxjs";
import { finalize, map } from "rxjs";
@Component({
selector: "app-standard-form",
@ -40,10 +41,11 @@ export class StandardFormComponent {
state: any;
nzPopoverVisible = false;
vendors: OptionItemInterface[] = [];
ngOnInit(): void {
this.api.getOrgList().subscribe((res) => {
this.orgList = res.body;
});
this.formGroup = this.fb.group({
id: this.fb.control("", []),
name: this.fb.control("", [FormValidators.required()]),
@ -52,11 +54,39 @@ export class StandardFormComponent {
});
this.formGroup.patchValue(this.state);
}
searchOrg = (k: string) => {
return this.api.getOrgList({ keyword: k }).pipe(
map((res) => {
return res.body.map((i) => ({
label: i.name,
value: i.id + "",
}));
})
);
};
onSelectOrg(v: OptionItemInterface[]) {
v.forEach((i) => {
if (!this.vendors.some((s) => s.value === i.value)) {
this.vendors.push(i);
}
});
this.formGroup.get("vendors")?.setValue(this.vendors.map((i) => i.value));
this.nzPopoverVisible = false;
}
removeOrg(v: string) {
this.vendors = this.vendors.filter((f) => f.value !== v);
this.formGroup.get("vendors")?.setValue(this.vendors.map((i) => i.value));
}
onSubmit(gotoSetting?: boolean) {
if (Utils.validateFormGroup(this.formGroup)) {
this.submitLoading = true;
const { foodCategoryDay, foodCategoryWeek, ingredient } = this.state;
this.api
.saveStandard(this.formGroup.value)
.saveStandard({ foodCategoryDay, foodCategoryWeek, ingredient, ...this.formGroup.value })
.pipe(
finalize(() => {
this.submitLoading = false;

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

@ -1,53 +1,198 @@
<app-page>
<form nz-form [formGroup]="formGroup" nzLayout="vertical">
<div nz-form nzLayout="vertical">
<nz-card nzTitle="食物种类及数量标准设置">
<div nz-row [nzGutter]="12">
<div nz-col nzSpan="12">
<div nz-col nzSpan="24">
<ng-template #dayBtnTpl>
<button nz-button nzType="link">
<button nz-button nzType="link" (click)="addFoodType('day')">
添加食物种类
</button>
</ng-template>
<nz-card nzType="inner" nzTitle="食物种类及数量标准(日)" [nzExtra]="dayBtnTpl">
<div class=" overflow-y-auto h-80">
add
<nz-empty *ngIf="foodCategoryDay.length === 0"></nz-empty>
<div *ngIf="foodCategoryDay.length > 0">
<div class="">
<div nz-row [nzGutter]="[48,12]">
<div nz-col nzSpan="12" *ngFor="let d of foodCategoryDay">
<div class="flex items-center">
<div class="w-60">
<nz-select nzPlaceHolder="请选择" class="w-full" [(ngModel)]="d.type">
<nz-option *ngFor="let cate of globalEnum.category"
[nzLabel]="cate.key"
[nzValue]="cate.value">
</nz-option>
</nz-select>
</div>
<div class="flex-1 mx-3">
<nz-input-group nzAddOnBefore="大于等于" nzAddOnAfter="种">
<input type="number" min="0" nz-input [(ngModel)]="d.value" />
</nz-input-group>
</div>
<div>
<button nz-button nzType="link" (click)="removeFoodType('day',d.type)">
<i nz-icon nzType="delete"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</nz-card>
</div>
<div nz-col nzSpan="12">
<div nz-col nzSpan="24" class="mt-4">
<ng-template #weekBtnTpl>
<button nz-button nzType="link">
<button nz-button nzType="link" (click)="addFoodType('week')">
添加食物种类
</button>
</ng-template>
<nz-card nzType="inner" nzTitle="食物种类及数量标准(周)" [nzExtra]="weekBtnTpl">
<div class=" overflow-y-auto h-80">
<nz-empty *ngIf="foodCategoryWeek.length === 0"></nz-empty>
<div *ngIf="foodCategoryWeek.length > 0">
<div class="">
<div nz-row [nzGutter]="[48,12]">
<div nz-col nzSpan="12" *ngFor="let w of foodCategoryWeek">
<div class="flex items-center">
<div class="w-60">
<nz-select nzPlaceHolder="请选择" class="w-full" [(ngModel)]="w.type">
<nz-option *ngFor="let cate of globalEnum.category"
[nzLabel]="cate.key"
[nzValue]="cate.value">
</nz-option>
</nz-select>
</div>
<div class="flex-1 mx-3">
<nz-input-group nzAddOnBefore="大于等于" nzAddOnAfter="种">
<input type="number" nz-input [(ngModel)]="w.value" />
</nz-input-group>
</div>
<div>
<button nz-button nzType="link" (click)="removeFoodType('week',w.type)">
<i nz-icon nzType="delete"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</nz-card>
</div>
</div>
</nz-card>
<nz-card nzTitle="人群名称1" class="mt-4"></nz-card>
<nz-card nzTitle="营养标准人群设置" class="mt-4 mb-20" [nzExtra]="peopleExtraTpl">
<nz-empty *ngIf="ingredient.length === 0"></nz-empty>
<ng-container *ngIf="ingredient.length > 0">
<nz-card
nzType="inner"
[nzTitle]="peopleGroupNamTpl"
[nzExtra]="peopleActionTpl"
*ngFor="let p of ingredient;let i = index"
class="mt-4">
<ng-template #peopleGroupNamTpl>
<div class="flex items-center">
<span class="mr-2">
人群名称:
</span>
<div class=" w-80">
<input nz-input placeholder="请输入人群名称" [(ngModel)]="p.name" />
</div>
</div>
</ng-template>
<ng-template #peopleActionTpl>
<nz-space>
<button *nzSpaceItem nz-button nzType="link" (click)="addNutrition(i)">
添加营养素
</button>
<button *nzSpaceItem nz-button nzType="link" (click)="removePeopleGroup(i)" nzDanger>
删除人群
</button>
</nz-space>
</ng-template>
<nz-empty *ngIf="p.nutritions.length === 0"></nz-empty>
<ng-container *ngIf="p.nutritions.length > 0">
<h3 class="mb-2">每人每天能量和营养素供给量标准</h3>
<div nz-row [nzGutter]="[48,12]">
<div nz-col nzSpan="12" *ngFor="let n of p.nutritions">
<div class="flex items-center">
<div class="w-40">
<nz-select nzPlaceHolder="请选择" class="w-full" [(ngModel)]="n.nutrition">
<nz-option *ngFor="let cate of globalEnum.nutrient "
[nzLabel]="cate.value + '(' + cate.measurement + ')'"
[nzValue]="cate.key">
</nz-option>
</nz-select>
</div>
<div class="flex-1 ml-2">
<nz-space nzAlign="center">
<input *nzSpaceItem nz-input placeholder="最小值" type="number"
[(ngModel)]="n.min" />
<div class="px-2" *nzSpaceItem>
~
</div>
<input *nzSpaceItem nz-input placeholder="最大值" type="number"
[(ngModel)]="n.max" />
</nz-space>
</div>
<div class="ml-2">
<nz-space nzAlign="center">
<div class="pr-3" *nzSpaceItem>
<label nz-checkbox [(ngModel)]="n.hasUl"
(ngModelChange)="ulChange(i,n.nutrition,$event)">
ul值
</label>
</div>
<div *nzSpaceItem class="w-20">
<ng-container>
<input [disabled]="!n.hasUl"
nz-input
placeholder=" ul值"
type="number"
[(ngModel)]="n.ul" />
</ng-container>
</div>
</nz-space>
</div>
<div class="ml-2">
<button nz-button nzType="link" (click)="removeNutrition(i,n.nutrition)">
<i nz-icon nzType="delete"></i>
</button>
</div>
</div>
</div>
</div>
</ng-container>
</nz-card>
</ng-container>
</nz-card>
<ng-template #peopleExtraTpl>
<button nz-button (click)="addPeopleGroup()">
添加人群
</button>
</ng-template>
</form>
</div>
<ng-template #formControlErrorTpl let-control>
<form-error-tips [control]="control"></form-error-tips>
</ng-template>
<div class="fixed-footter left-[218px] fixed bottom-0 right-0 bg-white z-10 pl-8 py-2">
<nz-space>
<button *nzSpaceItem nz-button nzType="primary" (click)="onSubmit()">
确定
</button>
<button *nzSpaceItem nz-button>
取消
</button>
</nz-space>
<div class="fixed-footter left-[218px] fixed bottom-0 right-0 bg-white z-10 py-2 ">
<div class="flex justify-center">
<nz-space>
<button *nzSpaceItem nz-button nzType="primary" (click)="onSubmit()">
确定
</button>
<button *nzSpaceItem nz-button>
取消
</button>
</nz-space>
</div>
</div>
</app-page>

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

@ -2,9 +2,23 @@ import { Component } from "@angular/core";
import { FormBuilder, FormGroup } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { ApiService } from "@cdk/services";
import { AnyObject } from "@cdk/types";
import { FormValidators } from "@cdk/validators";
import { NzMessageService } from "ng-zorro-antd/message";
export type StandardItemInterface = {
type: string;
value: number;
};
export type StandardPeopleInterface = {
ul?: number;
hasUl: boolean;
max: number;
min: number;
nutrition: string;
};
@Component({
selector: "app-standard-setting",
templateUrl: "./standard-setting.component.html",
@ -29,15 +43,155 @@ export class StandardSettingComponent {
}
}
state: any;
public globalEnum = this.api.globalEnum;
formGroup!: FormGroup;
state: any;
uploadLoading = false;
ngOnInit(): void {}
foodCategoryDay: StandardItemInterface[] = [];
foodCategoryWeek: StandardItemInterface[] = [];
ingredient: { name: string; nutritions: StandardPeopleInterface[] }[] = [];
ngOnInit(): void {
this.foodCategoryDay = this.parseFoodCategory(this.state?.foodCategoryDay);
this.foodCategoryWeek = this.parseFoodCategory(this.state?.foodCategoryWeek);
this.ingredient = this.parseIngredient(this.state?.ingredient);
}
parseIngredient(data: any): { name: string; nutritions: StandardPeopleInterface[] }[] {
if (!data) {
return [];
}
return Object.entries(data).map(([k, v]) => {
return {
name: k,
nutritions: !v
? []
: Object.entries(v).map(([kn, vn]) => {
return {
ul: vn?.ul ?? 0,
hasUl: !!vn?.ul,
max: vn?.max ?? 0,
min: vn?.min ?? 0,
nutrition: kn,
};
}),
};
});
}
parseFoodCategory(data: any): StandardItemInterface[] {
if (!data) {
return [];
}
return Object.entries(data).map(([k, v]) => {
return {
type: k,
value: v as number,
};
});
}
onSubmit() {
this.router.navigate(["/", "standard", "setting", "45"]);
if (this.foodCategoryDay.some((s) => !s.type || !s.value)) {
this.msg.error("请设置正确的食物种类及数量标准(日)");
return;
}
if (this.foodCategoryWeek.some((s) => !s.type || !s.value)) {
this.msg.error("请设置正确的食物种类及数量标准(周)");
return;
}
if (this.ingredient.some((s) => !s.name || !s.nutritions.some((sn) => sn.nutrition))) {
this.msg.error("请设置正确的营养标准人群");
return;
}
const foodCategoryDay = this.foodCategoryDay.reduce((a, c) => {
return {
...a,
[c.type]: c.value,
};
}, {} as AnyObject);
const foodCategoryWeek = this.foodCategoryWeek.reduce((a, c) => {
return {
...a,
[c.type]: c.value,
};
}, {} as AnyObject);
const ingredient = this.ingredient.reduce((a, c) => {
return {
...a,
[c.name]: c.nutritions.reduce((an, cn) => {
const ul = cn.hasUl ? { ul: cn.ul } : {};
return {
...an,
[cn.nutrition]: {
min: cn.min,
max: cn.max,
...ul,
},
};
}, {} as AnyObject),
};
}, {} as AnyObject);
this.api
.saveStandard({ id: this.state.id, foodCategoryDay, foodCategoryWeek, ingredient }, true)
.subscribe((res) => {
this.msg.success(res.desc);
this.router.navigate(["/standard/list"]);
});
}
addFoodType(type: string) {
const item = type === "day" ? this.foodCategoryDay : this.foodCategoryWeek;
const withoutSelectType = this.globalEnum.category.find((f) => !item.some((s) => s.type === f.key));
item.push({ type: withoutSelectType?.key ?? "", value: 1 });
}
removeFoodType(type: string, foodType: string) {
if (type === "day") {
this.foodCategoryDay = this.foodCategoryDay.filter((f) => f.type !== foodType);
} else {
this.foodCategoryWeek = this.foodCategoryWeek.filter((f) => f.type !== foodType);
}
}
addPeopleGroup() {
this.ingredient.push({
name: "",
nutritions: [],
});
}
removePeopleGroup(idx: number) {
this.ingredient = this.ingredient.filter((_, i) => i !== idx);
}
addNutrition(idx: number) {
const current = this.ingredient[idx];
const withoutSelectNutritions = this.globalEnum.nutrient.find(
(f) => !current.nutritions.some((s) => s.nutrition === f.key)
);
current.nutritions.push({
nutrition: withoutSelectNutritions?.key ?? "",
min: 0,
max: 0,
hasUl: false,
});
}
removeNutrition(idx: number, nutrition: string) {
this.ingredient[idx].nutritions = this.ingredient[idx].nutritions.filter((f) => f.nutrition !== nutrition);
}
ulChange(idx: number, nutrition: string, checked: boolean) {
this.ingredient[idx].nutritions = this.ingredient[idx].nutritions.map((i) => {
return i.nutrition === nutrition ? { ...i, ul: checked ? 1 : void 0 } : i;
});
}
}

1
projects/cdk/src/dtos/enum.dto.ts

@ -2,6 +2,7 @@ export type GlobalEnum = {
category: CategoryDTO[];
mark: MarkDTO[];
nutrient: NutrientDTO[];
venderType: CategoryDTO[];
};
export type CategoryDTO = {

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

@ -151,8 +151,15 @@ export class ApiService {
});
}
getOrgList() {
return this.http.get<ResponseType<OrgDTO[]>>("/api/vender");
getOrgList(query: {}) {
const q = Utils.objectStringify(query);
return this.http.get<ResponseType<OrgDTO[]>>(`/api/vender/select?${q}`);
}
getOrgPage(p: {}, q: {}) {
let params = Utils.objectStringify({ ...p, ...q }, { skipEmptyString: false, skipNull: false });
return this.http.get<ResponseType<PageResult<OrgDTO>>>(`/api/vender?${params}`);
}
checkOrgAccount(account: string) {
@ -170,9 +177,9 @@ export class ApiService {
}
deleteOrg(id: number) {
const params = new HttpParams().set("vender", id);
const params = Utils.objectToFormData({ venderId: id });
return this.http.delete<ResponseType>("/api/vender", {
params,
body: params,
});
}
@ -211,6 +218,11 @@ export class ApiService {
);
}
getFoodList(query: {}) {
const q = Utils.objectStringify(query);
return this.http.get<ResponseType<OrgDTO[]>>(`/api/vender/select?${q}`);
}
saveFood(food: AnyObject, isEdit?: boolean) {
const body = Utils.objectToFormData(food);
const method = isEdit ? "post" : "put";
@ -230,6 +242,7 @@ export class ApiService {
const params = Utils.objectToFormData({ mark, ingredient });
return this.http.put<ResponseType>("/api/ingredient/mark", params);
}
removeFoodMark(ingredient: string) {
const params = Utils.objectToFormData({ ingredient });
return this.http.delete<ResponseType>("/api/ingredient/mark", { body: params });
@ -258,8 +271,8 @@ export class ApiService {
}
saveStandard(v: AnyObject, isEdit?: boolean) {
const body = Utils.objectToFormData({ ...v, vendors: v["vendors"].join(",") });
const method = isEdit ? "post" : "put";
const body = Utils.objectToFormData({ ...v, vendors: v["vendors"]?.join(",") });
const method = v["id"] ? "post" : "put";
return this.http[method]<ResponseType>("/api/nutrition", body);
}
@ -267,4 +280,32 @@ export class ApiService {
const params = Utils.objectToFormData({ id });
return this.http.delete<ResponseType>(`/api/nutrition`, { body: params });
}
getDishPage(p: {}, q: {}) {
const params = Utils.objectStringify({ ...p, ...q });
return this.http.get<ResponseType<PageResult>>(`/api/dish?${params}`).pipe(
map((r) => {
if (Array.isArray(r.body.content)) {
r.body.content = r.body.content.map((o) => {
return {
...o,
foodArr: Object.entries(o.ingredient).map(([k, v]) => {
return {
key: k,
value: v,
label: k,
};
}),
};
});
}
return r;
})
);
}
deleteDish(ids: string[]) {
const params = Utils.objectToFormData({ id: ids.join(",") });
return this.http.delete<ResponseType>(`/api/dish`, { body: params });
}
}

1
projects/cdk/src/shared/components/index.ts

@ -0,0 +1 @@
export * from "./search-and-select/search-and-select.component";

47
projects/cdk/src/shared/components/search-and-select/search-and-select.component.html

@ -0,0 +1,47 @@
<div class="search-select flex flex-col">
<ng-template #suffixIconSearch>
<span nz-icon nzType="search"></span>
</ng-template>
<div class="flex">
<nz-input-group [nzSuffix]="suffixIconSearch" class="flex-1">
<input
class="w-full"
nz-input
nzShowSearch
nzServerSearch
[placeholder]="placeHolder"
[(ngModel)]="searchValue"
(ngModelChange)="search($event)" />
</nz-input-group>
<button nz-button nzType="primary" (click)="onSubmit()" class="ml-2">
确定
</button>
</div>
<div class="flex-1 select pt-2">
<nz-card nzSize="small">
<nz-empty *ngIf="listOfOption.length === 0"></nz-empty>
<ul
*ngIf="listOfOption.length > 0"
class="ant-dropdown-menu ant-dropdown-menu-root ant-dropdown-menu-light ant-dropdown-menu-vertical shadow-none">
<li
*ngFor="let o of listOfOption"
class="ant-dropdown-menu-item"
[ngClass]="{'ant-dropdown-menu-item-selected':selected.has(o.value)}"
(click)="handleSelect(o.value)">
{{o.label}}
</li>
</ul>
</nz-card>
</div>
<!-- <div class="flex items-center justify-end mt-2">
<nz-space>
<button *nzSpaceItem nz-button nzType="text">
取消
</button>
<button *nzSpaceItem nz-button nzType="primary">
确定
</button>
</nz-space>
</div> -->
</div>

17
projects/cdk/src/shared/components/search-and-select/search-and-select.component.less

@ -0,0 +1,17 @@
:host {
display: block;
width: 100%;
}
.search-select {
width: 100%;
min-height: 200px;
max-height: 400px;
.select {
overflow-y: auto;
}
}

58
projects/cdk/src/shared/components/search-and-select/search-and-select.component.ts

@ -0,0 +1,58 @@
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { OptionItemInterface } from "@cdk/types";
import { NzSelectModeType } from "ng-zorro-antd/select";
import { Observable, debounceTime } from "rxjs";
@Component({
selector: "search-and-select",
templateUrl: "./search-and-select.component.html",
styleUrls: ["./search-and-select.component.less"],
})
export class SearchAndSelectComponent {
constructor() {}
@Input() handleSearh!: (keyword: string) => Observable<OptionItemInterface[]>;
@Input() placeHolder: string = "输入关键字搜索然后选择";
@Input() mode: NzSelectModeType = "multiple";
@Output() onSelect = new EventEmitter<Array<OptionItemInterface>>();
listOfOption: Array<OptionItemInterface> = [];
searchValue = "";
selected = new Set<string>();
nzFilterOption = (): boolean => true;
search(value: string) {
if (!value) {
return;
}
this.handleSearh
.call(this, value)
.pipe(debounceTime(500))
.subscribe((data) => {
this.listOfOption = data;
// this.listOfOption = Array.from({ length: 2 }, (_, i) => ({ label: "dd" + i, value: i.toString() }));
});
}
handleSelect(v: string) {
if (this.selected.has(v)) {
this.selected.delete(v);
} else {
this.selected.add(v);
}
}
onSubmit() {
const selects: OptionItemInterface[] = [];
this.selected.forEach((i) => {
selects.push(this.listOfOption.find((f) => f.value === i)!);
});
this.onSelect.emit(selects);
}
}

13
projects/cdk/src/shared/shared.module.ts

@ -20,6 +20,7 @@ import {
// import { environment } from "@manage/environments/environment";
import { NgxPermissionsModule } from "ngx-permissions";
import { AppPageComponent } from "@cdk/app-page/app-page.component";
import { SearchAndSelectComponent } from "./components";
const ngModules = [CommonModule, HttpClientModule, FormsModule, RouterModule, ReactiveFormsModule];
const components: any = [];
@ -35,8 +36,16 @@ const cdks = [
] as any;
@NgModule({
declarations: [...components, ...directives],
declarations: [...components, ...directives, SearchAndSelectComponent],
imports: [...ngZorroModules, ...ngModules, ...cdks, AppPageComponent],
exports: [...ngZorroModules, ...ngModules, ...components, ...directives, ...cdks, AppPageComponent],
exports: [
...ngZorroModules,
...ngModules,
...components,
...directives,
...cdks,
AppPageComponent,
SearchAndSelectComponent,
],
})
export class SharedModule {}

1
projects/cdk/src/table-list/table-list/table-list.component.ts

@ -141,6 +141,7 @@ export class TableListComponent implements OnInit, OnChanges, AfterViewInit, OnD
})
)
.subscribe((f: ResponseType<PageResult>) => {
// alert(f.body.totalElements);
this.dataSource = f.body.content;
this.props.pager.total = f.body.totalElements;
this.totalPages = Math.ceil(this.props.pager.total / this.props.pager.size);

5
projects/cdk/src/types/index.ts

@ -6,6 +6,11 @@ export type DecText = number | string;
export type Augmented<O extends object> = O & AnyObject;
export type OptionItemInterface = Augmented<{
label: string;
value: string;
}>;
export interface ResponseType<T = any> {
body: T;
code: number;

12
projects/cdk/src/utils/index.ts

@ -1,7 +1,7 @@
import { HttpParams } from "@angular/common/http";
import { AbstractControl, FormControl, FormGroup } from "@angular/forms";
import { AnyObject } from "@cdk/types";
import queryString from "query-string";
import queryString, { StringifyOptions } from "query-string";
export class Utils {
static validateFormGroup(formGroup: FormGroup) {
@ -92,11 +92,13 @@ export class Utils {
}
};
static objectStringify(o: AnyObject) {
return queryString.stringify(o, {
static objectStringify(o: AnyObject, option?: StringifyOptions) {
const config = {
skipEmptyString: true,
skipNull: true,
arrayFormat: "comma",
});
arrayFormat: "comma" as any,
...option,
};
return queryString.stringify(o, config);
}
}

Loading…
Cancel
Save