diff --git a/README.md b/README.md index c391e60..a31d4b3 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,5 @@ # CateringWebApp -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.1.4. - -## Development server - -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. - -## Code scaffolding - -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. - -## Build - -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. - -## Running unit tests - -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). - -## Running end-to-end tests - -Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. - -## Further help - -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. ------ @@ -65,4 +40,14 @@ To get more help on the Angular CLI use `ng help` or go check out the [Angular C 1. 食谱审核 列表需要 提交审核时间、提交人 字段 2. 食谱列表 需要 返回一个字段 标识当前食谱 是否是 管理系统添加的还是业务系统添加的 -3. 食谱保存 菜品 报500,同样的数据 昨天都可以 \ No newline at end of file +3. 食谱保存 菜品 报500,同样的数据 昨天都可以 + +------- + +# 09/29 + +1. 业务端大屏 +2. 权限 +3. 详情&编辑食谱 -> 显示类型优化 & 分析 +4. 菜品分类 +5. 食材批量删除 \ No newline at end of file diff --git a/projects/admin/src/app/components/ingredient-status-list/ingredient-status-list.component.ts b/projects/admin/src/app/components/ingredient-status-list/ingredient-status-list.component.ts index fdedd23..2943ca3 100644 --- a/projects/admin/src/app/components/ingredient-status-list/ingredient-status-list.component.ts +++ b/projects/admin/src/app/components/ingredient-status-list/ingredient-status-list.component.ts @@ -68,19 +68,19 @@ export class IngredientStatusListComponent { { key: "vender", title: "单位" }, { key: "modify", title: "提交审核时间" }, - { key: "modify", title: "提交人" }, + { key: "operate", title: "提交人" }, ]); this.tableList = this.tableList.setOptions([ { title: "详情", premissions: [], - onClick: this.showFoodForm.bind(this), + onClick: this.preview.bind(this), }, { title: "导出", premissions: [], - onClick: this.showFoodForm.bind(this), + onClick: this.export.bind(this), }, { title: "通过", @@ -101,6 +101,19 @@ export class IngredientStatusListComponent { ]); } + preview({ id }: any) { + window.open(`/ingredient/preview?id=${id}`); + } + + export({ id }: any) { + this.msg.loading("导出中..."); + this.api.exportMenu(id).subscribe(() => { + setTimeout(() => { + this.msg.remove(); + }, 1500); + }); + } + fetchData(query: AnyObject, pager: AnyObject) { return this.api.getMenuStatusPage(pager, { ...query, status: this.status }).pipe( tap((res) => { diff --git a/projects/admin/src/app/pages/dish/dish.component.html b/projects/admin/src/app/pages/dish/dish.component.html index 9ef59ac..166ba03 100644 --- a/projects/admin/src/app/pages/dish/dish.component.html +++ b/projects/admin/src/app/pages/dish/dish.component.html @@ -87,4 +87,57 @@ 保存 + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ 主要原料:{{printData.ingredients.join(',')}} +
+
+ 1毫克(mg)钠相当于2.5毫克食盐 +
+
\ No newline at end of file diff --git a/projects/admin/src/app/pages/dish/dish.component.ts b/projects/admin/src/app/pages/dish/dish.component.ts index ea434dd..3b2c460 100644 --- a/projects/admin/src/app/pages/dish/dish.component.ts +++ b/projects/admin/src/app/pages/dish/dish.component.ts @@ -18,6 +18,7 @@ import { import { NzModalService } from "ng-zorro-antd/modal"; import { NzMessageService } from "ng-zorro-antd/message"; import { ResponseType } from "@cdk/types"; +import { PrintComponent } from "@cdk/shared/components"; @Component({ selector: "app-dish", @@ -34,6 +35,8 @@ export class DishComponent { @ViewChild("formFooterTpl") formFooterTpl!: TemplateRef<{}>; + @ViewChild("print") printRef!: PrintComponent; + private drawerRef?: NzDrawerRef; private destroy$ = new Subject(); @@ -42,6 +45,8 @@ export class DishComponent { public globalEnum = this.api.globalEnum; + public printData: any | null; + public tableList = new TableListOption(this.fetchData.bind(this), { selectable: true, frontPagination: false, @@ -114,7 +119,7 @@ export class DishComponent { { title: "打印营养标签", premissions: [], - onClick: this.showFoodForm.bind(this), + onClick: this.print.bind(this), }, { title: "编辑", @@ -129,6 +134,17 @@ export class DishComponent { ]); } + print(v: any) { + this.msg.loading("数据请求中,请不要刷新页面", { + nzDuration: 0, + }); + this.api.getDishLabel(v.id).subscribe((res) => { + this.printData = res.body[0]; + this.printRef.print(); + this.msg.remove(); + }); + } + fetchData(query: AnyObject, pager: AnyObject) { return this.api.getDishPage(pager, query).pipe( tap((res) => { diff --git a/projects/admin/src/app/pages/ingredients/ingredient-form/ingredient-form.component.html b/projects/admin/src/app/pages/ingredients/ingredient-form/ingredient-form.component.html index 0c7f3da..0db1e17 100644 --- a/projects/admin/src/app/pages/ingredients/ingredient-form/ingredient-form.component.html +++ b/projects/admin/src/app/pages/ingredients/ingredient-form/ingredient-form.component.html @@ -26,7 +26,7 @@ - - - - \ No newline at end of file + \ No newline at end of file diff --git a/projects/admin/src/app/pages/ingredients/ingredient-release/ingredient-release.component.ts b/projects/admin/src/app/pages/ingredients/ingredient-release/ingredient-release.component.ts index db00b05..f0b311a 100644 --- a/projects/admin/src/app/pages/ingredients/ingredient-release/ingredient-release.component.ts +++ b/projects/admin/src/app/pages/ingredients/ingredient-release/ingredient-release.component.ts @@ -22,10 +22,6 @@ export class IngredientReleaseComponent { private msg: NzMessageService ) {} - @ViewChild("foofFormFooterTpl") foofFormFooterTpl!: TemplateRef<{}>; - - private drawerRef?: NzDrawerRef; - public tableList = new TableListOption(this.fetchData.bind(this)); public queryForm = new FormGroup({ @@ -58,12 +54,12 @@ export class IngredientReleaseComponent { { title: "详情", premissions: [], - onClick: this.showFoodForm.bind(this), + onClick: this.preview.bind(this), }, { - title: "导出食谱", + title: "导出", premissions: [], - onClick: this.showFoodForm.bind(this), + onClick: this.export.bind(this), }, { @@ -74,6 +70,10 @@ export class IngredientReleaseComponent { ]); } + preview({ id }: any) { + window.open(`/ingredient/preview?id=${id}`); + } + fetchData(pager: AnyObject, query: AnyObject) { if (this.dateRange) { if (this.dateRange[0]) { @@ -129,19 +129,15 @@ export class IngredientReleaseComponent { return [startDate, endDate]; } - showFoodForm(food?: any) { - this.drawerRef = this.drawer.create({ - nzTitle: food ? "编辑菜品" : "新增菜品", - nzWidth: 700, - nzContent: DishFormComponent, - nzFooter: this.foofFormFooterTpl, + export({ id }: any) { + this.msg.loading("导出中..."); + this.api.exportMenu(id).subscribe(() => { + setTimeout(() => { + this.msg.remove(); + }, 1500); }); } - cancelFoodForm() { - this.drawerRef?.close(); - } - cancelRelease({ id }: any) { this.modal.confirm({ nzTitle: "警告", diff --git a/projects/admin/src/app/services/http.interceptor.ts b/projects/admin/src/app/services/http.interceptor.ts index 778c650..e993c7c 100644 --- a/projects/admin/src/app/services/http.interceptor.ts +++ b/projects/admin/src/app/services/http.interceptor.ts @@ -18,7 +18,7 @@ export class HTTPInterceptor implements HttpInterceptor { private msgFlag = false; - private localStroageKey = "catering"; + private localStroageKey = "catering_admin"; intercept(req: HttpRequest, next: HttpHandler): Observable> { const token = localStorage.getItem(this.localStroageKey); diff --git a/projects/admin/src/styles.less b/projects/admin/src/styles.less index 81f114b..19d2aab 100644 --- a/projects/admin/src/styles.less +++ b/projects/admin/src/styles.less @@ -92,4 +92,62 @@ li { z-index: 10; } +} + + +::-webkit-scrollbar { + width: 10px; + height: 10px; + background-color: rgba(240, 240, 240, 1); +} + +/*定义滚动条轨道 内阴影+圆角*/ +::-webkit-scrollbar-track { + box-shadow: inset 0 0 0px rgba(240, 240, 240, 0.5); + border-radius: 10px; + background-color: rgba(240, 240, 240, 0.5); +} + +/*定义滑块 内阴影+圆角*/ +::-webkit-scrollbar-thumb { + border-radius: 10px; + box-shadow: inset 0 0 0px rgba(240, 240, 240, 0.5); + background-color: rgba(0, 0, 0, 0.171); +} + + +@media print { + #root { + display: none; + } + + @page { + size: auto; + width: 100vw; + margin: 5mm auto !important; + } + + body { + background-color: transparent !important; + } + + * { + color: #000 !important; + } +} + + +.print-table { + width: 100%; + margin-bottom: 12px; + border-collapse: collapse; + border-spacing: 0; + border: 1px solid #e8e8e8; + + td, + th { + padding: 6px 12px; + border: 1px solid #000; + font-weight: normal; + } } \ No newline at end of file diff --git a/projects/cdk/src/ingredient/ingredient-dish/ingredient-dish.component.ts b/projects/cdk/src/ingredient/ingredient-dish/ingredient-dish.component.ts index 1c6c058..9b536d9 100644 --- a/projects/cdk/src/ingredient/ingredient-dish/ingredient-dish.component.ts +++ b/projects/cdk/src/ingredient/ingredient-dish/ingredient-dish.component.ts @@ -36,6 +36,8 @@ export class IngredientDishComponent implements OnChanges { @Input() menuBaisc: any | null; + @Input() client = false; + @Input() menuDishFormServer: any | null; expanded = new Set(); @@ -67,26 +69,27 @@ export class IngredientDishComponent implements OnChanges { } initMenuDish() { - const foodIds = new Set(); - this.menuDishFormServer.forEach((i: any) => { - i.ingredient.map((food: any) => { - foodIds.add(food.key); - }); - }); - forkJoin([this.api.getFoodList({ keys: Array.from(foodIds) })]).subscribe(([res]) => { - this.mealDishList = this.menuDishFormServer.map((i: any) => { - return { - ...i, - dishName: i.name, - mealIndex: this.menuBaisc.meals.findIndex((m: string) => m === i.meal), - items: i.ingredient.map((food: any) => { - const fd = res.body.find((f) => f.key === food.key); - food.foodName = fd.name; - return food; - }), - }; - }); - }); + this.mealDishList = this.menuDishFormServer; + // const foodIds = new Set(); + // this.menuDishFormServer.forEach((i: any) => { + // i.ingredient.map((food: any) => { + // foodIds.add(food.key); + // }); + // }); + // forkJoin([this.api.getFoodList({ keys: Array.from(foodIds) })]).subscribe(([res]) => { + // this.mealDishList = this.menuDishFormServer.map((i: any) => { + // return { + // ...i, + // dishName: i.name, + // mealIndex: this.menuBaisc.meals.findIndex((m: string) => m === i.meal), + // items: i.ingredient.map((food: any) => { + // const fd = res.body.find((f) => f.key === food.key); + // food.foodName = fd.name; + // return food; + // }), + // }; + // }); + // }); } initMenuBasic() { diff --git a/projects/cdk/src/ingredient/ingredient-form-basic/ingredient-form-basic.component.html b/projects/cdk/src/ingredient/ingredient-form-basic/ingredient-form-basic.component.html index 2146d52..0b49c99 100644 --- a/projects/cdk/src/ingredient/ingredient-form-basic/ingredient-form-basic.component.html +++ b/projects/cdk/src/ingredient/ingredient-form-basic/ingredient-form-basic.component.html @@ -29,7 +29,7 @@ - + 适用单位 diff --git a/projects/cdk/src/ingredient/ingredient-form-basic/ingredient-form-basic.component.ts b/projects/cdk/src/ingredient/ingredient-form-basic/ingredient-form-basic.component.ts index d63d723..1571358 100644 --- a/projects/cdk/src/ingredient/ingredient-form-basic/ingredient-form-basic.component.ts +++ b/projects/cdk/src/ingredient/ingredient-form-basic/ingredient-form-basic.component.ts @@ -18,6 +18,8 @@ export class IngredientFormBasicComponent implements OnChanges { @Input() menu: any | null; + @Input() client: boolean = false; + @Output() onSave = new EventEmitter(); private standardSearch$ = new Subject<{ id?: string; name?: string }>(); @@ -46,7 +48,7 @@ export class IngredientFormBasicComponent implements OnChanges { name: this.fb.control("", [FormValidators.required()]), nutrient: this.fb.control("", [FormValidators.required()]), day: this.fb.control(1, [FormValidators.required()]), - vendors: this.fb.control([], [FormValidators.required()]), + vendors: this.fb.control([], this.client ? [] : [FormValidators.required()]), month: this.fb.control([], [FormValidators.required()]), }); this.standardSearch$ @@ -64,7 +66,7 @@ export class IngredientFormBasicComponent implements OnChanges { if (getById) { data.body = [data.body]; } - data.body.forEach((item) => { + data.body.forEach((item: any) => { listOfOption.push({ label: item.name, value: item.id, @@ -113,13 +115,6 @@ export class IngredientFormBasicComponent implements OnChanges { onStandardChange(v: any) { const currentStandard = this.searchedStandard.find((f) => f.value === v); if (currentStandard) { - this.api.getOrgList({ vendors: currentStandard["vendors"] }).subscribe((res) => { - this.currentOrgs = res.body.map((i) => ({ - ...i, - value: String(i.id), - label: i.name, - })); - }); this.currentPeoples = Object.entries(currentStandard["ingredient"] ?? {}).map(([k, v]) => { return { label: k, @@ -128,6 +123,16 @@ export class IngredientFormBasicComponent implements OnChanges { ...(v as any), }; }); + if (this.client) { + return; + } + this.api.getOrgList({ vendors: currentStandard["vendors"] }).subscribe((res) => { + this.currentOrgs = res.body.map((i) => ({ + ...i, + value: String(i.id), + label: i.name, + })); + }); } } diff --git a/projects/cdk/src/ingredient/ingredient-meals/ingredient-meals.component.html b/projects/cdk/src/ingredient/ingredient-meals/ingredient-meals.component.html index c58041c..00996e4 100644 --- a/projects/cdk/src/ingredient/ingredient-meals/ingredient-meals.component.html +++ b/projects/cdk/src/ingredient/ingredient-meals/ingredient-meals.component.html @@ -103,7 +103,7 @@ 本餐生重总量 - {{totalObj[p]}} + {{totalObj[p]?.toFixed(2)}} diff --git a/projects/cdk/src/ingredient/ingredient-meals/ingredient-meals.component.ts b/projects/cdk/src/ingredient/ingredient-meals/ingredient-meals.component.ts index 6c5be65..c34b378 100644 --- a/projects/cdk/src/ingredient/ingredient-meals/ingredient-meals.component.ts +++ b/projects/cdk/src/ingredient/ingredient-meals/ingredient-meals.component.ts @@ -54,7 +54,7 @@ export class IngredientMealsComponent implements OnChanges, OnInit { // currentDishs: DishInterface[] = []; - totalObj: Record = {}; + totalObj: Record = {}; ngOnInit(): void { this.init(); @@ -101,16 +101,6 @@ export class IngredientMealsComponent implements OnChanges, OnInit { }); } - getTotal(dishId: number, people: string) { - const r = this.mealDishs.filter( - (f) => f.day === this.day && f["mealIndex"] === this.mealIndex && f.dish === dishId - ); - r.forEach((dish) => { - dish.items; - }); - return "ad"; - } - clearThisMeal() { this.modal.confirm({ nzTitle: "警告", @@ -119,6 +109,8 @@ export class IngredientMealsComponent implements OnChanges, OnInit { nzOnOk: () => { this.mealDishs = this.mealDishs.filter((f) => !(f.day === this.day && f["mealIndex"] === this.mealIndex)); this.onSaveDish.emit(this.mealDishs); + this.calcTotal(); + return true; }, }); } diff --git a/projects/cdk/src/ingredient/ingredient-preview/ingredient-preview.component.html b/projects/cdk/src/ingredient/ingredient-preview/ingredient-preview.component.html index c88707d..460a1c5 100644 --- a/projects/cdk/src/ingredient/ingredient-preview/ingredient-preview.component.html +++ b/projects/cdk/src/ingredient/ingredient-preview/ingredient-preview.component.html @@ -1,133 +1,159 @@ -
- 食谱名称 -
+ +
+ +
+ + + {{basic.name}} + +
-
- - Zhou Maomao - 18100000000 - Hangzhou, Zhejiang - Empty - -
- -
- - - - - - - -
\ No newline at end of file + +
\ No newline at end of file diff --git a/projects/cdk/src/ingredient/ingredient-preview/ingredient-preview.component.less b/projects/cdk/src/ingredient/ingredient-preview/ingredient-preview.component.less index aac7112..8eb3694 100644 --- a/projects/cdk/src/ingredient/ingredient-preview/ingredient-preview.component.less +++ b/projects/cdk/src/ingredient/ingredient-preview/ingredient-preview.component.less @@ -12,6 +12,21 @@ border-spacing: 0; border: 1px solid #e8e8e8; + .meal { + position: relative; + min-height: 200px; + padding-bottom: 42px; + + .total { + position: absolute; + bottom: 0; + + .td { + background-color: #fffbe6 !important; + } + } + } + thead { .table-day-row { display: flex; @@ -50,6 +65,29 @@ } } + tbody { + td { + vertical-align: top; + border: 1px solid #e8e8e8; + } + } + + .food-item { + &:not(.placeholder) { + &:hover .td { + background-color: #a8b0c238; + } + } + + } + + // .menu-food { + // ul li { + // &:last-child { + // margin-bottom: 42px; + // } + // } + // } .table-herder-ages { display: flex; diff --git a/projects/cdk/src/ingredient/ingredient-preview/ingredient-preview.component.ts b/projects/cdk/src/ingredient/ingredient-preview/ingredient-preview.component.ts index f617c88..38b61aa 100644 --- a/projects/cdk/src/ingredient/ingredient-preview/ingredient-preview.component.ts +++ b/projects/cdk/src/ingredient/ingredient-preview/ingredient-preview.component.ts @@ -1,8 +1,131 @@ import { Component } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; +import { ApiService } from "@cdk/services"; +import { NzMessageService } from "ng-zorro-antd/message"; +import { DishInterface } from "../ingredient-dish/ingredient-dish.component"; +import { forkJoin, map } from "rxjs"; @Component({ selector: "app-ingredient-preview", templateUrl: "./ingredient-preview.component.html", styleUrls: ["./ingredient-preview.component.less"], }) -export class IngredientPreviewComponent {} +export class IngredientPreviewComponent { + constructor( + private route: ActivatedRoute, + private router: Router, + private api: ApiService, + private msg: NzMessageService + ) {} + + basic: any | null; + + dishs: DishInterface[] = []; + + totalObj: Record | null = null; + + monthText = { + 1: "一月", + 2: "二月", + 3: "三月", + 4: "四月", + 5: "五月", + 6: "六月", + 7: "七月", + 8: "八月", + 9: "九月", + 10: "十月", + 11: "十一月", + 12: "十二月", + } as any; + + days: number[] = []; + + ngOnInit(): void { + const id = this.route.snapshot.queryParamMap.get("id"); + const snapshot = this.route.snapshot.queryParamMap.get("snapshot"); + const storage = snapshot ? sessionStorage.getItem(snapshot) : null; + + if (id) { + forkJoin([this.api.getMenuDist(id), this.api.getMenuItem(id)]) + .pipe( + map(([d, b]) => { + return [d.body, b.body]; + }) + ) + .subscribe(([dishs, basic]) => { + this.days = Array.from({ length: basic.day }, (_, i) => i + 1); + this.getStandardName(basic.nutrient); + this.basic = basic; + + this.initMenuDish(dishs); + }); + + return; + } + if (storage) { + try { + const { basic, dishs } = JSON.parse(storage); + this.days = Array.from({ length: basic.day }, (_, i) => i + 1); + this.getStandardName(basic.nutrient); + this.basic = basic; + this.dishs = dishs; + } catch (error) { + this.msg.error("解析食谱数据出错了,请重试!"); + this.router.navigate(["/ingredient/item/list"]); + } + return; + } + this.msg.error("没有找到食谱数据"); + this.router.navigate(["/ingredient/item/list"]); + } + + initMenuDish(menuDishFormServer: any[]) { + const foodIds = new Set(); + menuDishFormServer.forEach((i: any) => { + i.ingredient.map((food: any) => { + // 收集 食材 key 获取 食材名称 & 把 value 对象转换成 groupValues 数组 + foodIds.add(food.key); + food["groupValues"] = Object.entries(food.value).map(([peopleName, value]) => { + return { + peopleName, + value, + }; + }); + }); + }); + forkJoin([this.api.getFoodList({ keys: Array.from(foodIds) })]).subscribe(([res]) => { + this.dishs = menuDishFormServer.map((i: any) => { + return { + ...i, + dishName: i.name, + mealIndex: this.basic.meals.findIndex((m: string) => m === i.meal), + + items: i.ingredient.map((food: any) => { + const fd = res.body.find((f) => f.key === food.key); + food.foodName = fd.name; + return food; + }), + }; + }); + }); + } + + getStandardName(id: string) { + this.api.getStandard({ id }).subscribe((res) => { + this.basic["standardName"] = res.body.name; + }); + } + + calcTotal(day: number, meal: string, people: string) { + let total = 0; + this.dishs.forEach((d) => { + if (d.day === day && d.meal === meal) { + d.items.forEach((food) => { + total += food.value[people]; + }); + } + }); + return total.toFixed(2); + } +} diff --git a/projects/cdk/src/services/api.service.ts b/projects/cdk/src/services/api.service.ts index 09e8ebf..fd2584d 100644 --- a/projects/cdk/src/services/api.service.ts +++ b/projects/cdk/src/services/api.service.ts @@ -230,10 +230,10 @@ export class ApiService { ); } - downLoadFile(response: HttpResponse) { + downLoadFile(response: HttpResponse, defaultName = `${Date.now()}.xlsx`) { const fileNameFromHeader = response.headers.get("Content-Disposition"); if (fileNameFromHeader) { - const fileName = fileNameFromHeader.trim()?.split("''")?.[1]?.replace(/"/g, "") ?? `模板_${Date.now()}.xlsx`; + const fileName = fileNameFromHeader.trim()?.split("''")?.[1]?.replace(/"/g, "") ?? defaultName; const blob = new Blob([response.body as any]); const downloadLink = document.createElement("a"); downloadLink.href = URL.createObjectURL(blob); @@ -303,7 +303,7 @@ export class ApiService { getStandard(q: { id?: string; name?: string }) { const query = Utils.objectStringify(q); - return this.http.get>(`/api/nutrition/select?${query}`); + return this.http.get>(`/api/nutrition/select?${query}`); } saveStandard(v: AnyObject, isEdit?: boolean) { @@ -340,10 +340,11 @@ export class ApiService { ); } - getDishLabel(id: (string | number)[]) { - const query = Utils.objectStringify({ id }); - return this.http.get>(`/api/dish?${query}`); + getDishLabel(ids: (string | number)[]) { + const query = Utils.objectStringify({ ids }); + return this.http.get>(`/api/dish/label?${query}`); } + saveDish(v: AnyObject) { const body = Utils.objectToFormData(v); const method = v["id"] ? "post" : "put"; @@ -420,6 +421,10 @@ export class ApiService { return this.http.get(`/api/menu/dish?menuId=${menuId}`); } + getMenuDataVis() { + return this.http.get(`/api/menu/dish`); + } + saveMenuDist(d: {}) { return this.http.put(`/api/menu/dish/batch`, d); } @@ -438,4 +443,17 @@ export class ApiService { }) ); } + + exportMenu(id: number | string) { + return this.http + .get(`/api/menu/dish/export?id=${id}`, { + observe: "response", + responseType: "blob" as "json", + }) + .pipe( + tap((res) => { + this.downLoadFile(res); + }) + ); + } } diff --git a/projects/cdk/src/shared/components/index.ts b/projects/cdk/src/shared/components/index.ts index bdcf5e9..eabde4b 100644 --- a/projects/cdk/src/shared/components/index.ts +++ b/projects/cdk/src/shared/components/index.ts @@ -2,3 +2,4 @@ export * from "./search-and-select/search-and-select.component"; export * from "./month-select/month-select.component"; export * from "./org-select/org-select.component"; export * from "./dish-select/dish-select.component"; +export * from "./print/print.component"; diff --git a/projects/cdk/src/shared/components/print/print.component.html b/projects/cdk/src/shared/components/print/print.component.html new file mode 100644 index 0000000..9da9247 --- /dev/null +++ b/projects/cdk/src/shared/components/print/print.component.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/projects/cdk/src/shared/components/print/print.component.less b/projects/cdk/src/shared/components/print/print.component.less new file mode 100644 index 0000000..e69de29 diff --git a/projects/cdk/src/shared/components/print/print.component.ts b/projects/cdk/src/shared/components/print/print.component.ts new file mode 100644 index 0000000..3e9cdec --- /dev/null +++ b/projects/cdk/src/shared/components/print/print.component.ts @@ -0,0 +1,92 @@ +import { DomPortalOutlet, PortalOutlet, TemplatePortal } from "@angular/cdk/portal"; +import { + ApplicationRef, + Component, + ComponentFactoryResolver, + ElementRef, + Injector, + Input, + OnInit, + TemplateRef, + ViewChild, + ViewContainerRef, +} from "@angular/core"; + +@Component({ + selector: "app-print", + templateUrl: "./print.component.html", + styleUrls: ["./print.component.less"], +}) +export class PrintComponent implements OnInit { + @Input() content!: TemplateRef; + @ViewChild("iframe") iframe!: ElementRef; + private portalHost?: PortalOutlet; + public printed: boolean = false; + + constructor( + private componentFactoryResolver: ComponentFactoryResolver, + private injector: Injector, + private appRef: ApplicationRef, + private viewContainerRef: ViewContainerRef + ) {} + + ngOnInit(): void {} + + testCall() { + alert("testCall"); + } + + private _attachStyles(targetWindow: Window): void { + // Copy styles from parent window + document.querySelectorAll("style").forEach((htmlElement) => { + targetWindow.document.head.appendChild(htmlElement.cloneNode(true)); + }); + // Copy stylesheet link from parent window + const styleSheetElement = this._getStyleSheetElement(); + targetWindow.document.head.appendChild(styleSheetElement); + } + + private _getStyleSheetElement() { + const styleSheetElement = document.createElement("link"); + document.querySelectorAll("link").forEach((htmlElement) => { + if (htmlElement.rel === "stylesheet") { + const absoluteUrl = new URL(htmlElement.href).href; + styleSheetElement.rel = "stylesheet"; + styleSheetElement.type = "text/css"; + styleSheetElement.href = absoluteUrl; + } + }); + return styleSheetElement; + } + + print(): void { + const iframe = this.iframe.nativeElement; + const { contentDocument, contentWindow } = iframe; + if (contentDocument && contentWindow) { + this.portalHost = new DomPortalOutlet( + contentDocument.body, + this.componentFactoryResolver, + this.appRef, + this.injector + ); + + const portal = new TemplatePortal(this.content, this.viewContainerRef); + + if (!this.printed) { + this._attachStyles(contentWindow); + } + + this.portalHost.attach(portal); + + window.onafterprint = () => { + // Chrome 不会触发 + contentDocument.body.innerHTML = ""; + }; + + setTimeout(() => { + contentWindow.print(); + contentDocument.body.innerHTML = ""; + }, 500); + } + } +} diff --git a/projects/cdk/src/shared/shared.module.ts b/projects/cdk/src/shared/shared.module.ts index 57a1179..6bce007 100644 --- a/projects/cdk/src/shared/shared.module.ts +++ b/projects/cdk/src/shared/shared.module.ts @@ -20,7 +20,13 @@ import { // import { environment } from "@manage/environments/environment"; import { NgxPermissionsModule } from "ngx-permissions"; import { AppPageComponent } from "@cdk/app-page/app-page.component"; -import { SearchAndSelectComponent, MonthSelectComponent, OrgSelectComponent, DishSelectComponent } from "./components"; +import { + SearchAndSelectComponent, + MonthSelectComponent, + OrgSelectComponent, + DishSelectComponent, + PrintComponent, +} from "./components"; const ngModules = [CommonModule, HttpClientModule, FormsModule, RouterModule, ReactiveFormsModule]; const components: any = []; @@ -43,6 +49,7 @@ const cdks = [ MonthSelectComponent, DishSelectComponent, OrgSelectComponent, + PrintComponent, ], imports: [...ngZorroModules, ...ngModules, ...cdks, AppPageComponent], exports: [ @@ -56,6 +63,7 @@ const cdks = [ MonthSelectComponent, OrgSelectComponent, DishSelectComponent, + PrintComponent, ], }) export class SharedModule {} diff --git a/projects/client/src/app/app-routing.module.ts b/projects/client/src/app/app-routing.module.ts index 8a49e8b..f91da31 100644 --- a/projects/client/src/app/app-routing.module.ts +++ b/projects/client/src/app/app-routing.module.ts @@ -9,6 +9,10 @@ import { MealSettingComponent, OrgInfoComponent, ClientUserManageComponent, + IngredientFormComponent, + IngredientListComponent, + IngredientPreviewPageComponent, + IngredientReleaseComponent, } from "./pages"; import { AppLayoutComponent } from "./components"; import { authGuard } from "./services/auth.guard"; @@ -45,6 +49,47 @@ const routes: Routes = [ path: "dish", component: DishComponent, }, + { + path: "ingredient", + title: "食谱管理", + children: [ + { + path: "", + pathMatch: "full", + redirectTo: "item", + }, + { + path: "item", + title: "食谱库", + children: [ + { + path: "", + pathMatch: "full", + redirectTo: "list", + }, + { + path: "list", + component: IngredientListComponent, + }, + { + path: "form/:id", + component: IngredientFormComponent, + }, + ], + }, + + { + path: "preview", + title: "食谱预览", + component: IngredientPreviewPageComponent, + }, + { + path: "release", + title: "食谱发布计划", + component: IngredientReleaseComponent, + }, + ], + }, { path: "system", children: [ diff --git a/projects/client/src/app/app.module.ts b/projects/client/src/app/app.module.ts index 0cbc43b..c3a0cc5 100644 --- a/projects/client/src/app/app.module.ts +++ b/projects/client/src/app/app.module.ts @@ -13,7 +13,13 @@ import { AppRoutingModule } from "./app-routing.module"; import { AppComponent } from "./app.component"; import { IconsProviderModule, PROJECT_NAME, TableListModule } from "@cdk/public-api"; import { SharedModule } from "@cdk/shared/shared.module"; -import { AppLayoutComponent, OrgFormComponent, UserListComponent, RolePermissionComponent } from "./components"; +import { + AppLayoutComponent, + OrgFormComponent, + UserListComponent, + RolePermissionComponent, + DishFormComponent, +} from "./components"; import { DashboardComponent, LoginComponent, @@ -23,8 +29,13 @@ import { DishComponent, OrgInfoComponent, ClientUserManageComponent, + IngredientFormComponent, + IngredientListComponent, + IngredientPreviewPageComponent, + IngredientReleaseComponent, } from "./pages"; import { HTTPInterceptor } from "./services/http.interceptor"; +import { IngredientModule } from "@cdk/ingredient/ingredient.module"; registerLocaleData(zh); @@ -35,6 +46,7 @@ registerLocaleData(zh); OrgFormComponent, UserListComponent, RolePermissionComponent, + DishFormComponent, DashboardComponent, LoginComponent, @@ -44,6 +56,10 @@ registerLocaleData(zh); DishComponent, OrgInfoComponent, ClientUserManageComponent, + IngredientFormComponent, + IngredientListComponent, + IngredientPreviewPageComponent, + IngredientReleaseComponent, ], imports: [ BrowserModule, @@ -53,6 +69,7 @@ registerLocaleData(zh); BrowserAnimationsModule, IconsProviderModule, SharedModule, + IngredientModule, TableListModule, ], providers: [ diff --git a/projects/client/src/app/components/app-layout/app-layout.component.html b/projects/client/src/app/components/app-layout/app-layout.component.html index 219bde6..41e3b62 100644 --- a/projects/client/src/app/components/app-layout/app-layout.component.html +++ b/projects/client/src/app/components/app-layout/app-layout.component.html @@ -1,76 +1,82 @@ - - -
- -
- - -
    -
  • - 退出登录 -
  • -
-
+ + + + + + + +
+ +
+ + +
    +
  • + 退出登录 +
  • +
+
+
-
- - - + + + -
    -
  • - - 使用流程 -
  • -
  • - - 大屏显示 -
  • -
  • - - 配餐设置 -
  • -
  • - - 食材管理 -
  • -
  • - - 菜品管理 -
  • +
      +
    • + + 使用流程 +
    • +
    • + + 大屏显示 +
    • +
    • + + 配餐设置 +
    • +
    • + + 食材管理 +
    • +
    • + + 菜品管理 +
    • -
    • -
        -
      • 食谱库
      • -
      • 食谱发布计划
      • -
      -
    • +
    • +
        +
      • 食谱库
      • +
      • 食谱发布计划
      • +
      +
    • -
    • -
        -
      • 单位信息设置
      • -
      -
        -
      • 用户管理
      • -
      -
    • -
    - - - +
  • +
      +
    • 单位信息设置
    • +
    +
      +
    • 用户管理
    • +
    +
  • +
+
+ + +
- \ No newline at end of file + \ No newline at end of file diff --git a/projects/client/src/app/components/app-layout/app-layout.component.ts b/projects/client/src/app/components/app-layout/app-layout.component.ts index 2812452..0befa65 100644 --- a/projects/client/src/app/components/app-layout/app-layout.component.ts +++ b/projects/client/src/app/components/app-layout/app-layout.component.ts @@ -25,17 +25,21 @@ export class AppLayoutComponent implements OnInit { ) .subscribe((e) => { this.currentUrl = e.url; + this.fullPage = ["/ingredient/preview", "/data-vis"].some((s) => e.url.startsWith(s)); }); } + fullPage = false; account = this.api.account; unSubscribe$ = new Subject(); currentUrl: string = ""; - ngOnInit(): void { - console.log("this.account", this.account); + ngOnInit(): void {} + + openDataVis() { + window.open("/data-vis"); } logout() { diff --git a/projects/client/src/app/components/dish-form/dish-form.component.html b/projects/client/src/app/components/dish-form/dish-form.component.html new file mode 100644 index 0000000..7e1de51 --- /dev/null +++ b/projects/client/src/app/components/dish-form/dish-form.component.html @@ -0,0 +1,136 @@ +
+ + + + 菜品名称 + + + + + + + + 菜品标签 + + + + + + + + + + 适用月份 + + + + + + + + + 菜品图片 + + + +
+ +
+ +
+
+ + + + + +
+ + 食材名称 + + +
+
+ + + + + + +
    +
  • +
    + + +
    + + + +
    +
    + + + 是否主料 +
    + +
    +
  • +
+
+
+
+ + + \ No newline at end of file diff --git a/projects/client/src/app/components/dish-form/dish-form.component.less b/projects/client/src/app/components/dish-form/dish-form.component.less new file mode 100644 index 0000000..4ae17d1 --- /dev/null +++ b/projects/client/src/app/components/dish-form/dish-form.component.less @@ -0,0 +1,17 @@ +.month-wrap { + ::ng-deep { + .ant-checkbox-wrapper { + margin: 6px 0; + flex-basis: calc(100% / 6); + } + } +} + +.block-label { + ::ng-deep { + label { + display: inline-flex; + width: 100%; + } + } +} \ No newline at end of file diff --git a/projects/client/src/app/components/dish-form/dish-form.component.ts b/projects/client/src/app/components/dish-form/dish-form.component.ts new file mode 100644 index 0000000..e011aad --- /dev/null +++ b/projects/client/src/app/components/dish-form/dish-form.component.ts @@ -0,0 +1,230 @@ +import { Component, Input, OnInit } from "@angular/core"; +import { FormArray, FormBuilder, FormGroup } from "@angular/forms"; +import { ApiService } from "@cdk/services"; +import { Utils } from "@cdk/utils"; +import { FormValidators } from "@cdk/validators"; +import { NzMessageService } from "ng-zorro-antd/message"; +import { Subject, debounceTime, distinctUntilChanged, filter, finalize, switchMap, throttleTime } from "rxjs"; + +@Component({ + selector: "app-dish-form", + templateUrl: "./dish-form.component.html", + styleUrls: ["./dish-form.component.less"], +}) +export class DishFormComponent { + constructor(private fb: FormBuilder, private msg: NzMessageService, private api: ApiService) {} + + @Input() data: any; + + @Input() orgs: any[] = []; + + @Input() foods: any[] = []; + + private orgSearch$ = new Subject>(); + + private foodSearch$ = new Subject>(); + + formGroup!: FormGroup; + + selectedValue = null; + + orgListOfOption: Array<{ value: string; text: string }> = []; + + foodListOfOption: Array<{ value: string; text: string }> = []; + + searchedFood: Array<{ value: string; text: string }> = []; + + foodSelected: string[] = []; + + foodItemSelected: any[] = []; + + nzFilterOption = (): boolean => true; + + globalEnum = this.api.globalEnum; + + uploadLoading = false; + + addFoodVisible = false; + + get food(): FormArray { + return this.formGroup.get("ingredient") as FormArray; + } + + get icon() { + return this.formGroup.get("icon")?.value; + } + + ngOnInit(): void { + this.formGroup = this.fb.group({ + id: this.fb.control("", []), + name: this.fb.control("", [FormValidators.required()]), + icon: this.fb.control("", []), + mark: this.fb.control("", [FormValidators.required()]), + month: this.fb.control([], []), + }); + + this.orgSearch$ + .pipe( + debounceTime(500), + distinctUntilChanged(), + switchMap((q) => this.api.getOrgList(q)) + ) + .subscribe((data) => { + const listOfOption: Array<{ value: string; text: string }> = []; + data.body.forEach((item) => { + listOfOption.push({ + value: item.id.toString(), + text: item.name, + }); + }); + this.orgListOfOption = listOfOption; + }); + this.foodSearch$ + .pipe( + filter((f) => !!f), + debounceTime(500), + distinctUntilChanged(), + switchMap((q) => this.api.getFoodList(q)) + ) + .subscribe((data) => { + const listOfOption: Array<{ value: string; text: string }> = []; + data.body.forEach((item) => { + listOfOption.push({ + value: item.key, + text: item.name, + }); + }); + this.searchedFood = this.searchedFood.concat(listOfOption); + this.foodListOfOption = listOfOption; + }); + this.setValues(); + } + + setValues() { + this.orgListOfOption = this.orgs.map((i) => ({ text: i.name, value: i.id })); + + if (this.data) { + // this.allMonth = this.allMonth.map((i) => + // (this.data.month ?? []).includes(i.value) ? { ...i, checked: true } : i + // ); + this.foods.forEach((f) => { + const item = { text: f.name, value: f.key }; + this.foodListOfOption.push(item); + this.searchedFood.push(item); + const num = this.data.ingredient.find((i: any) => i.key === f.key); + if (num) { + this.foodItemSelected.push({ num: num.value, ...item, isMain: num.isMain }); + this.foodSelected.push(f.key); + } + }); + this.formGroup.patchValue({ + ...this.data, + mark: this.data.marks, + }); + } + } + + public getValues() { + let values = null; + console.log("this.formGroup.getRawValue()", this.formGroup.getRawValue(), this.foodItemSelected); + if (Utils.validateFormGroup(this.formGroup)) { + const value = this.formGroup.getRawValue(); + // const { _nutrition, key, name, type } = this.formGroup.getRawValue(); + let ingredient: any[] = []; + for (const f of this.foodItemSelected) { + let num = Number(f.num); + if (!num) { + this.msg.error(`请输入${f.value}-${f.text}的重量`); + return; + } + ingredient.push({ + isMain: f.isMain, + key: f.value, + value: num, + }); + } + const month = value.month.join(","); + + values = { + ...value, + + month, + ingredient, + }; + } + return values; + } + + onMainChange(e: boolean, key: string) { + this.foodItemSelected.forEach((i) => { + if (e) { + i.isMain = false; + if (i.value === key) { + i.isMain = true; + } + } else { + if (i.value === key) { + i.isMain = false; + } + } + }); + } + + searchOrg(value: string): void { + if (value) { + this.orgSearch$.next({ keyword: value }); + } + } + + searchFood(value: string): void { + if (value) { + this.foodSearch$.next({ keyword: value }); + } + } + + onFoodSelected(v: string[]) { + this.foodItemSelected = []; + this.searchedFood.forEach((item) => { + if (this.foodItemSelected.some((s) => s.value === item.value)) { + return; + } + if (v.includes(item.value)) { + this.foodItemSelected.push(item); + } + }); + + // this.foodItemSelected = this.searchedFood.filter((f) => { + // return v.includes(f.value) + // }); + } + + addFood() { + this.food.push( + this.fb.group({ + name: this.fb.control("", [FormValidators.required()]), + tag: this.fb.control(0, [FormValidators.required()]), + weight: this.fb.control(0, [FormValidators.required()]), + }) + ); + } + + removeFood(idx: number) { + this.food.removeAt(idx); + } + + onFileChange(e: Event) { + const target = e.target as HTMLInputElement; + const file = target.files![0]; + target.value = ""; + if (file.size / 1024 / 1024 >= 5) { + this.msg.error("图片大小不能超过5M"); + return; + } + const fileReader = new FileReader(); + fileReader.onload = () => { + const base64 = fileReader.result as string; + this.formGroup.get("icon")?.setValue(base64); + }; + fileReader.readAsDataURL(file); + } +} diff --git a/projects/client/src/app/components/index.ts b/projects/client/src/app/components/index.ts index 0a1cfeb..b8efa1c 100644 --- a/projects/client/src/app/components/index.ts +++ b/projects/client/src/app/components/index.ts @@ -2,3 +2,4 @@ export * from "./app-layout/app-layout.component"; export * from "./org-form/org-form.component"; export * from "./user-list/user-list.component"; export * from "./role-permission/role-permission.component"; +export * from "./dish-form/dish-form.component"; diff --git a/projects/client/src/app/pages/data-vis/data-vis.component.html b/projects/client/src/app/pages/data-vis/data-vis.component.html index edf50d8..b3c093e 100644 --- a/projects/client/src/app/pages/data-vis/data-vis.component.html +++ b/projects/client/src/app/pages/data-vis/data-vis.component.html @@ -1 +1,40 @@ -

data-vis works!

+
+
+ +

成都市实验小学西区分校食谱营养报告

+
{{showTime}}
+
+
+ + +
+
+
今日带量食谱
+
+ +
+
+
+
今日食材种类
+
+ +
+
+
+
+
+
今日营养分析
+
+ +
+
+
+ + + +
+
\ No newline at end of file diff --git a/projects/client/src/app/pages/data-vis/data-vis.component.less b/projects/client/src/app/pages/data-vis/data-vis.component.less index e69de29..0f76381 100644 --- a/projects/client/src/app/pages/data-vis/data-vis.component.less +++ b/projects/client/src/app/pages/data-vis/data-vis.component.less @@ -0,0 +1,240 @@ +@charset "utf-8"; + +/* CSS Document */ +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.body { + width: 100vw; + height: 100vh; + position: relative; + color: #fff; + font-size: 16px; + background-color: #012059; + /* background: radial-gradient(50% 35%, #034f8e, #034987, #02366d, #002353); */ +} + +li { + list-style-type: none; +} + +i { + margin: 0px; + padding: 0px; + text-indent: 0px; +} + +img { + border: none; + max-width: 100%; +} + +a { + text-decoration: none; + color: #fff; +} + +a.active, +a:focus { + outline: none !important; + text-decoration: none; +} + +ol, +ul, +p, +h1, +h2, +h3, +h4, +h5, +h6 { + padding: 0; + margin: 0; +} + +a:hover { + color: #06c; + text-decoration: none !important; +} + +.clearfix:after, +.clearfix:before { + display: table; + content: " "; +} + +.clearfix:after { + clear: both; +} + +.pulll_left { + float: left; +} + +.pulll_right { + float: right; +} + +i { + font-style: normal; +} + +.text-w { + color: #ffe400; +} + +.text-d { + color: #ff6316; +} + +.text-s { + color: #14e144; +} + +.text-b { + color: #07e5ff; +} + +.head { + position: relative; + height: 90px; + margin: 0 15px; + padding-right: 60px; + display: flex; + align-items: center; +} + +.head h1 { + font-size: 30px; + letter-spacing: -2px; + text-align: center; + line-height: 90px; + color: #daf9ff; + position: absolute; + top: 0; + left: 0; + right: 0; +} + +.head .menu ul { + font-size: 0; +} + +.head .menu li { + display: inline-block; + position: relative; + margin: 25px 15px; +} + +.head .menu li a { + display: block; + font-size: 18px; + color: #fff; + line-height: 40px; + padding: 0 15px; +} + +.head .time { + position: absolute; + top: 0; + right: 0; + line-height: 90px; + font-family: electronicFont; + font-size: 28px; +} + + +.head .menu li a:hover { + color: #f4e925; +} + +.logo { + height: 60px; + + img { + display: block; + + height: 100%; + } +} + + +.mainbox { + padding: 0px 10px 10px; + +} + +.nav1 { + margin-left: -6px; + margin-right: -6px; +} + +.nav1>li { + padding: 0 6px; + float: left; +} + +.box { + border: 1px solid rgba(7, 118, 181, 0.5); + box-shadow: inset 0 0 10px rgba(7, 118, 181, 0.4); + margin-bottom: 12px; + position: relative; +} + +.tit { + padding: 10px 10px 10px 25px; + border-bottom: 1px solid rgba(7, 118, 181, 0.7); + font-size: 16px; + font-weight: 500; + position: relative; +} + +.tit:before, +.tit01:before { + position: absolute; + content: ""; + width: 6px; + height: 6px; + background: rgba(22, 214, 255, 0.9); + box-shadow: 0 0 5px rgba(22, 214, 255, 0.9); + border-radius: 10px; + left: 10px; + top: 18px; +} + +.tit:after, +.box:before { + width: 100%; + height: 1px; + content: ""; + position: absolute; + left: 0; + bottom: -1px; + background: linear-gradient(to right, #076ead, #4ba6e0, #076ead); + box-shadow: 0 0 5px rgba(131, 189, 227, 1); + opacity: 0.6; +} + +.box:before { + top: -1px; +} + +.boxnav { + padding: 10px; +} + + +// .mapc { +// position: absolute; +// top: 10%; +// left: 50%; +// width: 500px; +// height: 500px; +// transform: translateX(-50%); +// background: url(/assets/diz/1/bg3.png) no-repeat center center; +// background-size: cover; +// } \ No newline at end of file diff --git a/projects/client/src/app/pages/data-vis/data-vis.component.ts b/projects/client/src/app/pages/data-vis/data-vis.component.ts index 8fc3e64..c71c598 100644 --- a/projects/client/src/app/pages/data-vis/data-vis.component.ts +++ b/projects/client/src/app/pages/data-vis/data-vis.component.ts @@ -1,10 +1,33 @@ -import { Component } from '@angular/core'; +import { Component } from "@angular/core"; +import { ApiService } from "@cdk/services"; +import { format } from "date-fns"; +import { Subject, interval, takeUntil } from "rxjs"; @Component({ - selector: 'app-data-vis', - templateUrl: './data-vis.component.html', - styleUrls: ['./data-vis.component.less'] + selector: "app-data-vis", + templateUrl: "./data-vis.component.html", + styleUrls: ["./data-vis.component.less"], }) export class DataVisComponent { + constructor(private api: ApiService) {} + destroy$ = new Subject(); + showTime: string = ""; + + ngOnInit(): void { + interval(1000) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.showTime = format(new Date(), "yyyy-MM-dd HH:mm:ss"); + }); + + this.api.getMenuDataVis().subscribe((res) => { + console.log("res", res); + }); + } + + ngOnDestroy(): void { + this.destroy$.next(null); + this.destroy$.complete(); + } } diff --git a/projects/client/src/app/pages/dish/dish.component.html b/projects/client/src/app/pages/dish/dish.component.html index e498386..e1cda24 100644 --- a/projects/client/src/app/pages/dish/dish.component.html +++ b/projects/client/src/app/pages/dish/dish.component.html @@ -24,7 +24,8 @@
- + - + - + - + + + + - + - +
+ *ngIf="data" + [ngStyle]="{'background-image':'url(' + data + ')'}"> +
+
+ + {{ tableOrg[data] ? tableOrg[data].name : '-'}} + + +
+ + + {{tableFoods[item.key]['name']}}:{{item.value}} g + +
- {{data}} -
@@ -71,4 +85,67 @@
- \ No newline at end of file + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ 主要原料:{{printData.ingredients.join(',')}} +
+
+ 1毫克(mg)钠相当于2.5毫克食盐 +
+
+
\ No newline at end of file diff --git a/projects/client/src/app/pages/dish/dish.component.ts b/projects/client/src/app/pages/dish/dish.component.ts index 6e206f6..6395918 100644 --- a/projects/client/src/app/pages/dish/dish.component.ts +++ b/projects/client/src/app/pages/dish/dish.component.ts @@ -1,45 +1,87 @@ -import { Component, OnDestroy, OnInit, TemplateRef, ViewChild } from "@angular/core"; +import { Component, OnInit, TemplateRef, ViewChild } from "@angular/core"; import { FormControl, FormGroup } from "@angular/forms"; -import { AnyObject, TableListOption } from "@cdk/public-api"; import { NzDrawerRef, NzDrawerService } from "ng-zorro-antd/drawer"; -import { NzModalService } from "ng-zorro-antd/modal"; -import { Subject, takeUntil } from "rxjs"; +import { AnyObject, OrgDTO, TableListOption } from "@cdk/public-api"; +import { DishFormComponent } from "@client/app/components"; import { ApiService } from "@cdk/services"; +import { + Subject, + debounceTime, + distinctUntilChanged, + filter, + finalize, + lastValueFrom, + switchMap, + takeUntil, + tap, +} from "rxjs"; +import { NzModalService } from "ng-zorro-antd/modal"; +import { NzMessageService } from "ng-zorro-antd/message"; +import { ResponseType } from "@cdk/types"; +import { PrintComponent } from "@cdk/shared/components"; @Component({ selector: "app-dish", templateUrl: "./dish.component.html", styleUrls: ["./dish.component.less"], }) -export class DishComponent implements OnInit, OnDestroy { - constructor(private drawer: NzDrawerService, private api: ApiService, private modal: NzModalService) {} +export class DishComponent { + constructor( + private drawer: NzDrawerService, + private api: ApiService, + private modal: NzModalService, + private msg: NzMessageService + ) {} + + @ViewChild("formFooterTpl") formFooterTpl!: TemplateRef<{}>; - @ViewChild("foofFormFooterTpl") foofFormFooterTpl!: TemplateRef<{}>; + @ViewChild("print") printRef!: PrintComponent; - tempImg = "https://cdn.pixabay.com/photo/2023/08/08/18/01/butterfly-8177925_1280.jpg"; + private drawerRef?: NzDrawerRef; + + private destroy$ = new Subject(); + + private orgSearch$ = new Subject(); + + public globalEnum = this.api.globalEnum; + + public printData: any | null; public tableList = new TableListOption(this.fetchData.bind(this), { selectable: true, + frontPagination: false, }); public queryForm = new FormGroup({ - type: new FormControl({ value: "A", disabled: false }), - name: new FormControl("addd"), + keyword: new FormControl(""), + mark: new FormControl(""), + vendors: new FormControl(""), }); - private destroy$ = new Subject(); - public selectedIds: string[] = []; - temp = Array.from({ length: 50 }, (_, i) => i); + tableOrg: { [k: number]: OrgDTO } = {}; + + tableFoods: { [k: string]: any } = {}; + + listOfOption: Array<{ value: number; text: string }> = []; + + nzFilterOption = (): boolean => true; + + submitLoading = false; ngOnInit(): void { this.initTableList(); + this.tableList.getState$.pipe(takeUntil(this.destroy$)).subscribe((res) => { this.selectedIds = res.selectedKeys as Array; }); } + searchOrg = (k: string) => { + this.orgSearch$.next(k); + }; + ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); @@ -48,18 +90,17 @@ export class DishComponent implements OnInit, OnDestroy { 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: "ingredient", title: "食材及含量", width: "30%" }, ]); this.tableList = this.tableList.setOptions([ { title: "打印营养标签", premissions: [], - onClick: this.showFoodForm.bind(this), + onClick: this.print.bind(this), }, { title: "编辑", @@ -74,26 +115,100 @@ export class DishComponent implements OnInit, OnDestroy { ]); } - fetchData(pager: AnyObject, query: AnyObject) { - return this.api.page(pager, query); + fetchData(query: AnyObject, pager: AnyObject) { + return this.api.getDishPage(pager, query).pipe( + tap((res) => { + this.getTableColumData(res); + }) + ); + } + + getTableColumData(res: ResponseType) { + if (Array.isArray(res.body.content)) { + const vendors = res.body.content.map((i: any) => i.vender); + const foodKeys = new Set( + res.body.content.reduce((a: string[], c: any) => { + return a.concat(c.ingredient.map((i: any) => i.key)); + }, [] as string[]) + ); + + if (foodKeys.size > 0) { + this.api.getFoodList({ keys: Array.from(foodKeys) }).subscribe((foods) => { + if (Array.isArray(foods.body)) { + this.tableFoods = foods.body.reduce((a, c) => { + return { + ...a, + [c.key]: c, + }; + }, {} as AnyObject); + } + }); + } + } + } + + print(v: any) { + this.msg.loading("数据请求中,请不要刷新页面", { + nzDuration: 0, + }); + this.api.getDishLabel(v.id).subscribe((res) => { + this.printData = res.body[0]; + this.printRef.print(); + this.msg.remove(); + }); + } + + showFoodForm(data?: any) { + this.drawerRef = this.drawer.create({ + nzTitle: data ? "编辑菜品" : "新增菜品", + nzWidth: 700, + nzContent: DishFormComponent, + nzContentParams: { + data, + orgs: Object.values(this.tableOrg), + foods: Object.values(this.tableFoods), + }, + nzFooter: this.formFooterTpl, + }); + } + + cancelForm() { + 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) + .pipe( + finalize(() => { + this.submitLoading = false; + }) + ) + .subscribe((res) => { + this.msg.success(res.desc); + this.tableList.run(); + this.cancelForm(); + }); + } + } } deleteItem(v?: any) { const ids = v ? [v.id] : this.selectedIds; this.modal.confirm({ nzTitle: "警告", - nzContent: "是否要删除该食材?", + nzContent: `是否要删除${ids.length}个菜品?`, nzOkDanger: true, - nzOnOk: () => {}, + nzOnOk: async () => { + const res = await lastValueFrom(this.api.deleteDish(ids)); + this.msg.success(res.desc); + this.tableList.run(); + }, }); } - - showFoodForm(food?: any) { - // this.drawerRef = this.drawer.create({ - // nzTitle: food ? "编辑菜品" : "新增菜品", - // nzWidth: 700, - // nzContent: DishFormComponent, - // nzFooter: this.formFooterTpl, - // }); - } } diff --git a/projects/client/src/app/pages/index.ts b/projects/client/src/app/pages/index.ts index 9c9a897..db7acb9 100644 --- a/projects/client/src/app/pages/index.ts +++ b/projects/client/src/app/pages/index.ts @@ -7,3 +7,8 @@ export * from "./dish/dish.component"; export * from "./system/org-info/org-info.component"; export * from "./system/user-manage/user-manage.component"; + +export * from "./ingredients/ingredient-list/ingredient-list.component"; +export * from "./ingredients/ingredient-form/ingredient-form.component"; +export * from "./ingredients/ingredient-preview-page/ingredient-preview-page.component"; +export * from "./ingredients/ingredient-release/ingredient-release.component"; diff --git a/projects/client/src/app/pages/ingredients/ingredient-form/ingredient-form.component.html b/projects/client/src/app/pages/ingredients/ingredient-form/ingredient-form.component.html new file mode 100644 index 0000000..74bb5a1 --- /dev/null +++ b/projects/client/src/app/pages/ingredients/ingredient-form/ingredient-form.component.html @@ -0,0 +1,51 @@ + + +
+ + + + +
+ + + + +
+
+ +
+ + + + + + + + + +
+
+ +
+ + +
+
+
\ No newline at end of file diff --git a/projects/client/src/app/pages/ingredients/ingredient-form/ingredient-form.component.less b/projects/client/src/app/pages/ingredients/ingredient-form/ingredient-form.component.less new file mode 100644 index 0000000..7d41880 --- /dev/null +++ b/projects/client/src/app/pages/ingredients/ingredient-form/ingredient-form.component.less @@ -0,0 +1,7 @@ +.day-item { + ::ng-deep { + .ant-card-head-title { + overflow: visible; + } + } +} \ No newline at end of file diff --git a/projects/client/src/app/pages/ingredients/ingredient-form/ingredient-form.component.ts b/projects/client/src/app/pages/ingredients/ingredient-form/ingredient-form.component.ts new file mode 100644 index 0000000..7428dd0 --- /dev/null +++ b/projects/client/src/app/pages/ingredients/ingredient-form/ingredient-form.component.ts @@ -0,0 +1,162 @@ +import { Component, OnInit, ViewChild } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; +import { ConfirmIngredientComponent } from "@cdk/ingredient/confirm-ingredient/confirm-ingredient.component"; +import { IngredientDishComponent } from "@cdk/ingredient/ingredient-dish/ingredient-dish.component"; +import { MealDishInterface } from "@cdk/ingredient/ingredient-meals/ingredient-meals.component"; +import { ApiService } from "@cdk/services"; +import { OptionItemInterface } from "@cdk/types"; +import { NzMessageService } from "ng-zorro-antd/message"; +import { NzModalService } from "ng-zorro-antd/modal"; + +@Component({ + selector: "app-ingredient-form", + templateUrl: "./ingredient-form.component.html", + styleUrls: ["./ingredient-form.component.less"], +}) +export class IngredientFormComponent implements OnInit { + constructor( + private modal: NzModalService, + private msg: NzMessageService, + private router: Router, + private route: ActivatedRoute, + private api: ApiService + ) { + this.id = this.route.snapshot.paramMap.get("id"); + } + + @ViewChild("menuDish") menuDish!: IngredientDishComponent; + + step = 0; + + id: string | null = ""; + + menuItem: any = null; + + menuDishFormServer: any = null; + + ngOnInit(): void { + this.step = this.id && this.id !== "create" ? 1 : 0; + this.getDetail(); + } + + getDetail() { + if (this.id && this.id !== "create") { + this.api.getMenuItem(this.id).subscribe((res) => { + if (res.body) { + this.menuItem = res.body; + } + }); + this.api.getMenuDist(this.id).subscribe((res) => { + if (Array.isArray(res.body)) { + res.body.forEach((d) => { + d.ingredient.forEach((f: any) => { + f["groupValues"] = Object.entries(f.value).map(([peopleName, value]) => { + return { + peopleName, + value, + }; + }); + }); + }); + this.menuDishFormServer = res.body; + } + }); + } + } + + onStepChange(basicInfo: any) { + this.step = 1; + this.menuItem = { + ...basicInfo, + menuIds: basicInfo.menuId, + meals: basicInfo.meals.reduce( + (a: string[], c: OptionItemInterface) => (c["checked"] ? a.concat(c.value) : a), + [] as string[] + ), + crows: basicInfo.peoples.reduce( + (a: string[], c: OptionItemInterface) => (c["checked"] ? a.concat(c.value) : a), + [] as string[] + ), + }; + } + + createNewMenu() { + this.modal.confirm({ + nzTitle: "警告", + nzContent: "新建食谱将清空本次所有的配餐数据,确认要清空吗?", + nzOnOk: () => { + this.menuDish.mealDishList = []; + }, + }); + } + + confirmSave() { + this.modal.create({ + nzTitle: "确认食谱信息", + nzContent: ConfirmIngredientComponent, + nzData: this.menuItem, + nzWidth: 650, + nzOnOk: () => { + const { mealDishList } = this.menuDish; + + mealDishList.forEach((dish) => { + dish["dishId"] = dish.dish; + dish.items = dish.items.map((i) => { + return { + ...i, + value: (i["groupValues"] as any[]).reduce((a, c) => { + return { + ...a, + [c.peopleName]: c.value, + }; + }, {} as Record), + }; + }); + }); + console.log("mealDishList", mealDishList); + this.api + .saveMenuDist({ + menuIds: this.menuItem.menuIds ?? [this.menuItem.id], + dishes: mealDishList, + }) + .subscribe((res) => { + this.msg.success(res.desc); + this.router.navigate(["/ingredient/item/list"]); + }); + }, + }); + } + + formatData(menuObject: any) { + let dishes: any[] = []; + Object.entries(menuObject).forEach(([day, v]) => { + Object.entries(v as Record).forEach(([mealIndex, dishList]) => { + dishList.forEach((dish) => { + dishes.push({ + ...dish, + day: Number(day), + meal: this.menuItem.meals[mealIndex], + items: dish.foods.map((food) => { + return { + ...food, + value: food.groupValues.reduce((a, c) => { + return { + ...a, + [c.peopleName]: c.value, + }; + }, {} as Record), + }; + }), + }); + }); + }); + }); + + const toServer = { + menuIds: this.menuItem.menuIds ?? [this.menuItem.id], + dishes, + }; + + return toServer; + } +} diff --git a/projects/client/src/app/pages/ingredients/ingredient-list/ingredient-list.component.html b/projects/client/src/app/pages/ingredients/ingredient-list/ingredient-list.component.html new file mode 100644 index 0000000..09b67e5 --- /dev/null +++ b/projects/client/src/app/pages/ingredients/ingredient-list/ingredient-list.component.html @@ -0,0 +1,94 @@ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + {{data | date:'yyyy-MM-dd HH:mm:ss'}} + + + {{ tableOrg[data] ? tableOrg[data].name : '-'}} + + + + {{item}} + + + {{data}} 天 + + + {{statusTextMap[data]}} + + +
+ + + 全年 + + + + + {{monthText[item]}} + + +
+
+ + {{data}} + +
+
+
+
+
+
+ + + +
+ + + 发布日期 + + + + + +
+
\ No newline at end of file diff --git a/projects/client/src/app/pages/ingredients/ingredient-list/ingredient-list.component.less b/projects/client/src/app/pages/ingredients/ingredient-list/ingredient-list.component.less new file mode 100644 index 0000000..e69de29 diff --git a/projects/client/src/app/pages/ingredients/ingredient-list/ingredient-list.component.ts b/projects/client/src/app/pages/ingredients/ingredient-list/ingredient-list.component.ts new file mode 100644 index 0000000..762f731 --- /dev/null +++ b/projects/client/src/app/pages/ingredients/ingredient-list/ingredient-list.component.ts @@ -0,0 +1,220 @@ +import { Component, OnInit, TemplateRef, ViewChild } from "@angular/core"; +import { FormControl, FormGroup } from "@angular/forms"; +import { NzDrawerRef, NzDrawerService } from "ng-zorro-antd/drawer"; +import { AnyObject, OrgDTO, TableListOption } from "@cdk/public-api"; +import { ApiService } from "@cdk/services"; +import { NzModalService } from "ng-zorro-antd/modal"; +import { lastValueFrom, tap } from "rxjs"; +import { NzMessageService } from "ng-zorro-antd/message"; +import { MyResponse } from "@cdk/types"; +import { Router } from "@angular/router"; + +@Component({ + selector: "app-ingredient-list", + templateUrl: "./ingredient-list.component.html", + styleUrls: ["./ingredient-list.component.less"], +}) +export class IngredientListComponent { + constructor( + private drawer: NzDrawerService, + private api: ApiService, + private modal: NzModalService, + private msg: NzMessageService, + private router: Router + ) {} + + globalEnum = this.api.globalEnum; + + statusTextMap: Record = {}; + + @ViewChild("releaseStartTimeTpl") releaseStartTimeTpl!: TemplateRef<{}>; + + private drawerRef?: NzDrawerRef; + + public tableList = new TableListOption(this.fetchData.bind(this), { + frontPagination: false, + }); + + public queryForm = new FormGroup({ + name: new FormControl(""), + vender: new FormControl(""), + status: new FormControl(""), + }); + + startTime: Date | null = null; + + tableOrg: { [k: number]: OrgDTO } = {}; + + monthText = { + 1: "一月", + 2: "二月", + 3: "三月", + 4: "四月", + 5: "五月", + 6: "六月", + 7: "七月", + 8: "八月", + 9: "九月", + 10: "十月", + 11: "十一月", + 12: "十二月", + } as any; + + ngOnInit(): void { + this.statusTextMap = this.globalEnum.menuStatus.reduce((a, c) => { + return { + ...a, + [String(c.label)]: c.value, + }; + }, {} as Record); + this.initTableList(); + } + + initTableList() { + this.tableList.scroll = { x: null }; + this.tableList = this.tableList.setColumns([ + { key: "name", title: "食谱名称" }, + + { key: "meals", title: "包含餐次" }, + { key: "month", title: "适用月份", width: "300px" }, + { key: "day", title: "周期" }, + { key: "status", title: "状态" }, + { key: "modify", title: "更新时间" }, + { key: "operate", title: "创建人" }, + ]); + + this.tableList = this.tableList.setOptions([ + { + title: "详情", + premissions: [], + onClick: this.preview.bind(this), + }, + { + title: "导出", + premissions: [], + + onClick: this.export.bind(this), + }, + { + title: "审核", + premissions: [], + onClick: this.shenhe.bind(this), + visible(v) { + // 0 提交审核 编辑 删除 + // 1 审核中 删除 + // 2 发布 编辑 删除 + // 3 提交审核 编辑 删除 + // 4 提交审核 编辑 删除 + return [0, 3, 4].includes(v.status); + }, + }, + { + title: "发布", + premissions: [], + onClick: this.release.bind(this), + visible(v) { + return [2].includes(v.status); + }, + }, + { + title: "禁用", + premissions: [], + onClick: this.disableMenu.bind(this), + visible(v) { + return [2].includes(v.status); + }, + }, + { + title: "编辑", + premissions: [], + onClick: (v) => { + this.router.navigate([`/ingredient/item/form/${v["id"]}`]); + }, + visible(v) { + return [0, 3, 4].includes(v.status); + }, + }, + { + title: "删除", + premissions: [], + onClick: this.deleteItem.bind(this), + }, + ]); + } + + fetchData(query: AnyObject, pager: AnyObject) { + return this.api.getMenuPage(pager, query).pipe(); + } + + preview({ id }: any) { + window.open(`/ingredient/preview?id=${id}`); + } + + export({ id }: any) { + this.msg.loading("导出中..."); + this.api.exportMenu(id).subscribe(() => { + setTimeout(() => { + this.msg.remove(); + }, 1500); + }); + } + + cancelFoodForm() { + this.drawerRef?.close(); + } + + shenhe({ id }: any) { + this.modal.confirm({ + nzTitle: "警告", + nzContent: `是否要将该食谱提交审核?`, + nzOnOk: async () => { + const res = await lastValueFrom(this.api.submitMenuForReview(id)); + this.msg.success(res.desc); + this.tableList.run(); + }, + }); + } + + release({ id, day }: any) { + this.modal.create({ + nzTitle: "发布食谱", + nzContent: this.releaseStartTimeTpl, + nzOnOk: async () => { + if (!this.startTime) { + this.msg.error("请选择发布日期"); + return false; + } + const res = await lastValueFrom(this.api.release(id, this.startTime, day)); + this.msg.success(res.desc); + this.tableList.run(); + return true; + }, + }); + } + + deleteItem({ id }: any) { + this.modal.confirm({ + nzTitle: "警告", + nzContent: `是否要删除该食谱?`, + nzOkDanger: true, + nzOnOk: async () => { + const res = await lastValueFrom(this.api.deleteMenu(id)); + this.msg.success(res.desc); + this.tableList.run(); + }, + }); + } + + disableMenu({ id }: any) { + this.modal.confirm({ + nzTitle: "警告", + nzContent: `是否要禁用该食谱?`, + nzOkDanger: true, + nzOnOk: async () => { + const res = await lastValueFrom(this.api.disableMenu(id)); + this.msg.success(res.desc); + this.tableList.run(); + }, + }); + } +} diff --git a/projects/client/src/app/pages/ingredients/ingredient-preview-page/ingredient-preview-page.component.html b/projects/client/src/app/pages/ingredients/ingredient-preview-page/ingredient-preview-page.component.html new file mode 100644 index 0000000..f8c79cc --- /dev/null +++ b/projects/client/src/app/pages/ingredients/ingredient-preview-page/ingredient-preview-page.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/projects/client/src/app/pages/ingredients/ingredient-preview-page/ingredient-preview-page.component.less b/projects/client/src/app/pages/ingredients/ingredient-preview-page/ingredient-preview-page.component.less new file mode 100644 index 0000000..e69de29 diff --git a/projects/client/src/app/pages/ingredients/ingredient-preview-page/ingredient-preview-page.component.ts b/projects/client/src/app/pages/ingredients/ingredient-preview-page/ingredient-preview-page.component.ts new file mode 100644 index 0000000..d525827 --- /dev/null +++ b/projects/client/src/app/pages/ingredients/ingredient-preview-page/ingredient-preview-page.component.ts @@ -0,0 +1,13 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; + +@Component({ + selector: "app-ingredient-preview-page", + templateUrl: "./ingredient-preview-page.component.html", + styleUrls: ["./ingredient-preview-page.component.less"], +}) +export class IngredientPreviewPageComponent implements OnInit { + constructor(private route: ActivatedRoute, private router: Router) {} + + ngOnInit(): void {} +} diff --git a/projects/client/src/app/pages/ingredients/ingredient-release/ingredient-release.component.html b/projects/client/src/app/pages/ingredients/ingredient-release/ingredient-release.component.html new file mode 100644 index 0000000..dbba394 --- /dev/null +++ b/projects/client/src/app/pages/ingredients/ingredient-release/ingredient-release.component.html @@ -0,0 +1,64 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{data | date:'yyyy-MM-dd HH:mm:ss'}} + + + + {{ tableOrg[data] ? tableOrg[data].name : '-'}} + + + {{item}} + + + {{data}} 天 + + + + {{data}} + + + + + + +
+
\ No newline at end of file diff --git a/projects/client/src/app/pages/ingredients/ingredient-release/ingredient-release.component.less b/projects/client/src/app/pages/ingredients/ingredient-release/ingredient-release.component.less new file mode 100644 index 0000000..e69de29 diff --git a/projects/client/src/app/pages/ingredients/ingredient-release/ingredient-release.component.ts b/projects/client/src/app/pages/ingredients/ingredient-release/ingredient-release.component.ts new file mode 100644 index 0000000..4cbc7c5 --- /dev/null +++ b/projects/client/src/app/pages/ingredients/ingredient-release/ingredient-release.component.ts @@ -0,0 +1,129 @@ +import { Component, OnInit, TemplateRef, ViewChild } from "@angular/core"; +import { FormControl, FormGroup } from "@angular/forms"; +import { NzDrawerRef, NzDrawerService } from "ng-zorro-antd/drawer"; +import { AnyObject, MyResponse, OrgDTO, TableListOption } from "@cdk/public-api"; +import { ApiService } from "@cdk/services"; +import { lastValueFrom, tap } from "rxjs"; +import { endOfWeek, format, startOfWeek, subWeeks } from "date-fns"; +import { NzModalService } from "ng-zorro-antd/modal"; +import { NzMessageService } from "ng-zorro-antd/message"; + +@Component({ + selector: "app-ingredient-release", + templateUrl: "./ingredient-release.component.html", + styleUrls: ["./ingredient-release.component.less"], +}) +export class IngredientReleaseComponent { + constructor( + private drawer: NzDrawerService, + private api: ApiService, + private modal: NzModalService, + private msg: NzMessageService + ) {} + + public tableList = new TableListOption(this.fetchData.bind(this)); + + public queryForm = new FormGroup({ + name: new FormControl(""), + vender: new FormControl(""), + }); + + tableOrg: { [k: number]: OrgDTO } = {}; + + week: number = 0; + + dateRange: Date[] | null = null; + + ngOnInit(): void { + this.initTableList(); + } + + initTableList() { + this.tableList.scroll = { x: null }; + this.tableList = this.tableList.setColumns([ + { key: "name", title: "食谱名称" }, + + { key: "meals", title: "包含餐次" }, + { key: "day", title: "周期" }, + { key: "created", title: "创建时间" }, + { key: "dateRange", title: "应用时间" }, + ]); + + this.tableList = this.tableList.setOptions([ + { + title: "详情", + premissions: [], + onClick: this.preview.bind(this), + }, + { + title: "导出", + premissions: [], + onClick: this.export.bind(this), + }, + + { + title: "取消发布", + premissions: [], + onClick: this.cancelRelease.bind(this), + }, + ]); + } + + fetchData(pager: AnyObject, query: AnyObject) { + if (this.dateRange) { + if (this.dateRange[0]) { + query["startTime"] = format(this.dateRange[0], "yyyy-MM-dd"); + } + if (this.dateRange[1]) { + query["endTime"] = format(this.dateRange[1], "yyyy-MM-dd"); + } + } + + return this.api.getMenuReleasePage(pager, query).pipe(); + } + + preview({ id }: any) { + window.open(`/ingredient/preview?id=${id}`); + } + + onWeekChange(e: -1 | 0 | 1) { + if (e !== 0) { + const range = this.getWeekDates(e); + this.dateRange = range; + } else { + this.dateRange = null; + } + } + + getWeekDates(offset: -1 | 1) { + const now = new Date(); + const startDate = + offset === -1 ? startOfWeek(subWeeks(now, 1), { weekStartsOn: 1 }) : startOfWeek(now, { weekStartsOn: 1 }); + const endDate = + offset === -1 ? endOfWeek(subWeeks(now, 1), { weekStartsOn: 1 }) : endOfWeek(now, { weekStartsOn: 1 }); + + return [startDate, endDate]; + } + + export({ id }: any) { + this.msg.loading("导出中..."); + this.api.exportMenu(id).subscribe(() => { + setTimeout(() => { + this.msg.remove(); + }, 1500); + }); + } + + cancelRelease({ id }: any) { + this.modal.confirm({ + nzTitle: "警告", + nzContent: "是否要取消发布该食谱?", + nzOkDanger: true, + nzOnOk: async () => { + const res = await lastValueFrom(this.api.cancelRelease(id)); + this.msg.success(res.desc); + this.tableList.run(); + }, + }); + } +} diff --git a/projects/client/src/assets/diz/1/bg3.png b/projects/client/src/assets/diz/1/bg3.png new file mode 100644 index 0000000..37d3c0c Binary files /dev/null and b/projects/client/src/assets/diz/1/bg3.png differ diff --git a/projects/client/src/assets/diz/1/m1-bg.png b/projects/client/src/assets/diz/1/m1-bg.png new file mode 100644 index 0000000..1eb5cb0 Binary files /dev/null and b/projects/client/src/assets/diz/1/m1-bg.png differ diff --git a/projects/client/src/assets/diz/1/m2-bg.png b/projects/client/src/assets/diz/1/m2-bg.png new file mode 100644 index 0000000..cefb215 Binary files /dev/null and b/projects/client/src/assets/diz/1/m2-bg.png differ diff --git a/projects/client/src/assets/diz/1/m3-bg.png b/projects/client/src/assets/diz/1/m3-bg.png new file mode 100644 index 0000000..2138288 Binary files /dev/null and b/projects/client/src/assets/diz/1/m3-bg.png differ diff --git a/projects/client/src/styles.less b/projects/client/src/styles.less index a18a81e..34659f3 100644 --- a/projects/client/src/styles.less +++ b/projects/client/src/styles.less @@ -56,4 +56,61 @@ li { z-index: 10; } +} + + +::-webkit-scrollbar { + width: 10px; + height: 10px; + background-color: rgba(240, 240, 240, 1); +} + +/*定义滚动条轨道 内阴影+圆角*/ +::-webkit-scrollbar-track { + box-shadow: inset 0 0 0px rgba(240, 240, 240, 0.5); + border-radius: 10px; + background-color: rgba(240, 240, 240, 0.5); +} + +/*定义滑块 内阴影+圆角*/ +::-webkit-scrollbar-thumb { + border-radius: 10px; + box-shadow: inset 0 0 0px rgba(240, 240, 240, 0.5); + background-color: rgba(0, 0, 0, 0.171); +} + +@media print { + #root { + display: none; + } + + @page { + size: auto; + width: 100vw; + margin: 5mm auto !important; + } + + body { + background-color: transparent !important; + } + + * { + color: #000 !important; + } +} + + +.print-table { + width: 100%; + margin-bottom: 12px; + border-collapse: collapse; + border-spacing: 0; + border: 1px solid #e8e8e8; + + td, + th { + padding: 6px 12px; + border: 1px solid #000; + font-weight: normal; + } } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index e5306b0..3e44330 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,9 @@ "@admin/*": [ "./projects/admin/src/*", ], + "@client/*": [ + "./projects/client/src/*", + ], }, "outDir": "./dist/out-tsc", "forceConsistentCasingInFileNames": true,