100 changed files with 8944 additions and 275 deletions
@ -1,10 +1,130 @@ |
|||
{ |
|||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json", |
|||
"version": 1, |
|||
"cli": { |
|||
"packageManager": "pnpm" |
|||
}, |
|||
"newProjectRoot": "projects", |
|||
"projects": { |
|||
} |
|||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json", |
|||
"version": 1, |
|||
"cli": { |
|||
"packageManager": "pnpm" |
|||
}, |
|||
"newProjectRoot": "projects", |
|||
"projects": { |
|||
"admin": { |
|||
"projectType": "application", |
|||
"schematics": { |
|||
"@schematics/angular:component": { |
|||
"style": "less" |
|||
} |
|||
}, |
|||
"root": "projects/admin", |
|||
"sourceRoot": "projects/admin/src", |
|||
"prefix": "app", |
|||
"architect": { |
|||
"build": { |
|||
"builder": "@angular-devkit/build-angular:browser", |
|||
"options": { |
|||
"outputPath": "dist/admin", |
|||
"index": "projects/admin/src/index.html", |
|||
"main": "projects/admin/src/main.ts", |
|||
"polyfills": ["zone.js"], |
|||
"tsConfig": "projects/admin/tsconfig.app.json", |
|||
"inlineStyleLanguage": "less", |
|||
"assets": [ |
|||
"projects/admin/src/favicon.ico", |
|||
"projects/admin/src/assets", |
|||
{ |
|||
"glob": "**/*", |
|||
"input": "./node_modules/@ant-design/icons-angular/src/inline-svg/", |
|||
"output": "/assets/" |
|||
} |
|||
], |
|||
"styles": ["projects/admin/src/styles.less"], |
|||
"scripts": [] |
|||
}, |
|||
"configurations": { |
|||
"production": { |
|||
"budgets": [ |
|||
{ |
|||
"type": "initial", |
|||
"maximumWarning": "500kb", |
|||
"maximumError": "1mb" |
|||
}, |
|||
{ |
|||
"type": "anyComponentStyle", |
|||
"maximumWarning": "2kb", |
|||
"maximumError": "4kb" |
|||
} |
|||
], |
|||
"outputHashing": "all" |
|||
}, |
|||
"development": { |
|||
"buildOptimizer": false, |
|||
"optimization": false, |
|||
"vendorChunk": true, |
|||
"extractLicenses": false, |
|||
"sourceMap": true, |
|||
"namedChunks": true |
|||
} |
|||
}, |
|||
"defaultConfiguration": "production" |
|||
}, |
|||
"serve": { |
|||
"builder": "@angular-devkit/build-angular:dev-server", |
|||
"configurations": { |
|||
"production": { |
|||
"browserTarget": "admin:build:production" |
|||
}, |
|||
"development": { |
|||
"browserTarget": "admin:build:development" |
|||
} |
|||
}, |
|||
"defaultConfiguration": "development" |
|||
}, |
|||
"extract-i18n": { |
|||
"builder": "@angular-devkit/build-angular:extract-i18n", |
|||
"options": { |
|||
"browserTarget": "admin:build" |
|||
} |
|||
}, |
|||
"test": { |
|||
"builder": "@angular-devkit/build-angular:karma", |
|||
"options": { |
|||
"polyfills": ["zone.js", "zone.js/testing"], |
|||
"tsConfig": "projects/admin/tsconfig.spec.json", |
|||
"inlineStyleLanguage": "less", |
|||
"assets": ["projects/admin/src/favicon.ico", "projects/admin/src/assets"], |
|||
"styles": ["projects/admin/src/styles.less"], |
|||
"scripts": [] |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"cdk": { |
|||
"projectType": "library", |
|||
"root": "projects/cdk", |
|||
"sourceRoot": "projects/cdk/src", |
|||
"prefix": "lib", |
|||
"architect": { |
|||
"build": { |
|||
"builder": "@angular-devkit/build-angular:ng-packagr", |
|||
"options": { |
|||
"project": "projects/cdk/ng-package.json" |
|||
}, |
|||
"configurations": { |
|||
"production": { |
|||
"tsConfig": "projects/cdk/tsconfig.lib.prod.json" |
|||
}, |
|||
"development": { |
|||
"tsConfig": "projects/cdk/tsconfig.lib.json" |
|||
} |
|||
}, |
|||
"defaultConfiguration": "production" |
|||
}, |
|||
"test": { |
|||
"builder": "@angular-devkit/build-angular:karma", |
|||
"options": { |
|||
"tsConfig": "projects/cdk/tsconfig.spec.json", |
|||
"polyfills": ["zone.js", "zone.js/testing"] |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -1,37 +1,47 @@ |
|||
{ |
|||
"name": "catering-web-app", |
|||
"version": "0.0.0", |
|||
"scripts": { |
|||
"ng": "ng", |
|||
"start": "ng serve", |
|||
"build": "ng build", |
|||
"watch": "ng build --watch --configuration development", |
|||
"test": "ng test" |
|||
}, |
|||
"private": true, |
|||
"dependencies": { |
|||
"@angular/animations": "^16.1.0", |
|||
"@angular/common": "^16.1.0", |
|||
"@angular/compiler": "^16.1.0", |
|||
"@angular/core": "^16.1.0", |
|||
"@angular/forms": "^16.1.0", |
|||
"@angular/platform-browser": "^16.1.0", |
|||
"@angular/platform-browser-dynamic": "^16.1.0", |
|||
"@angular/router": "^16.1.0", |
|||
"rxjs": "~7.8.0", |
|||
"tslib": "^2.3.0", |
|||
"zone.js": "~0.13.0" |
|||
}, |
|||
"devDependencies": { |
|||
"@angular/cli": "~16.1.4", |
|||
"@angular/compiler-cli": "^16.1.0", |
|||
"@types/jasmine": "~4.3.0", |
|||
"jasmine-core": "~4.6.0", |
|||
"karma": "~6.4.0", |
|||
"karma-chrome-launcher": "~3.2.0", |
|||
"karma-coverage": "~2.2.0", |
|||
"karma-jasmine": "~5.1.0", |
|||
"karma-jasmine-html-reporter": "~2.1.0", |
|||
"typescript": "~5.1.3" |
|||
} |
|||
"name": "catering-web-app", |
|||
"version": "0.0.0", |
|||
"scripts": { |
|||
"ng": "ng", |
|||
"start": "ng serve", |
|||
"build": "ng build", |
|||
"watch": "ng build --watch --configuration development", |
|||
"test": "ng test" |
|||
}, |
|||
"private": true, |
|||
"dependencies": { |
|||
"@angular/animations": "^16.1.0", |
|||
"@angular/cdk": "^16.2.0", |
|||
"@angular/common": "^16.1.0", |
|||
"@angular/compiler": "^16.1.0", |
|||
"@angular/core": "^16.1.0", |
|||
"@angular/forms": "^16.1.0", |
|||
"@angular/platform-browser": "^16.1.0", |
|||
"@angular/platform-browser-dynamic": "^16.1.0", |
|||
"@angular/router": "^16.1.0", |
|||
"@ant-design/icons-angular": "^16.0.0", |
|||
"immer": "^10.0.2", |
|||
"ng-zorro-antd": "16.1.0", |
|||
"ngx-permissions": "^15.0.1", |
|||
"rxjs": "~7.8.0", |
|||
"tslib": "^2.3.0", |
|||
"zone.js": "~0.13.0" |
|||
}, |
|||
"devDependencies": { |
|||
"@angular-devkit/build-angular": "^16.1.4", |
|||
"@angular/cli": "~16.1.4", |
|||
"@angular/compiler-cli": "^16.1.0", |
|||
"@types/jasmine": "~4.3.0", |
|||
"autoprefixer": "^10.4.14", |
|||
"jasmine-core": "~4.6.0", |
|||
"karma": "~6.4.0", |
|||
"karma-chrome-launcher": "~3.2.0", |
|||
"karma-coverage": "~2.2.0", |
|||
"karma-jasmine": "~5.1.0", |
|||
"karma-jasmine-html-reporter": "~2.1.0", |
|||
"ng-packagr": "^16.0.0", |
|||
"postcss": "^8.4.27", |
|||
"tailwindcss": "^3.3.3", |
|||
"typescript": "~5.1.3" |
|||
} |
|||
} |
|||
|
|||
File diff suppressed because it is too large
@ -0,0 +1,70 @@ |
|||
import { NgModule } from "@angular/core"; |
|||
import { Routes, RouterModule } from "@angular/router"; |
|||
import { |
|||
DishComponent, |
|||
FoodComponent, |
|||
HomeComponent, |
|||
IngredientFormComponent, |
|||
IngredientListComponent, |
|||
IngredientReleaseComponent, |
|||
IngredientReviewComponent, |
|||
LoginComponent, |
|||
} from "./pages"; |
|||
import { AppLayoutComponent } from "./components"; |
|||
|
|||
const routes: Routes = [ |
|||
{ path: "login", component: LoginComponent }, |
|||
{ |
|||
path: "", |
|||
component: AppLayoutComponent, |
|||
children: [ |
|||
{ path: "", pathMatch: "full", redirectTo: "home" }, |
|||
{ path: "home", component: HomeComponent }, |
|||
{ path: "food", component: FoodComponent }, |
|||
{ path: "dish", component: DishComponent }, |
|||
{ |
|||
path: "ingredient", |
|||
children: [ |
|||
{ |
|||
path: "", |
|||
pathMatch: "full", |
|||
redirectTo: "item", |
|||
}, |
|||
{ |
|||
path: "item", |
|||
children: [ |
|||
{ |
|||
path: "", |
|||
pathMatch: "full", |
|||
redirectTo: "list", |
|||
}, |
|||
{ |
|||
path: "list", |
|||
component: IngredientListComponent, |
|||
}, |
|||
{ |
|||
path: "form/:id", |
|||
component: IngredientFormComponent, |
|||
}, |
|||
], |
|||
}, |
|||
|
|||
{ |
|||
path: "review", |
|||
component: IngredientReviewComponent, |
|||
}, |
|||
{ |
|||
path: "release", |
|||
component: IngredientReleaseComponent, |
|||
}, |
|||
], |
|||
}, |
|||
], |
|||
}, |
|||
]; |
|||
|
|||
@NgModule({ |
|||
imports: [RouterModule.forRoot(routes)], |
|||
exports: [RouterModule], |
|||
}) |
|||
export class AppRoutingModule {} |
|||
@ -0,0 +1 @@ |
|||
<router-outlet></router-outlet> |
|||
@ -0,0 +1,4 @@ |
|||
:host { |
|||
display: block; |
|||
height: 100%; |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
import { Component } from '@angular/core'; |
|||
|
|||
@Component({ |
|||
selector: 'app-root', |
|||
templateUrl: './app.component.html', |
|||
styleUrls: ['./app.component.less'] |
|||
}) |
|||
export class AppComponent { |
|||
isCollapsed = false; |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
import { NgModule } from "@angular/core"; |
|||
import { BrowserModule } from "@angular/platform-browser"; |
|||
|
|||
import { AppRoutingModule } from "./app-routing.module"; |
|||
import { AppComponent } from "./app.component"; |
|||
import { NZ_I18N } from "ng-zorro-antd/i18n"; |
|||
import { zh_CN } from "ng-zorro-antd/i18n"; |
|||
import { registerLocaleData } from "@angular/common"; |
|||
import zh from "@angular/common/locales/zh"; |
|||
import { FormsModule } from "@angular/forms"; |
|||
import { HTTP_INTERCEPTORS, HttpClientModule } from "@angular/common/http"; |
|||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; |
|||
import { IconsProviderModule } from "./icons-provider.module"; |
|||
|
|||
import { SharedModule } from "./shared/shared.module"; |
|||
import { |
|||
AppLayoutComponent, |
|||
AppPageComponent, |
|||
FoodFormComponent, |
|||
DishFormComponent, |
|||
IngredientFormBasicComponent, |
|||
} from "./components"; |
|||
import { |
|||
HomeComponent, |
|||
LoginComponent, |
|||
FoodComponent, |
|||
DishComponent, |
|||
IngredientListComponent, |
|||
IngredientReleaseComponent, |
|||
IngredientReviewComponent, |
|||
IngredientFormComponent, |
|||
} from "./pages"; |
|||
import { HTTPInterceptor } from "./services/http.interceptor"; |
|||
|
|||
registerLocaleData(zh); |
|||
|
|||
@NgModule({ |
|||
declarations: [ |
|||
AppComponent, |
|||
AppLayoutComponent, |
|||
AppPageComponent, |
|||
FoodFormComponent, |
|||
DishFormComponent, |
|||
IngredientFormBasicComponent, |
|||
|
|||
HomeComponent, |
|||
LoginComponent, |
|||
FoodComponent, |
|||
DishComponent, |
|||
IngredientListComponent, |
|||
IngredientReleaseComponent, |
|||
IngredientReviewComponent, |
|||
IngredientFormComponent, |
|||
], |
|||
imports: [ |
|||
BrowserModule, |
|||
AppRoutingModule, |
|||
FormsModule, |
|||
HttpClientModule, |
|||
BrowserAnimationsModule, |
|||
IconsProviderModule, |
|||
SharedModule, |
|||
], |
|||
providers: [ |
|||
{ provide: NZ_I18N, useValue: zh_CN }, |
|||
{ provide: HTTP_INTERCEPTORS, useClass: HTTPInterceptor, multi: true }, |
|||
], |
|||
bootstrap: [AppComponent], |
|||
}) |
|||
export class AppModule {} |
|||
@ -0,0 +1,52 @@ |
|||
<nz-layout class="app-layout"> |
|||
<nz-header class="app-header"> |
|||
<div class="flex items-center justify-between h-full"> |
|||
<div class="logo flex items-center h-full"> |
|||
<img class="block h-[40px] mr-2" src="../assets/images/jl-logo.png" /> |
|||
<span class="text-lg text-white font-bold"> |
|||
智慧配餐管理后台 |
|||
</span> |
|||
</div> |
|||
|
|||
</div> |
|||
</nz-header> |
|||
<nz-layout class="app-layout-main"> |
|||
<nz-sider nzWidth="200px" nzTheme="light" class="sider-menu"> |
|||
<ul nz-menu nzMode="inline"> |
|||
<li nz-menu-item class="k-icon" [routerLink]="['/','food']" nzMatchRouter> |
|||
<span nz-icon nzType="k-icon:carrot" nzTheme="outline"></span> |
|||
<span>食材管理</span> |
|||
</li> |
|||
<li nz-menu-item class="k-icon" [routerLink]="['/','dish']" nzMatchRouter> |
|||
<span nz-icon nzType="k-icon:food" nzTheme="outline"></span> |
|||
<span>菜品管理</span> |
|||
</li> |
|||
|
|||
|
|||
<li nz-submenu nzTitle="食谱管理" nzIcon="book"> |
|||
<ul> |
|||
<li nz-menu-item nzMatchRouter [routerLink]="['/','ingredient','item']">食谱库</li> |
|||
<li nz-menu-item nzMatchRouter [routerLink]="['/','ingredient','review']">食谱审核</li> |
|||
<li nz-menu-item nzMatchRouter [routerLink]="['/','ingredient','release']">食谱发布计划</li> |
|||
</ul> |
|||
</li> |
|||
<li nz-menu-item nz-icon="profile"> |
|||
<span nz-icon nzType="profile" nzTheme="outline"></span> |
|||
<span>人群营养标准管理</span> |
|||
</li> |
|||
<li nz-menu-item> |
|||
<span nz-icon nzType="usergroup-add" nzTheme="outline"></span> |
|||
<span>单位管理</span> |
|||
</li> |
|||
<li nz-submenu nzTitle="系统设置" nzIcon="setting"> |
|||
<ul> |
|||
<li nz-menu-item>用户管理</li> |
|||
</ul> |
|||
</li> |
|||
</ul> |
|||
</nz-sider> |
|||
<nz-layout class="inner-layout"> |
|||
<router-outlet></router-outlet> |
|||
</nz-layout> |
|||
</nz-layout> |
|||
</nz-layout> |
|||
@ -0,0 +1,59 @@ |
|||
:host { |
|||
display: flex; |
|||
flex-direction: column; |
|||
min-height: 100%; |
|||
} |
|||
|
|||
@header-height: 48px; |
|||
|
|||
.app-layout { |
|||
display: flex; |
|||
flex-direction: column; |
|||
flex: 1; |
|||
min-height: 100%; |
|||
} |
|||
|
|||
.app-header { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
z-index: 100; |
|||
height: @header-height; |
|||
line-height: @header-height; |
|||
padding: 0 24px; |
|||
} |
|||
|
|||
|
|||
.k-icon { |
|||
::ng-deep { |
|||
.anticon { |
|||
font-size: 16px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.sider-menu { |
|||
position: fixed; |
|||
top: @header-height; |
|||
left: 0; |
|||
bottom: 0; |
|||
z-index: 100; |
|||
} |
|||
|
|||
.app-layout-main { |
|||
min-height: 100%; |
|||
} |
|||
|
|||
.inner-layout { |
|||
padding-top: @header-height; |
|||
padding-left: 200px; |
|||
|
|||
::ng-deep { |
|||
router-outlet+* { |
|||
display: flex; |
|||
flex-direction: column; |
|||
min-height: 100%; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
import { Component } from "@angular/core"; |
|||
|
|||
@Component({ |
|||
selector: "app-layout", |
|||
templateUrl: "./app-layout.component.html", |
|||
styleUrls: ["./app-layout.component.less"], |
|||
}) |
|||
export class AppLayoutComponent {} |
|||
@ -0,0 +1,17 @@ |
|||
<div class="app-page flex flex-col h-full " [ngClass]="{'m-4' : !full}"> |
|||
<div class="app-header flex justify-between mb-2" *ngIf="pageTitle || pageExtra"> |
|||
<div class="app-page-title" *ngIf="pageTitle"> |
|||
<h2 *nzStringTemplateOutlet="pageTitle" class="mb-1"> |
|||
{{pageTitle}} |
|||
</h2> |
|||
</div> |
|||
<div class="app-page-extra"> |
|||
<ng-container *nzStringTemplateOutlet="pageExtra"> |
|||
{{pageExtra}} |
|||
</ng-container> |
|||
</div> |
|||
</div> |
|||
<main class="app-page-body flex-1 "> |
|||
<ng-content></ng-content> |
|||
</main> |
|||
</div> |
|||
@ -0,0 +1,9 @@ |
|||
:host { |
|||
display: flex; |
|||
flex-direction: column; |
|||
min-height: 100%; |
|||
} |
|||
|
|||
.app-page { |
|||
border-radius: 10px; |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
import { Component, Input, TemplateRef } from "@angular/core"; |
|||
|
|||
@Component({ |
|||
selector: "app-page", |
|||
templateUrl: "./app-page.component.html", |
|||
styleUrls: ["./app-page.component.less"], |
|||
}) |
|||
export class AppPageComponent { |
|||
constructor() {} |
|||
|
|||
@Input() pageTitle?: TemplateRef<{}> | string; |
|||
|
|||
@Input() full: boolean = false; |
|||
|
|||
@Input() pageExtra?: TemplateRef<{}> | string; |
|||
} |
|||
@ -0,0 +1,139 @@ |
|||
<form nz-form [formGroup]="formGroup" nzLayout="vertical"> |
|||
<nz-form-item> |
|||
<nz-form-label nzRequired> |
|||
单位 |
|||
</nz-form-label> |
|||
<nz-form-control [nzErrorTip]="formControlErrorTpl"> |
|||
<nz-select |
|||
formControlName="unit" |
|||
[nzOptions]="[]" |
|||
nzMode="multiple" |
|||
nzPlaceHolder="请选择单位,多选时在多个单位均添加此菜品"> |
|||
|
|||
</nz-select> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
<nz-form-item> |
|||
<nz-form-label nzRequired> |
|||
菜品名称 |
|||
</nz-form-label> |
|||
<nz-form-control [nzErrorTip]="formControlErrorTpl"> |
|||
<input placeholder="请输入菜品名称" nz-input formControlName="name" /> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
<nz-form-item> |
|||
<nz-form-label nzRequired> |
|||
菜品标签 |
|||
</nz-form-label> |
|||
<nz-form-control [nzErrorTip]="formControlErrorTpl"> |
|||
<nz-select |
|||
formControlName="tag" |
|||
[nzOptions]="[]" |
|||
|
|||
nzPlaceHolder="请选择菜品标签"> |
|||
|
|||
</nz-select> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
<nz-form-item> |
|||
<nz-form-label> |
|||
适用月份 |
|||
</nz-form-label> |
|||
<nz-form-control [nzErrorTip]="formControlErrorTpl"> |
|||
<div> |
|||
<label |
|||
nz-checkbox |
|||
[(ngModel)]="allMonthChecked" |
|||
[ngModelOptions]="{standalone: true}" |
|||
(ngModelChange)="updateAllMonthChecked()" |
|||
[nzIndeterminate]="indeterminate"> |
|||
全年 |
|||
</label> |
|||
</div> |
|||
<nz-divider nzDashed class="my-1"></nz-divider> |
|||
<nz-checkbox-group [ngModel]="allMonth" |
|||
class="flex flex-wrap month-wrap" |
|||
formControlName="month" |
|||
(ngModelChange)="monthChecked()"> |
|||
</nz-checkbox-group> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
|
|||
<nz-form-item> |
|||
<nz-form-label> |
|||
菜品图片 |
|||
</nz-form-label> |
|||
<nz-form-control [nzErrorTip]="formControlErrorTpl"> |
|||
<button class="upload-btn " nz-button [nzLoading]="uploadLoading"> |
|||
<i nz-icon nzType="upload"></i> |
|||
上传图片 |
|||
<input type="file" formControlName="img" (change)="onFileChange($event)" /> |
|||
</button> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
|
|||
<!-- <nz-form-item> |
|||
<nz-form-label nzRequired> |
|||
食材名称 |
|||
</nz-form-label> |
|||
<nz-form-control [nzErrorTip]="formControlErrorTpl"> |
|||
<input placeholder="请输入食材名称/编号检索" nz-input formControlName="food" /> |
|||
</nz-form-control> |
|||
</nz-form-item> --> |
|||
|
|||
<nz-form-item> |
|||
<nz-form-label nzRequired class="block-label"> |
|||
<div class="flex justify-between items-center flex-1"> |
|||
<span class="flex-1"> |
|||
食材名称 |
|||
</span> |
|||
<a nz-button nzType="link" (click)="addFoodVisible = true"> |
|||
<span nz-icon nzType="plus"></span> |
|||
<span> |
|||
添加食材 |
|||
</span> |
|||
</a> |
|||
</div> |
|||
</nz-form-label> |
|||
<nz-form-control [nzErrorTip]="formControlErrorTpl"> |
|||
<div *ngIf="addFoodVisible"> |
|||
<nz-select nzSize="large" nzShowSearch nzPlaceHolder="请输入食材名称/编号检索"> |
|||
|
|||
</nz-select> |
|||
<div class="flex justify-end my-2"> |
|||
<nz-space> |
|||
<button *nzSpaceItem nz-button nzType="text" (click)="addFoodVisible = false">取消</button> |
|||
<button *nzSpaceItem nz-button nzType="primary" (click)="addFood()">确定</button> |
|||
</nz-space> |
|||
</div> |
|||
</div> |
|||
<ul formArrayName="food"> |
|||
<li class="mb-2" *ngFor="let n of food.controls;let i = index" [formGroupName]="i"> |
|||
<div class="flex items-center"> |
|||
<div class="pr-2"> |
|||
食材名称: |
|||
</div> |
|||
<div class="pr-2"> |
|||
<nz-select class="!w-[200px]" nzPlaceHolder="食材标签" |
|||
formControlName="tag"> |
|||
</nz-select> |
|||
</div> |
|||
<div class="flex-1 pr-2"> |
|||
<nz-input-group [nzAddOnAfter]="'g'" class="w-full"> |
|||
<input nz-input formControlName="weight" [placeholder]="'请输入xxx重量'" /> |
|||
</nz-input-group> |
|||
</div> |
|||
<div> |
|||
<button nz-button (click)="removeFood(i)"> |
|||
<span nz-icon nzType="delete"></span> |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</li> |
|||
</ul> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
</form> |
|||
<ng-template #formControlErrorTpl let-control> |
|||
<form-error-tips [control]="control"></form-error-tips> |
|||
</ng-template> |
|||
@ -0,0 +1,17 @@ |
|||
.month-wrap { |
|||
::ng-deep { |
|||
.ant-checkbox-wrapper { |
|||
margin: 6px 0; |
|||
flex-basis: calc(100% / 6); |
|||
} |
|||
} |
|||
} |
|||
|
|||
.block-label { |
|||
::ng-deep { |
|||
label { |
|||
display: inline-flex; |
|||
width: 100%; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,122 @@ |
|||
import { Component, OnInit } from "@angular/core"; |
|||
import { FormArray, FormBuilder, FormGroup } from "@angular/forms"; |
|||
import { FormValidators } from "@cdk/validators"; |
|||
import { NzMessageService } from "ng-zorro-antd/message"; |
|||
import { finalize } 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) {} |
|||
|
|||
formGroup!: FormGroup; |
|||
|
|||
allMonth = [ |
|||
{ value: "1", label: "一月", checked: false }, |
|||
{ value: "2", label: "二月", checked: false }, |
|||
{ value: "3", label: "三月", checked: false }, |
|||
{ value: "4", label: "四月", checked: false }, |
|||
{ value: "5", label: "五月", checked: false }, |
|||
{ value: "6", label: "六月", checked: false }, |
|||
{ value: "7", label: "七月", checked: false }, |
|||
{ value: "8", label: "八月", checked: false }, |
|||
{ value: "9", label: "九月", checked: false }, |
|||
{ value: "10", label: "十月", checked: false }, |
|||
{ value: "11", label: "十一月", checked: false }, |
|||
{ value: "12", label: "十二月", checked: false }, |
|||
]; |
|||
|
|||
allMonthChecked = false; |
|||
|
|||
indeterminate = false; |
|||
|
|||
uploadLoading = false; |
|||
|
|||
addFoodVisible = false; |
|||
|
|||
get food(): FormArray { |
|||
return this.formGroup.get("food") as FormArray; |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
this.formGroup = this.fb.group({ |
|||
id: this.fb.control("", [FormValidators.required()]), |
|||
unit: this.fb.control([], [FormValidators.required()]), |
|||
name: this.fb.control("", [FormValidators.required()]), |
|||
img: this.fb.control("", []), |
|||
tag: this.fb.control([], []), |
|||
food: this.fb.array([], [FormValidators.required()]), |
|||
month: this.fb.control([], []), |
|||
}); |
|||
} |
|||
|
|||
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); |
|||
} |
|||
|
|||
updateAllMonthChecked() { |
|||
this.indeterminate = false; |
|||
if (this.allMonthChecked) { |
|||
this.allMonth = this.allMonth.map((item) => ({ |
|||
...item, |
|||
checked: true, |
|||
})); |
|||
} else { |
|||
this.allMonth = this.allMonth.map((item) => ({ |
|||
...item, |
|||
checked: false, |
|||
})); |
|||
} |
|||
} |
|||
|
|||
monthChecked() { |
|||
if (this.allMonth.every((item) => !item.checked)) { |
|||
this.allMonthChecked = false; |
|||
this.indeterminate = false; |
|||
} else if (this.allMonth.every((item) => item.checked)) { |
|||
this.allMonthChecked = true; |
|||
this.indeterminate = false; |
|||
} else { |
|||
this.indeterminate = true; |
|||
} |
|||
} |
|||
|
|||
onFileChange(e: Event) { |
|||
const target = e.target as HTMLInputElement; |
|||
const file = target.files![0]; |
|||
target.value = ""; |
|||
const formData = new FormData(); |
|||
const fileReader = new FileReader(); |
|||
fileReader.onload = () => { |
|||
const base64 = fileReader.result as string; |
|||
|
|||
const v = base64.split("base64,")[1]; |
|||
}; |
|||
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);
|
|||
// });
|
|||
} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
<form nz-form [formGroup]="formGroup" nzLayout="vertical"> |
|||
<nz-form-item> |
|||
<nz-form-label nzRequired> |
|||
食材编号 |
|||
</nz-form-label> |
|||
<nz-form-control [nzErrorTip]="formControlErrorTpl"> |
|||
<input placeholder="请输入食材编号" nz-input formControlName="id" /> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
<nz-form-item> |
|||
<nz-form-label nzRequired> |
|||
食材名称 |
|||
</nz-form-label> |
|||
<nz-form-control [nzErrorTip]="formControlErrorTpl"> |
|||
<input placeholder="请输入食材名称" nz-input formControlName="name" /> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
<nz-form-item> |
|||
<nz-form-label nzRequired> |
|||
食材类型 |
|||
</nz-form-label> |
|||
<nz-form-control [nzErrorTip]="formControlErrorTpl"> |
|||
<input placeholder="请输入食材类型" nz-input formControlName="type" /> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
<nz-form-item> |
|||
<nz-form-label nzRequired class="block-label"> |
|||
<div class="flex justify-between items-center flex-1"> |
|||
<span class="flex-1"> |
|||
营养素(每100g可食部) |
|||
</span> |
|||
<a nz-button nzType="link" (click)="createNutrition()"> |
|||
<span nz-icon nzType="plus"></span> |
|||
<span> |
|||
添加营养素 |
|||
</span> |
|||
</a> |
|||
</div> |
|||
</nz-form-label> |
|||
<nz-form-control [nzErrorTip]="formControlErrorTpl"> |
|||
<ul formArrayName="nutrition"> |
|||
<li class="mb-2" *ngFor="let n of nutrition.controls;let i = index" [formGroupName]="i"> |
|||
<div class="flex"> |
|||
<div> |
|||
<nz-select class="!w-[160px]" nzPlaceHolder="营养素" |
|||
formControlName="nutritionName"> |
|||
</nz-select> |
|||
</div> |
|||
<div class="flex-1 px-2"> |
|||
<nz-input-group [nzAddOnAfter]="'μgRAE'" class="w-full"> |
|||
<input nz-input formControlName="nutritionNum" /> |
|||
</nz-input-group> |
|||
</div> |
|||
<div> |
|||
<button nz-button (click)="removeNutrition(i)"> |
|||
<span nz-icon nzType="delete"></span> |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</li> |
|||
|
|||
|
|||
|
|||
</ul> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
</form> |
|||
<ng-template #formControlErrorTpl let-control> |
|||
<form-error-tips [control]="control"></form-error-tips> |
|||
</ng-template> |
|||
@ -0,0 +1,8 @@ |
|||
.block-label { |
|||
::ng-deep { |
|||
label { |
|||
display: inline-flex; |
|||
width: 100%; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
import { Component, OnInit } from "@angular/core"; |
|||
import { FormArray, FormBuilder, FormGroup } from "@angular/forms"; |
|||
import { FormValidators } from "@cdk/validators"; |
|||
|
|||
@Component({ |
|||
selector: "app-food-form", |
|||
templateUrl: "./food-form.component.html", |
|||
styleUrls: ["./food-form.component.less"], |
|||
}) |
|||
export class FoodFormComponent implements OnInit { |
|||
constructor(private fb: FormBuilder) {} |
|||
|
|||
formGroup!: FormGroup; |
|||
|
|||
get nutrition(): FormArray { |
|||
return this.formGroup.get("nutrition") as FormArray; |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
this.formGroup = this.fb.group({ |
|||
id: this.fb.control("", [FormValidators.required()]), |
|||
name: this.fb.control("", [FormValidators.required()]), |
|||
type: this.fb.control("", [FormValidators.required()]), |
|||
nutrition: this.fb.array([], [FormValidators.required()]), |
|||
}); |
|||
} |
|||
|
|||
createNutrition() { |
|||
this.nutrition.push( |
|||
this.fb.group({ |
|||
nutritionName: this.fb.control("", [FormValidators.required()]), |
|||
nutritionNum: this.fb.control(0, [FormValidators.required()]), |
|||
}) |
|||
); |
|||
} |
|||
|
|||
removeNutrition(idx: number) { |
|||
this.nutrition.removeAt(idx); |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
export * from "./app-layout/app-layout.component"; |
|||
export * from "./app-page/app-page.component"; |
|||
|
|||
export * from "./food-form/food-form.component"; |
|||
export * from "./dish-form/dish-form.component"; |
|||
|
|||
export * from "./ingredient-form-basic/ingredient-form-basic.component"; |
|||
@ -0,0 +1,86 @@ |
|||
<form nz-form [formGroup]="formGroup" nzLayout="vertical"> |
|||
<nz-form-item> |
|||
<nz-form-label nzRequired nzSpan="6"> |
|||
适用单位 |
|||
</nz-form-label> |
|||
<nz-form-control [nzErrorTip]="formControlErrorTpl" nzSpan="12"> |
|||
<nz-select [nzOptions]="[]" nzPlaceHolder="请选择单位"></nz-select> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
<nz-form-item> |
|||
<nz-form-label nzRequired nzSpan="6"> |
|||
天数 |
|||
</nz-form-label> |
|||
<nz-form-control [nzErrorTip]="formControlErrorTpl"> |
|||
<nz-radio-group formControlName="day"> |
|||
<label nz-radio nzValue="1">1天</label> |
|||
<label nz-radio nzValue="2">2天</label> |
|||
<label nz-radio nzValue="3">3天</label> |
|||
<label nz-radio nzValue="4">4天</label> |
|||
<label nz-radio nzValue="5">5天</label> |
|||
<label nz-radio nzValue="6">6天</label> |
|||
<label nz-radio nzValue="7">7天</label> |
|||
</nz-radio-group> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
<nz-form-item> |
|||
<nz-form-label nzRequired nzSpan="6"> |
|||
标题 |
|||
</nz-form-label> |
|||
<nz-form-control [nzErrorTip]="formControlErrorTpl" nzSpan="12"> |
|||
<nz-checkbox-wrapper class="flex flex-wrap checkbox-wrap"> |
|||
<label nz-checkbox nzValue="1">早餐</label> |
|||
<label nz-checkbox nzValue="2">午餐</label> |
|||
<label nz-checkbox nzValue="3">晚餐</label> |
|||
</nz-checkbox-wrapper> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
|
|||
<nz-form-item> |
|||
<nz-form-label nzRequired nzSpan="6"> |
|||
标准 |
|||
</nz-form-label> |
|||
<nz-form-control [nzErrorTip]="formControlErrorTpl" nzSpan="12"> |
|||
<nz-select [nzOptions]="[]" nzPlaceHolder="请选择标准"></nz-select> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
<nz-form-item> |
|||
<nz-form-label nzRequired nzSpan="6"> |
|||
人群显示 |
|||
</nz-form-label> |
|||
<nz-form-control [nzErrorTip]="formControlErrorTpl"> |
|||
<nz-checkbox-group [ngModel]="ages" |
|||
class="flex flex-wrap checkbox-wrap" |
|||
formControlName="month" |
|||
(ngModelChange)="ageChange()"> |
|||
</nz-checkbox-group> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
<nz-divider></nz-divider> |
|||
<nz-form-item> |
|||
<nz-form-label nzRequired nzSpan="6"> |
|||
批量修改重量 |
|||
</nz-form-label> |
|||
<nz-form-control [nzErrorTip]="formControlErrorTpl" nzSpan="12"> |
|||
<nz-switch></nz-switch> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
<nz-form-item> |
|||
|
|||
<nz-form-control> |
|||
<nz-space> |
|||
<button *nzSpaceItem nz-button nzType="primary" (click)="onSubmit()"> |
|||
确定 |
|||
</button> |
|||
<button *nzSpaceItem nz-button> |
|||
取消 |
|||
</button> |
|||
</nz-space> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
|
|||
|
|||
</form> |
|||
<ng-template #formControlErrorTpl let-control> |
|||
<form-error-tips [control]="control"></form-error-tips> |
|||
</ng-template> |
|||
@ -0,0 +1,7 @@ |
|||
.checkbox-wrap { |
|||
::ng-deep { |
|||
&>label { |
|||
flex-basis: 130px; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
import { Component, EventEmitter, OnInit, Output } from "@angular/core"; |
|||
import { FormArray, FormBuilder, FormGroup } from "@angular/forms"; |
|||
import { FormValidators } from "@cdk/validators"; |
|||
|
|||
@Component({ |
|||
selector: "app-ingredient-form-basic", |
|||
templateUrl: "./ingredient-form-basic.component.html", |
|||
styleUrls: ["./ingredient-form-basic.component.less"], |
|||
}) |
|||
export class IngredientFormBasicComponent { |
|||
constructor(private fb: FormBuilder) {} |
|||
|
|||
@Output() onSave = new EventEmitter(); |
|||
|
|||
formGroup!: FormGroup; |
|||
|
|||
ages = [ |
|||
{ value: "1", label: "6-8岁(男)", checked: false }, |
|||
{ value: "2", label: "6-8岁(女)", checked: false }, |
|||
{ value: "3", label: "9-11岁(男)", checked: false }, |
|||
{ value: "4", label: "9-11岁(女)", checked: false }, |
|||
{ value: "5", label: "12-14岁(男)", checked: false }, |
|||
{ value: "6", label: "12-14岁(女)", checked: false }, |
|||
]; |
|||
|
|||
ngOnInit(): void { |
|||
this.formGroup = this.fb.group({ |
|||
id: this.fb.control("", [FormValidators.required()]), |
|||
unit: this.fb.control("", [FormValidators.required()]), |
|||
day: this.fb.control("1", [FormValidators.required()]), |
|||
name: this.fb.control("", [FormValidators.required()]), |
|||
food: this.fb.array([], [FormValidators.required()]), |
|||
tag: this.fb.control([], []), |
|||
month: this.fb.control([], []), |
|||
}); |
|||
} |
|||
|
|||
ageChange() {} |
|||
|
|||
onSubmit() { |
|||
this.onSave.emit(); |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
import { NgModule } from "@angular/core"; |
|||
import { NZ_ICONS, NzIconModule, NzIconService } from "ng-zorro-antd/icon"; |
|||
|
|||
import { MenuFoldOutline, MenuUnfoldOutline, FormOutline, DashboardOutline } from "@ant-design/icons-angular/icons"; |
|||
|
|||
const icons = [MenuFoldOutline, MenuUnfoldOutline, DashboardOutline, FormOutline]; |
|||
|
|||
@NgModule({ |
|||
imports: [NzIconModule], |
|||
exports: [NzIconModule], |
|||
providers: [{ provide: NZ_ICONS, useValue: icons }], |
|||
}) |
|||
export class IconsProviderModule { |
|||
constructor(private icon: NzIconService) { |
|||
// this.icon.addIconLiteral('/ass','SVG')
|
|||
} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
<app-page> |
|||
<ng-template #pageExtraTpl> |
|||
<nz-space> |
|||
<button *nzSpaceItem nz-button>批量删除</button> |
|||
<button *nzSpaceItem nz-button>批量下载营养标签</button> |
|||
<button *nzSpaceItem nz-button nzType="primary" (click)="showFoodForm()"> |
|||
<i nz-icon nzType="plus"></i> |
|||
新增菜品 |
|||
</button> |
|||
</nz-space> |
|||
</ng-template> |
|||
<div class="h-full overflow-hidden bg-white rounded-lg"> |
|||
|
|||
<nz-card [nzBordered]="false" nzTitle="菜品管理"> |
|||
<table-list [props]="tableList" [search]="searchTpl" [action]="pageExtraTpl" [formGroup]="queryForm" |
|||
[renderColumns]="renderColumnsTpl"> |
|||
|
|||
<ng-template #actionTpl> |
|||
<button nz-button>批量删除</button> |
|||
</ng-template> |
|||
<ng-template #searchTpl> |
|||
<nz-form-item class="w-40"> |
|||
<nz-form-control> |
|||
<nz-select nzPlaceHolder="单位" [nzOptions]="[]"></nz-select> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
<nz-form-item class="w-40"> |
|||
<nz-form-control> |
|||
<nz-select nzPlaceHolder="菜品标签" [nzOptions]="[]"></nz-select> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
<nz-form-item> |
|||
<nz-form-control> |
|||
<input nz-input placeholder="请输入菜品名称" formControlName="name" /> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
</ng-template> |
|||
<ng-template #renderColumnsTpl let-data let-key="key" let-row="row"> |
|||
<ng-container [ngSwitch]="key"> |
|||
<ng-container *ngSwitchCase="'img'"> |
|||
<div class="dish-img overflow-auto" |
|||
[ngStyle]="{'background-image':'url(' + tempImg + ')'}"> |
|||
</div> |
|||
</ng-container> |
|||
<ng-container *ngSwitchDefault> |
|||
|
|||
{{data}} |
|||
|
|||
</ng-container> |
|||
</ng-container> |
|||
</ng-template> |
|||
</table-list> |
|||
</nz-card> |
|||
</div> |
|||
</app-page> |
|||
|
|||
|
|||
<ng-template #formFooterTpl> |
|||
<nz-space> |
|||
<button *nzSpaceItem nz-button (click)="cancelFoodForm()"> |
|||
取消 |
|||
</button> |
|||
<button *nzSpaceItem nz-button nzType="primary"> |
|||
保存 |
|||
</button> |
|||
</nz-space> |
|||
</ng-template> |
|||
@ -0,0 +1,8 @@ |
|||
.dish-img { |
|||
width: 64px; |
|||
height: 64px; |
|||
border-radius: 10px; |
|||
background-size: cover; |
|||
background-position: center; |
|||
background-repeat: no-repeat; |
|||
} |
|||
@ -0,0 +1,81 @@ |
|||
import { Component, OnInit, TemplateRef, ViewChild } from "@angular/core"; |
|||
import { FormControl, FormGroup } from "@angular/forms"; |
|||
import { NzDrawerRef, NzDrawerService } from "ng-zorro-antd/drawer"; |
|||
import { AnyObject, TableListOption } from "@cdk/public-api"; |
|||
import { DishFormComponent } from "@admin/app/components"; |
|||
import { ApiService } from "@admin/app/services"; |
|||
|
|||
@Component({ |
|||
selector: "app-dish", |
|||
templateUrl: "./dish.component.html", |
|||
styleUrls: ["./dish.component.less"], |
|||
}) |
|||
export class DishComponent { |
|||
constructor(private drawer: NzDrawerService, private api: ApiService) {} |
|||
|
|||
@ViewChild("formFooterTpl") formFooterTpl!: TemplateRef<{}>; |
|||
|
|||
private drawerRef?: NzDrawerRef; |
|||
|
|||
tempImg = "https://cdn.pixabay.com/photo/2023/08/08/18/01/butterfly-8177925_1280.jpg"; |
|||
|
|||
public tableList = new TableListOption(this.fetchData.bind(this), { |
|||
selectable: true, |
|||
}); |
|||
|
|||
public queryForm = new FormGroup({ |
|||
name: new FormControl(""), |
|||
}); |
|||
|
|||
ngOnInit(): void { |
|||
this.initTableList(); |
|||
} |
|||
|
|||
initTableList() { |
|||
this.tableList.scroll = { x: null }; |
|||
this.tableList = this.tableList.setColumns([ |
|||
{ key: "img", title: "菜品图片", width: "66px" }, |
|||
{ key: "name", title: "菜品名称" }, |
|||
{ key: "name", title: "菜品标签" }, |
|||
{ key: "name", title: "食材及含量", width: "30%" }, |
|||
{ key: "name", title: "单位" }, |
|||
]); |
|||
|
|||
this.tableList = this.tableList.setOptions([ |
|||
{ |
|||
title: "下载营养标签", |
|||
premissions: [], |
|||
onClick: this.showFoodForm.bind(this), |
|||
}, |
|||
{ |
|||
title: "编辑", |
|||
premissions: [], |
|||
onClick: this.showFoodForm.bind(this), |
|||
}, |
|||
{ |
|||
title: "删除", |
|||
premissions: [], |
|||
onClick: this.deleteItem.bind(this), |
|||
}, |
|||
]); |
|||
} |
|||
|
|||
fetchData(query: AnyObject, pager: AnyObject) { |
|||
return this.api.page(pager, query); |
|||
} |
|||
|
|||
showFoodForm(food?: any) { |
|||
this.drawerRef = this.drawer.create({ |
|||
nzTitle: food ? "编辑菜品" : "新增菜品", |
|||
nzWidth: 700, |
|||
nzContent: DishFormComponent, |
|||
nzFooter: this.formFooterTpl, |
|||
}); |
|||
} |
|||
|
|||
cancelFoodForm() { |
|||
this.drawerRef?.close(); |
|||
} |
|||
|
|||
deleteItem() {} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
<app-page> |
|||
<ng-template #pageExtraTpl> |
|||
<nz-space> |
|||
<button *nzSpaceItem nz-button>批量删除</button> |
|||
<button *nzSpaceItem nz-button>导入食材清单</button> |
|||
<button *nzSpaceItem nz-button nzType="primary" (click)="showFoodForm()"> |
|||
<i nz-icon nzType="plus"></i> |
|||
新增食材 |
|||
</button> |
|||
</nz-space> |
|||
</ng-template> |
|||
<div nz-row class="h-full overflow-hidden bg-white rounded-lg"> |
|||
<div nz-col nzFlex="220px" class="food-type"> |
|||
<nz-card class="h-full" [nzBordered]="false" nzTitle="食材类型" [nzBodyStyle]="{padding:'1px 0 0 0'}"> |
|||
<ul nz-menu nzMode="inline"> |
|||
<li nz-menu-item> |
|||
<a> |
|||
全部 |
|||
</a> |
|||
</li> |
|||
<li nz-menu-item> |
|||
<a> |
|||
谷薯类 |
|||
</a> |
|||
</li> |
|||
<li nz-menu-item> |
|||
<a> |
|||
大豆类及其制品 |
|||
</a> |
|||
</li> |
|||
<li nz-menu-item> |
|||
<a> |
|||
蔬菜类 |
|||
</a> |
|||
</li> |
|||
</ul> |
|||
</nz-card> |
|||
</div> |
|||
<div nz-col nzFlex="1" class="flex-1 overflow-hidden bg-white "> |
|||
|
|||
<nz-card [nzBordered]="false" nzTitle="食材管理"> |
|||
<table-list [props]="tableList" [search]="searchTpl" [action]="pageExtraTpl" [formGroup]="queryForm"> |
|||
|
|||
<ng-template #actionTpl> |
|||
<button nz-button>批量删除</button> |
|||
</ng-template> |
|||
<ng-template #searchTpl> |
|||
<nz-form-item> |
|||
<nz-form-control> |
|||
<input nz-input placeholder="请输入食材名称/编号" formControlName="name" /> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
</ng-template> |
|||
</table-list> |
|||
</nz-card> |
|||
|
|||
</div> |
|||
</div> |
|||
</app-page> |
|||
|
|||
|
|||
<ng-template #foofFormFooterTpl> |
|||
<nz-space> |
|||
<button *nzSpaceItem nz-button (click)="cancelFoodForm()"> |
|||
取消 |
|||
</button> |
|||
<button *nzSpaceItem nz-button nzType="primary"> |
|||
保存 |
|||
</button> |
|||
</nz-space> |
|||
</ng-template> |
|||
@ -0,0 +1,9 @@ |
|||
.food-type { |
|||
border-right: 1px solid #e8e8e8; |
|||
|
|||
::ng-deep { |
|||
.ant-menu-inline { |
|||
border-right: none; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
import { FoodFormComponent } from "@admin/app/components"; |
|||
import { ApiService } from "@admin/app/services"; |
|||
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"; |
|||
|
|||
@Component({ |
|||
selector: "app-food", |
|||
templateUrl: "./food.component.html", |
|||
styleUrls: ["./food.component.less"], |
|||
}) |
|||
export class FoodComponent implements OnInit { |
|||
constructor(private drawer: NzDrawerService, private api: ApiService) {} |
|||
|
|||
@ViewChild("foofFormFooterTpl") foofFormFooterTpl!: TemplateRef<{}>; |
|||
|
|||
private drawerRef?: NzDrawerRef; |
|||
|
|||
public tableList = new TableListOption(this.fetchData.bind(this)); |
|||
|
|||
public queryForm = new FormGroup({ |
|||
name: new FormControl(""), |
|||
}); |
|||
|
|||
ngOnInit(): void { |
|||
this.initTableList(); |
|||
} |
|||
|
|||
initTableList() { |
|||
this.tableList.scroll = { x: null }; |
|||
this.tableList = this.tableList.setColumns([ |
|||
{ key: "name", title: "食材编号" }, |
|||
{ key: "name", title: "食材名称" }, |
|||
{ key: "name", title: "食材类型" }, |
|||
{ key: "name", title: "营养素(每100g可食部)", width: "50%" }, |
|||
{ key: "name", title: "更新日期" }, |
|||
]); |
|||
|
|||
this.tableList = this.tableList.setOptions([ |
|||
{ |
|||
title: "编辑", |
|||
premissions: [], |
|||
onClick: this.showFoodForm.bind(this), |
|||
}, |
|||
{ |
|||
title: "删除", |
|||
premissions: [], |
|||
onClick: this.deleteItem.bind(this), |
|||
}, |
|||
]); |
|||
} |
|||
|
|||
fetchData(query: AnyObject, pager: AnyObject) { |
|||
return this.api.page(pager, query); |
|||
} |
|||
|
|||
showFoodForm(food?: any) { |
|||
this.drawerRef = this.drawer.create({ |
|||
nzTitle: food ? "编辑食材" : "新增食材", |
|||
nzWidth: 520, |
|||
nzContent: FoodFormComponent, |
|||
nzFooter: this.foofFormFooterTpl, |
|||
}); |
|||
} |
|||
|
|||
cancelFoodForm() { |
|||
this.drawerRef?.close(); |
|||
} |
|||
|
|||
deleteItem() {} |
|||
} |
|||
@ -0,0 +1 @@ |
|||
<p>home works!</p> |
|||
@ -0,0 +1,10 @@ |
|||
import { Component } from '@angular/core'; |
|||
|
|||
@Component({ |
|||
selector: 'app-home', |
|||
templateUrl: './home.component.html', |
|||
styleUrls: ['./home.component.less'] |
|||
}) |
|||
export class HomeComponent { |
|||
|
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
export * from "./home/home.component"; |
|||
|
|||
export * from "./login/login.component"; |
|||
|
|||
export * from "./food/food.component"; |
|||
export * from "./dish/dish.component"; |
|||
|
|||
export * from "./ingredients/ingredient-list/ingredient-list.component"; |
|||
export * from "./ingredients/ingredient-review/ingredient-review.component"; |
|||
export * from "./ingredients/ingredient-release/ingredient-release.component"; |
|||
export * from "./ingredients/ingredient-form/ingredient-form.component"; |
|||
@ -0,0 +1,279 @@ |
|||
<app-page [full]="true"> |
|||
|
|||
<div class="p-4" *ngIf="step === 0"> |
|||
<nz-card nzTitle="录入食谱基础信息"> |
|||
<app-ingredient-form-basic (onSave)="onStepChange()"></app-ingredient-form-basic> |
|||
</nz-card> |
|||
</div> |
|||
|
|||
<ng-container *ngIf="step === 1"> |
|||
<nz-card nzSize="small"> |
|||
<div class="flex justify-between"> |
|||
<div class="flex-1 "> |
|||
<button nz-button (click)="step = 0"> |
|||
配置 |
|||
</button> |
|||
</div> |
|||
|
|||
<nz-space> |
|||
<button *nzSpaceItem nz-button nzType="primary"> |
|||
食谱营养分析 |
|||
</button> |
|||
<button *nzSpaceItem nz-button nzType="primary"> |
|||
新建食谱 |
|||
</button> |
|||
<button *nzSpaceItem nz-button> |
|||
导入食谱 |
|||
</button> |
|||
<button *nzSpaceItem nz-button> |
|||
食谱预览 |
|||
</button> |
|||
<button *nzSpaceItem nz-button nzType="primary"> |
|||
保存 |
|||
</button> |
|||
<button *nzSpaceItem nz-button> |
|||
另存为 |
|||
</button> |
|||
</nz-space> |
|||
</div> |
|||
</nz-card> |
|||
|
|||
<div class="p-4"> |
|||
<nz-card nzTitle="第一天" class="mb-4"> |
|||
<nz-card-tab> |
|||
<nz-tabset nzSize="large"> |
|||
<nz-tab nzTitle="早餐"></nz-tab> |
|||
<nz-tab nzTitle="午餐"></nz-tab> |
|||
<nz-tab nzTitle="晚餐"></nz-tab> |
|||
</nz-tabset> |
|||
</nz-card-tab> |
|||
<div> |
|||
<nz-table nzTemplateMode [nzBordered]="true" nzSize="small"> |
|||
<thead> |
|||
<tr> |
|||
<th> |
|||
菜品 |
|||
</th> |
|||
<th> |
|||
食材 |
|||
</th> |
|||
<th> |
|||
轻体力(体重过低) |
|||
</th> |
|||
<th> |
|||
轻体力(正常体重) |
|||
</th> |
|||
<th> |
|||
休息(超重/肥胖) |
|||
</th> |
|||
<th> |
|||
轻体力(体重过低) |
|||
</th> |
|||
<th> |
|||
轻体力(正常体重) |
|||
</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr> |
|||
<td [rowSpan]="2"> |
|||
<div class="flex justify-between"> |
|||
<span> |
|||
番茄煎蛋面 |
|||
</span> |
|||
<button nz-button nzType="text"> |
|||
<i nz-icon nzType="more"></i> |
|||
</button> |
|||
</div> |
|||
</td> |
|||
<td> |
|||
|
|||
<div class="flex justify-between"> |
|||
<span> |
|||
番茄 |
|||
</span> |
|||
<button nz-button nzType="text"> |
|||
<i nz-icon nzType="more"></i> |
|||
</button> |
|||
</div> |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
</tr> |
|||
<tr> |
|||
|
|||
<td> |
|||
|
|||
<div class="flex justify-between"> |
|||
<span> |
|||
面条 |
|||
</span> |
|||
<button nz-button nzType="text"> |
|||
<i nz-icon nzType="more"></i> |
|||
</button> |
|||
</div> |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
</tr> |
|||
<tr> |
|||
<td [rowSpan]="4"> |
|||
|
|||
<div class="flex justify-between"> |
|||
<span> |
|||
鸡蛋青菜面 |
|||
</span> |
|||
<button nz-button nzType="text"> |
|||
<i nz-icon nzType="more"></i> |
|||
</button> |
|||
</div> |
|||
</td> |
|||
<td> |
|||
<div class="flex justify-between"> |
|||
<span> |
|||
食用油 |
|||
</span> |
|||
<button nz-button nzType="text"> |
|||
<i nz-icon nzType="more"></i> |
|||
</button> |
|||
</div> |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
</tr> |
|||
<tr> |
|||
<td> |
|||
|
|||
<div class="flex justify-between"> |
|||
<span> |
|||
小白菜[青菜] |
|||
</span> |
|||
<button nz-button nzType="text"> |
|||
<i nz-icon nzType="more"></i> |
|||
</button> |
|||
</div> |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
</tr> |
|||
<tr> |
|||
<td> |
|||
|
|||
<div class="flex justify-between"> |
|||
<span> |
|||
鸡蛋(均值) |
|||
</span> |
|||
<button nz-button nzType="text"> |
|||
<i nz-icon nzType="more"></i> |
|||
</button> |
|||
</div> |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
</tr> |
|||
<tr> |
|||
<td> |
|||
|
|||
<div class="flex justify-between"> |
|||
<span> |
|||
面条(均值) |
|||
</span> |
|||
<button nz-button nzType="text"> |
|||
<i nz-icon nzType="more"></i> |
|||
</button> |
|||
</div> |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
<td> |
|||
1 |
|||
</td> |
|||
</tr> |
|||
</tbody> |
|||
</nz-table> |
|||
</div> |
|||
</nz-card> |
|||
<nz-card nzTitle="第二天" class="mb-4"></nz-card> |
|||
<nz-card nzTitle="第三天" class="mb-4"></nz-card> |
|||
<nz-card nzTitle="第四天" class="mb-4"></nz-card> |
|||
<nz-card nzTitle="第五天" class="mb-4"></nz-card> |
|||
<nz-card nzTitle="第六天" class="mb-4"></nz-card> |
|||
<nz-card nzTitle="第七天" class="mb-4"></nz-card> |
|||
</div> |
|||
</ng-container> |
|||
</app-page> |
|||
@ -0,0 +1,25 @@ |
|||
import { IngredientFormBasicComponent } from "@admin/app/components"; |
|||
import { Component, OnInit } from "@angular/core"; |
|||
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) {} |
|||
|
|||
step = 1; |
|||
|
|||
ngOnInit(): void {} |
|||
|
|||
onStepChange() { |
|||
this.step = 1; |
|||
console.log(456); |
|||
} |
|||
|
|||
showForm(food?: any) {} |
|||
|
|||
cancelForm() {} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
<app-page> |
|||
<ng-template #pageExtraTpl> |
|||
<nz-space> |
|||
<button *nzSpaceItem nz-button nzType="primary" [routerLink]="['/','ingredient','item','form','create']"> |
|||
<i nz-icon nzType="plus"></i> |
|||
创建食谱 |
|||
</button> |
|||
</nz-space> |
|||
</ng-template> |
|||
<div class="h-full overflow-hidden bg-white rounded-lg"> |
|||
|
|||
<nz-card [nzBordered]="false" nzTitle="食谱库"> |
|||
<table-list [props]="tableList" [search]="searchTpl" [action]="pageExtraTpl" [formGroup]="queryForm" |
|||
[renderColumns]="renderColumnsTpl"> |
|||
|
|||
<ng-template #actionTpl> |
|||
<button nz-button>批量删除</button> |
|||
</ng-template> |
|||
<ng-template #searchTpl> |
|||
<nz-form-item class="w-40"> |
|||
<nz-form-control> |
|||
<nz-select nzPlaceHolder="单位" [nzOptions]="[]"></nz-select> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
<nz-form-item class="w-40"> |
|||
<nz-form-control> |
|||
<nz-select nzPlaceHolder="状态" [nzOptions]="[]"></nz-select> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
<nz-form-item> |
|||
<nz-form-control> |
|||
<input nz-input placeholder="请输入食谱名称" formControlName="name" /> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
</ng-template> |
|||
<ng-template #renderColumnsTpl let-data let-key="key" let-row="row"> |
|||
<ng-container [ngSwitch]="key"> |
|||
<!-- <ng-container *ngSwitchCase="'img'"> |
|||
<div class="dish-img overflow-auto" |
|||
[ngStyle]="{'background-image':'url(' + tempImg + ')'}"> |
|||
</div> |
|||
</ng-container> --> |
|||
<ng-container *ngSwitchDefault> |
|||
|
|||
{{data}} |
|||
|
|||
</ng-container> |
|||
</ng-container> |
|||
</ng-template> |
|||
</table-list> |
|||
</nz-card> |
|||
</div> |
|||
</app-page> |
|||
|
|||
|
|||
<ng-template #foofFormFooterTpl> |
|||
<nz-space> |
|||
<button *nzSpaceItem nz-button (click)="cancelFoodForm()"> |
|||
取消 |
|||
</button> |
|||
<button *nzSpaceItem nz-button nzType="primary"> |
|||
保存 |
|||
</button> |
|||
</nz-space> |
|||
</ng-template> |
|||
@ -0,0 +1,98 @@ |
|||
import { Component, OnInit, TemplateRef, ViewChild } from "@angular/core"; |
|||
import { FormControl, FormGroup } from "@angular/forms"; |
|||
import { NzDrawerRef, NzDrawerService } from "ng-zorro-antd/drawer"; |
|||
import { AnyObject, TableListOption } from "@cdk/public-api"; |
|||
import { DishFormComponent } from "@admin/app/components"; |
|||
import { ApiService } from "@admin/app/services"; |
|||
|
|||
@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) {} |
|||
|
|||
@ViewChild("foofFormFooterTpl") foofFormFooterTpl!: TemplateRef<{}>; |
|||
|
|||
private drawerRef?: NzDrawerRef; |
|||
|
|||
tempImg = "https://cdn.pixabay.com/photo/2023/08/08/18/01/butterfly-8177925_1280.jpg"; |
|||
|
|||
public tableList = new TableListOption(this.fetchData.bind(this)); |
|||
|
|||
public queryForm = new FormGroup({ |
|||
name: new FormControl(""), |
|||
}); |
|||
|
|||
ngOnInit(): void { |
|||
this.initTableList(); |
|||
} |
|||
|
|||
initTableList() { |
|||
this.tableList.scroll = { x: null }; |
|||
this.tableList = this.tableList.setColumns([ |
|||
{ key: "name", title: "食谱名称" }, |
|||
{ key: "name", title: "单位" }, |
|||
{ key: "name", title: "包含餐次" }, |
|||
{ key: "name", title: "单位" }, |
|||
{ key: "name", title: "适用月份" }, |
|||
{ key: "name", title: "周期" }, |
|||
{ key: "name", title: "状态" }, |
|||
{ key: "name", title: "更新时间" }, |
|||
{ key: "name", title: "创建人" }, |
|||
]); |
|||
|
|||
this.tableList = this.tableList.setOptions([ |
|||
{ |
|||
title: "详情", |
|||
premissions: [], |
|||
onClick: this.showFoodForm.bind(this), |
|||
}, |
|||
{ |
|||
title: "导出", |
|||
premissions: [], |
|||
onClick: this.showFoodForm.bind(this), |
|||
}, |
|||
{ |
|||
title: "审核", |
|||
premissions: [], |
|||
onClick: this.deleteItem.bind(this), |
|||
}, |
|||
{ |
|||
title: "发布", |
|||
premissions: [], |
|||
onClick: this.deleteItem.bind(this), |
|||
}, |
|||
{ |
|||
title: "编辑", |
|||
premissions: [], |
|||
onClick: this.deleteItem.bind(this), |
|||
}, |
|||
{ |
|||
title: "删除", |
|||
premissions: [], |
|||
onClick: this.deleteItem.bind(this), |
|||
}, |
|||
]); |
|||
} |
|||
|
|||
fetchData(query: AnyObject, pager: AnyObject) { |
|||
return this.api.page(pager, query); |
|||
} |
|||
|
|||
showFoodForm(food?: any) { |
|||
this.drawerRef = this.drawer.create({ |
|||
nzTitle: food ? "编辑菜品" : "新增菜品", |
|||
nzWidth: 700, |
|||
nzContent: DishFormComponent, |
|||
nzFooter: this.foofFormFooterTpl, |
|||
}); |
|||
} |
|||
|
|||
cancelFoodForm() { |
|||
this.drawerRef?.close(); |
|||
} |
|||
|
|||
deleteItem() {} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
<app-page> |
|||
|
|||
<div class="h-full overflow-hidden bg-white rounded-lg"> |
|||
|
|||
<nz-card [nzBordered]="false" nzTitle="食谱发布计划"> |
|||
<table-list [props]="tableList" [search]="searchTpl" [formGroup]="queryForm" |
|||
[renderColumns]="renderColumnsTpl"> |
|||
|
|||
|
|||
<ng-template #searchTpl> |
|||
<nz-form-item> |
|||
<nz-form-control> |
|||
<input nz-input placeholder="请输入食谱名称" formControlName="name" /> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
<nz-form-item class="w-40"> |
|||
<nz-form-control> |
|||
<nz-select nzPlaceHolder="单位" [nzOptions]="[]"></nz-select> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
<nz-form-item> |
|||
<nz-form-control> |
|||
<nz-space> |
|||
<nz-radio-group *nzSpaceItem nzButtonStyle="solid"> |
|||
<label nz-radio-button nzValue="A">全部</label> |
|||
<label nz-radio-button nzValue="B">本周</label> |
|||
<label nz-radio-button nzValue="C">上周</label> |
|||
</nz-radio-group> |
|||
<ng-container *nzSpaceItem> |
|||
<nz-range-picker |
|||
[nzShowTime]="{ nzFormat: 'HH:mm' }" |
|||
nzFormat="yyyy-MM-dd HH:mm" |
|||
ngModel> |
|||
</nz-range-picker> |
|||
</ng-container> |
|||
</nz-space> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
|
|||
</ng-template> |
|||
<ng-template #renderColumnsTpl let-data let-key="key" let-row="row"> |
|||
<ng-container [ngSwitch]="key"> |
|||
<!-- <ng-container *ngSwitchCase="'img'"> |
|||
<div class="dish-img overflow-auto" |
|||
[ngStyle]="{'background-image':'url(' + tempImg + ')'}"> |
|||
</div> |
|||
</ng-container> --> |
|||
<ng-container *ngSwitchDefault> |
|||
|
|||
{{data}} |
|||
|
|||
</ng-container> |
|||
</ng-container> |
|||
</ng-template> |
|||
</table-list> |
|||
</nz-card> |
|||
</div> |
|||
</app-page> |
|||
|
|||
|
|||
<ng-template #foofFormFooterTpl> |
|||
<nz-space> |
|||
<button *nzSpaceItem nz-button (click)="cancelFoodForm()"> |
|||
取消 |
|||
</button> |
|||
<button *nzSpaceItem nz-button nzType="primary"> |
|||
保存 |
|||
</button> |
|||
</nz-space> |
|||
</ng-template> |
|||
@ -0,0 +1,81 @@ |
|||
import { Component, OnInit, TemplateRef, ViewChild } from "@angular/core"; |
|||
import { FormControl, FormGroup } from "@angular/forms"; |
|||
import { NzDrawerRef, NzDrawerService } from "ng-zorro-antd/drawer"; |
|||
import { AnyObject, TableListOption } from "@cdk/public-api"; |
|||
import { DishFormComponent } from "@admin/app/components"; |
|||
import { ApiService } from "@admin/app/services"; |
|||
|
|||
@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) {} |
|||
|
|||
@ViewChild("foofFormFooterTpl") foofFormFooterTpl!: TemplateRef<{}>; |
|||
|
|||
private drawerRef?: NzDrawerRef; |
|||
|
|||
tempImg = "https://cdn.pixabay.com/photo/2023/08/08/18/01/butterfly-8177925_1280.jpg"; |
|||
|
|||
public tableList = new TableListOption(this.fetchData.bind(this)); |
|||
|
|||
public queryForm = new FormGroup({ |
|||
name: new FormControl(""), |
|||
}); |
|||
|
|||
ngOnInit(): void { |
|||
this.initTableList(); |
|||
} |
|||
|
|||
initTableList() { |
|||
this.tableList.scroll = { x: null }; |
|||
this.tableList = this.tableList.setColumns([ |
|||
{ key: "name", title: "食谱名称" }, |
|||
{ key: "name", title: "单位" }, |
|||
{ key: "name", title: "包含餐次" }, |
|||
{ key: "name", title: "周期" }, |
|||
{ key: "name", title: "创建时间" }, |
|||
{ key: "name", title: "应用时间" }, |
|||
]); |
|||
|
|||
this.tableList = this.tableList.setOptions([ |
|||
{ |
|||
title: "详情", |
|||
premissions: [], |
|||
onClick: this.showFoodForm.bind(this), |
|||
}, |
|||
{ |
|||
title: "导出食谱", |
|||
premissions: [], |
|||
onClick: this.showFoodForm.bind(this), |
|||
}, |
|||
|
|||
{ |
|||
title: "取消发布", |
|||
premissions: [], |
|||
onClick: this.showFoodForm.bind(this), |
|||
}, |
|||
]); |
|||
} |
|||
|
|||
fetchData(query: AnyObject, pager: AnyObject) { |
|||
return this.api.page(pager, query); |
|||
} |
|||
|
|||
showFoodForm(food?: any) { |
|||
this.drawerRef = this.drawer.create({ |
|||
nzTitle: food ? "编辑菜品" : "新增菜品", |
|||
nzWidth: 700, |
|||
nzContent: DishFormComponent, |
|||
nzFooter: this.foofFormFooterTpl, |
|||
}); |
|||
} |
|||
|
|||
cancelFoodForm() { |
|||
this.drawerRef?.close(); |
|||
} |
|||
|
|||
deleteItem() {} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
<app-page> |
|||
<ng-template #pageExtraTpl> |
|||
<nz-space> |
|||
<button *nzSpaceItem nz-button nzType="primary" (click)="showFoodForm()"> |
|||
<i nz-icon nzType="plus"></i> |
|||
批量通过 |
|||
</button> |
|||
</nz-space> |
|||
</ng-template> |
|||
<div class="h-full overflow-hidden bg-white rounded-lg"> |
|||
|
|||
<nz-card [nzBordered]="false" nzTitle="食谱审核"> |
|||
<table-list [props]="tableList" [search]="searchTpl" [action]="pageExtraTpl" [formGroup]="queryForm" |
|||
[renderColumns]="renderColumnsTpl"> |
|||
|
|||
<ng-template #actionTpl> |
|||
<button nz-button>批量删除</button> |
|||
</ng-template> |
|||
<ng-template #searchTpl> |
|||
<nz-form-item class="w-40"> |
|||
<nz-form-control> |
|||
<nz-select nzPlaceHolder="单位" [nzOptions]="[]"></nz-select> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
|
|||
<nz-form-item> |
|||
<nz-form-control> |
|||
<input nz-input placeholder="请输入食谱名称" formControlName="name" /> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
</ng-template> |
|||
<ng-template #renderColumnsTpl let-data let-key="key" let-row="row"> |
|||
<ng-container [ngSwitch]="key"> |
|||
<!-- <ng-container *ngSwitchCase="'img'"> |
|||
<div class="dish-img overflow-auto" |
|||
[ngStyle]="{'background-image':'url(' + tempImg + ')'}"> |
|||
</div> |
|||
</ng-container> --> |
|||
<ng-container *ngSwitchDefault> |
|||
|
|||
{{data}} |
|||
|
|||
</ng-container> |
|||
</ng-container> |
|||
</ng-template> |
|||
</table-list> |
|||
</nz-card> |
|||
</div> |
|||
</app-page> |
|||
|
|||
|
|||
<ng-template #foofFormFooterTpl> |
|||
<nz-space> |
|||
<button *nzSpaceItem nz-button (click)="cancelFoodForm()"> |
|||
取消 |
|||
</button> |
|||
<button *nzSpaceItem nz-button nzType="primary"> |
|||
保存 |
|||
</button> |
|||
</nz-space> |
|||
</ng-template> |
|||
@ -0,0 +1,81 @@ |
|||
import { Component, OnInit, TemplateRef, ViewChild } from "@angular/core"; |
|||
import { FormControl, FormGroup } from "@angular/forms"; |
|||
import { NzDrawerRef, NzDrawerService } from "ng-zorro-antd/drawer"; |
|||
import { AnyObject, TableListOption } from "@cdk/public-api"; |
|||
import { DishFormComponent } from "@admin/app/components"; |
|||
import { ApiService } from "@admin/app/services"; |
|||
|
|||
@Component({ |
|||
selector: "app-ingredient-review", |
|||
templateUrl: "./ingredient-review.component.html", |
|||
styleUrls: ["./ingredient-review.component.less"], |
|||
}) |
|||
export class IngredientReviewComponent { |
|||
constructor(private drawer: NzDrawerService, private api: ApiService) {} |
|||
|
|||
@ViewChild("foofFormFooterTpl") foofFormFooterTpl!: TemplateRef<{}>; |
|||
|
|||
private drawerRef?: NzDrawerRef; |
|||
|
|||
tempImg = "https://cdn.pixabay.com/photo/2023/08/08/18/01/butterfly-8177925_1280.jpg"; |
|||
|
|||
public tableList = new TableListOption(this.fetchData.bind(this), { |
|||
selectable: true, |
|||
}); |
|||
|
|||
public queryForm = new FormGroup({ |
|||
name: new FormControl(""), |
|||
}); |
|||
|
|||
ngOnInit(): void { |
|||
this.initTableList(); |
|||
} |
|||
|
|||
initTableList() { |
|||
this.tableList.scroll = { x: null }; |
|||
this.tableList = this.tableList.setColumns([ |
|||
{ key: "name", title: "食谱名称" }, |
|||
{ key: "name", title: "单位" }, |
|||
{ key: "name", title: "提交审核时间" }, |
|||
{ key: "name", title: "提交人" }, |
|||
]); |
|||
|
|||
this.tableList = this.tableList.setOptions([ |
|||
{ |
|||
title: "详情", |
|||
premissions: [], |
|||
onClick: this.showFoodForm.bind(this), |
|||
}, |
|||
{ |
|||
title: "导出食谱", |
|||
premissions: [], |
|||
onClick: this.showFoodForm.bind(this), |
|||
}, |
|||
|
|||
{ |
|||
title: "取消发布", |
|||
premissions: [], |
|||
onClick: this.showFoodForm.bind(this), |
|||
}, |
|||
]); |
|||
} |
|||
|
|||
fetchData(query: AnyObject, pager: AnyObject) { |
|||
return this.api.page(pager, query); |
|||
} |
|||
|
|||
showFoodForm(food?: any) { |
|||
this.drawerRef = this.drawer.create({ |
|||
nzTitle: food ? "编辑菜品" : "新增菜品", |
|||
nzWidth: 700, |
|||
nzContent: DishFormComponent, |
|||
nzFooter: this.foofFormFooterTpl, |
|||
}); |
|||
} |
|||
|
|||
cancelFoodForm() { |
|||
this.drawerRef?.close(); |
|||
} |
|||
|
|||
deleteItem() {} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
<section class="h-full flex items-center justify-center"> |
|||
<div class="login"> |
|||
<!-- <div> |
|||
<h1 class="text-center my-[25px]"> |
|||
<img class="logo" [src]="'/assets/images/jl-logo.png'" /> |
|||
|
|||
</h1> |
|||
</div> --> |
|||
<div class="card shadow-2xl"> |
|||
<div class="img"></div> |
|||
<div class="form py-5 px-10 flex-1"> |
|||
<div class="text-left "> |
|||
<h2 class="mt-4 text-3xl font-bold">智慧配餐管理后台</h2> |
|||
<h3 class="mt-10 text-xl">登录</h3> |
|||
|
|||
<form nz-form [formGroup]="loginForm" class="mt-10"> |
|||
<nz-form-item> |
|||
<nz-form-control [nzErrorTip]="formErrorTipsTpl"> |
|||
<nz-input-group [nzPrefix]="prefixTemplateUser" nzSize="large"> |
|||
<input nz-input nzSize="large" placeholder="账户" formControlName="uid" /> |
|||
</nz-input-group> |
|||
<ng-template #prefixTemplateUser> |
|||
<span nz-icon nzType="user"></span> |
|||
<nz-divider nzType="vertical"></nz-divider> |
|||
</ng-template> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
<nz-form-item> |
|||
<nz-form-control [nzErrorTip]="formErrorTipsTpl"> |
|||
<nz-input-group [nzPrefix]="prefixTemplatePassword" nzSize="large"> |
|||
<input nz-input type="password" placeholder="密码" formControlName="password" /> |
|||
</nz-input-group> |
|||
<ng-template #prefixTemplatePassword> |
|||
<span nz-icon nzType="lock"></span> |
|||
<nz-divider nzType="vertical"></nz-divider> |
|||
</ng-template> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
<nz-form-item> |
|||
<nz-form-control> |
|||
<button nz-button |
|||
nzType="primary" |
|||
nzBlock |
|||
class="btn" |
|||
nzSize="large" |
|||
(click)="onLogin()" |
|||
[nzLoading]="loading"> |
|||
登录 |
|||
</button> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
</form> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</section> |
|||
|
|||
<ng-template #formErrorTipsTpl let-control> |
|||
<div class="text-left"> |
|||
<!-- <dec-form-error-tips [control]="control"></dec-form-error-tips> --> |
|||
</div> |
|||
</ng-template> |
|||
@ -0,0 +1,65 @@ |
|||
// |
|||
|
|||
:host { |
|||
display: block; |
|||
width: 100vw; |
|||
height: 100vh; |
|||
position: relative; |
|||
background-color: #edf0f5; |
|||
background-repeat: no-repeat; |
|||
background-size: cover; |
|||
|
|||
} |
|||
|
|||
.login { |
|||
position: relative; |
|||
z-index: 1; |
|||
|
|||
h1 { |
|||
margin-bottom: 24px; |
|||
font-size: 24px; |
|||
font-weight: 400; |
|||
color: #fff; |
|||
} |
|||
|
|||
.logo { |
|||
display: inline-block; |
|||
height: 36px; |
|||
} |
|||
|
|||
.card { |
|||
width: 960px; |
|||
height: 60vh; |
|||
min-height: 410px; |
|||
display: flex; |
|||
align-items: center; |
|||
background-color: #fff; |
|||
|
|||
.img { |
|||
display: block; |
|||
width: 560px; |
|||
height: 100%; |
|||
background-image: url('/assets/images/login.jpg'); |
|||
background-size: cover; |
|||
background-position: center; |
|||
} |
|||
} |
|||
|
|||
.form { |
|||
|
|||
margin: 0 auto; |
|||
background-color: #fff; |
|||
border-radius: 6px; |
|||
} |
|||
|
|||
p { |
|||
font-size: 16px; |
|||
line-height: 24px; |
|||
letter-spacing: 0.23em; |
|||
text-shadow: 0px 8px 20px rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.btn { |
|||
margin-top: 16px; |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
import { Component } from "@angular/core"; |
|||
import { FormControl, FormGroup } from "@angular/forms"; |
|||
import { Router } from "@angular/router"; |
|||
import { NzMessageService } from "ng-zorro-antd/message"; |
|||
import { FormValidators } from "projects/cdk/src/public-api"; |
|||
import { Utils } from "projects/cdk/src/utils"; |
|||
import { finalize, lastValueFrom } from "rxjs"; |
|||
import { ApiService } from "../../services"; |
|||
|
|||
@Component({ |
|||
selector: "app-login", |
|||
templateUrl: "./login.component.html", |
|||
styleUrls: ["./login.component.less"], |
|||
}) |
|||
export class LoginComponent { |
|||
constructor(private msg: NzMessageService, private api: ApiService, private router: Router) {} |
|||
|
|||
public loginForm = new FormGroup({ |
|||
uid: new FormControl("", [FormValidators.required("请输入账户")]), |
|||
password: new FormControl("", [FormValidators.required("请输入密码")]), |
|||
}); |
|||
|
|||
public loading: boolean = false; |
|||
|
|||
ngOnInit(): void {} |
|||
|
|||
async onLogin() { |
|||
if (Utils.validateFormGroup(this.loginForm)) { |
|||
const { value } = this.loginForm; |
|||
this.loading = true; |
|||
const res = await lastValueFrom( |
|||
this.api.login(value).pipe( |
|||
finalize(() => { |
|||
this.loading = false; |
|||
}) |
|||
) |
|||
); |
|||
this.msg.success(res.desc); |
|||
this.router.navigate(["/"]); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
import { HttpClient } from "@angular/common/http"; |
|||
import { Injectable } from "@angular/core"; |
|||
import { map } from "rxjs"; |
|||
|
|||
@Injectable({ |
|||
providedIn: "root", |
|||
}) |
|||
export class ApiService { |
|||
constructor(private http: HttpClient) {} |
|||
|
|||
login(v: {}) { |
|||
return this.http.post<any>("/", v); |
|||
} |
|||
|
|||
page(v: {}, q: {}) { |
|||
return this.http.get<any>("https://jsonplaceholder.typicode.com/users", v).pipe( |
|||
map((r) => { |
|||
return { |
|||
total: 10, |
|||
content: r, |
|||
}; |
|||
}) |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
import { Inject, Injectable } from "@angular/core"; |
|||
import { |
|||
HttpRequest, |
|||
HttpHandler, |
|||
HttpEvent, |
|||
HttpInterceptor, |
|||
HttpErrorResponse, |
|||
HttpResponse, |
|||
} from "@angular/common/http"; |
|||
import { catchError, Observable, switchMap, tap, throwError, timer } from "rxjs"; |
|||
import { Router } from "@angular/router"; |
|||
import { NzMessageService } from "ng-zorro-antd/message"; |
|||
import { ResponseType } from "@cdk/types"; |
|||
|
|||
@Injectable({ providedIn: "root" }) |
|||
export class HTTPInterceptor implements HttpInterceptor { |
|||
constructor(private router: Router, private msg: NzMessageService) {} |
|||
|
|||
private msgFlag = false; |
|||
|
|||
private localStroageKey = "catering"; |
|||
|
|||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { |
|||
const token = localStorage.getItem(this.localStroageKey); |
|||
|
|||
if (token) { |
|||
req = req.clone({ |
|||
// headers: req.headers.set('Authorization', `Bearer ${token}`),
|
|||
headers: req.headers.set("Authorization", token), |
|||
}); |
|||
} |
|||
|
|||
return this.handleResult(next, req); |
|||
} |
|||
|
|||
private handleResult(next: HttpHandler, authReq: HttpRequest<any>): Observable<HttpEvent<any>> { |
|||
return next.handle(authReq).pipe( |
|||
tap((res) => { |
|||
if (res instanceof HttpResponse) { |
|||
const Authorization = res.headers.get("Authorization"); |
|||
if (Authorization) { |
|||
localStorage.setItem(this.localStroageKey, Authorization); |
|||
} |
|||
|
|||
// if (this.decConfig.triggerError) {
|
|||
// this.decConfig.triggerError(res);
|
|||
// }
|
|||
|
|||
if (res.body?.success === false && res.body.desc) { |
|||
throw new HttpErrorResponse({ error: res.body }); |
|||
} |
|||
} |
|||
}), |
|||
|
|||
catchError((err: HttpErrorResponse) => { |
|||
const throwErr = throwError(() => err); |
|||
if (this.msgFlag) { |
|||
return throwErr; |
|||
} |
|||
|
|||
setTimeout(() => { |
|||
this.msgFlag = false; |
|||
}, 1500); |
|||
const error: ResponseType = err.error; |
|||
this.msgFlag = true; |
|||
|
|||
if (error.success === false) { |
|||
this.msg.error(error.desc); |
|||
switch (error.code) { |
|||
case 401: |
|||
this.router.navigate(["/", "login"]); |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
} else { |
|||
this.msg.error("服务器出错了!"); |
|||
} |
|||
|
|||
return throwErr; |
|||
}) |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1 @@ |
|||
export * from "./api.service"; |
|||
@ -0,0 +1,102 @@ |
|||
import { NzGridModule } from "ng-zorro-antd/grid"; |
|||
import { NzCardModule } from "ng-zorro-antd/card"; |
|||
import { NzStatisticModule } from "ng-zorro-antd/statistic"; |
|||
import { NzIconModule } from "ng-zorro-antd/icon"; |
|||
import { NzToolTipModule } from "ng-zorro-antd/tooltip"; |
|||
import { NzTableModule } from "ng-zorro-antd/table"; |
|||
import { NzSegmentedModule } from "ng-zorro-antd/segmented"; |
|||
import { NzSpaceModule } from "ng-zorro-antd/space"; |
|||
import { NzDatePickerModule } from "ng-zorro-antd/date-picker"; |
|||
import { NzListModule } from "ng-zorro-antd/list"; |
|||
import { NzInputModule } from "ng-zorro-antd/input"; |
|||
import { NzButtonModule } from "ng-zorro-antd/button"; |
|||
import { NzTagModule } from "ng-zorro-antd/tag"; |
|||
import { NzBadgeModule } from "ng-zorro-antd/badge"; |
|||
import { NzPaginationModule } from "ng-zorro-antd/pagination"; |
|||
import { NzDividerModule } from "ng-zorro-antd/divider"; |
|||
import { NzSelectModule } from "ng-zorro-antd/select"; |
|||
import { NzModalModule } from "ng-zorro-antd/modal"; |
|||
import { NzMessageModule } from "ng-zorro-antd/message"; |
|||
import { NzDrawerModule } from "ng-zorro-antd/drawer"; |
|||
import { NzFormModule } from "ng-zorro-antd/form"; |
|||
import { NzDescriptionsModule } from "ng-zorro-antd/descriptions"; |
|||
import { NzTabsModule } from "ng-zorro-antd/tabs"; |
|||
import { NzProgressModule } from "ng-zorro-antd/progress"; |
|||
import { NzAvatarModule } from "ng-zorro-antd/avatar"; |
|||
import { NzMenuModule } from "ng-zorro-antd/menu"; |
|||
import { NzDropDownModule } from "ng-zorro-antd/dropdown"; |
|||
import { NzTreeSelectModule } from "ng-zorro-antd/tree-select"; |
|||
import { NzRadioModule } from "ng-zorro-antd/radio"; |
|||
import { NzCheckboxModule } from "ng-zorro-antd/checkbox"; |
|||
import { NzCalendarModule } from "ng-zorro-antd/calendar"; |
|||
import { NzSkeletonModule } from "ng-zorro-antd/skeleton"; |
|||
import { NzTimelineModule } from "ng-zorro-antd/timeline"; |
|||
import { NzEmptyModule } from "ng-zorro-antd/empty"; |
|||
import { NzSpinModule } from "ng-zorro-antd/spin"; |
|||
import { NzResultModule } from "ng-zorro-antd/result"; |
|||
import { NzCascaderModule } from "ng-zorro-antd/cascader"; |
|||
import { NzAutocompleteModule } from "ng-zorro-antd/auto-complete"; |
|||
import { NzPopoverModule } from "ng-zorro-antd/popover"; |
|||
import { NzPageHeaderModule } from "ng-zorro-antd/page-header"; |
|||
import { NzTreeModule } from "ng-zorro-antd/tree"; |
|||
import { NzSwitchModule } from "ng-zorro-antd/switch"; |
|||
import { NzCarouselModule } from "ng-zorro-antd/carousel"; |
|||
import { NzTimePickerModule } from "ng-zorro-antd/time-picker"; |
|||
import { NzImageModule } from "ng-zorro-antd/image"; |
|||
import { NzInputNumberModule } from "ng-zorro-antd/input-number"; |
|||
import { NzLayoutModule } from "ng-zorro-antd/layout"; |
|||
import { NzBreadCrumbModule } from "ng-zorro-antd/breadcrumb"; |
|||
import { NzOutletModule } from "ng-zorro-antd/core/outlet"; |
|||
|
|||
export const ngZorroModules = [ |
|||
NzOutletModule, |
|||
NzBreadCrumbModule, |
|||
NzLayoutModule, |
|||
NzInputNumberModule, |
|||
NzImageModule, |
|||
NzTimePickerModule, |
|||
NzCarouselModule, |
|||
NzSwitchModule, |
|||
NzTreeModule, |
|||
NzPageHeaderModule, |
|||
NzPopoverModule, |
|||
NzAutocompleteModule, |
|||
NzCascaderModule, |
|||
NzResultModule, |
|||
NzSpinModule, |
|||
NzGridModule, |
|||
NzCardModule, |
|||
NzStatisticModule, |
|||
NzIconModule, |
|||
NzToolTipModule, |
|||
NzTableModule, |
|||
NzSegmentedModule, |
|||
NzSpaceModule, |
|||
NzDatePickerModule, |
|||
NzListModule, |
|||
NzInputModule, |
|||
NzButtonModule, |
|||
NzTagModule, |
|||
NzBadgeModule, |
|||
NzPaginationModule, |
|||
NzDividerModule, |
|||
NzSelectModule, |
|||
NzSelectModule, |
|||
NzModalModule, |
|||
NzMessageModule, |
|||
NzDrawerModule, |
|||
NzFormModule, |
|||
NzDescriptionsModule, |
|||
NzTabsModule, |
|||
NzProgressModule, |
|||
NzAvatarModule, |
|||
NzMenuModule, |
|||
NzDropDownModule, |
|||
NzTreeSelectModule, |
|||
NzRadioModule, |
|||
NzCalendarModule, |
|||
NzCheckboxModule, |
|||
NzSkeletonModule, |
|||
NzTimelineModule, |
|||
NzEmptyModule, |
|||
]; |
|||
@ -0,0 +1,41 @@ |
|||
import { RouterModule } from "@angular/router"; |
|||
import { NgModule } from "@angular/core"; |
|||
import { CommonModule } from "@angular/common"; |
|||
import { FormsModule, ReactiveFormsModule } from "@angular/forms"; |
|||
import { HttpClientModule } from "@angular/common/http"; |
|||
import { ngZorroModules } from "./ng-zorro"; |
|||
// import { AuthorizationLayoutComponent, BgBorderComponent } from "./components";
|
|||
// import { DecCornerDirective } from "./directives";
|
|||
import { |
|||
// DecModule,
|
|||
FormErrorTipsComponent, |
|||
StorageModule, |
|||
TableListModule, |
|||
// InputSpaceErrorDirective,
|
|||
// PublicPathPipe,
|
|||
// TableListModule,
|
|||
// StorageModule,
|
|||
// QuickDateRangeComponent,
|
|||
} from "@cdk/public-api"; |
|||
// import { environment } from "@manage/environments/environment";
|
|||
import { NgxPermissionsModule } from "ngx-permissions"; |
|||
|
|||
const ngModules = [CommonModule, HttpClientModule, FormsModule, RouterModule, ReactiveFormsModule]; |
|||
const components: any = []; |
|||
const directives: any[] = []; |
|||
const cdks = [ |
|||
// DecModule
|
|||
FormErrorTipsComponent, |
|||
// InputSpaceErrorDirective,
|
|||
// PublicPathPipe,
|
|||
TableListModule, |
|||
StorageModule, |
|||
// QuickDateRangeComponent,
|
|||
] as any; |
|||
|
|||
@NgModule({ |
|||
declarations: [...components, ...directives], |
|||
imports: [...ngZorroModules, ...ngModules, ...cdks], |
|||
exports: [...ngZorroModules, ...ngModules, ...components, ...directives, ...cdks], |
|||
}) |
|||
export class SharedModule {} |
|||
|
After Width: | Height: | Size: 192 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 948 B |
File diff suppressed because one or more lines are too long
@ -0,0 +1,7 @@ |
|||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; |
|||
|
|||
import { AppModule } from './app/app.module'; |
|||
|
|||
|
|||
platformBrowserDynamic().bootstrapModule(AppModule) |
|||
.catch(err => console.error(err)); |
|||
@ -0,0 +1,39 @@ |
|||
// Custom Theming for NG-ZORRO |
|||
// For more information: https://ng.ant.design/docs/customize-theme/en |
|||
@import "../../../node_modules/ng-zorro-antd/ng-zorro-antd.less"; |
|||
|
|||
// Override less variables to here |
|||
// View all variables: https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/components/style/themes/default.less |
|||
|
|||
// @primary-color: #1890ff; |
|||
/* You can add global styles to this file, and also import other style files */ |
|||
|
|||
@tailwind utilities; |
|||
|
|||
html, |
|||
|
|||
body { |
|||
height: 100vh; |
|||
} |
|||
|
|||
ul, |
|||
li { |
|||
list-style: none; |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
|
|||
.upload-btn { |
|||
position: relative; |
|||
|
|||
input { |
|||
display: block; |
|||
height: 100%; |
|||
background-color: red; |
|||
position: absolute; |
|||
inset: 0; |
|||
opacity: 0; |
|||
font-size: 0; |
|||
cursor: pointer; |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
/* To learn more about this file see: https://angular.io/config/tsconfig. */ |
|||
{ |
|||
"extends": "../../tsconfig.json", |
|||
"compilerOptions": { |
|||
"outDir": "../../out-tsc/app", |
|||
"types": [] |
|||
}, |
|||
"files": [ |
|||
"src/main.ts" |
|||
], |
|||
"include": [ |
|||
"src/**/*.d.ts" |
|||
] |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
/* To learn more about this file see: https://angular.io/config/tsconfig. */ |
|||
{ |
|||
"extends": "../../tsconfig.json", |
|||
"compilerOptions": { |
|||
"outDir": "../../out-tsc/spec", |
|||
"types": [ |
|||
"jasmine" |
|||
] |
|||
}, |
|||
"include": [ |
|||
"src/**/*.spec.ts", |
|||
"src/**/*.d.ts" |
|||
] |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
# Cdk |
|||
|
|||
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.1.0. |
|||
|
|||
## Code scaffolding |
|||
|
|||
Run `ng generate component component-name --project cdk` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project cdk`. |
|||
> Note: Don't forget to add `--project cdk` or else it will be added to the default project in your `angular.json` file. |
|||
|
|||
## Build |
|||
|
|||
Run `ng build cdk` to build the project. The build artifacts will be stored in the `dist/` directory. |
|||
|
|||
## Publishing |
|||
|
|||
After building your library with `ng build cdk`, go to the dist folder `cd dist/cdk` and run `npm publish`. |
|||
|
|||
## Running unit tests |
|||
|
|||
Run `ng test cdk` to execute the unit tests via [Karma](https://karma-runner.github.io). |
|||
|
|||
## 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. |
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json", |
|||
"dest": "../../dist/cdk", |
|||
"lib": { |
|||
"entryFile": "src/public-api.ts" |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
{ |
|||
"name": "cdk", |
|||
"version": "0.0.1", |
|||
"peerDependencies": { |
|||
"@angular/common": "^16.1.0", |
|||
"@angular/core": "^16.1.0" |
|||
}, |
|||
"dependencies": { |
|||
"tslib": "^2.3.0" |
|||
}, |
|||
"sideEffects": false |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
<ng-container *ngFor="let item of control.errors | keyvalue"> |
|||
<ng-container *ngIf="item.value?.message;else defaultTipsTpl"> |
|||
{{item.value.message}} |
|||
</ng-container> |
|||
<ng-template #defaultTipsTpl> |
|||
<ng-container [ngSwitch]="item.key"> |
|||
<div *ngSwitchCase="'required'"> |
|||
不能为空 |
|||
</div> |
|||
<div *ngSwitchCase="'inputTrim'"> |
|||
首末字符不能为空格 |
|||
</div> |
|||
<div *ngSwitchCase="'email'"> |
|||
请输入正确的邮箱地址 |
|||
</div> |
|||
<div *ngSwitchCase="'maxlength'"> |
|||
最多输入{{item.value.requiredLength}}位字符 |
|||
</div> |
|||
<div *ngSwitchCase="'minlength'"> |
|||
最少输入{{item.value.requiredLength}}位字符 |
|||
</div> |
|||
<div *ngSwitchCase="'min'"> |
|||
不能小于{{item.value.min}} |
|||
</div> |
|||
<div *ngSwitchCase="'max'"> |
|||
不能大于{{item.value.max}} |
|||
</div> |
|||
<div *ngSwitchCase="'pattern'"> |
|||
请输入正确的内容 |
|||
</div> |
|||
<div *ngSwitchDefault> |
|||
字段验证失败 |
|||
</div> |
|||
</ng-container> |
|||
</ng-template> |
|||
</ng-container> |
|||
@ -0,0 +1,28 @@ |
|||
import { CommonModule } from "@angular/common"; |
|||
import { Component, Input, OnChanges, OnInit, SimpleChanges } from "@angular/core"; |
|||
import { FormControl, FormGroup } from "@angular/forms"; |
|||
|
|||
@Component({ |
|||
standalone: true, |
|||
selector: "form-error-tips", |
|||
templateUrl: "./form-error-tips.component.html", |
|||
styleUrls: ["./form-error-tips.component.less"], |
|||
imports: [CommonModule], |
|||
}) |
|||
export class FormErrorTipsComponent implements OnInit, OnChanges { |
|||
constructor() {} |
|||
|
|||
@Input() control!: FormControl; |
|||
|
|||
ngOnChanges(changes: SimpleChanges): void { |
|||
// console.log("FormErrorTipsComponent changes", changes["control"]?.currentValue);
|
|||
// const formControl: FormControl = changes["control"].currentValue;
|
|||
// const root = formControl.root as FormGroup;
|
|||
// console.log("formControl.root", formControl);
|
|||
// if (formControl && !this.label) {
|
|||
// if(initLabelFormControlNameMaps.has(formControl))
|
|||
// }
|
|||
} |
|||
|
|||
ngOnInit(): void {} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator, ValidatorFn } from "@angular/forms"; |
|||
import { Directive } from "@angular/core"; |
|||
|
|||
const VALIDATE_WHITE_SPACE_REGEX = /^[\s]|[\s]$/; |
|||
|
|||
export function stringWhiteSpaceForbiddenValidator(): ValidatorFn { |
|||
return (control: AbstractControl): { [key: string]: any } | null => { |
|||
const isInvalid = VALIDATE_WHITE_SPACE_REGEX.test(control.value); |
|||
return isInvalid ? { inputTrim: { value: control.value } } : null; |
|||
}; |
|||
} |
|||
|
|||
@Directive({ |
|||
standalone: true, |
|||
selector: "[nz-input]", |
|||
providers: [{ provide: NG_VALIDATORS, useExisting: InputSpaceErrorDirective, multi: true }], |
|||
}) |
|||
export class InputSpaceErrorDirective implements Validator { |
|||
constructor() {} |
|||
|
|||
validate(control: AbstractControl): ValidationErrors | null { |
|||
return stringWhiteSpaceForbiddenValidator()(control); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
/* |
|||
* Public API Surface of cdk |
|||
*/ |
|||
|
|||
export * from "./types"; |
|||
export * from "./utils"; |
|||
export * from "./validators"; |
|||
// export * from "./dec-module/dec.module";
|
|||
export * from "./form-error-tips/form-error-tips.component"; |
|||
export * from "./input-space-error/input-space-error.directive"; |
|||
// export * from "./public-path/public-path.pipe";
|
|||
// export * from "./quick-date-range/quick-date-range.component";
|
|||
export * from "./table-list"; |
|||
export * from "./storage"; |
|||
@ -0,0 +1,17 @@ |
|||
<nz-space> |
|||
<nz-radio-group |
|||
*nzSpaceItem |
|||
[ngModel]="quick" |
|||
(ngModelChange)="onQuickChange($event)"> |
|||
<label *ngFor="let item of dateFilterOptions" |
|||
nz-radio-button |
|||
[nzValue]="item.value"> |
|||
{{item.label}} |
|||
</label> |
|||
</nz-radio-group> |
|||
<nz-range-picker |
|||
*nzSpaceItem |
|||
[ngModel]="range" |
|||
(ngModelChange)="onRangeChange($event)"> |
|||
</nz-range-picker> |
|||
</nz-space> |
|||
@ -0,0 +1,94 @@ |
|||
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from "@angular/forms"; |
|||
import { NzRadioModule } from "ng-zorro-antd/radio"; |
|||
import { NzSpaceModule } from "ng-zorro-antd/space"; |
|||
import { CommonModule } from "@angular/common"; |
|||
import { Component, OnInit } from "@angular/core"; |
|||
import { NzDatePickerModule } from "ng-zorro-antd/date-picker"; |
|||
import { subDays, format } from "date-fns"; |
|||
|
|||
@Component({ |
|||
standalone: true, |
|||
selector: "dec-quick-date-range", |
|||
templateUrl: "./quick-date-range.component.html", |
|||
styleUrls: ["./quick-date-range.component.css"], |
|||
imports: [CommonModule, NzDatePickerModule, NzSpaceModule, NzRadioModule, FormsModule], |
|||
providers: [ |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
multi: true, |
|||
useExisting: QuickDateRangeComponent, |
|||
}, |
|||
], |
|||
}) |
|||
export class QuickDateRangeComponent implements OnInit, ControlValueAccessor { |
|||
constructor() {} |
|||
|
|||
dateFilterOptions = [ |
|||
{ |
|||
label: "今日", |
|||
value: 0, |
|||
}, |
|||
{ |
|||
label: "近7日", |
|||
value: 7, |
|||
}, |
|||
{ |
|||
label: "近30日", |
|||
value: 30, |
|||
}, |
|||
{ |
|||
label: "近90日", |
|||
value: 90, |
|||
}, |
|||
]; |
|||
|
|||
quick: number | null = null; |
|||
|
|||
range: Date[] = []; |
|||
|
|||
onQuickChange(v: number) { |
|||
this.quick = v; |
|||
this.range = this.getDateRange(v).map((d) => new Date(d)); |
|||
const val = this.range.map((date) => format(date, "yyyy-MM-dd")); |
|||
// console.log("val", val);
|
|||
this.onChange(val); |
|||
} |
|||
|
|||
getDateRange(n: number): [string, string] { |
|||
const today = new Date(); |
|||
const previousDay = subDays(today, n); |
|||
const formattedToday = format(today, "yyyy-MM-dd"); |
|||
const formattedPreviousDay = format(previousDay, "yyyy-MM-dd"); |
|||
return [formattedPreviousDay, formattedToday]; |
|||
} |
|||
|
|||
ngOnInit(): void {} |
|||
|
|||
onRangeChange(v: Date[]) { |
|||
this.quick = null; |
|||
this.range = v; |
|||
const val = v.map((date) => format(date, "yyyy-MM-dd")); |
|||
this.onChange(val); |
|||
} |
|||
|
|||
onChange(v: any) {} |
|||
|
|||
ontouch(v: any) {} |
|||
|
|||
writeValue(v: string[]): void { |
|||
// console.log("v", v);
|
|||
if (v && Array.isArray(v)) { |
|||
this.range = v?.map((d) => new Date(d)); |
|||
} else { |
|||
this.onQuickChange(90); |
|||
} |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.onChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
this.ontouch = fn; |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
import { Injectable } from "@angular/core"; |
|||
import { NavigationEnd, Router } from "@angular/router"; |
|||
import { filter } from "rxjs/operators"; |
|||
import { DecSafeAny } from "../types"; |
|||
import { StorageService } from "./storage.service"; |
|||
|
|||
type IcacheUrlMap = Record<string, Array<string>>; |
|||
|
|||
export class CacheItem<T = any> { |
|||
constructor(private storage: StorageService, private cacheKey: string) {} |
|||
|
|||
getItem(): T { |
|||
return this.storage.get(this.cacheKey, { stroage: "session" }); |
|||
} |
|||
|
|||
setItem(query: T) { |
|||
this.storage.set(this.cacheKey, query, { stroage: "session" }); |
|||
} |
|||
|
|||
remove() { |
|||
this.storage.set(this.cacheKey, null, { stroage: "session" }); |
|||
} |
|||
} |
|||
|
|||
@Injectable() |
|||
export class CacheService { |
|||
constructor(private router: Router, private storage: StorageService) {} |
|||
|
|||
/** |
|||
* cacheKey 与 url 之间的映射 |
|||
*/ |
|||
private get cacheUrlMap(): IcacheUrlMap { |
|||
return this.storage.get("cacheUrlMap") ?? {}; |
|||
} |
|||
|
|||
private set cacheUrlMap(o: IcacheUrlMap) { |
|||
this.storage.set("cacheUrlMap", o); |
|||
} |
|||
|
|||
listen() { |
|||
this.router.events.pipe(filter((f): f is NavigationEnd => f instanceof NavigationEnd)).subscribe((r) => { |
|||
const currentUrl = r.url; |
|||
for (const [api, urls] of Object.entries(this.cacheUrlMap)) { |
|||
if (!urls.some((s) => currentUrl.startsWith(s))) { |
|||
this.storage.remove(api, { stroage: "session" }); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param cacheKey 缓存的key |
|||
* @param cacheInUrl 当前 cacheKey 该url中缓存,若页面跳转到非url中时删除 缓存的数据 |
|||
* @return 返回一个可以操作该缓存的对象 getItem & setItem |
|||
*/ |
|||
initCache(cacheKey: string, cacheInUrl?: string): CacheItem; |
|||
initCache(cacheKey: string, cacheInUrl?: Array<string>): CacheItem; |
|||
initCache(cacheKey: string, cacheInUrl?: string | Array<string>): CacheItem { |
|||
if (!cacheInUrl) { |
|||
cacheInUrl = [this.router.url]; |
|||
} |
|||
if (!this.cacheUrlMap[cacheKey]) { |
|||
cacheInUrl = (Array.isArray(cacheInUrl) ? cacheInUrl : [cacheInUrl]) as Array<string>; |
|||
const storageData = this.cacheUrlMap; |
|||
storageData[cacheKey] = cacheInUrl; |
|||
this.cacheUrlMap = storageData; |
|||
} |
|||
return new CacheItem(this.storage, cacheKey); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
export * from "./storage.module"; |
|||
export * from "./storage.service"; |
|||
export * from "./cache.service"; |
|||
@ -0,0 +1,15 @@ |
|||
import { NgModule } from "@angular/core"; |
|||
import { CacheService } from "./cache.service"; |
|||
import { StorageService } from "./storage.service"; |
|||
|
|||
@NgModule({ |
|||
declarations: [], |
|||
imports: [], |
|||
providers: [StorageService, CacheService], |
|||
exports: [], |
|||
}) |
|||
export class StorageModule { |
|||
constructor(private cache: CacheService) { |
|||
this.cache.listen(); |
|||
} |
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
import { Injectable } from "@angular/core"; |
|||
|
|||
export interface IFunc<T> { |
|||
(prev?: T): T; |
|||
} |
|||
|
|||
interface Option<T = any> { |
|||
stroage?: "local" | "session"; |
|||
defaultValue?: T | IFunc<T>; |
|||
serializer?: (v: T) => string; |
|||
deserializer?: (v: string) => T; |
|||
} |
|||
|
|||
export const isFunction = (value: unknown): value is Function => typeof value === "function"; |
|||
|
|||
@Injectable() |
|||
export class StorageService { |
|||
constructor() {} |
|||
|
|||
private parseOption(option?: Option) { |
|||
const storage = option?.stroage === "session" ? sessionStorage : localStorage; |
|||
const serializer = option?.serializer ? option?.serializer : JSON.stringify; |
|||
const deserializer = option?.deserializer ? option?.deserializer : JSON.parse; |
|||
let defaultValue = option?.defaultValue; |
|||
|
|||
if (isFunction(option?.defaultValue)) { |
|||
defaultValue = option?.defaultValue(); |
|||
} |
|||
|
|||
return { |
|||
storage, |
|||
serializer, |
|||
deserializer, |
|||
defaultValue, |
|||
}; |
|||
} |
|||
|
|||
get<T>(key: string, option?: Option<T>) { |
|||
const { storage, deserializer, defaultValue } = this.parseOption(option); |
|||
try { |
|||
const val = storage.getItem(key); |
|||
if (val) { |
|||
return deserializer(val); |
|||
} |
|||
} catch (error) { |
|||
console.error(error); |
|||
} |
|||
return defaultValue; |
|||
} |
|||
|
|||
set<T>(key: string, value: T | IFunc<T>, option?: Option<T>) { |
|||
const { storage, serializer, defaultValue } = this.parseOption(option); |
|||
const val = (isFunction(value) ? value() : value) ?? defaultValue; |
|||
if (typeof val === "undefined") { |
|||
storage.removeItem(key); |
|||
} else { |
|||
try { |
|||
storage.setItem(key, serializer(val)); |
|||
} catch (error) { |
|||
console.error(error); |
|||
} |
|||
} |
|||
} |
|||
|
|||
remove(key: string, option?: Pick<Option, "stroage">) { |
|||
const { storage } = this.parseOption(option); |
|||
storage.removeItem(key); |
|||
} |
|||
|
|||
clear(option?: Pick<Option, "stroage">) { |
|||
const { storage } = this.parseOption(option); |
|||
storage.clear(); |
|||
} |
|||
|
|||
keys(option?: Pick<Option, "stroage">) { |
|||
const { storage } = this.parseOption(option); |
|||
return Object.keys(storage); |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
export * from "./table-list.module"; |
|||
|
|||
export * from "./table-list-options"; |
|||
export * from "./table-list/table-list.component"; |
|||
export * from "./table-operation/table-operation.component"; |
|||
@ -0,0 +1,143 @@ |
|||
import { Observable, Subject } from "rxjs"; |
|||
import { produce, immerable, setAutoFreeze } from "immer"; |
|||
import { DecSafeAny, TableListColumns, TableOperation } from "../types"; |
|||
import { EventEmitter } from "@angular/core"; |
|||
|
|||
type IfetchData = (...args: DecSafeAny[]) => Observable<DecSafeAny>; |
|||
|
|||
type ITableListConfig = { |
|||
manual?: boolean; |
|||
cacheKey?: string; |
|||
cacheTo?: string[]; |
|||
rowKey?: string; |
|||
columnKey?: string; |
|||
selectable?: boolean; |
|||
withOutDefaultColumns?: boolean; |
|||
theadSettable?: boolean; |
|||
pageFromZero?: boolean; |
|||
frontPagination?: boolean; |
|||
}; |
|||
|
|||
type TableListState = { |
|||
selectedKeys: number[]; |
|||
}; |
|||
|
|||
type TableListPager = { |
|||
page: number; |
|||
size: number; |
|||
total: number; |
|||
loading: boolean; |
|||
sort: { [K: string]: "ascend" | "descend" }; |
|||
}; |
|||
|
|||
setAutoFreeze(false); |
|||
|
|||
export class TableListOption { |
|||
[immerable] = true; |
|||
|
|||
trigger$ = new Subject<DecSafeAny>(); |
|||
|
|||
getState$ = new EventEmitter<TableListState>(); |
|||
|
|||
columns: TableListColumns[] = []; |
|||
|
|||
operations!: TableOperation[]; |
|||
|
|||
fetchData: IfetchData; |
|||
|
|||
manual?: boolean = false; |
|||
|
|||
cacheKey?: string; |
|||
|
|||
cacheTo?: string[]; |
|||
|
|||
rowKey: string; |
|||
|
|||
columnKey?: string; |
|||
|
|||
selectable: boolean = false; |
|||
|
|||
withOutDefaultColumns?: boolean; |
|||
|
|||
pageFromZero?: boolean; |
|||
|
|||
theadSettable: boolean = false; |
|||
|
|||
frontPagination: boolean = true; |
|||
|
|||
scroll: { x?: string | null; y?: string | null } = { x: "1000px", y: null }; |
|||
|
|||
pager: TableListPager = { |
|||
page: 1, |
|||
size: 5, |
|||
loading: false, |
|||
total: 0, |
|||
sort: {}, |
|||
}; |
|||
|
|||
constructor(fetchData: IfetchData, config?: ITableListConfig) { |
|||
this.fetchData = fetchData; |
|||
this.manual = config?.manual; |
|||
this.cacheKey = config?.cacheKey; |
|||
this.cacheTo = config?.cacheTo; |
|||
this.rowKey = config?.rowKey ?? "id"; |
|||
this.selectable = config?.selectable ?? false; |
|||
this.withOutDefaultColumns = config?.withOutDefaultColumns; |
|||
this.theadSettable = config?.theadSettable ?? false; |
|||
this.columnKey = config?.columnKey; |
|||
this.pageFromZero = config?.pageFromZero; |
|||
this.frontPagination = config?.frontPagination ?? true; |
|||
} |
|||
|
|||
/** |
|||
* 设置表格的列,❗❗不可变数据❗❗ |
|||
* @param columns TableListColumns |
|||
* @returns |
|||
*/ |
|||
setColumns(columns: TableListColumns[]) { |
|||
return produce(this, (draft) => { |
|||
// if (!this.withOutDefaultColumns) {
|
|||
// columns = columns.concat(
|
|||
// { key: "createTime", title: "创建时间", sort: true },
|
|||
// { key: "updateTime", title: "更新时间", sort: true, visible: false }
|
|||
// );
|
|||
// draft.pager.sort = { createTime: "descend" };
|
|||
// }
|
|||
draft.columns = columns; |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 设置表格的操作项,❗❗不可变数据❗❗ |
|||
* @param columns TableListColumns |
|||
* @returns |
|||
*/ |
|||
setOptions(operations: TableOperation[]) { |
|||
return produce(this, (draft) => { |
|||
draft.operations = operations; |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 设置表格CacheKey,❗❗不可变数据❗❗ |
|||
* @param columns TableListColumns |
|||
* @returns |
|||
*/ |
|||
setCacheKey(cacheKey: string) { |
|||
return produce(this, (draft) => { |
|||
draft.cacheKey = cacheKey; |
|||
}); |
|||
} |
|||
|
|||
run(e?: DecSafeAny) { |
|||
setTimeout(() => { |
|||
// 防止 还没有 subscribe 就 next 了
|
|||
this.trigger$.next(e); |
|||
}, 10); |
|||
} |
|||
|
|||
reset() { |
|||
this.pager.page = 1; |
|||
this.run(); |
|||
} |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
import { NgxPermissionsModule } from "ngx-permissions"; |
|||
import { NgModule } from "@angular/core"; |
|||
import { CommonModule } from "@angular/common"; |
|||
import { RouterModule } from "@angular/router"; |
|||
import { DragDropModule } from "@angular/cdk/drag-drop"; |
|||
import { FormsModule, ReactiveFormsModule } from "@angular/forms"; |
|||
import { NzDrawerModule } from "ng-zorro-antd/drawer"; |
|||
import { NzDropDownModule } from "ng-zorro-antd/dropdown"; |
|||
import { NzCheckboxModule } from "ng-zorro-antd/checkbox"; |
|||
import { NzEmptyModule } from "ng-zorro-antd/empty"; |
|||
import { NzTableModule } from "ng-zorro-antd/table"; |
|||
import { NzPaginationModule } from "ng-zorro-antd/pagination"; |
|||
import { NzCardModule } from "ng-zorro-antd/card"; |
|||
import { NzFormModule } from "ng-zorro-antd/form"; |
|||
import { NzSpaceModule } from "ng-zorro-antd/space"; |
|||
import { NzIconModule } from "ng-zorro-antd/icon"; |
|||
import { NzSwitchModule } from "ng-zorro-antd/switch"; |
|||
import { NzSkeletonModule } from "ng-zorro-antd/skeleton"; |
|||
import { NzDividerModule } from "ng-zorro-antd/divider"; |
|||
import { NzPopoverModule } from "ng-zorro-antd/popover"; |
|||
import { NzButtonModule } from "ng-zorro-antd/button"; |
|||
import { TableListComponent } from "./table-list/table-list.component"; |
|||
import { TableOperationComponent } from "./table-operation/table-operation.component"; |
|||
import { TdOverflowDirective } from "./td-overflow.directive"; |
|||
|
|||
@NgModule({ |
|||
declarations: [TableListComponent, TableOperationComponent, TdOverflowDirective], |
|||
imports: [ |
|||
CommonModule, |
|||
RouterModule, |
|||
FormsModule, |
|||
ReactiveFormsModule, |
|||
DragDropModule, |
|||
|
|||
NzSwitchModule, |
|||
NzDividerModule, |
|||
NzCheckboxModule, |
|||
NzDrawerModule, |
|||
NzDropDownModule, |
|||
NzEmptyModule, |
|||
NzTableModule, |
|||
NzPaginationModule, |
|||
NzCardModule, |
|||
NzFormModule, |
|||
NzSpaceModule, |
|||
NzButtonModule, |
|||
NzPopoverModule, |
|||
NzIconModule, |
|||
NzSkeletonModule, |
|||
NgxPermissionsModule.forRoot(), |
|||
], |
|||
exports: [TableListComponent, TableOperationComponent], |
|||
}) |
|||
export class TableListModule { |
|||
constructor() {} |
|||
} |
|||
@ -0,0 +1,225 @@ |
|||
<form nz-form class="query-form mb-2" [formGroup]="formGroup" [nzLayout]="searchLayout"> |
|||
<div |
|||
*ngIf="search || action"> |
|||
<div nz-row> |
|||
<div nz-col nzFlex="auto" nz-row [nzGutter]="[12,12]" class="search-row"> |
|||
<ng-container *ngIf="search"> |
|||
<ng-container [ngTemplateOutlet]="search"></ng-container> |
|||
<div nz-col nzFlex="120px"> |
|||
<nz-form-item> |
|||
<nz-form-label nzNoColon></nz-form-label> |
|||
<nz-form-control> |
|||
<nz-space *ngIf="search"> |
|||
<button nz-button nzGhost nzType="primary" *nzSpaceItem (click)="doQuery()"> |
|||
查询 |
|||
</button> |
|||
<button nz-button *nzSpaceItem (click)="reset()"> |
|||
重置 |
|||
</button> |
|||
</nz-space> |
|||
</nz-form-control> |
|||
</nz-form-item> |
|||
</div> |
|||
</ng-container> |
|||
</div> |
|||
<div nz-col class="flex items-center justify-end"> |
|||
<ng-container *ngIf="action"> |
|||
<ng-container [ngTemplateOutlet]="action"></ng-container> |
|||
</ng-container> |
|||
</div> |
|||
</div> |
|||
|
|||
</div> |
|||
</form> |
|||
|
|||
|
|||
<ng-template #renderTableTpl> |
|||
<div class="table-card table-list shadow-sm "> |
|||
<div #tableEl class="overflow-auto"> |
|||
<nz-table |
|||
#basicTable |
|||
|
|||
[nzData]="dataSource" |
|||
[nzPageSizeOptions]="[5,10,20,]" |
|||
[nzLoading]="props.pager.loading" |
|||
[(nzPageSize)]="props.pager.size" |
|||
[(nzPageIndex)]="props.pager.page" |
|||
[nzTotal]="props.pager.total" |
|||
|
|||
[nzFrontPagination]="props.frontPagination" |
|||
[nzShowPagination]="true" |
|||
[nzShowSizeChanger]="true" |
|||
[nzShowQuickJumper]="true" |
|||
(nzPageIndexChange)="onPageChange()" |
|||
(nzPageSizeChange)="onPageChange()" |
|||
[nzTableLayout]="props.scroll.x ? 'fixed' : 'auto' " |
|||
[nzScroll]="props.scroll"> |
|||
<thead> |
|||
<tr> |
|||
<th |
|||
*ngIf="props.selectable" |
|||
[nzChecked]="dataSource && selection.selected.length === dataSource.length && dataSource.length !== 0" |
|||
(nzCheckedChange)="onChecked($event)" |
|||
nzWidth="40px"> |
|||
</th> |
|||
<ng-container *ngFor="let col of columns"> |
|||
<th *ngIf="col.visible" |
|||
[nzWidth]="col.width || null " |
|||
[nzShowSort]="col.sort" |
|||
[nzSortDirections]="['ascend', 'descend']" |
|||
[nzSortOrder]="props.pager.sort[col.key] || null" |
|||
(nzSortOrderChange)="onSort($event,col.key)"> |
|||
{{col.title}} |
|||
</th> |
|||
</ng-container> |
|||
<th *ngIf="operation.length" [nzWidth]="optionWidth" [nzRight]="!!props.scroll.x"> |
|||
操作 |
|||
</th> |
|||
<th nzRight nzWidth="40px" *ngIf="props.theadSettable"> |
|||
<span (click)="toggleColumnVisible(columnVisibleSettingTpl)" |
|||
class="cursor-pointer" |
|||
nz-icon |
|||
nzType="setting" |
|||
nzTheme="outline"> |
|||
</span> |
|||
</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr *ngFor="let dataItem of basicTable.data" (click)="onTrClick(dataItem)" |
|||
[ngClass]="rowClass"> |
|||
<td *ngIf="props.selectable" [nzChecked]="selection.isSelected(dataItem[props.rowKey])" |
|||
(nzCheckedChange)="onChecked($event,dataItem[props.rowKey])"> |
|||
</td> |
|||
<ng-container *ngFor="let col of columns"> |
|||
<td *ngIf="col.visible" nzEllipsis> |
|||
|
|||
<ng-container [ngSwitch]="col.key"> |
|||
<ng-container *ngSwitchCase="'createTime'"> |
|||
<ng-container |
|||
[ngTemplateOutlet]="dateTimeTpl" |
|||
[ngTemplateOutletContext]="{$implicit:dataItem[col.key]}"> |
|||
</ng-container> |
|||
</ng-container> |
|||
<ng-container *ngSwitchCase="'updateTime'"> |
|||
<ng-container |
|||
[ngTemplateOutlet]="dateTimeTpl" |
|||
[ngTemplateOutletContext]="{$implicit:dataItem[col.key]}"> |
|||
</ng-container> |
|||
</ng-container> |
|||
|
|||
<ng-container *ngSwitchDefault> |
|||
<ng-container *ngIf="renderColumns else defaultTdTpl" |
|||
[ngTemplateOutlet]="renderColumns" |
|||
[ngTemplateOutletContext]="{ |
|||
$implicit:dataItem[col.key], |
|||
key:col.key, |
|||
row:dataItem, |
|||
column:col |
|||
}"> |
|||
</ng-container> |
|||
<ng-template #defaultTdTpl> |
|||
{{dataItem[col.key]}} |
|||
</ng-template> |
|||
</ng-container> |
|||
</ng-container> |
|||
</td> |
|||
</ng-container> |
|||
|
|||
<td *ngIf="operation.length" [nzRight]="!!props.scroll.x" class="operation"> |
|||
<dec-table-operation [rowData]="dataItem" [options]="operation"> |
|||
</dec-table-operation> |
|||
</td> |
|||
<td nzRight *ngIf="props.theadSettable"></td> |
|||
</tr> |
|||
</tbody> |
|||
</nz-table> |
|||
</div> |
|||
|
|||
</div> |
|||
</ng-template> |
|||
|
|||
|
|||
|
|||
<ng-container *ngIf="renderItem else renderTableTpl"> |
|||
<ng-container *ngIf="props.pager.loading"> |
|||
<nz-card [nzBordered]="false"> |
|||
<nz-skeleton [nzActive]="true"></nz-skeleton> |
|||
</nz-card> |
|||
</ng-container> |
|||
<div class="custom-render" *ngIf="!props.pager.loading"> |
|||
<ng-container *ngIf="dataSource.length > 0 else emptyTpl"> |
|||
<div> |
|||
<ng-container |
|||
[ngTemplateOutlet]="renderItem" |
|||
[ngTemplateOutletContext]="{$implicit:dataSource}"> |
|||
</ng-container> |
|||
</div> |
|||
<div class="mt-4 flex justify-end"> |
|||
<nz-pagination [nzPageSizeOptions]="[10,20,50,100]" |
|||
[(nzPageSize)]="props.pager.size" |
|||
[(nzPageIndex)]="props.pager.page" |
|||
[nzTotal]="props.pager.total" |
|||
[nzShowTotal]="totalTpl" |
|||
[nzShowSizeChanger]="true" |
|||
[nzShowQuickJumper]="true" |
|||
(nzPageIndexChange)="onPageChange()" |
|||
(nzPageSizeChange)="onPageChange()"> |
|||
</nz-pagination> |
|||
</div> |
|||
</ng-container> |
|||
<ng-template #emptyTpl> |
|||
<nz-card [nzBordered]="false"> |
|||
<nz-empty></nz-empty> |
|||
</nz-card> |
|||
</ng-template> |
|||
</div> |
|||
</ng-container> |
|||
|
|||
|
|||
|
|||
<ng-template #columnVisibleSettingTpl> |
|||
<nz-checkbox-wrapper class="w-full"> |
|||
<ul cdkDropList (cdkDropListDropped)="colunmsSort($event)" class="columns-list"> |
|||
<li *ngFor="let item of columns" |
|||
class="table-setting-item cursor-pointer" cdkDrag> |
|||
<div class="columns-item-placeholder" *cdkDragPlaceholder></div> |
|||
<div class="flex items-center justify-between p-2"> |
|||
<div class="flex-shrink-0"> |
|||
<span nz-icon nzType="holder" nzTheme="outline"></span> |
|||
</div> |
|||
<div class="flex-1 pl-1"> |
|||
{{item.title}} |
|||
</div> |
|||
<div class="flex-shrink-0"> |
|||
<nz-switch |
|||
nzSize="small" |
|||
[disabled]="!!item.disabled" |
|||
[(ngModel)]="item.visible" |
|||
(ngModelChange)="onColumnsChange()"> |
|||
</nz-switch> |
|||
</div> |
|||
</div> |
|||
</li> |
|||
</ul> |
|||
</nz-checkbox-wrapper> |
|||
</ng-template> |
|||
|
|||
<ng-template #totalTpl let-total> |
|||
<ng-container *ngIf="showTotal"> |
|||
<ng-template [ngTemplateOutlet]="showTotal" [ngTemplateOutletContext]="{$implicit:props.pager}"></ng-template> |
|||
</ng-container> |
|||
<ng-container *ngIf="!showTotal"> |
|||
共{{total}}条 |
|||
</ng-container> |
|||
</ng-template> |
|||
|
|||
<ng-template #dateTimeTpl let-date> |
|||
{{date| date:'yyyy-MM-dd HH:mm:ss'}} |
|||
</ng-template> |
|||
|
|||
|
|||
<!-- 为了计算操作栏的宽度 ---- start --> |
|||
<dec-table-operation #operationEl [rowData]="{}" [options]="operation" class="hidden-option"> |
|||
</dec-table-operation> |
|||
<!-- 为了计算操作栏的宽度 ---- end --> |
|||
@ -0,0 +1,119 @@ |
|||
.advance-search-btn { |
|||
position: relative; |
|||
.up-arrow { |
|||
position: absolute; |
|||
bottom: calc(-100% + 10px); |
|||
left: 50%; |
|||
transform: translateX(-50%); |
|||
color: #fff; |
|||
font-size: 28px; |
|||
filter: drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.1)); |
|||
} |
|||
.double-arrow { |
|||
transform: rotate(90deg); |
|||
transition: transform 0.3s; |
|||
&.up { |
|||
transform: rotate(-90deg); |
|||
} |
|||
} |
|||
} |
|||
|
|||
.hidden-option { |
|||
display: inline-block; |
|||
height: 0; |
|||
opacity: 0; |
|||
overflow: hidden; |
|||
pointer-events: none; |
|||
} |
|||
|
|||
.table-list { |
|||
td { |
|||
word-break: break-all; |
|||
} |
|||
th { |
|||
white-space: nowrap; |
|||
} |
|||
::ng-deep { |
|||
.ant-table-pagination.ant-pagination { |
|||
padding: 0 16px; |
|||
} |
|||
|
|||
// tbody { |
|||
// tr td:last-child { |
|||
// width: 0; |
|||
// } |
|||
// } |
|||
} |
|||
} |
|||
|
|||
.advance-search { |
|||
::ng-deep { |
|||
.ant-card-body { |
|||
padding: 16px 24px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.search-row { |
|||
::ng-deep { |
|||
> * { |
|||
padding: 6px; |
|||
} |
|||
} |
|||
} |
|||
.query-form { |
|||
::ng-deep { |
|||
.hor { |
|||
nz-form-item { |
|||
align-items: center; |
|||
} |
|||
} |
|||
nz-form-item { |
|||
margin-bottom: 0; |
|||
|
|||
// flex-direction: column; |
|||
// align-items: flex-start; |
|||
// justify-content: flex-start; |
|||
// margin-right: 12px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
::ng-deep { |
|||
.table-settings { |
|||
.ant-drawer-body { |
|||
padding: 12px 0; |
|||
} |
|||
} |
|||
.table-setting-item { |
|||
background-color: #fff; |
|||
&:hover { |
|||
background-color: var(--bg-light); |
|||
} |
|||
} |
|||
} |
|||
|
|||
.cdk-drag-preview { |
|||
box-sizing: border-box; |
|||
border-radius: 4px; |
|||
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12); |
|||
} |
|||
|
|||
.cdk-drag-animating { |
|||
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1); |
|||
} |
|||
|
|||
.table-setting-item:last-child { |
|||
border: none; |
|||
} |
|||
|
|||
.columns-list.cdk-drop-list-dragging .table-setting-item:not(.cdk-drag-placeholder) { |
|||
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1); |
|||
} |
|||
|
|||
.columns-item-placeholder { |
|||
background: #fff; |
|||
border: 3px dotted var(--p); |
|||
min-height: 38px; |
|||
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1); |
|||
} |
|||
@ -0,0 +1,386 @@ |
|||
import { |
|||
Component, |
|||
EventEmitter, |
|||
Input, |
|||
OnChanges, |
|||
OnInit, |
|||
Output, |
|||
SimpleChanges, |
|||
TemplateRef, |
|||
ViewChild, |
|||
Pipe, |
|||
PipeTransform, |
|||
AfterViewInit, |
|||
ElementRef, |
|||
ViewChildren, |
|||
QueryList, |
|||
Renderer2, |
|||
ChangeDetectorRef, |
|||
OnDestroy, |
|||
} from "@angular/core"; |
|||
import { CommonModule } from "@angular/common"; |
|||
import { SelectionModel } from "@angular/cdk/collections"; |
|||
import { CdkDragDrop, moveItemInArray } from "@angular/cdk/drag-drop"; |
|||
|
|||
import { Router } from "@angular/router"; |
|||
import { debounceTime, finalize } from "rxjs/operators"; |
|||
import { FormsModule, FormControl, FormGroup, AbstractControl } from "@angular/forms"; |
|||
import { NzDrawerModule, NzDrawerService } from "ng-zorro-antd/drawer"; |
|||
|
|||
import { DecSafeAny, PageResult, TableListColumns, TableOperation } from "../../types"; |
|||
import { TableListOption } from "../table-list-options"; |
|||
import { CacheItem, CacheService, StorageService } from "../../storage"; |
|||
import { TableOperationComponent } from "../table-operation/table-operation.component"; |
|||
|
|||
@Pipe({ |
|||
name: "operationFilter", |
|||
}) |
|||
export class OperationPipe implements PipeTransform { |
|||
transform(operations: TableOperation[], rowItem: any): TableOperation[] { |
|||
return operations?.filter((f) => (f.visible ? f.visible(rowItem) : true)) ?? []; |
|||
} |
|||
} |
|||
|
|||
const DATE_RANGE_FIELDS = ["createTime", "updateTime"]; |
|||
|
|||
@Component({ |
|||
selector: "table-list", |
|||
templateUrl: "./table-list.component.html", |
|||
styleUrls: ["./table-list.component.less"], |
|||
}) |
|||
export class TableListComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy { |
|||
constructor( |
|||
// private modal: NzModalService,
|
|||
private cdr: ChangeDetectorRef, |
|||
private el: ElementRef, |
|||
private router: Router, |
|||
private drawerService: NzDrawerService, |
|||
private storage: StorageService, |
|||
private cacheService: CacheService |
|||
) {} |
|||
|
|||
@Input() props!: TableListOption; |
|||
|
|||
// https://github.com/angular/angular/issues/13761
|
|||
@Input() formGroup = new FormGroup<any>({}); |
|||
|
|||
/** |
|||
* 在表格模式下渲染具体每一个列的模板。 |
|||
* $implicit:dataItem[col.key] `具体的值`、 |
|||
* key:col.key `字段名`、 |
|||
* row:dataItem `当前行数据`、 |
|||
* column:col `当前列`、 |
|||
*/ |
|||
@Input() renderColumns?: TemplateRef<{}>; |
|||
|
|||
/** |
|||
* 不使用表格模式,自定义渲染 |
|||
*/ |
|||
@Input() renderItem?: TemplateRef<{}>; |
|||
|
|||
@Input() action?: TemplateRef<{}>; |
|||
|
|||
@Input() search?: TemplateRef<{}>; |
|||
|
|||
@Input() searchLayout: "horizontal" | "vertical" = "horizontal"; |
|||
|
|||
/** |
|||
* @deprecated |
|||
*/ |
|||
@Input() advanceSearch?: TemplateRef<{}>; |
|||
|
|||
@Input() showTotal?: TemplateRef<{}>; |
|||
|
|||
@Input() beforeReset?: Function; |
|||
|
|||
@Input() rowClass?: string; |
|||
|
|||
// @Input() resizeable?: boolean;
|
|||
|
|||
@Output() onRowClick = new EventEmitter(); |
|||
|
|||
dataSource: DecSafeAny[] = []; |
|||
|
|||
totalPages = 0; |
|||
|
|||
public selection = new SelectionModel<number>(true); |
|||
|
|||
public advanceSearchVisible: boolean = false; |
|||
|
|||
public optionWidth: string = "200px"; |
|||
|
|||
private cache?: CacheItem; |
|||
|
|||
get columns(): TableListColumns[] { |
|||
return this.props.columns ?? []; |
|||
} |
|||
|
|||
get operation(): TableOperation[] { |
|||
return this.props.operations ?? []; |
|||
} |
|||
|
|||
get createTime() { |
|||
return this.formGroup.get("createTime"); |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
this.props.trigger$.pipe(debounceTime(100)).subscribe((e?: any) => { |
|||
if (this.props.fetchData) { |
|||
this.props.pager.loading = true; |
|||
this.selection.clear(); |
|||
this.emitState(); |
|||
const query = this.formGroup.getRawValue(); |
|||
// this.saveQueryDataToCache(query);
|
|||
const pager = this.parsePager(); |
|||
this.props |
|||
.fetchData(pager, this.parseQueryValue(), e) |
|||
.pipe( |
|||
finalize(() => { |
|||
this.props.pager.loading = false; |
|||
}) |
|||
) |
|||
.subscribe((f: PageResult) => { |
|||
this.dataSource = f.records ?? f.content; |
|||
console.log("this.dataSource", f); |
|||
this.props.pager.total = f.total; |
|||
this.totalPages = Math.ceil(f.total / this.props.pager.size); |
|||
this.checkPage(); |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
setTimeout(() => { |
|||
this.emitState(); |
|||
}, 10); |
|||
|
|||
// 初始化的时候?
|
|||
if (!this.props.manual) { |
|||
this.props.run(); |
|||
} |
|||
this.getCacheData(); |
|||
} |
|||
|
|||
ngOnDestroy(): void { |
|||
// console.log("ngOnDestroy");
|
|||
} |
|||
|
|||
ngAfterViewInit(): void { |
|||
const opEl: HTMLDivElement = this.el.nativeElement.querySelector(".hidden-option"); |
|||
|
|||
if (opEl) { |
|||
setTimeout(() => { |
|||
const paddingX = 2 * 24; |
|||
this.optionWidth = Math.ceil(opEl.offsetWidth + paddingX) + "px"; |
|||
this.cdr.detectChanges(); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
ngOnChanges(changes: SimpleChanges): void { |
|||
const props = changes["props"]; |
|||
const currentProps = props?.currentValue; |
|||
const previousProps = props?.previousValue; |
|||
if (currentProps) { |
|||
if (previousProps?.["cacheKey"] !== currentProps?.["cacheKey"]) { |
|||
this.removeOldCache(); |
|||
this.getCacheData(); |
|||
} |
|||
this.parseColumus(currentProps?.["columns"]); |
|||
this.parseFormControls(currentProps?.["queryForm"]); |
|||
} |
|||
} |
|||
|
|||
private saveQueryDataToCache(query: {}) { |
|||
if (this.cache) { |
|||
const { page, size, sort, total } = this.props.pager; |
|||
this.cache.setItem({ page, size, sort, total, ...query }); |
|||
} |
|||
} |
|||
|
|||
private removeOldCache() { |
|||
this.cache?.remove(); |
|||
} |
|||
|
|||
private getCacheData() { |
|||
let { cacheKey, cacheTo = [] } = this.props; |
|||
|
|||
this.formGroup.reset(); |
|||
if (!cacheKey) { |
|||
cacheKey = `DATA_CACHE_${this.formatePathname()}`; |
|||
} |
|||
if (!this.cache) { |
|||
this.cache = this.cacheService.initCache(cacheKey, [this.router.url, ...cacheTo]); |
|||
} |
|||
const cacheData = this.cache?.getItem(); |
|||
if (cacheData) { |
|||
const { page, size, sort, total, ...query } = cacheData; |
|||
this.props.pager = { |
|||
...this.props.pager, |
|||
total, |
|||
page, |
|||
size, |
|||
sort, |
|||
}; |
|||
|
|||
this.formGroup.patchValue(query); |
|||
} |
|||
} |
|||
|
|||
private parsePager() { |
|||
let { page, size } = this.props.pager; |
|||
if (this.props.pageFromZero) { |
|||
page = page - 1; |
|||
} |
|||
const pager = { page: page, current: page, size, sort: this.formatSort() }; |
|||
return pager; |
|||
} |
|||
|
|||
private parseQueryValue(): {} { |
|||
const o = Object.create(null); |
|||
Object.entries(this.formGroup.getRawValue()).forEach(([k, v]) => { |
|||
if (DATE_RANGE_FIELDS.includes(k) && Array.isArray(v)) { |
|||
const from = v?.[0] instanceof Date ? v?.[0]?.toISOString() : v[0]; |
|||
const to = v?.[1] instanceof Date ? v?.[1]?.toISOString() : v[1]; |
|||
o[k] = { from, to }; |
|||
} else { |
|||
o[k] = v; |
|||
} |
|||
}); |
|||
return o; |
|||
} |
|||
|
|||
private checkPage() { |
|||
const maxPages = this.totalPages === 0 ? 1 : this.totalPages; |
|||
if (maxPages < this.props.pager.page) { |
|||
this.props.pager.page = maxPages; |
|||
this.reload(); |
|||
} |
|||
} |
|||
|
|||
private emitState() { |
|||
this.props.getState$.emit({ selectedKeys: this.selection.selected }); |
|||
} |
|||
|
|||
private parseColumus(currentCols: TableListColumns[]) { |
|||
if (this.props.columns.some((s) => s.coverStorage)) { |
|||
this.onColumnsChange(); |
|||
} |
|||
const colsFormStorage: TableListColumns[] = this.storage.get(this.formatePathname()) ?? []; |
|||
this.props.columns = currentCols |
|||
.map((i: TableListColumns) => { |
|||
const storageCol = colsFormStorage.find((f) => f.key === i.key); |
|||
let visible = i.visible !== void 0 ? i.visible : true; |
|||
let width = i.width; |
|||
// if (["createTime", "updateTime"].includes(i.key)) {
|
|||
// width = "180px";
|
|||
// }
|
|||
return { |
|||
...i, |
|||
visible, |
|||
width, |
|||
...(storageCol ?? {}), |
|||
}; |
|||
}) |
|||
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)); |
|||
} |
|||
|
|||
private parseFormControls(formGroup?: FormGroup) { |
|||
// if (!this.props.withOutDefaultColumns) {
|
|||
// this.formGroup.addControl("updateTime", new FormControl(null));
|
|||
// this.formGroup.addControl("createTime", new FormControl(null));
|
|||
// }
|
|||
} |
|||
|
|||
formatSort() { |
|||
const { sort } = this.props.pager; |
|||
if (!sort) { |
|||
return null; |
|||
} |
|||
const sortArr = [...Object.entries(sort)]?.[0]; |
|||
if (!sortArr) { |
|||
return null; |
|||
} |
|||
return `${sortArr[0]},${sortArr[1] === "ascend" ? "asc" : "desc"}`; |
|||
} |
|||
|
|||
colunmsSort(event: unknown) { |
|||
const e = event as CdkDragDrop<TableListColumns[]>; |
|||
moveItemInArray(this.props.columns, e.previousIndex, e.currentIndex); |
|||
this.onColumnsChange(); |
|||
} |
|||
|
|||
reload() { |
|||
this.props.run(); |
|||
} |
|||
|
|||
onPageChange() { |
|||
if (!this.props.frontPagination) { |
|||
this.props.run(); |
|||
} |
|||
} |
|||
|
|||
doQuery() { |
|||
this.props.pager.page = 1; |
|||
this.reload(); |
|||
} |
|||
|
|||
reset() { |
|||
this.beforeReset?.(); |
|||
this.formGroup.reset(); |
|||
this.doQuery(); |
|||
} |
|||
|
|||
onSort(v: string | null, fieldName: string) { |
|||
this.props.pager.sort = { [fieldName]: v as "ascend" | "descend" }; |
|||
this.reload(); |
|||
} |
|||
|
|||
toggleAdvanceSearch(show?: boolean) { |
|||
if (typeof show === "boolean") { |
|||
this.advanceSearchVisible = show; |
|||
} else { |
|||
this.advanceSearchVisible = !this.advanceSearchVisible; |
|||
} |
|||
} |
|||
|
|||
filteroperation(dataItem: any): TableOperation[] { |
|||
return this.operation?.filter((f) => (f.visible ? f.visible(dataItem) : true)) ?? []; |
|||
} |
|||
|
|||
toggleColumnVisible(nzContent: TemplateRef<DecSafeAny>) { |
|||
this.drawerService.create({ |
|||
nzTitle: "设置展示项", |
|||
nzWidth: 280, |
|||
nzContent, |
|||
nzWrapClassName: "table-settings", |
|||
}); |
|||
} |
|||
|
|||
private formatePathname() { |
|||
return this.props.columnKey ?? this.router.url.replace(/\//g, "_"); |
|||
} |
|||
|
|||
onColumnsChange() { |
|||
const columnStorageKey = `COLUMN_${this.formatePathname()}`; |
|||
this.storage.set( |
|||
columnStorageKey, |
|||
this.columns.map((i, idx) => { |
|||
const { key, width, visible } = i; |
|||
return { key, width, visible, order: idx }; |
|||
}) |
|||
); |
|||
} |
|||
|
|||
onChecked(checked: boolean, rowKey?: DecSafeAny) { |
|||
const fn = checked ? "select" : "deselect"; |
|||
const rowKeys = rowKey ? [rowKey] : this.dataSource.map((i) => i[this.props.rowKey]); |
|||
this.selection[fn](...rowKeys); |
|||
this.emitState(); |
|||
} |
|||
|
|||
onTrClick(dataItem: DecSafeAny) { |
|||
if (this.onRowClick) { |
|||
this.onRowClick.emit(dataItem); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
<div class="operation"> |
|||
<ng-container *ngFor="let item of options.slice(0,maximum); let i = index; let last = last"> |
|||
<span [ngClass]="{disabled:item.disabled}"> |
|||
<a [ngClass]="{danger:item.danger}" [href]="item.href" [routerLink]="item.link" |
|||
(click)="onClick(item)"> |
|||
{{item.title}} |
|||
</a> |
|||
</span> |
|||
<nz-divider nzType="vertical" *ngIf="!last"></nz-divider> |
|||
</ng-container> |
|||
<ng-container *ngIf="options.length > maximum"> |
|||
<nz-divider nzType="vertical"></nz-divider> |
|||
<a nz-dropdown [nzDropdownMenu]="menu"> |
|||
更多操作 |
|||
<i nz-icon nzType="down" nzTheme="outline"></i> |
|||
</a> |
|||
<nz-dropdown-menu #menu="nzDropdownMenu"> |
|||
<ul nz-menu class="operation"> |
|||
<li |
|||
*ngFor="let item of options.slice(maximum);" |
|||
nz-menu-item |
|||
(click)="onClick(item)" |
|||
[nzDanger]="item.danger" |
|||
[nzDisabled]="item.disabled"> |
|||
{{item.title}} |
|||
</li> |
|||
</ul> |
|||
</nz-dropdown-menu> |
|||
</ng-container> |
|||
</div> |
|||
@ -0,0 +1,25 @@ |
|||
.operation { |
|||
display: inline-block; |
|||
min-width: 120px; |
|||
} |
|||
|
|||
.danger { |
|||
color: var(--red); |
|||
} |
|||
.disabled { |
|||
cursor: not-allowed; |
|||
a { |
|||
pointer-events: none; |
|||
color: rgba(0, 0, 0, 0.65); |
|||
} |
|||
} |
|||
|
|||
.operation { |
|||
a { |
|||
// color: rgba(0, 0, 0, 0.65); |
|||
&:hover { |
|||
color: var(--p); |
|||
text-decoration: underline; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
import { Component, Input, OnInit } from "@angular/core"; |
|||
import { NgxPermissionsService } from "ngx-permissions"; |
|||
|
|||
import { DecSafeAny, TableOperation } from "../../types"; |
|||
|
|||
@Component({ |
|||
selector: "dec-table-operation", |
|||
templateUrl: "./table-operation.component.html", |
|||
styleUrls: ["./table-operation.component.less"], |
|||
}) |
|||
export class TableOperationComponent implements OnInit { |
|||
constructor(private premission: NgxPermissionsService) {} |
|||
|
|||
@Input() options: TableOperation[] = []; |
|||
|
|||
@Input() maximum: number = 3; |
|||
|
|||
@Input() rowData: DecSafeAny; |
|||
|
|||
ngOnInit(): void { |
|||
// this.options = this.options.filter(async (f) => {
|
|||
// if (f.visible) {
|
|||
// return f.visible(this.rowData);
|
|||
// }
|
|||
// if (f.premissions.length) {
|
|||
// console.log("f.premissions", f.premissions, this.premission.hasPermission(f.premissions));
|
|||
// return await this.premission.hasPermission(f.premissions);
|
|||
// }
|
|||
// return true;
|
|||
// });
|
|||
this.filterOption(); |
|||
} |
|||
|
|||
async filterOption() { |
|||
const o = []; |
|||
for (const f of this.options) { |
|||
let visible = true; |
|||
if (typeof f.visible === "function") { |
|||
visible = f.visible(this.rowData); |
|||
} |
|||
if (!visible) { |
|||
continue; |
|||
} |
|||
if (f.premissions.length) { |
|||
const r = await this.premission.hasPermission(f.premissions); |
|||
if (r) { |
|||
o.push(f); |
|||
} |
|||
continue; |
|||
} |
|||
o.push(f); |
|||
} |
|||
this.options = o; |
|||
} |
|||
|
|||
onClick(item: TableOperation) { |
|||
if (item.onClick) { |
|||
item?.onClick(this.rowData); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
import { AfterContentInit, AfterViewInit, ChangeDetectorRef, Directive, ElementRef, Input } from "@angular/core"; |
|||
import { NzPopoverDirective } from "ng-zorro-antd/popover"; |
|||
|
|||
@Directive({ |
|||
selector: "[jwTdOverflow]", |
|||
}) |
|||
export class TdOverflowDirective implements AfterViewInit { |
|||
@Input("jwTdOverflow") content!: string; |
|||
|
|||
constructor( |
|||
private elementRef: ElementRef, |
|||
private popoverDirective: NzPopoverDirective, |
|||
private cdr: ChangeDetectorRef |
|||
) {} |
|||
|
|||
ngOnInit() {} |
|||
|
|||
ngAfterViewInit(): void { |
|||
const element = this.elementRef.nativeElement as HTMLElement; |
|||
console.log("element", element.offsetWidth, element.scrollWidth); |
|||
// 如果元素的实际宽度大于可见宽度,就使用 nz-popover 指令来显示完整的内容
|
|||
if (element.offsetWidth < element.scrollWidth) { |
|||
this.popoverDirective.content = this.content; |
|||
} else { |
|||
this.popoverDirective.trigger = null; |
|||
this.popoverDirective.content = "da"; |
|||
element.textContent = this.content; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
export type AnyObject = { [k: string]: any }; |
|||
|
|||
export type DecSafeAny = any; |
|||
|
|||
export type DecText = number | string; |
|||
|
|||
export type Augmented<O extends object> = O & AnyObject; |
|||
|
|||
export interface ResponseType<T = any> { |
|||
body: T; |
|||
code: number; |
|||
desc: string; |
|||
success: boolean; |
|||
} |
|||
|
|||
export interface TableListColumns { |
|||
key: string; |
|||
title: string; |
|||
visible?: boolean; |
|||
width?: string; |
|||
sort?: boolean; |
|||
order?: number; |
|||
disabled?: boolean; |
|||
coverStorage?: boolean; |
|||
} |
|||
|
|||
export interface TableOperation { |
|||
title: string; |
|||
href?: string; |
|||
link?: string[]; |
|||
target?: string; |
|||
premissions: string[]; |
|||
danger?: boolean; |
|||
disabled?: boolean; |
|||
onClick?: (v: DecSafeAny) => void; |
|||
visible?: (v: DecSafeAny) => boolean; |
|||
} |
|||
|
|||
export interface PageResult<T = DecSafeAny> { |
|||
total: number; |
|||
content: T[]; |
|||
records: T[]; |
|||
} |
|||
|
|||
export interface AuthInterface { |
|||
role: string; |
|||
userId: string; |
|||
userName: string; |
|||
permissionList: AuthPermissionInterface[]; |
|||
} |
|||
|
|||
export interface AuthPermissionInterface { |
|||
name: string; |
|||
roleId: string; |
|||
scope: 1 | 0; |
|||
type: number; |
|||
value: "true" | "false"; |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
import { AbstractControl, FormControl, FormGroup } from "@angular/forms"; |
|||
|
|||
export class Utils { |
|||
static validateFormGroup(formGroup: FormGroup) { |
|||
if (!formGroup.valid) { |
|||
Object.keys(formGroup.controls).forEach((field) => { |
|||
const control = formGroup.get(field); |
|||
if (control instanceof FormControl) { |
|||
control.markAsDirty(); |
|||
control.markAsTouched({ onlySelf: true }); |
|||
control.updateValueAndValidity({ onlySelf: true }); |
|||
} else if (control instanceof FormGroup) { |
|||
Utils.validateFormGroup(control); |
|||
} |
|||
}); |
|||
} |
|||
return formGroup.valid; |
|||
} |
|||
|
|||
static validateFormControl(control: AbstractControl) { |
|||
control.markAsDirty(); |
|||
control.markAsTouched({ onlySelf: true }); |
|||
control.updateValueAndValidity({ onlySelf: true }); |
|||
return control.valid; |
|||
} |
|||
|
|||
static queryify(query: {}): string { |
|||
const o = Object.create(null); |
|||
Object.entries(query).forEach(([k, v]) => { |
|||
if (v !== void 0 && v !== null) { |
|||
o[k] = v; |
|||
} |
|||
}); |
|||
return new URLSearchParams(o).toString(); |
|||
} |
|||
|
|||
static getHostByEnvironment(prod?: boolean) { |
|||
const protocol = window.location.protocol; |
|||
const host = prod ? window.location.host : `localhost:${window.location.port}`; |
|||
return `${protocol}//${host}`; |
|||
} |
|||
|
|||
// static detectionImagePath(jobId: string, imgName: string) {
|
|||
// return `/record/${jobId}/detect/${imgName}`;
|
|||
// }
|
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
import { ValidatorFn, Validators } from "@angular/forms"; |
|||
|
|||
export class FormValidators { |
|||
static required(message?: string): ValidatorFn { |
|||
return (control) => { |
|||
const error = Validators.required(control); |
|||
return error ? { ...error, required: { message } } : null; |
|||
}; |
|||
} |
|||
static maxLength(maxLength: number, message?: string): ValidatorFn { |
|||
return (control) => { |
|||
const error = Validators.maxLength(maxLength)(control); |
|||
if (error) { |
|||
error["maxlength"]["message"] = message; |
|||
return error; |
|||
} |
|||
return null; |
|||
}; |
|||
} |
|||
static minLength(maxLength: number, message?: string): ValidatorFn { |
|||
return (control) => { |
|||
const error = Validators.minLength(maxLength)(control); |
|||
if (error) { |
|||
error["minlength"]["message"] = message; |
|||
return error; |
|||
} |
|||
return null; |
|||
}; |
|||
} |
|||
static pattern(pattern: RegExp | string, message?: string): ValidatorFn { |
|||
return (control) => { |
|||
const error = Validators.pattern(pattern)(control); |
|||
if (error) { |
|||
error["pattern"]["message"] = message; |
|||
return error; |
|||
} |
|||
return null; |
|||
}; |
|||
} |
|||
static min(maxLength: number, message?: string): ValidatorFn { |
|||
return (control) => { |
|||
const error = Validators.min(maxLength)(control); |
|||
if (error) { |
|||
error["min"]["message"] = message; |
|||
return error; |
|||
} |
|||
return null; |
|||
}; |
|||
} |
|||
static max(maxLength: number, message?: string): ValidatorFn { |
|||
return (control) => { |
|||
const error = Validators.max(maxLength)(control); |
|||
if (error) { |
|||
error["max"]["message"] = message; |
|||
return error; |
|||
} |
|||
return null; |
|||
}; |
|||
} |
|||
static email(message?: string): ValidatorFn { |
|||
return (control) => { |
|||
const error = Validators.email(control); |
|||
return error ? { ...error, email: { message } } : null; |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
/* To learn more about this file see: https://angular.io/config/tsconfig. */ |
|||
{ |
|||
"extends": "../../tsconfig.json", |
|||
"compilerOptions": { |
|||
"outDir": "../../out-tsc/lib", |
|||
"declaration": true, |
|||
"declarationMap": true, |
|||
"inlineSources": true, |
|||
"types": [] |
|||
}, |
|||
"exclude": [ |
|||
"**/*.spec.ts" |
|||
] |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
/* To learn more about this file see: https://angular.io/config/tsconfig. */ |
|||
{ |
|||
"extends": "./tsconfig.lib.json", |
|||
"compilerOptions": { |
|||
"declarationMap": false |
|||
}, |
|||
"angularCompilerOptions": { |
|||
"compilationMode": "partial" |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
/* To learn more about this file see: https://angular.io/config/tsconfig. */ |
|||
{ |
|||
"extends": "../../tsconfig.json", |
|||
"compilerOptions": { |
|||
"outDir": "../../out-tsc/spec", |
|||
"types": [ |
|||
"jasmine" |
|||
] |
|||
}, |
|||
"include": [ |
|||
"**/*.spec.ts", |
|||
"**/*.d.ts" |
|||
] |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
/** @type {import('tailwindcss').Config} */ |
|||
module.exports = { |
|||
content: ["./projects/**/*.{html,ts}"], |
|||
theme: { |
|||
extend: {}, |
|||
}, |
|||
plugins: [], |
|||
}; |
|||
@ -1,33 +1,42 @@ |
|||
/* To learn more about this file see: https://angular.io/config/tsconfig. */ |
|||
{ |
|||
"compileOnSave": false, |
|||
"compilerOptions": { |
|||
"baseUrl": "./", |
|||
"outDir": "./dist/out-tsc", |
|||
"forceConsistentCasingInFileNames": true, |
|||
"strict": true, |
|||
"noImplicitOverride": true, |
|||
"noPropertyAccessFromIndexSignature": true, |
|||
"noImplicitReturns": true, |
|||
"noFallthroughCasesInSwitch": true, |
|||
"sourceMap": true, |
|||
"declaration": false, |
|||
"downlevelIteration": true, |
|||
"experimentalDecorators": true, |
|||
"moduleResolution": "node", |
|||
"importHelpers": true, |
|||
"target": "ES2022", |
|||
"module": "ES2022", |
|||
"useDefineForClassFields": false, |
|||
"lib": [ |
|||
"ES2022", |
|||
"dom" |
|||
] |
|||
}, |
|||
"angularCompilerOptions": { |
|||
"enableI18nLegacyMessageIdFormat": false, |
|||
"strictInjectionParameters": true, |
|||
"strictInputAccessModifiers": true, |
|||
"strictTemplates": true |
|||
} |
|||
"compileOnSave": false, |
|||
"compilerOptions": { |
|||
"baseUrl": "./", |
|||
"paths": { |
|||
"@cdk/*": [ |
|||
"./projects/cdk/src/*", |
|||
"dist/cdk", |
|||
], |
|||
"@admin/*": [ |
|||
"./projects/admin/src/*", |
|||
], |
|||
}, |
|||
"outDir": "./dist/out-tsc", |
|||
"forceConsistentCasingInFileNames": true, |
|||
"strict": true, |
|||
"noImplicitOverride": true, |
|||
"noPropertyAccessFromIndexSignature": true, |
|||
"noImplicitReturns": true, |
|||
"noFallthroughCasesInSwitch": true, |
|||
"sourceMap": true, |
|||
"declaration": false, |
|||
"downlevelIteration": true, |
|||
"experimentalDecorators": true, |
|||
"moduleResolution": "node", |
|||
"importHelpers": true, |
|||
"target": "ES2022", |
|||
"module": "ES2022", |
|||
"useDefineForClassFields": false, |
|||
"lib": [ |
|||
"ES2022", |
|||
"dom" |
|||
] |
|||
}, |
|||
"angularCompilerOptions": { |
|||
"enableI18nLegacyMessageIdFormat": false, |
|||
"strictInjectionParameters": true, |
|||
"strictInputAccessModifiers": true, |
|||
"strictTemplates": true |
|||
} |
|||
} |
|||
Loading…
Reference in new issue