100 changed files with 8944 additions and 275 deletions
@ -1,10 +1,130 @@ |
|||||
{ |
{ |
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json", |
"$schema": "./node_modules/@angular/cli/lib/config/schema.json", |
||||
"version": 1, |
"version": 1, |
||||
"cli": { |
"cli": { |
||||
"packageManager": "pnpm" |
"packageManager": "pnpm" |
||||
}, |
}, |
||||
"newProjectRoot": "projects", |
"newProjectRoot": "projects", |
||||
"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", |
"name": "catering-web-app", |
||||
"version": "0.0.0", |
"version": "0.0.0", |
||||
"scripts": { |
"scripts": { |
||||
"ng": "ng", |
"ng": "ng", |
||||
"start": "ng serve", |
"start": "ng serve", |
||||
"build": "ng build", |
"build": "ng build", |
||||
"watch": "ng build --watch --configuration development", |
"watch": "ng build --watch --configuration development", |
||||
"test": "ng test" |
"test": "ng test" |
||||
}, |
}, |
||||
"private": true, |
"private": true, |
||||
"dependencies": { |
"dependencies": { |
||||
"@angular/animations": "^16.1.0", |
"@angular/animations": "^16.1.0", |
||||
"@angular/common": "^16.1.0", |
"@angular/cdk": "^16.2.0", |
||||
"@angular/compiler": "^16.1.0", |
"@angular/common": "^16.1.0", |
||||
"@angular/core": "^16.1.0", |
"@angular/compiler": "^16.1.0", |
||||
"@angular/forms": "^16.1.0", |
"@angular/core": "^16.1.0", |
||||
"@angular/platform-browser": "^16.1.0", |
"@angular/forms": "^16.1.0", |
||||
"@angular/platform-browser-dynamic": "^16.1.0", |
"@angular/platform-browser": "^16.1.0", |
||||
"@angular/router": "^16.1.0", |
"@angular/platform-browser-dynamic": "^16.1.0", |
||||
"rxjs": "~7.8.0", |
"@angular/router": "^16.1.0", |
||||
"tslib": "^2.3.0", |
"@ant-design/icons-angular": "^16.0.0", |
||||
"zone.js": "~0.13.0" |
"immer": "^10.0.2", |
||||
}, |
"ng-zorro-antd": "16.1.0", |
||||
"devDependencies": { |
"ngx-permissions": "^15.0.1", |
||||
"@angular/cli": "~16.1.4", |
"rxjs": "~7.8.0", |
||||
"@angular/compiler-cli": "^16.1.0", |
"tslib": "^2.3.0", |
||||
"@types/jasmine": "~4.3.0", |
"zone.js": "~0.13.0" |
||||
"jasmine-core": "~4.6.0", |
}, |
||||
"karma": "~6.4.0", |
"devDependencies": { |
||||
"karma-chrome-launcher": "~3.2.0", |
"@angular-devkit/build-angular": "^16.1.4", |
||||
"karma-coverage": "~2.2.0", |
"@angular/cli": "~16.1.4", |
||||
"karma-jasmine": "~5.1.0", |
"@angular/compiler-cli": "^16.1.0", |
||||
"karma-jasmine-html-reporter": "~2.1.0", |
"@types/jasmine": "~4.3.0", |
||||
"typescript": "~5.1.3" |
"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. */ |
/* To learn more about this file see: https://angular.io/config/tsconfig. */ |
||||
{ |
{ |
||||
"compileOnSave": false, |
"compileOnSave": false, |
||||
"compilerOptions": { |
"compilerOptions": { |
||||
"baseUrl": "./", |
"baseUrl": "./", |
||||
"outDir": "./dist/out-tsc", |
"paths": { |
||||
"forceConsistentCasingInFileNames": true, |
"@cdk/*": [ |
||||
"strict": true, |
"./projects/cdk/src/*", |
||||
"noImplicitOverride": true, |
"dist/cdk", |
||||
"noPropertyAccessFromIndexSignature": true, |
], |
||||
"noImplicitReturns": true, |
"@admin/*": [ |
||||
"noFallthroughCasesInSwitch": true, |
"./projects/admin/src/*", |
||||
"sourceMap": true, |
], |
||||
"declaration": false, |
}, |
||||
"downlevelIteration": true, |
"outDir": "./dist/out-tsc", |
||||
"experimentalDecorators": true, |
"forceConsistentCasingInFileNames": true, |
||||
"moduleResolution": "node", |
"strict": true, |
||||
"importHelpers": true, |
"noImplicitOverride": true, |
||||
"target": "ES2022", |
"noPropertyAccessFromIndexSignature": true, |
||||
"module": "ES2022", |
"noImplicitReturns": true, |
||||
"useDefineForClassFields": false, |
"noFallthroughCasesInSwitch": true, |
||||
"lib": [ |
"sourceMap": true, |
||||
"ES2022", |
"declaration": false, |
||||
"dom" |
"downlevelIteration": true, |
||||
] |
"experimentalDecorators": true, |
||||
}, |
"moduleResolution": "node", |
||||
"angularCompilerOptions": { |
"importHelpers": true, |
||||
"enableI18nLegacyMessageIdFormat": false, |
"target": "ES2022", |
||||
"strictInjectionParameters": true, |
"module": "ES2022", |
||||
"strictInputAccessModifiers": true, |
"useDefineForClassFields": false, |
||||
"strictTemplates": true |
"lib": [ |
||||
} |
"ES2022", |
||||
|
"dom" |
||||
|
] |
||||
|
}, |
||||
|
"angularCompilerOptions": { |
||||
|
"enableI18nLegacyMessageIdFormat": false, |
||||
|
"strictInjectionParameters": true, |
||||
|
"strictInputAccessModifiers": true, |
||||
|
"strictTemplates": true |
||||
|
} |
||||
} |
} |
||||
Loading…
Reference in new issue