commit
a3d0b27f55
220 changed files with 23306 additions and 0 deletions
@ -0,0 +1,106 @@ |
|||||
|
# Logs |
||||
|
logs |
||||
|
*.log |
||||
|
npm-debug.log* |
||||
|
yarn-debug.log* |
||||
|
yarn-error.log* |
||||
|
lerna-debug.log* |
||||
|
|
||||
|
# Diagnostic reports (https://nodejs.org/api/report.html) |
||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json |
||||
|
|
||||
|
# Runtime data |
||||
|
pids |
||||
|
*.pid |
||||
|
*.seed |
||||
|
*.pid.lock |
||||
|
.angular |
||||
|
|
||||
|
# Directory for instrumented libs generated by jscoverage/JSCover |
||||
|
lib-cov |
||||
|
|
||||
|
# Coverage directory used by tools like istanbul |
||||
|
coverage |
||||
|
*.lcov |
||||
|
|
||||
|
# nyc test coverage |
||||
|
.nyc_output |
||||
|
|
||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) |
||||
|
.grunt |
||||
|
|
||||
|
# Bower dependency directory (https://bower.io/) |
||||
|
bower_components |
||||
|
|
||||
|
# node-waf configuration |
||||
|
.lock-wscript |
||||
|
|
||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html) |
||||
|
build/Release |
||||
|
|
||||
|
# Dependency directories |
||||
|
node_modules/ |
||||
|
jspm_packages/ |
||||
|
|
||||
|
# TypeScript v1 declaration files |
||||
|
typings/ |
||||
|
src/*.js |
||||
|
|
||||
|
# TypeScript cache |
||||
|
*.tsbuildinfo |
||||
|
|
||||
|
# Optional npm cache directory |
||||
|
.npm |
||||
|
|
||||
|
# Optional eslint cache |
||||
|
.eslintcache |
||||
|
|
||||
|
# Microbundle cache |
||||
|
.rpt2_cache/ |
||||
|
.rts2_cache_cjs/ |
||||
|
.rts2_cache_es/ |
||||
|
.rts2_cache_umd/ |
||||
|
|
||||
|
# Optional REPL history |
||||
|
.node_repl_history |
||||
|
|
||||
|
# Output of 'npm pack' |
||||
|
*.tgz |
||||
|
|
||||
|
# Yarn Integrity file |
||||
|
.yarn-integrity |
||||
|
|
||||
|
# dotenv environment variables file |
||||
|
.env |
||||
|
.env.test |
||||
|
|
||||
|
# parcel-bundler cache (https://parceljs.org/) |
||||
|
.cache |
||||
|
.angular |
||||
|
# Next.js build output |
||||
|
.next |
||||
|
|
||||
|
# Nuxt.js build / generate output |
||||
|
.nuxt |
||||
|
/dist |
||||
|
|
||||
|
# Gatsby files |
||||
|
.cache/ |
||||
|
# Comment in the public line in if your project uses Gatsby and *not* Next.js |
||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support |
||||
|
# public |
||||
|
|
||||
|
# vuepress build output |
||||
|
.vuepress/dist |
||||
|
|
||||
|
# Serverless directories |
||||
|
.serverless/ |
||||
|
|
||||
|
# FuseBox cache |
||||
|
.fusebox/ |
||||
|
|
||||
|
# DynamoDB Local files |
||||
|
.dynamodb/ |
||||
|
|
||||
|
# TernJS port file |
||||
|
.tern-port |
||||
@ -0,0 +1,8 @@ |
|||||
|
{ |
||||
|
"singleQuote": true, |
||||
|
"trailingComma": "all", |
||||
|
"useTabs": true, |
||||
|
"tabWidth": 4, |
||||
|
"semi": false, |
||||
|
"printWidth": 120 |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
# LicenseWebApp |
||||
|
|
||||
|
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.0.3. |
||||
|
|
||||
|
## Development server |
||||
|
|
||||
|
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. |
||||
|
|
||||
|
## Code scaffolding |
||||
|
|
||||
|
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. |
||||
|
|
||||
|
## Build |
||||
|
|
||||
|
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. |
||||
|
|
||||
|
## Running unit tests |
||||
|
|
||||
|
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). |
||||
|
|
||||
|
## Running end-to-end tests |
||||
|
|
||||
|
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. |
||||
|
|
||||
|
## Further help |
||||
|
|
||||
|
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. |
||||
@ -0,0 +1,120 @@ |
|||||
|
{ |
||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json", |
||||
|
"version": 1, |
||||
|
"newProjectRoot": "projects", |
||||
|
"projects": { |
||||
|
"license-web-app": { |
||||
|
"projectType": "application", |
||||
|
"schematics": { |
||||
|
"@schematics/angular:component": { |
||||
|
"style": "less" |
||||
|
} |
||||
|
}, |
||||
|
"root": "", |
||||
|
"sourceRoot": "src", |
||||
|
"prefix": "app", |
||||
|
"architect": { |
||||
|
"build": { |
||||
|
"builder": "@angular-devkit/build-angular:application", |
||||
|
"options": { |
||||
|
"outputPath": "dist/license-web-app", |
||||
|
"index": "src/index.html", |
||||
|
"browser": "src/main.ts", |
||||
|
"polyfills": [ |
||||
|
"zone.js" |
||||
|
], |
||||
|
"tsConfig": "tsconfig.app.json", |
||||
|
"inlineStyleLanguage": "less", |
||||
|
"assets": [ |
||||
|
"src/favicon.ico", |
||||
|
"src/assets", |
||||
|
{ |
||||
|
"glob": "**/*", |
||||
|
"input": "./node_modules/@ant-design/icons-angular/src/inline-svg/", |
||||
|
"output": "/assets/" |
||||
|
}, |
||||
|
{ |
||||
|
"glob": "**/*", |
||||
|
"input": "node_modules/tinymce", |
||||
|
"output": "/assets/tinymce/" |
||||
|
} |
||||
|
], |
||||
|
"styles": [ |
||||
|
"src/styles.less" |
||||
|
], |
||||
|
"scripts": [ |
||||
|
"node_modules/tinymce/tinymce.min.js" |
||||
|
] |
||||
|
}, |
||||
|
"configurations": { |
||||
|
"production": { |
||||
|
"budgets": [ |
||||
|
{ |
||||
|
"type": "initial", |
||||
|
"maximumWarning": "10mb", |
||||
|
"maximumError": "10mb" |
||||
|
}, |
||||
|
{ |
||||
|
"type": "anyComponentStyle", |
||||
|
"maximumWarning": "10mb", |
||||
|
"maximumError": "10mb" |
||||
|
} |
||||
|
], |
||||
|
"outputHashing": "all" |
||||
|
}, |
||||
|
"development": { |
||||
|
"optimization": false, |
||||
|
"extractLicenses": false, |
||||
|
"sourceMap": true |
||||
|
} |
||||
|
}, |
||||
|
"defaultConfiguration": "production" |
||||
|
}, |
||||
|
"serve": { |
||||
|
"builder": "@angular-devkit/build-angular:dev-server", |
||||
|
"configurations": { |
||||
|
"production": { |
||||
|
"buildTarget": "license-web-app:build:production" |
||||
|
}, |
||||
|
"development": { |
||||
|
"buildTarget": "license-web-app:build:development" |
||||
|
} |
||||
|
}, |
||||
|
"defaultConfiguration": "development", |
||||
|
"options": { |
||||
|
"port": 6001, |
||||
|
"host": "0.0.0.0" |
||||
|
} |
||||
|
}, |
||||
|
"extract-i18n": { |
||||
|
"builder": "@angular-devkit/build-angular:extract-i18n", |
||||
|
"options": { |
||||
|
"buildTarget": "license-web-app:build" |
||||
|
} |
||||
|
}, |
||||
|
"test": { |
||||
|
"builder": "@angular-devkit/build-angular:karma", |
||||
|
"options": { |
||||
|
"polyfills": [ |
||||
|
"zone.js", |
||||
|
"zone.js/testing" |
||||
|
], |
||||
|
"tsConfig": "tsconfig.spec.json", |
||||
|
"inlineStyleLanguage": "less", |
||||
|
"assets": [ |
||||
|
"src/favicon.ico", |
||||
|
"src/assets" |
||||
|
], |
||||
|
"styles": [ |
||||
|
"src/styles.less" |
||||
|
], |
||||
|
"scripts": [] |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"cli": { |
||||
|
"analytics": false |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,47 @@ |
|||||
|
{ |
||||
|
"name": "admin-web-app", |
||||
|
"version": "0.0.0", |
||||
|
"scripts": { |
||||
|
"ng": "ng", |
||||
|
"start": "ng serve --proxy-config proxy/proxy.conf.js", |
||||
|
"start:remote": "ng serve --proxy-config proxy/proxy.conf.remote.js", |
||||
|
"build": "ng build", |
||||
|
"watch": "ng build --watch --configuration development", |
||||
|
"test": "ng test" |
||||
|
}, |
||||
|
"private": true, |
||||
|
"dependencies": { |
||||
|
"@angular/animations": "^17.0.0", |
||||
|
"@angular/common": "^17.0.0", |
||||
|
"@angular/compiler": "^17.0.0", |
||||
|
"@angular/core": "^17.0.0", |
||||
|
"@angular/forms": "^17.0.0", |
||||
|
"@angular/platform-browser": "^17.0.0", |
||||
|
"@angular/platform-browser-dynamic": "^17.0.0", |
||||
|
"@angular/router": "^17.0.0", |
||||
|
"@tinymce/tinymce-angular": "^7.0.0", |
||||
|
"echarts": "^5.4.3", |
||||
|
"ng-zorro-antd": "^17.0.1", |
||||
|
"rxjs": "~7.8.0", |
||||
|
"tinymce": "^7.0.0", |
||||
|
"tslib": "^2.3.0", |
||||
|
"zone.js": "~0.14.2" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"@angular-devkit/build-angular": "^17.0.3", |
||||
|
"@angular/cli": "^17.0.3", |
||||
|
"@angular/compiler-cli": "^17.0.0", |
||||
|
"@types/jasmine": "~5.1.0", |
||||
|
"autoprefixer": "^10.4.16", |
||||
|
"jasmine-core": "~5.1.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", |
||||
|
"postcss": "^8.4.31", |
||||
|
"prettier": "3.1", |
||||
|
"tailwindcss": "^3.3.5", |
||||
|
"typescript": "~5.2.2" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// const remoteTarget = "http://10.168.1.60:8300";
|
||||
|
const remoteTarget = 'http://47.109.27.8:8280/' |
||||
|
const localTarget = 'http://localhost:8300' |
||||
|
|
||||
|
const devProxy = (remote) => { |
||||
|
let config = {} |
||||
|
const target = remote ? remoteTarget : localTarget |
||||
|
|
||||
|
return { |
||||
|
'/api': { |
||||
|
target, |
||||
|
secure: false, |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = devProxy |
||||
@ -0,0 +1,2 @@ |
|||||
|
const devProxy = require('./devserver') |
||||
|
module.exports = devProxy() |
||||
@ -0,0 +1,2 @@ |
|||||
|
const devProxy = require("./devserver"); |
||||
|
module.exports = devProxy(true); |
||||
@ -0,0 +1 @@ |
|||||
|
<router-outlet></router-outlet> |
||||
@ -0,0 +1,4 @@ |
|||||
|
:host { |
||||
|
display: block; |
||||
|
height: 100%; |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
import { Component } from '@angular/core' |
||||
|
import { SharedModule } from './shared/shared.module' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-root', |
||||
|
standalone: true, |
||||
|
imports: [SharedModule], |
||||
|
templateUrl: './app.component.html', |
||||
|
styleUrl: './app.component.less', |
||||
|
}) |
||||
|
export class AppComponent {} |
||||
@ -0,0 +1,68 @@ |
|||||
|
import { APP_INITIALIZER, ApplicationConfig, importProvidersFrom } from '@angular/core' |
||||
|
import { provideRouter } from '@angular/router' |
||||
|
|
||||
|
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 { provideAnimations } from '@angular/platform-browser/animations' |
||||
|
import { zh_CN, provideNzI18n } from 'ng-zorro-antd/i18n' |
||||
|
import { routes } from './app.routes' |
||||
|
import { ServerPaginatedTableService } from './components/server-paginated-table' |
||||
|
import { LocalHttpInterceptorService } from './services' |
||||
|
import { PermissionModule } from './shared/permission/permission.module' |
||||
|
import { TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular' |
||||
|
|
||||
|
registerLocaleData(zh) |
||||
|
|
||||
|
export const APPOINTMENT_FROM = [ |
||||
|
{ value: 1, label: '美团' }, |
||||
|
{ value: 2, label: '大众点评' }, |
||||
|
{ value: 3, label: '小红书' }, |
||||
|
{ value: 4, label: '抖音' }, |
||||
|
] |
||||
|
|
||||
|
export function initializeApp(configService: ServerPaginatedTableService) { |
||||
|
return () => { |
||||
|
configService.initial() |
||||
|
configService.setConfig({ |
||||
|
formatPaginationData(v) { |
||||
|
return { |
||||
|
current: v.pageIndex, |
||||
|
pageSize: v.pageSize, |
||||
|
} |
||||
|
}, |
||||
|
formatServiceData(v) { |
||||
|
console.log('v', v) |
||||
|
return { |
||||
|
total: v.data.total, |
||||
|
totalPages: Math.ceil(v.data.records / 5), |
||||
|
data: v.data.records, |
||||
|
} |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const appConfig: ApplicationConfig = { |
||||
|
providers: [ |
||||
|
provideRouter(routes), |
||||
|
provideNzI18n(zh_CN), |
||||
|
importProvidersFrom(FormsModule), |
||||
|
importProvidersFrom(HttpClientModule), |
||||
|
importProvidersFrom(PermissionModule.forRoot()), |
||||
|
provideAnimations(), |
||||
|
{ |
||||
|
provide: HTTP_INTERCEPTORS, |
||||
|
useClass: LocalHttpInterceptorService, |
||||
|
multi: true, |
||||
|
}, |
||||
|
{ |
||||
|
provide: APP_INITIALIZER, |
||||
|
useFactory: initializeApp, |
||||
|
deps: [ServerPaginatedTableService], |
||||
|
multi: true, |
||||
|
}, |
||||
|
{ provide: TINYMCE_SCRIPT_SRC, useValue: 'tinymce/tinymce.min.js' }, |
||||
|
], |
||||
|
} |
||||
@ -0,0 +1,245 @@ |
|||||
|
import { Routes } from '@angular/router' |
||||
|
import { DashboardComponent } from './pages/dashboard/dashboard.component' |
||||
|
import { LayoutComponent } from './components/layout/layout.component' |
||||
|
import { ProfileComponent } from './pages/profile/profile.component' |
||||
|
import { ProfileBasicComponent } from './pages/profile-basic/profile-basic.component' |
||||
|
import { ProfileAccountComponent } from './pages/profile-account/profile-account.component' |
||||
|
import { LoginComponent } from './pages/login/login.component' |
||||
|
import { authGuard } from './guards' |
||||
|
import { permissionGuard } from './shared/permission/permission.guard' |
||||
|
import { ForbiddenComponent } from './pages/forbidden/forbidden.component' |
||||
|
import { NotfoundComponent } from './pages/notfound/notfound.component' |
||||
|
import { SystemComponent } from './pages/system/system.component' |
||||
|
import { OrgComponent } from './pages/org/org.component' |
||||
|
import { OrgEmployeeComponent } from './pages/org-employee/org-employee.component' |
||||
|
import { OrgSettingComponent } from './pages/org-setting/org-setting.component' |
||||
|
import { FixedAssetComponent } from './pages/fixed-asset/fixed-asset.component' |
||||
|
import { FixedAssetBelongComponent } from './pages/fixed-asset/ledger/fixed-asset-belong/fixed-asset-belong.component' |
||||
|
import { FixedAssetEmployeeComponent } from './pages/fixed-asset/ledger/fixed-asset-employee/fixed-asset-employee.component' |
||||
|
import { FixedAssetOrgComponent } from './pages/fixed-asset/ledger/fixed-asset-org/fixed-asset-org.component' |
||||
|
import { FixedAssetCategoryComponent } from './pages/fixed-asset/ledger/fixed-asset-category/fixed-asset-category.component' |
||||
|
import { FixedAssetPositionComponent } from './pages/fixed-asset/ledger/fixed-asset-position/fixed-asset-position.component' |
||||
|
import { FixedAssetMyownComponent } from './pages/fixed-asset/ledger/fixed-asset-myown/fixed-asset-myown.component' |
||||
|
import { FixedAssetLedgerComponent } from './pages/fixed-asset/ledger/fixed-asset-ledger/fixed-asset-ledger.component' |
||||
|
import { FixedAssetSearchComponent } from './pages/fixed-asset/ledger/fixed-asset-search/fixed-asset-search.component' |
||||
|
import { FixedAssetManageComponent } from './pages/fixed-asset/manage/fixed-asset-manage/fixed-asset-manage.component' |
||||
|
import { FixedAssetManageEntryComponent } from './pages/fixed-asset/manage/fixed-asset-manage-entry/fixed-asset-manage-entry.component' |
||||
|
import { FixedAssetManageDistributionComponent } from './pages/fixed-asset/manage/fixed-asset-manage-distribution/fixed-asset-manage-distribution.component' |
||||
|
import { FixedAssetManageReturnComponent } from './pages/fixed-asset/manage/fixed-asset-manage-return/fixed-asset-manage-return.component' |
||||
|
import { FixedAssetManageBorrowComponent } from './pages/fixed-asset/manage/fixed-asset-manage-borrow/fixed-asset-manage-borrow.component' |
||||
|
import { FixedAssetManageRevertComponent } from './pages/fixed-asset/manage/fixed-asset-manage-revert/fixed-asset-manage-revert.component' |
||||
|
import { FixedAssetManageAllotComponent } from './pages/fixed-asset/manage/fixed-asset-manage-allot/fixed-asset-manage-allot.component' |
||||
|
import { FixedAssetManageTransferComponent } from './pages/fixed-asset/manage/fixed-asset-manage-transfer/fixed-asset-manage-transfer.component' |
||||
|
import { FixedAssetManageScrapComponent } from './pages/fixed-asset/manage/fixed-asset-manage-scrap/fixed-asset-manage-scrap.component' |
||||
|
|
||||
|
export const routes: Routes = [ |
||||
|
{ |
||||
|
path: 'login', |
||||
|
component: LoginComponent, |
||||
|
}, |
||||
|
{ |
||||
|
path: 'forbidden', |
||||
|
component: ForbiddenComponent, |
||||
|
}, |
||||
|
{ |
||||
|
path: '', |
||||
|
component: LayoutComponent, |
||||
|
canActivate: [authGuard], |
||||
|
children: [ |
||||
|
{ |
||||
|
path: '', |
||||
|
pathMatch: 'full', |
||||
|
redirectTo: 'dashboard', |
||||
|
}, |
||||
|
{ |
||||
|
path: 'dashboard', |
||||
|
component: DashboardComponent, |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
path: 'profile', |
||||
|
component: ProfileComponent, |
||||
|
|
||||
|
children: [ |
||||
|
{ |
||||
|
path: '', |
||||
|
pathMatch: 'full', |
||||
|
redirectTo: 'basic', |
||||
|
}, |
||||
|
{ |
||||
|
path: 'basic', |
||||
|
component: ProfileBasicComponent, |
||||
|
}, |
||||
|
{ |
||||
|
path: 'account', |
||||
|
component: ProfileAccountComponent, |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
{ |
||||
|
path: 'system', |
||||
|
component: SystemComponent, |
||||
|
children: [ |
||||
|
{ |
||||
|
path: '', |
||||
|
pathMatch: 'full', |
||||
|
redirectTo: 'user', |
||||
|
}, |
||||
|
{ |
||||
|
path: 'user', |
||||
|
component: SystemComponent, |
||||
|
canActivate: [permissionGuard], |
||||
|
data: { |
||||
|
permission: 'user', |
||||
|
}, |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
{ |
||||
|
path: 'org', |
||||
|
component: OrgComponent, |
||||
|
title: '人事管理', |
||||
|
children: [ |
||||
|
{ |
||||
|
path: '', |
||||
|
pathMatch: 'full', |
||||
|
redirectTo: 'setting', |
||||
|
}, |
||||
|
{ |
||||
|
path: 'setting', |
||||
|
component: OrgSettingComponent, |
||||
|
title: '组织架构', |
||||
|
}, |
||||
|
{ |
||||
|
path: 'employee', |
||||
|
component: OrgEmployeeComponent, |
||||
|
title: '员工管理', |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
{ |
||||
|
path: 'fixed-asset', |
||||
|
title: '固资管理', |
||||
|
component: FixedAssetComponent, |
||||
|
children: [ |
||||
|
{ |
||||
|
path: '', |
||||
|
pathMatch: 'full', |
||||
|
redirectTo: 'ledger', |
||||
|
}, |
||||
|
{ |
||||
|
path: 'ledger', |
||||
|
title: '资产台账', |
||||
|
component: FixedAssetLedgerComponent, |
||||
|
children: [ |
||||
|
{ |
||||
|
path: '', |
||||
|
pathMatch: 'full', |
||||
|
redirectTo: 'asset-search', |
||||
|
}, |
||||
|
{ |
||||
|
path: 'asset-search', |
||||
|
title: '资产公共台账', |
||||
|
component: FixedAssetSearchComponent, |
||||
|
}, |
||||
|
{ |
||||
|
path: 'asset-belong', |
||||
|
title: '归属组织台账', |
||||
|
component: FixedAssetBelongComponent, |
||||
|
}, |
||||
|
{ |
||||
|
path: 'asset-org', |
||||
|
title: '使用组织台账', |
||||
|
component: FixedAssetOrgComponent, |
||||
|
}, |
||||
|
{ |
||||
|
path: 'asset-position', |
||||
|
title: '存放位置台账', |
||||
|
component: FixedAssetPositionComponent, |
||||
|
}, |
||||
|
{ |
||||
|
path: 'asset-employee', |
||||
|
title: '员工查询台账', |
||||
|
component: FixedAssetEmployeeComponent, |
||||
|
}, |
||||
|
{ |
||||
|
path: 'asset-category', |
||||
|
title: '分类查询台账', |
||||
|
component: FixedAssetCategoryComponent, |
||||
|
}, |
||||
|
{ |
||||
|
path: 'asset-my-own', |
||||
|
title: '所管资产台账', |
||||
|
component: FixedAssetMyownComponent, |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
{ |
||||
|
path: 'manage', |
||||
|
// title: '固资管理',
|
||||
|
children: [ |
||||
|
{ |
||||
|
path: '', |
||||
|
redirectTo: 'list', |
||||
|
pathMatch: 'full', |
||||
|
}, |
||||
|
{ |
||||
|
path: 'list', |
||||
|
component: FixedAssetManageComponent, |
||||
|
title: '资产管理', |
||||
|
}, |
||||
|
{ |
||||
|
path: 'entry', |
||||
|
component: FixedAssetManageEntryComponent, |
||||
|
title: '资产入库', |
||||
|
}, |
||||
|
{ |
||||
|
path: 'distribution', |
||||
|
component: FixedAssetManageDistributionComponent, |
||||
|
title: '资产派发', |
||||
|
}, |
||||
|
{ |
||||
|
path: 'return', |
||||
|
component: FixedAssetManageReturnComponent, |
||||
|
title: '资产退还', |
||||
|
}, |
||||
|
{ |
||||
|
path: 'borrow', |
||||
|
component: FixedAssetManageBorrowComponent, |
||||
|
title: '资产借用', |
||||
|
}, |
||||
|
{ |
||||
|
path: 'revert', |
||||
|
component: FixedAssetManageRevertComponent, |
||||
|
title: '资产归还', |
||||
|
}, |
||||
|
{ |
||||
|
path: 'allot', |
||||
|
component: FixedAssetManageAllotComponent, |
||||
|
title: '资产调拨', |
||||
|
}, |
||||
|
{ |
||||
|
path: 'transfer', |
||||
|
component: FixedAssetManageTransferComponent, |
||||
|
title: '资产转换', |
||||
|
}, |
||||
|
{ |
||||
|
path: 'scrap', |
||||
|
component: FixedAssetManageScrapComponent, |
||||
|
title: '资产报废', |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
{ |
||||
|
path: 'not-found', |
||||
|
component: NotfoundComponent, |
||||
|
}, |
||||
|
{ |
||||
|
path: '**', |
||||
|
redirectTo: 'not-found', |
||||
|
}, |
||||
|
] |
||||
@ -0,0 +1,32 @@ |
|||||
|
<div class="page-header"> |
||||
|
<div class="flex items-center mb-3 px-4 py-2 shadow bg-white rounded-sm"> |
||||
|
<div class="flex-1"> |
||||
|
<nz-breadcrumb> |
||||
|
<nz-breadcrumb-item> |
||||
|
<a href="/">首页</a> |
||||
|
</nz-breadcrumb-item> |
||||
|
@for (b of breadcrumbs; track $index; let last = $last) { |
||||
|
@if (last) { |
||||
|
<nz-breadcrumb-item>{{ b.label }}</nz-breadcrumb-item> |
||||
|
} @else { |
||||
|
<nz-breadcrumb-item> |
||||
|
<a [routerLink]="b.url">{{ b.label }}</a> |
||||
|
</nz-breadcrumb-item> |
||||
|
} |
||||
|
} |
||||
|
</nz-breadcrumb> |
||||
|
</div> |
||||
|
@if (actions) { |
||||
|
<div class="flex-1 flex justify-end items-center"> |
||||
|
<ng-container |
||||
|
[ngTemplateOutlet]="actions" |
||||
|
[ngTemplateOutletContext]="{ |
||||
|
$implicit: 12 |
||||
|
}" |
||||
|
/> |
||||
|
</div> |
||||
|
} |
||||
|
</div> |
||||
|
<div></div> |
||||
|
</div> |
||||
|
<ng-content></ng-content> |
||||
@ -0,0 +1,6 @@ |
|||||
|
:host { |
||||
|
@apply p-3; |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
@ -0,0 +1,68 @@ |
|||||
|
import { Component, Input, OnDestroy, TemplateRef } from '@angular/core' |
||||
|
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router' |
||||
|
import { BreadcrumbInterface, BreadcrumbService } from 'app/services/breadcrumb.service' |
||||
|
import { SharedModule } from 'app/shared/shared.module' |
||||
|
import { Subscription, filter } from 'rxjs' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-page', |
||||
|
standalone: true, |
||||
|
imports: [SharedModule], |
||||
|
templateUrl: './app-page.component.html', |
||||
|
styleUrl: './app-page.component.less', |
||||
|
}) |
||||
|
export class AppPageComponent implements OnDestroy { |
||||
|
constructor( |
||||
|
private route: ActivatedRoute, |
||||
|
private router: Router, |
||||
|
) { |
||||
|
this.subscriptions.add( |
||||
|
this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe(() => { |
||||
|
this.getBreadcrumb() |
||||
|
}), |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
private readonly subscriptions = new Subscription() |
||||
|
|
||||
|
@Input() actions?: TemplateRef<{}> |
||||
|
|
||||
|
breadcrumbs: BreadcrumbInterface[] = [] |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.getBreadcrumb() |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy(): void { |
||||
|
this.subscriptions.unsubscribe() |
||||
|
} |
||||
|
|
||||
|
getBreadcrumb() { |
||||
|
this.breadcrumbs = this.buildBreadcrumb(this.route.root) |
||||
|
} |
||||
|
|
||||
|
private buildBreadcrumb( |
||||
|
route: ActivatedRoute, |
||||
|
url: string = '', |
||||
|
breadcrumbs: BreadcrumbInterface[] = [], |
||||
|
): BreadcrumbInterface[] { |
||||
|
const routeConfig = route.routeConfig |
||||
|
const label = routeConfig ? routeConfig.title : '' |
||||
|
const path = routeConfig ? routeConfig.path : '' |
||||
|
|
||||
|
const nextUrl = url !== '/' ? `${url}/${path}` : '' |
||||
|
|
||||
|
const breadcrumb = { |
||||
|
label: label as string, |
||||
|
url: nextUrl, |
||||
|
} |
||||
|
|
||||
|
const newBreadcrumbs = breadcrumb.label ? [...breadcrumbs, breadcrumb] : [...breadcrumbs] |
||||
|
|
||||
|
if (route.firstChild) { |
||||
|
return this.buildBreadcrumb(route.firstChild, nextUrl, newBreadcrumbs) |
||||
|
} |
||||
|
|
||||
|
return newBreadcrumbs |
||||
|
} |
||||
|
} |
||||
@ -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,82 @@ |
|||||
|
<header class="fixed z-20 left-0 top-0 right-0 shadow-md header"> |
||||
|
<div class="flex h-full px-4 app-width mx-auto"> |
||||
|
<a class="logo flex items-center"> |
||||
|
<!-- <img src="/assets/images/logo.png" class="w-8" /> --> |
||||
|
<span class="ml-2 text-2xl font-bold text-white">固资系统</span> |
||||
|
</a> |
||||
|
|
||||
|
<ul class="ml-8 nav flex"> |
||||
|
<li class="nav-item"> |
||||
|
<a routerLink="/dashboard" routerLinkActive="active">首页</a> |
||||
|
</li> |
||||
|
<li class="nav-item"> |
||||
|
<a routerLink="/fixed-asset" routerLinkActive="active">固资管理</a> |
||||
|
</li> |
||||
|
<li class="nav-item"> |
||||
|
<a routerLink="/org" routerLinkActive="active">人事管理</a> |
||||
|
</li> |
||||
|
<li class="nav-item"> |
||||
|
<a routerLink="/system" routerLinkActive="active">系统管理</a> |
||||
|
</li> |
||||
|
|
||||
|
<!-- <li |
||||
|
class="nav-item" |
||||
|
*appPermission="['jwClient:model', 'jwClient:list', 'jwClient:add', 'jwClient:edit', 'jwClient:delete']" |
||||
|
> |
||||
|
<a routerLink="/entity" routerLinkActive="active">授权实体</a> |
||||
|
</li> |
||||
|
<li |
||||
|
class="nav-item" |
||||
|
*appPermission="[ |
||||
|
'jwProduct:model', |
||||
|
'jwProduct:list', |
||||
|
'jwProduct:add', |
||||
|
'jwProduct:edit', |
||||
|
'jwProduct:delete' |
||||
|
]" |
||||
|
> |
||||
|
<a routerLink="/product" routerLinkActive="active">产品管理</a> |
||||
|
</li> |
||||
|
|
||||
|
<li |
||||
|
class="nav-item" |
||||
|
*appPermission="[ |
||||
|
'userGroup:model', |
||||
|
'sysUser:model', |
||||
|
'sysUser:list', |
||||
|
'sysUser:add', |
||||
|
'sysUser:edit', |
||||
|
'sysUser:delete' |
||||
|
]" |
||||
|
> |
||||
|
<a routerLink="/contacts" routerLinkActive="active">用户管理</a> |
||||
|
</li> |
||||
|
<li |
||||
|
class="nav-item" |
||||
|
*appPermission="['sysRole:model', 'sysRole:list', 'sysRole:add', 'sysRole:edit', 'sysRole:delete']" |
||||
|
> |
||||
|
<a routerLink="/role" routerLinkActive="active">角色管理</a> |
||||
|
</li> |
||||
|
<li class="nav-item" *appPermission="['sysLog:model']"> |
||||
|
<a routerLink="/log" routerLinkActive="active">操作记录</a> |
||||
|
</li> --> |
||||
|
</ul> |
||||
|
|
||||
|
<div class="ml-auto flex items-center"> |
||||
|
<a nz-button nzType="text" nz-dropdown [nzDropdownMenu]="menu"> |
||||
|
<nz-avatar nzSrc="/assets/images/avatar4.png" /> |
||||
|
<span class="ml-2"> |
||||
|
{{ api.authInfo.name || 'admin' }} |
||||
|
</span> |
||||
|
<span nz-icon nzType="down"></span> |
||||
|
</a> |
||||
|
<nz-dropdown-menu #menu="nzDropdownMenu"> |
||||
|
<ul nz-menu nzSelectable> |
||||
|
<li nz-menu-item routerLink="/profile">个人中心</li> |
||||
|
<li nz-menu-item (click)="logout()">退出登录</li> |
||||
|
</ul> |
||||
|
</nz-dropdown-menu> |
||||
|
</div> |
||||
|
</div> |
||||
|
</header> |
||||
|
<div class="h-12"></div> |
||||
@ -0,0 +1,32 @@ |
|||||
|
.header { |
||||
|
background-color: var(--primary); |
||||
|
color: #fff; |
||||
|
|
||||
|
a { |
||||
|
color: rgba(255, 255, 255, .8); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.nav { |
||||
|
&-item { |
||||
|
|
||||
|
|
||||
|
a { |
||||
|
@apply h-12 px-5 overflow-hidden text-center flex items-center; |
||||
|
font-size: 15px; |
||||
|
|
||||
|
&.active { |
||||
|
background-color: rgba(255, 255, 255, .4); |
||||
|
color: #fff; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&:hover { |
||||
|
a { |
||||
|
color: #fff; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
import { Component, OnInit, inject } from '@angular/core' |
||||
|
import { SharedModule } from '../../shared/shared.module' |
||||
|
import { ApiService, LocalHttpInterceptorService } from 'app/services' |
||||
|
import { Router } from '@angular/router' |
||||
|
import { NzMessageService } from 'ng-zorro-antd/message' |
||||
|
import { NzModalService } from 'ng-zorro-antd/modal' |
||||
|
import { PermissionService } from 'app/shared/permission/permission.service' |
||||
|
|
||||
|
@Component({ |
||||
|
standalone: true, |
||||
|
selector: 'app-header', |
||||
|
templateUrl: './header.component.html', |
||||
|
styleUrls: ['./header.component.less'], |
||||
|
imports: [SharedModule], |
||||
|
}) |
||||
|
export class HeaderComponent implements OnInit { |
||||
|
constructor( |
||||
|
private local: LocalHttpInterceptorService, |
||||
|
private router: Router, |
||||
|
private msg: NzMessageService, |
||||
|
private modal: NzModalService, |
||||
|
public api: ApiService, |
||||
|
) {} |
||||
|
|
||||
|
ngOnInit(): void {} |
||||
|
|
||||
|
permission = inject(PermissionService) |
||||
|
|
||||
|
logout() { |
||||
|
this.modal.confirm({ |
||||
|
nzTitle: '退出登录', |
||||
|
nzContent: '确认要退出当前账户吗?', |
||||
|
nzOnOk: () => { |
||||
|
this.permission.reset() |
||||
|
this.local.removeAccess() |
||||
|
this.router.navigate(['/login']) |
||||
|
this.msg.success('退出成功') |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,5 @@ |
|||||
|
// export * from "./form-error-tips/form-error-tips.component";
|
||||
|
// export * from "./server-paginated-table";
|
||||
|
|
||||
|
export * from './header/header.component' |
||||
|
export * from './layout/layout.component' |
||||
@ -0,0 +1,4 @@ |
|||||
|
<app-header></app-header> |
||||
|
<main class="app-width mx-auto"> |
||||
|
<router-outlet></router-outlet> |
||||
|
</main> |
||||
@ -0,0 +1,15 @@ |
|||||
|
:host { |
||||
|
display: block; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.app-width { |
||||
|
::ng-deep { |
||||
|
router-outlet+* { |
||||
|
display: block; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
// overflow: hidden; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
import { Component } from '@angular/core'; |
||||
|
import { HeaderComponent } from "../header/header.component"; |
||||
|
import { SharedModule } from "../../shared/shared.module"; |
||||
|
|
||||
|
@Component({ |
||||
|
standalone: true, |
||||
|
selector: 'app-layout', |
||||
|
templateUrl: './layout.component.html', |
||||
|
styleUrls: ['./layout.component.less'], |
||||
|
imports: [SharedModule, HeaderComponent] |
||||
|
}) |
||||
|
export class LayoutComponent { |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
<div class="flex items-center"> |
||||
|
<div class="flex-1"> |
||||
|
<nz-date-picker |
||||
|
class="w-full" |
||||
|
[nzShowTime]="showTime" |
||||
|
nzShowTime |
||||
|
[nzFormat]="_format" |
||||
|
[(ngModel)]="startValue" |
||||
|
(ngModelChange)="onStartValueChange($event)" |
||||
|
nzPlaceHolder="开始时间" |
||||
|
(nzOnOpenChange)="handleStartOpenChange($event)" |
||||
|
> |
||||
|
</nz-date-picker> |
||||
|
</div> |
||||
|
<div class="px-1">~</div> |
||||
|
<div class="flex-1"> |
||||
|
<nz-date-picker |
||||
|
class="w-full" |
||||
|
#endDatePicker |
||||
|
[nzShowTime]="showTime" |
||||
|
[(ngModel)]="endValue" |
||||
|
(ngModelChange)="onEndValueChange($event)" |
||||
|
nzPlaceHolder="结束时间" |
||||
|
[nzFormat]="_format" |
||||
|
(nzOnOpenChange)="handleEndOpenChange($event)" |
||||
|
> |
||||
|
</nz-date-picker> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,96 @@ |
|||||
|
import { CommonModule } from '@angular/common' |
||||
|
import { Component, Input, ViewChild, forwardRef } from '@angular/core' |
||||
|
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms' |
||||
|
import { SharedModule } from 'app/shared/shared.module' |
||||
|
import { NzDatePickerComponent, NzDatePickerModule } from 'ng-zorro-antd/date-picker' |
||||
|
import { NzSpaceModule } from 'ng-zorro-antd/space' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-range-picker', |
||||
|
standalone: true, |
||||
|
imports: [CommonModule, FormsModule, ReactiveFormsModule, NzDatePickerModule, NzSpaceModule], |
||||
|
templateUrl: './range-picker.component.html', |
||||
|
styleUrl: './range-picker.component.less', |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
multi: true, |
||||
|
useExisting: forwardRef(() => RangePickerComponent), |
||||
|
}, |
||||
|
], |
||||
|
}) |
||||
|
export class RangePickerComponent implements ControlValueAccessor { |
||||
|
constructor() {} |
||||
|
|
||||
|
@Input() showTime = false |
||||
|
|
||||
|
@Input() format?: string |
||||
|
|
||||
|
get _format() { |
||||
|
if (this.format) { |
||||
|
return this.format |
||||
|
} |
||||
|
return this.showTime ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd' |
||||
|
} |
||||
|
|
||||
|
startValue: Date | null = null |
||||
|
|
||||
|
endValue: Date | null = null |
||||
|
|
||||
|
@ViewChild('endDatePicker') endDatePicker!: NzDatePickerComponent |
||||
|
|
||||
|
disabledStartDate = (startValue: Date): boolean => { |
||||
|
if (!startValue || !this.endValue) { |
||||
|
return false |
||||
|
} |
||||
|
return startValue.getTime() > this.endValue.getTime() |
||||
|
} |
||||
|
|
||||
|
disabledEndDate = (endValue: Date): boolean => { |
||||
|
if (!endValue || !this.startValue) { |
||||
|
return false |
||||
|
} |
||||
|
return endValue.getTime() <= this.startValue.getTime() |
||||
|
} |
||||
|
|
||||
|
handleStartOpenChange(open: boolean): void { |
||||
|
if (!open) { |
||||
|
this.endDatePicker.open() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
handleEndOpenChange(open: boolean): void {} |
||||
|
|
||||
|
onTouched = () => {} |
||||
|
|
||||
|
onChange(v: (Date | null)[]) { |
||||
|
console.log('v', v) |
||||
|
} |
||||
|
|
||||
|
onStartValueChange(value: Date | null): void { |
||||
|
this.startValue = value |
||||
|
this.onChange([this.startValue, this.endValue]) |
||||
|
this.onTouched() |
||||
|
} |
||||
|
|
||||
|
onEndValueChange(value: Date | null): void { |
||||
|
this.endValue = value |
||||
|
this.onChange([this.startValue, this.endValue]) |
||||
|
this.onTouched() |
||||
|
} |
||||
|
|
||||
|
writeValue(v: any): void { |
||||
|
this.startValue = v?.[0] ? new Date(v[0]) : null |
||||
|
this.endValue = v?.[1] ? new Date(v[1]) : null |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.onChange = fn |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
this.onTouched = fn |
||||
|
} |
||||
|
|
||||
|
setDisabledState?(isDisabled: boolean): void {} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
<button |
||||
|
nz-button |
||||
|
nzType="text" |
||||
|
nz-popover |
||||
|
nzPopoverTrigger="click" |
||||
|
[nzPopoverContent]="innerDatePickerTpl" |
||||
|
nzPopoverOverlayClassName="inner-date-picker" |
||||
|
> |
||||
|
<span> |
||||
|
{{ date | date: 'yyyy-MM-dd' }} |
||||
|
</span> |
||||
|
<span nz-icon nzType="calendar" nzTheme="outline"></span> |
||||
|
</button> |
||||
|
|
||||
|
<ng-template #innerDatePickerTpl> |
||||
|
<nz-date-picker nzInline [(ngModel)]="date" /> |
||||
|
</ng-template> |
||||
@ -0,0 +1,12 @@ |
|||||
|
::ng-deep { |
||||
|
.inner-date-picker { |
||||
|
|
||||
|
.ant-popover-inner-content { |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
.ant-picker-dropdown { |
||||
|
padding: 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
import { Component } from '@angular/core' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-date-query', |
||||
|
|
||||
|
templateUrl: './date-query.component.html', |
||||
|
styleUrl: './date-query.component.less', |
||||
|
}) |
||||
|
export class DateQueryComponent { |
||||
|
constructor() {} |
||||
|
|
||||
|
date?: Date |
||||
|
range: Array<Date | null> = [] |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
export * from './server-paginated-table.component' |
||||
|
export * from './server-paginated-table.module' |
||||
|
export * from './server-paginated-table.service' |
||||
|
export * from './table-action.directive' |
||||
|
export * from './table-form.directive' |
||||
|
export * from './query-item/query-item.component' |
||||
@ -0,0 +1,8 @@ |
|||||
|
<nz-form-item> |
||||
|
@if (label) { |
||||
|
<nz-form-label>{{ label }}</nz-form-label> |
||||
|
} |
||||
|
<nz-form-control class="query-control"> |
||||
|
<ng-content></ng-content> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
@ -0,0 +1,7 @@ |
|||||
|
.query-control { |
||||
|
::ng-deep { |
||||
|
input { |
||||
|
width: 100px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
import { Component, Input } from '@angular/core' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-query-item', |
||||
|
templateUrl: './query-item.component.html', |
||||
|
styleUrl: './query-item.component.less', |
||||
|
}) |
||||
|
export class QueryItemComponent { |
||||
|
@Input() label?: string |
||||
|
} |
||||
@ -0,0 +1,164 @@ |
|||||
|
<div> |
||||
|
@if (formGroup) { |
||||
|
<form [formGroup]="formGroup" (ngSubmit)="onSubmit($event)"> |
||||
|
<ng-template [ngTemplateOutlet]="topTpl"></ng-template> |
||||
|
</form> |
||||
|
} @else { |
||||
|
<form (ngSubmit)="onSubmit($event)"> |
||||
|
<ng-template [ngTemplateOutlet]="topTpl"></ng-template> |
||||
|
</form> |
||||
|
} |
||||
|
</div> |
||||
|
<ng-template #topTpl> |
||||
|
@if (formContentChild) { |
||||
|
<div class="formgroup-container"> |
||||
|
<div class="form-items"> |
||||
|
<div class="inline-flex flex-wrap query-wrapper"> |
||||
|
<ng-template [ngTemplateOutlet]="formContentChild"></ng-template> |
||||
|
<nz-space class="inline-flex"> |
||||
|
<button *nzSpaceItem nz-button nzType="primary">查询</button> |
||||
|
<button *nzSpaceItem nz-button type="button" (click)="reset()">重置</button> |
||||
|
</nz-space> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
<!-- @if (actionContentChild) { |
||||
|
<div class="action-container pb-3"> |
||||
|
<ng-template [ngTemplateOutlet]="actionContentChild"> </ng-template> |
||||
|
</div> |
||||
|
} --> |
||||
|
</ng-template> |
||||
|
|
||||
|
<ng-template #columnsSettingTpl> |
||||
|
<ul> |
||||
|
@for (c of options.columns; track $index) { |
||||
|
<li> |
||||
|
<label nz-checkbox [(nzChecked)]="c.visible" (nzCheckedChange)="onColumnsChange()"> |
||||
|
{{ c.title }} |
||||
|
</label> |
||||
|
</li> |
||||
|
} |
||||
|
</ul> |
||||
|
</ng-template> |
||||
|
|
||||
|
<nz-card [nzBordered]="false" [nzSize]="'small'" class="shadow"> |
||||
|
<nz-table |
||||
|
#tableRef |
||||
|
class="server-paginated-table" |
||||
|
nzShowSizeChanger |
||||
|
[nzShowTotal]="totalTpl" |
||||
|
[nzData]="pagination.data" |
||||
|
[nzFrontPagination]="options.config.frontPagination" |
||||
|
[nzLoading]="pagination.loading" |
||||
|
[nzTotal]="pagination.total" |
||||
|
[(nzPageSize)]="pagination.pageSize" |
||||
|
[(nzPageIndex)]="pagination.pageIndex" |
||||
|
(nzPageIndexChange)="onPageChange()" |
||||
|
(nzPageSizeChange)="onPageChange()" |
||||
|
> |
||||
|
@if (actionContentChild && selected.size > 0) { |
||||
|
<div class="table-selected-action flex items-center"> |
||||
|
<span>已选择 ({{ selected.size }}) 项目</span> |
||||
|
<ng-template [ngTemplateOutlet]="actionContentChild"></ng-template> |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
<thead> |
||||
|
<tr> |
||||
|
@if (options.config.selectable) { |
||||
|
<th |
||||
|
nzWidth="60px" |
||||
|
nzLabel="Select all" |
||||
|
[nzChecked]="selected.size > 0 && tableRef.data.length > 0" |
||||
|
[nzIndeterminate]="indeterminate" |
||||
|
(nzCheckedChange)="onAllChecked($event)" |
||||
|
></th> |
||||
|
} |
||||
|
@for (th of options.columns; track th.key) { |
||||
|
@if (th.visible) { |
||||
|
<th [nzColumnKey]="th.key" [nzWidth]="th.width ?? null">{{ th.title }}</th> |
||||
|
} |
||||
|
} |
||||
|
@if (options.operate.length > 0) { |
||||
|
<th nzWidth="100px">操作</th> |
||||
|
} |
||||
|
<th nzWidth="60px"> |
||||
|
<div |
||||
|
class="columns-setting w-full cursor-pointer" |
||||
|
(click)="handleColumnsSetting(columnsSettingTpl)" |
||||
|
> |
||||
|
<i nz-icon nzType="setting" class=""></i> |
||||
|
</div> |
||||
|
</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
@for (data of tableRef.data; track $index) { |
||||
|
<tr> |
||||
|
@if (options.config.selectable) { |
||||
|
<td |
||||
|
[nzChecked]="selected.has(String(data[options.config.rowKey!]))" |
||||
|
(nzCheckedChange)="onRowSelected($event, data)" |
||||
|
></td> |
||||
|
} |
||||
|
@for (td of options.columns; track td.key) { |
||||
|
<ng-container> |
||||
|
@if (td.visible) { |
||||
|
<td nzEllipsis [attr.title]="renderTitle(data[td.key])"> |
||||
|
<ng-container [ngSwitch]="td.key"> |
||||
|
<ng-container *ngSwitchDefault> |
||||
|
@if (renderColumn) { |
||||
|
<ng-container |
||||
|
[ngTemplateOutlet]="renderColumn" |
||||
|
[ngTemplateOutletContext]="{ |
||||
|
$implicit: data[td.key], |
||||
|
key: td.key, |
||||
|
row: data, |
||||
|
column: td |
||||
|
}" |
||||
|
> |
||||
|
</ng-container> |
||||
|
} @else { |
||||
|
{{ data[td.key] }} |
||||
|
} |
||||
|
</ng-container> |
||||
|
</ng-container> |
||||
|
</td> |
||||
|
} @else {} |
||||
|
</ng-container> |
||||
|
} |
||||
|
|
||||
|
@if (options.operate.length > 0) { |
||||
|
<td> |
||||
|
<button nz-button nzType="text" nz-dropdown [nzDropdownMenu]="menu"> |
||||
|
<span nz-icon nzType="more" nzTheme="outline" class="more-icon"></span> |
||||
|
</button> |
||||
|
<nz-dropdown-menu #menu="nzDropdownMenu"> |
||||
|
<ul nz-menu class="operate-list"> |
||||
|
@for (operate of options.operate; track operate.title) { |
||||
|
@if (operate?.visible(data)) { |
||||
|
<li |
||||
|
nz-menu-item |
||||
|
(click)="onOperateClick(operate, data)" |
||||
|
[routerLink]="operate.link" |
||||
|
[nzDanger]="operate.danger" |
||||
|
[nzDisabled]="operate.disabled" |
||||
|
> |
||||
|
{{ operate.title }} |
||||
|
</li> |
||||
|
} |
||||
|
} |
||||
|
</ul> |
||||
|
</nz-dropdown-menu> |
||||
|
</td> |
||||
|
} |
||||
|
<td></td> |
||||
|
</tr> |
||||
|
} |
||||
|
</tbody> |
||||
|
</nz-table> |
||||
|
</nz-card> |
||||
|
|
||||
|
<ng-template #totalTpl let-total> 共{{ total }}条 </ng-template> |
||||
@ -0,0 +1,90 @@ |
|||||
|
.form-items { |
||||
|
|
||||
|
|
||||
|
::ng-deep { |
||||
|
.ant-form-item { |
||||
|
margin-bottom: 0; |
||||
|
} |
||||
|
|
||||
|
.ant-form-item-label { |
||||
|
text-align: left; |
||||
|
} |
||||
|
|
||||
|
.ant-divider-horizontal { |
||||
|
margin: 12px 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.columns-setting { |
||||
|
background-color: #f2f3f5; |
||||
|
} |
||||
|
|
||||
|
.query-wrapper { |
||||
|
::ng-deep { |
||||
|
&>* { |
||||
|
@apply mr-3 mb-3; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.formgroup-container { |
||||
|
::ng-deep { |
||||
|
nz-form-item { |
||||
|
padding-left: 10px; |
||||
|
background-color: #fff; |
||||
|
border-radius: 4px; |
||||
|
|
||||
|
input { |
||||
|
border-color: transparent !important; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.server-paginated-table { |
||||
|
|
||||
|
.table-selected-action { |
||||
|
height: 41px; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 60px; |
||||
|
right: 0; |
||||
|
background-color: #f2f3f5; |
||||
|
} |
||||
|
|
||||
|
::ng-deep { |
||||
|
|
||||
|
table { |
||||
|
table-layout: fixed !important; |
||||
|
} |
||||
|
|
||||
|
.ant-table-thead>tr>th { |
||||
|
white-space: nowrap; |
||||
|
} |
||||
|
|
||||
|
.ant-table-thead>tr>th, |
||||
|
.ant-table-tbody>tr>td, |
||||
|
.ant-table tfoot>tr>th, |
||||
|
.ant-table tfoot>tr>td { |
||||
|
padding: 9px 16px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.operate-list { |
||||
|
::ng-deep { |
||||
|
&>li { |
||||
|
min-width: 150px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.more-icon { |
||||
|
::ng-deep { |
||||
|
svg { |
||||
|
transform: scale(1.3); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,359 @@ |
|||||
|
import { |
||||
|
ChangeDetectorRef, |
||||
|
Component, |
||||
|
ContentChild, |
||||
|
Input, |
||||
|
OnChanges, |
||||
|
OnInit, |
||||
|
SimpleChanges, |
||||
|
TemplateRef, |
||||
|
} from '@angular/core' |
||||
|
import { |
||||
|
AnyObject, |
||||
|
CacheStateInterface, |
||||
|
FetchDataInterface, |
||||
|
PaginationInterface, |
||||
|
ServerPaginatedTableService, |
||||
|
TableColumnsInterface, |
||||
|
persistKey, |
||||
|
} from './server-paginated-table.service' |
||||
|
import { finalize, map } from 'rxjs' |
||||
|
import { FormGroup } from '@angular/forms' |
||||
|
import { TableFormDirective } from './table-form.directive' |
||||
|
import { Router } from '@angular/router' |
||||
|
import { TableActionDirective } from './table-action.directive' |
||||
|
import { NzDrawerService } from 'ng-zorro-antd/drawer' |
||||
|
|
||||
|
export interface TableOperationInterface { |
||||
|
title: string |
||||
|
link?: string[] |
||||
|
premissions?: string[] |
||||
|
danger?: boolean |
||||
|
disabled?: boolean |
||||
|
onClick?: (v: any) => void |
||||
|
visible?: (v: any) => boolean |
||||
|
} |
||||
|
|
||||
|
export interface TableOptionConfigInterface { |
||||
|
rowKey?: string |
||||
|
cacheUrls: string[] |
||||
|
cacheKey?: string |
||||
|
manual?: boolean |
||||
|
frontPagination?: boolean |
||||
|
selectable?: boolean |
||||
|
} |
||||
|
|
||||
|
export class TableOption { |
||||
|
constructor(fetchData: FetchDataInterface) |
||||
|
constructor( |
||||
|
fetchData: FetchDataInterface, |
||||
|
columns?: TableColumnsInterface[], |
||||
|
operate?: TableOperationInterface[], |
||||
|
config?: Partial<TableOptionConfigInterface>, |
||||
|
) |
||||
|
constructor(fetchData: FetchDataInterface, config: Partial<TableOptionConfigInterface>) |
||||
|
constructor( |
||||
|
fetchData: FetchDataInterface, |
||||
|
configOrColumns?: Partial<TableOptionConfigInterface> | TableColumnsInterface[], |
||||
|
operate?: TableOperationInterface[], |
||||
|
config?: Partial<TableOptionConfigInterface>, |
||||
|
) { |
||||
|
this.fetchData = fetchData |
||||
|
let _config = config |
||||
|
if (Array.isArray(configOrColumns)) { |
||||
|
this.setColumn(configOrColumns) |
||||
|
} else if (configOrColumns) { |
||||
|
_config = configOrColumns |
||||
|
} |
||||
|
if (operate) { |
||||
|
this.setRowOperate(operate) |
||||
|
} |
||||
|
if (_config) { |
||||
|
this.setConfig(_config) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
config: TableOptionConfigInterface = { |
||||
|
cacheKey: persistKey + location.pathname, |
||||
|
cacheUrls: [], |
||||
|
} |
||||
|
|
||||
|
fetchData: FetchDataInterface |
||||
|
|
||||
|
columns: TableColumnsInterface[] = [] |
||||
|
|
||||
|
operate: TableOperationInterface[] = [] |
||||
|
|
||||
|
ref!: ServerPaginatedTableComponent |
||||
|
|
||||
|
columnsCacheKey = 'SERVER_PAGINATED_V1' |
||||
|
|
||||
|
setCommponentRef(ref: ServerPaginatedTableComponent) { |
||||
|
this.ref = ref |
||||
|
} |
||||
|
|
||||
|
setConfig(config: Partial<TableOptionConfigInterface>): TableOption { |
||||
|
this.config = { |
||||
|
...this.config, |
||||
|
rowKey: this.config.rowKey ?? config.rowKey ?? 'id', |
||||
|
...(config ?? {}), |
||||
|
} |
||||
|
return this |
||||
|
} |
||||
|
|
||||
|
setColumn(columns: TableColumnsInterface[]): TableOption { |
||||
|
const cachedColumns = this.getCachedColumns() |
||||
|
const cacheKey = this.config.cacheKey! |
||||
|
const currentTableColumns = cachedColumns[cacheKey] ?? [] |
||||
|
console.log('currentTableColumns', cachedColumns, cacheKey, currentTableColumns) |
||||
|
this.columns = columns.map((i) => { |
||||
|
const cachedItem = currentTableColumns.find((c) => c.key === i.key) |
||||
|
const visible = cachedItem?.visible ?? i.visible |
||||
|
return { |
||||
|
...i, |
||||
|
...cachedItem, |
||||
|
visible: typeof visible !== 'boolean' ? true : visible, |
||||
|
} |
||||
|
}) |
||||
|
return this |
||||
|
} |
||||
|
|
||||
|
setRowOperate(operate: TableOperationInterface[]): TableOption { |
||||
|
this.operate = operate.map((i) => { |
||||
|
return { |
||||
|
...i, |
||||
|
visible: i.visible ?? (() => true), |
||||
|
} |
||||
|
}) |
||||
|
return this |
||||
|
} |
||||
|
|
||||
|
getCachedColumns() { |
||||
|
let cachedColumns: null | Record<string, TableColumnsInterface[]> = null |
||||
|
try { |
||||
|
const str = localStorage.getItem(this.columnsCacheKey) |
||||
|
if (str) { |
||||
|
cachedColumns = JSON.parse(str) as Record<string, TableColumnsInterface[]> |
||||
|
} |
||||
|
} catch (error) {} |
||||
|
return cachedColumns ?? {} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-server-paginated-table', |
||||
|
templateUrl: './server-paginated-table.component.html', |
||||
|
styleUrls: ['./server-paginated-table.component.less'], |
||||
|
}) |
||||
|
export class ServerPaginatedTableComponent implements OnInit, OnChanges { |
||||
|
constructor( |
||||
|
private cdr: ChangeDetectorRef, |
||||
|
private pageTable: ServerPaginatedTableService, |
||||
|
private router: Router, |
||||
|
private drawer: NzDrawerService, |
||||
|
) {} |
||||
|
|
||||
|
@Input() options!: TableOption |
||||
|
|
||||
|
/** |
||||
|
* 查询表单 |
||||
|
* https://github.com/angular/angular/issues/13761
|
||||
|
*/ |
||||
|
@Input() formGroup?: FormGroup |
||||
|
|
||||
|
@Input() simpleSearch = false |
||||
|
|
||||
|
@Input() initValue: AnyObject = {} |
||||
|
|
||||
|
/** |
||||
|
* 在表格模式下渲染具体每一个列的模板。 |
||||
|
* $implicit:dataItem[col.key] `具体的值`、 |
||||
|
* key:col.key `字段名`、 |
||||
|
* row:dataItem `当前行数据`、 |
||||
|
* column:col `当前列`、 |
||||
|
*/ |
||||
|
@Input() renderColumn?: TemplateRef<{}> |
||||
|
|
||||
|
/** |
||||
|
* 不使用表格模式,自定义渲染 |
||||
|
*/ |
||||
|
@Input() renderItem?: TemplateRef<{}> |
||||
|
|
||||
|
@ContentChild(TableFormDirective, { static: true, read: TemplateRef }) |
||||
|
formContentChild!: TemplateRef<any> |
||||
|
|
||||
|
@ContentChild(TableActionDirective, { static: true, read: TemplateRef }) |
||||
|
actionContentChild!: TemplateRef<{ $implicit: number }> |
||||
|
|
||||
|
pagination: PaginationInterface = { |
||||
|
loading: false, |
||||
|
data: [], |
||||
|
total: 0, |
||||
|
totalPages: 0, |
||||
|
pageSize: 10, |
||||
|
pageIndex: 1, |
||||
|
} |
||||
|
|
||||
|
String = String |
||||
|
|
||||
|
selected = new Set<string>() |
||||
|
|
||||
|
indeterminate = false |
||||
|
|
||||
|
ngOnChanges(changes: SimpleChanges): void {} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
if (!this.options) { |
||||
|
throw new Error('请设置正确的options') |
||||
|
} |
||||
|
this.options.setCommponentRef(this) |
||||
|
// this.options.config.cacheKey = this.cacheKeyCreator()
|
||||
|
this.setCachedDataToState() |
||||
|
if (!this.options.config.manual) { |
||||
|
this.run() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private setCachedDataToState() { |
||||
|
const cachedState = this.pageTable.getCachedState() |
||||
|
if (cachedState) { |
||||
|
const cacheKey = this.options.config.cacheKey! |
||||
|
const currentPath = cachedState.paths[cacheKey] |
||||
|
const currentState = cachedState.state[cacheKey] |
||||
|
if (currentState && Array.isArray(currentPath) && currentPath.includes(this.router.url)) { |
||||
|
const { page, query } = currentState |
||||
|
this.pagination.pageIndex = page.pageIndex |
||||
|
this.pagination.pageSize = page.pageSize |
||||
|
this.formGroup?.patchValue(query) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
renderTitle(d: any) { |
||||
|
return typeof d === 'number' || typeof d === 'string' ? d : '' |
||||
|
} |
||||
|
|
||||
|
private saveCacheState() { |
||||
|
const cachedState = this.pageTable.getCachedState() |
||||
|
|
||||
|
const { pageIndex, pageSize } = this.pagination |
||||
|
const query = this.formGroup?.value ?? {} |
||||
|
const cacheKey = this.options.config.cacheKey! |
||||
|
|
||||
|
const state: CacheStateInterface = { |
||||
|
paths: { |
||||
|
...cachedState?.paths, |
||||
|
[cacheKey]: this.options.config.cacheUrls.concat(this.router.url), |
||||
|
}, |
||||
|
state: { |
||||
|
...cachedState?.state, |
||||
|
[cacheKey]: { |
||||
|
page: { pageIndex, pageSize }, |
||||
|
query, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
sessionStorage.setItem(persistKey, JSON.stringify(state)) |
||||
|
} |
||||
|
|
||||
|
private cacheKeyCreator() { |
||||
|
if (this.options.config.cacheKey) { |
||||
|
return this.options.config.cacheKey |
||||
|
} |
||||
|
|
||||
|
return persistKey + this.router.url.replaceAll('/', '_') |
||||
|
} |
||||
|
|
||||
|
private run() { |
||||
|
const { pageIndex, pageSize } = this.pagination |
||||
|
const page = this.pageTable.globalConfig.formatPaginationData({ pageIndex, pageSize }) |
||||
|
const query = this.formGroup?.value ?? {} |
||||
|
|
||||
|
this.pagination.loading = true |
||||
|
this.options |
||||
|
.fetchData(page, query) |
||||
|
.pipe( |
||||
|
map(this.pageTable.globalConfig.formatServiceData), |
||||
|
finalize(() => { |
||||
|
this.saveCacheState() |
||||
|
this.pagination.loading = false |
||||
|
}), |
||||
|
) |
||||
|
.subscribe((res) => { |
||||
|
this.pagination = { |
||||
|
...this.pagination, |
||||
|
...res, |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
reload() { |
||||
|
this.run() |
||||
|
} |
||||
|
|
||||
|
search() { |
||||
|
this.pagination.pageIndex = 1 |
||||
|
this.reload() |
||||
|
} |
||||
|
|
||||
|
reset() { |
||||
|
this.formGroup?.reset(this.initValue) |
||||
|
this.search() |
||||
|
} |
||||
|
|
||||
|
onPageChange() { |
||||
|
if (!this.options.config.frontPagination) { |
||||
|
this.reload() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onOperateClick(item: TableOperationInterface, data: any) { |
||||
|
if (item.onClick) { |
||||
|
item?.onClick(data) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onSubmit(e: Event) { |
||||
|
this.search() |
||||
|
} |
||||
|
|
||||
|
handleColumnsSetting(nzContent: TemplateRef<any>) { |
||||
|
this.drawer.create({ |
||||
|
nzTitle: '表头设置', |
||||
|
nzContent, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
onColumnsChange() { |
||||
|
const cacheKey = this.options.config.cacheKey! |
||||
|
const cachedColumns = this.options.getCachedColumns() |
||||
|
const newCachedColumns = { |
||||
|
...cachedColumns, |
||||
|
[cacheKey]: this.options.columns, |
||||
|
} |
||||
|
localStorage.setItem(this.options.columnsCacheKey, JSON.stringify(newCachedColumns)) |
||||
|
} |
||||
|
|
||||
|
onAllChecked(checked: boolean) { |
||||
|
if (checked) { |
||||
|
const rowKey = this.options.config.rowKey! |
||||
|
this.pagination.data.forEach((item: any) => { |
||||
|
this.selected.add(String(item[rowKey])) |
||||
|
}) |
||||
|
} else { |
||||
|
this.selected.clear() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onRowSelected(selected: boolean, row: any) { |
||||
|
const id = String(row[this.options.config.rowKey!]) |
||||
|
console.log('selected', selected, id) |
||||
|
if (selected) { |
||||
|
this.selected.add(id) |
||||
|
} else { |
||||
|
this.selected.delete(id) |
||||
|
} |
||||
|
console.log('this.selected', this.selected) |
||||
|
this.indeterminate = this.selected.size > 0 && this.selected.size < this.pagination.data.length |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
import { NgModule } from '@angular/core' |
||||
|
import { CommonModule } from '@angular/common' |
||||
|
import { NzTableModule } from 'ng-zorro-antd/table' |
||||
|
import { NzSpaceModule } from 'ng-zorro-antd/space' |
||||
|
import { NzButtonModule } from 'ng-zorro-antd/button' |
||||
|
import { NzDividerModule } from 'ng-zorro-antd/divider' |
||||
|
import { NzIconModule } from 'ng-zorro-antd/icon' |
||||
|
import { NzCardModule } from 'ng-zorro-antd/card' |
||||
|
import { NzFormModule } from 'ng-zorro-antd/form' |
||||
|
import { NzPopoverModule } from 'ng-zorro-antd/popover' |
||||
|
import { NzDropDownModule } from 'ng-zorro-antd/dropdown' |
||||
|
import { NzDrawerModule } from 'ng-zorro-antd/drawer' |
||||
|
import { NzCheckboxModule } from 'ng-zorro-antd/checkbox' |
||||
|
import { NzDatePickerModule } from 'ng-zorro-antd/date-picker' |
||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms' |
||||
|
import { ServerPaginatedTableComponent } from './server-paginated-table.component' |
||||
|
import { QueryItemComponent } from './query-item/query-item.component' |
||||
|
import { DateQueryComponent } from './date-query/date-query.component' |
||||
|
import { TableFormDirective } from './table-form.directive' |
||||
|
import { TableActionDirective } from './table-action.directive' |
||||
|
import { RouterModule } from '@angular/router' |
||||
|
|
||||
|
@NgModule({ |
||||
|
declarations: [ |
||||
|
ServerPaginatedTableComponent, |
||||
|
QueryItemComponent, |
||||
|
DateQueryComponent, |
||||
|
TableFormDirective, |
||||
|
TableActionDirective, |
||||
|
], |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
RouterModule, |
||||
|
FormsModule, |
||||
|
ReactiveFormsModule, |
||||
|
NzTableModule, |
||||
|
NzSpaceModule, |
||||
|
NzButtonModule, |
||||
|
NzDividerModule, |
||||
|
NzIconModule, |
||||
|
NzDropDownModule, |
||||
|
NzCardModule, |
||||
|
NzFormModule, |
||||
|
NzPopoverModule, |
||||
|
NzDatePickerModule, |
||||
|
NzDrawerModule, |
||||
|
NzCheckboxModule, |
||||
|
], |
||||
|
exports: [ |
||||
|
ServerPaginatedTableComponent, |
||||
|
QueryItemComponent, |
||||
|
DateQueryComponent, |
||||
|
TableFormDirective, |
||||
|
TableActionDirective, |
||||
|
], |
||||
|
}) |
||||
|
export class ServerPaginatedTableModule {} |
||||
@ -0,0 +1,95 @@ |
|||||
|
import { Injectable } from '@angular/core' |
||||
|
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router' |
||||
|
import { Observable, filter } from 'rxjs' |
||||
|
|
||||
|
export type AnyObject = Record<string, any> |
||||
|
|
||||
|
export interface ServerPaginatedTableConfigInterface { |
||||
|
formatServiceData: (v: any) => TableRequiredDataInterface |
||||
|
formatPaginationData: (v: PageSizeAndIndexInterface) => any |
||||
|
} |
||||
|
|
||||
|
export interface TableRequiredDataInterface<T = any> { |
||||
|
total: number |
||||
|
totalPages: number |
||||
|
data: T |
||||
|
} |
||||
|
|
||||
|
export type FetchDataInterface = (pagination: PageSizeAndIndexInterface, query: Record<string, any>) => Observable<any> |
||||
|
|
||||
|
export type PageSizeAndIndexInterface = { |
||||
|
pageSize: number |
||||
|
pageIndex: number |
||||
|
} |
||||
|
|
||||
|
export interface PaginationInterface extends TableRequiredDataInterface, PageSizeAndIndexInterface { |
||||
|
loading: boolean |
||||
|
} |
||||
|
|
||||
|
export interface TableColumnsInterface { |
||||
|
key: string |
||||
|
title: string |
||||
|
visible?: boolean |
||||
|
width?: string |
||||
|
sort?: boolean |
||||
|
order?: number |
||||
|
disabled?: boolean |
||||
|
coverStorage?: boolean |
||||
|
} |
||||
|
|
||||
|
export interface CacheStateInterface { |
||||
|
paths: Record<string, string[]> |
||||
|
state: Record< |
||||
|
string, |
||||
|
{ |
||||
|
query: AnyObject |
||||
|
page: PageSizeAndIndexInterface |
||||
|
} |
||||
|
> |
||||
|
} |
||||
|
|
||||
|
export const persistKey = 'SERVER_PAGINATED_TABLE_V1' |
||||
|
|
||||
|
@Injectable({ |
||||
|
providedIn: 'root', |
||||
|
}) |
||||
|
export class ServerPaginatedTableService { |
||||
|
constructor(private router: Router) {} |
||||
|
|
||||
|
globalConfig: ServerPaginatedTableConfigInterface = { |
||||
|
formatServiceData: (v: any) => v, |
||||
|
formatPaginationData: (v: any) => v, |
||||
|
} |
||||
|
|
||||
|
initial() { |
||||
|
this.router.events.pipe(filter((f): f is NavigationEnd => f instanceof NavigationEnd)).subscribe((e) => { |
||||
|
const cachedState = this.getCachedState() |
||||
|
if (cachedState) { |
||||
|
Object.entries(cachedState.paths).forEach(([cacheKey, persistPaths]) => { |
||||
|
if (!persistPaths.includes(e.url)) { |
||||
|
Reflect.deleteProperty(cachedState.state, cacheKey) |
||||
|
sessionStorage.setItem(persistKey, JSON.stringify(cachedState)) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
return this |
||||
|
} |
||||
|
|
||||
|
getCachedState(): CacheStateInterface | null { |
||||
|
let cachedState: null | CacheStateInterface = null |
||||
|
try { |
||||
|
const str = sessionStorage.getItem(persistKey) |
||||
|
if (str) { |
||||
|
cachedState = JSON.parse(str) as CacheStateInterface |
||||
|
} |
||||
|
} catch (error) {} |
||||
|
|
||||
|
return cachedState |
||||
|
} |
||||
|
|
||||
|
setConfig(config: Partial<ServerPaginatedTableConfigInterface>) { |
||||
|
this.globalConfig = { ...this.globalConfig, ...config } |
||||
|
return this |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
import { Directive, TemplateRef } from "@angular/core"; |
||||
|
|
||||
|
@Directive({ |
||||
|
selector: "[appTableAction]", |
||||
|
exportAs: "appTableAction", |
||||
|
}) |
||||
|
export class TableActionDirective { |
||||
|
constructor(public templateRef: TemplateRef<{}>) {} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
import { Directive, TemplateRef } from "@angular/core"; |
||||
|
|
||||
|
@Directive({ |
||||
|
selector: "[appTableForm]", |
||||
|
exportAs: "appTableForm", |
||||
|
}) |
||||
|
export class TableFormDirective { |
||||
|
constructor(public templateRef: TemplateRef<{}>) {} |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
<form nz-form [formGroup]="formGroup"> |
||||
|
@if (!data.value) { |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzSpan="4" nzRequired>用户名</nz-form-label> |
||||
|
<nz-form-control nzSpan="16" [nzErrorTip]="formErrorTipsTpl"> |
||||
|
<input nz-input placeholder="请输入用户名" formControlName="username" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzSpan="4" nzRequired>密码</nz-form-label> |
||||
|
<nz-form-control nzSpan="16" [nzErrorTip]="formErrorTipsTpl"> |
||||
|
<input nz-input type="password" placeholder="请输入密码" formControlName="password" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
} |
||||
|
|
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzSpan="4" nzRequired>姓名</nz-form-label> |
||||
|
<nz-form-control nzSpan="16" [nzErrorTip]="formErrorTipsTpl"> |
||||
|
<input nz-input placeholder="请输入姓名" formControlName="name" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
|
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzSpan="4" nzRequired>角色</nz-form-label> |
||||
|
<nz-form-control nzSpan="16" [nzErrorTip]="formErrorTipsTpl"> |
||||
|
<nz-select nzPlaceHolder="请选择角色" formControlName="roleId"> |
||||
|
@for (item of data.roleList; track item.id) { |
||||
|
<nz-option [nzValue]="item.id" [nzLabel]="item.roleName"></nz-option> |
||||
|
} |
||||
|
</nz-select> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
|
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzSpan="4">备注</nz-form-label> |
||||
|
<nz-form-control nzSpan="16"> |
||||
|
<textarea nz-input placeholder="请输入备注" formControlName="remark"></textarea> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
<!-- <nz-form-item> |
||||
|
<nz-form-label nzSpan="4">登录限制</nz-form-label> |
||||
|
<nz-form-control nzSpan="16"> |
||||
|
<nz-switch></nz-switch> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzSpan="4">限制规则</nz-form-label> |
||||
|
<nz-form-control nzSpan="16"> |
||||
|
<div> |
||||
|
<nz-input-group nzAddOnBefore="登录失败达到" nzAddOnAfter="次"> |
||||
|
<input type="number" nz-input /> |
||||
|
</nz-input-group> |
||||
|
</div> |
||||
|
<div class="mt-2"> |
||||
|
<ng-template #nzAddOnAfterTpl> |
||||
|
<nz-select class="!w-20"> |
||||
|
<nz-option nzValue="m" nzLabel="分钟"></nz-option> |
||||
|
<nz-option nzValue="h" nzLabel="小时"></nz-option> |
||||
|
<nz-option nzValue="d" nzLabel="天"></nz-option> |
||||
|
</nz-select> |
||||
|
</ng-template> |
||||
|
<nz-input-group nzAddOnBefore="锁定账号" [nzAddOnAfter]="nzAddOnAfterTpl"> |
||||
|
<input type="number" nz-input /> |
||||
|
</nz-input-group> |
||||
|
</div> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> --> |
||||
|
<!-- <nz-form-item> |
||||
|
<nz-form-control nzOffset="4"> |
||||
|
<button nz-button nzType="primary">保存</button> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> --> |
||||
|
</form> |
||||
|
|
||||
|
<ng-template #formErrorTipsTpl let-control> |
||||
|
<form-error-tips [control]="control"></form-error-tips> |
||||
|
</ng-template> |
||||
@ -0,0 +1,46 @@ |
|||||
|
import { Component, OnInit, inject } from '@angular/core' |
||||
|
import { FormBuilder, FormControl, FormGroup } from '@angular/forms' |
||||
|
import { RoleListDTO } from 'app/services/api.dto' |
||||
|
import { SharedModule } from 'app/shared/shared.module' |
||||
|
import { FormValidators } from 'app/utils' |
||||
|
import { NZ_MODAL_DATA } from 'ng-zorro-antd/modal' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-user-form', |
||||
|
standalone: true, |
||||
|
imports: [SharedModule], |
||||
|
templateUrl: './user-form.component.html', |
||||
|
styleUrl: './user-form.component.less', |
||||
|
}) |
||||
|
export class UserFormComponent implements OnInit { |
||||
|
constructor(private fb: FormBuilder) {} |
||||
|
|
||||
|
data = inject<{ roleList: RoleListDTO[]; value: {} }>(NZ_MODAL_DATA) |
||||
|
|
||||
|
formGroup!: FormGroup |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.formGroup = this.fb.group({ |
||||
|
name: new FormControl('', [FormValidators.required('请输入姓名'), FormValidators.maxLength(30)]), |
||||
|
roleId: new FormControl('', [FormValidators.required('请选择角色')]), |
||||
|
remark: new FormControl(''), |
||||
|
}) |
||||
|
if (this.data.value) { |
||||
|
this.formGroup.patchValue(this.data.value) |
||||
|
} else { |
||||
|
this.formGroup.addControl( |
||||
|
'username', |
||||
|
new FormControl('', [FormValidators.required('请输入用户名'), FormValidators.maxLength(30)]), |
||||
|
) |
||||
|
this.formGroup.addControl('password', new FormControl('', [FormValidators.required('请输入密码')])) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public getValues() { |
||||
|
let values = null |
||||
|
if (FormValidators.validateFormGroup(this.formGroup)) { |
||||
|
values = this.formGroup.value |
||||
|
} |
||||
|
return values |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
import { inject } from '@angular/core' |
||||
|
import { CanActivateFn, Router, UrlTree } from '@angular/router' |
||||
|
import { ApiService, LocalHttpInterceptorService } from 'app/services' |
||||
|
import { map } from 'rxjs' |
||||
|
|
||||
|
export const authGuard: CanActivateFn = (route, state) => { |
||||
|
const local = inject(LocalHttpInterceptorService) |
||||
|
const accessData = local.getAccess() |
||||
|
let token = accessData?.access_token |
||||
|
if (token) { |
||||
|
return true |
||||
|
} |
||||
|
const router = inject(Router) |
||||
|
return router.createUrlTree(['/login']) |
||||
|
// const api = inject(ApiService)
|
||||
|
// if (!localStorage.getItem('sc')) {
|
||||
|
// return router.createUrlTree(['/login'])
|
||||
|
// }
|
||||
|
// return api.getAuthInfo(true).pipe(map((r) => !!r))
|
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
export * from "./auth.guard"; |
||||
@ -0,0 +1,12 @@ |
|||||
|
<form nz-form [formGroup]="formGroup"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label> 部门名称 </nz-form-label> |
||||
|
<nz-form-control [nzErrorTip]="formErrorTipsTpl"> |
||||
|
<input nz-input placeholder="请输入部门名称" formControlName="name" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</form> |
||||
|
|
||||
|
<ng-template #formErrorTipsTpl let-control> |
||||
|
<form-error-tips [control]="control"></form-error-tips> |
||||
|
</ng-template> |
||||
@ -0,0 +1,38 @@ |
|||||
|
import { Component, OnInit, inject } from '@angular/core' |
||||
|
import { FormControl, FormGroup } from '@angular/forms' |
||||
|
import { SharedModule } from 'app/shared/shared.module' |
||||
|
import { FormValidators } from 'app/utils' |
||||
|
import { NZ_MODAL_DATA } from 'ng-zorro-antd/modal' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-contact-group-form', |
||||
|
standalone: true, |
||||
|
imports: [SharedModule], |
||||
|
templateUrl: './contact-group-form.component.html', |
||||
|
styleUrl: './contact-group-form.component.less', |
||||
|
}) |
||||
|
export class ContactGroupFormComponent implements OnInit { |
||||
|
constructor() {} |
||||
|
|
||||
|
data = inject(NZ_MODAL_DATA) |
||||
|
|
||||
|
formGroup = new FormGroup({ |
||||
|
id: new FormControl(), |
||||
|
name: new FormControl('', [FormValidators.required('请输入部门名称'), FormValidators.maxLength(30)]), |
||||
|
parentId: new FormControl(), |
||||
|
}) |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
if (this.data) { |
||||
|
this.formGroup.patchValue(this.data) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public getValues() { |
||||
|
let values = null |
||||
|
if (FormValidators.validateFormGroup(this.formGroup)) { |
||||
|
values = this.formGroup.value |
||||
|
} |
||||
|
return values |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
<div class="data-tree"> |
||||
|
<nz-tree |
||||
|
nzBlockNode |
||||
|
[nzSelectedKeys]="contact.selecedKeys" |
||||
|
[nzExpandedKeys]="contact.expandedKeys" |
||||
|
[nzData]="nodes" |
||||
|
(nzClick)="activeNode($event)" |
||||
|
(nzExpandChange)="handleExpandedKeysChange($event)" |
||||
|
[nzTreeTemplate]="nzTreeTemplate" |
||||
|
> |
||||
|
</nz-tree> |
||||
|
</div> |
||||
|
|
||||
|
<ng-template #nzTreeTemplate let-node let-origin="origin"> |
||||
|
<div class="flex items-center justify-between overflow-hidden"> |
||||
|
<span class="flex-1 overflow-hidden text-ellipsis">{{ node.title }}</span> |
||||
|
|
||||
|
@if (origin.id !== '1') { |
||||
|
<button nz-button nzType="text" nz-dropdown [nzDropdownMenu]="menu" (click)="$event.stopPropagation()"> |
||||
|
<i nz-icon nzType="more"></i> |
||||
|
</button> |
||||
|
} |
||||
|
<nz-dropdown-menu #menu="nzDropdownMenu"> |
||||
|
<ul nz-menu> |
||||
|
<!-- <li nz-menu-item (click)="createGroup(node)">新增下级部门</li> --> |
||||
|
<li nz-menu-item (click)="editGroup(node)">编辑</li> |
||||
|
<li nz-menu-item (click)="deleteGroup(node)">删除</li> |
||||
|
</ul> |
||||
|
</nz-dropdown-menu> |
||||
|
</div> |
||||
|
</ng-template> |
||||
@ -0,0 +1,83 @@ |
|||||
|
import { Component, EventEmitter, Input, Output, OnChanges, OnInit, SimpleChanges } from '@angular/core' |
||||
|
|
||||
|
import { NzContextMenuService, NzDropdownMenuComponent } from 'ng-zorro-antd/dropdown' |
||||
|
import { NzFormatEmitEvent, NzTreeNode, NzTreeNodeOptions } from 'ng-zorro-antd/tree' |
||||
|
import { SharedModule } from 'app/shared/shared.module' |
||||
|
import { Persist } from 'app/utils' |
||||
|
import { ContactService } from '../contact.service' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-contact-group', |
||||
|
standalone: true, |
||||
|
imports: [SharedModule], |
||||
|
providers: [ContactService], |
||||
|
templateUrl: './contact-group.component.html', |
||||
|
styleUrl: './contact-group.component.less', |
||||
|
}) |
||||
|
export class ContactGroupComponent implements OnInit, OnChanges { |
||||
|
constructor( |
||||
|
private nzContextMenuService: NzContextMenuService, |
||||
|
public contact: ContactService, |
||||
|
) {} |
||||
|
|
||||
|
@Input() nodes: NzTreeNodeOptions[] = [] |
||||
|
|
||||
|
@Input() active!: Persist |
||||
|
|
||||
|
@Output() onSelect = new EventEmitter() |
||||
|
|
||||
|
@Output() onCreate = new EventEmitter() |
||||
|
|
||||
|
@Output() onEdit = new EventEmitter() |
||||
|
|
||||
|
@Output() onDelete = new EventEmitter() |
||||
|
|
||||
|
ngOnInit(): void {} |
||||
|
|
||||
|
ngOnChanges(changes: SimpleChanges) {} |
||||
|
|
||||
|
openFolder(data: NzTreeNode | NzFormatEmitEvent): void { |
||||
|
if (data instanceof NzTreeNode) { |
||||
|
data.isExpanded = !data.isExpanded |
||||
|
} else { |
||||
|
const node = data.node |
||||
|
if (node) { |
||||
|
node.isExpanded = !node.isExpanded |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
activeNode(data: NzFormatEmitEvent): void { |
||||
|
this.contact.contactGroup.update((state) => { |
||||
|
return { |
||||
|
...state, |
||||
|
selecedKeys: [data.node!.key], |
||||
|
} |
||||
|
}) |
||||
|
this.onSelect.emit() |
||||
|
} |
||||
|
|
||||
|
handleExpandedKeysChange(expanded: NzFormatEmitEvent) { |
||||
|
this.contact.contactGroup.update((state) => { |
||||
|
return { |
||||
|
...state, |
||||
|
expandedKeys: expanded?.keys ?? [], |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
contextMenu($event: MouseEvent, menu: NzDropdownMenuComponent): void { |
||||
|
this.nzContextMenuService.create($event, menu) |
||||
|
} |
||||
|
|
||||
|
createGroup(node: NzTreeNode): void { |
||||
|
this.onCreate.emit(node.origin) |
||||
|
} |
||||
|
|
||||
|
editGroup(node: NzTreeNode): void { |
||||
|
this.onEdit.emit(node.origin) |
||||
|
} |
||||
|
deleteGroup(node: NzTreeNode): void { |
||||
|
this.onDelete.emit(node.origin.key) |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,97 @@ |
|||||
|
<nz-card [nzBordered]="false" [nzBodyStyle]="{ padding: 0, height }"> |
||||
|
<div class="flex contacts h-full"> |
||||
|
<div class="group w-56 h-full flex flex-col"> |
||||
|
<div> |
||||
|
<h3 class="px-4 pt-4 flex items-center justify-between"> |
||||
|
<span>部门</span> |
||||
|
<button nz-button nzType="text" (click)="showGroupForm()"> |
||||
|
<i nz-icon nzType="plus"></i> |
||||
|
</button> |
||||
|
</h3> |
||||
|
</div> |
||||
|
|
||||
|
<div class="flex-1 overflow-y-auto"> |
||||
|
<app-contact-group |
||||
|
[nodes]="groups" |
||||
|
(onSelect)="handleGroupSelect()" |
||||
|
(onCreate)="showGroupForm($event)" |
||||
|
(onEdit)="showGroupForm($event, true)" |
||||
|
(onDelete)="deleteUserGroup($event)" |
||||
|
> |
||||
|
</app-contact-group> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="flex-1 p-4 overflow-auto"> |
||||
|
<app-server-paginated-table |
||||
|
[formGroup]="queryForm" |
||||
|
[renderColumn]="renderColumnTpl" |
||||
|
[options]="tableOption" |
||||
|
> |
||||
|
<div *appTableForm nz-row [nzGutter]="[24, 24]"> |
||||
|
<div nz-col nzSpan="8"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzFlex="80px">用户名</nz-form-label> |
||||
|
<nz-form-control nzFlex="1"> |
||||
|
<input nz-input placeholder="请输入用户名" formControlName="userName" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</div> |
||||
|
<div nz-col nzSpan="8"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzFlex="80px">姓名</nz-form-label> |
||||
|
<nz-form-control nzFlex="1"> |
||||
|
<input nz-input placeholder="请输入姓名" formControlName="name" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</div> |
||||
|
<div nz-col nzSpan="8"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzFlex="80px">状态</nz-form-label> |
||||
|
<nz-form-control nzFlex="1"> |
||||
|
<nz-select nzPlaceHolder="请选择状态" formControlName="enable" nzAllowClear> |
||||
|
<nz-option [nzValue]="true" nzLabel="启用"></nz-option> |
||||
|
<nz-option [nzValue]="false" nzLabel="禁用"></nz-option> |
||||
|
</nz-select> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</div> |
||||
|
<div nz-col nzSpan="8"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzFlex="80px">创建时间</nz-form-label> |
||||
|
<nz-form-control nzFlex="1"> |
||||
|
<!-- <nz-range-picker formControlName="createTime"></nz-range-picker> --> |
||||
|
<app-range-picker [showTime]="true" formControlName="createTime"></app-range-picker> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div *appTableAction> |
||||
|
<nz-space> |
||||
|
<button nz-button nzType="primary" (click)="handleCreateUser()">添加用户</button> |
||||
|
</nz-space> |
||||
|
</div> |
||||
|
<ng-template #renderColumnTpl let-data let-key="key"> |
||||
|
@switch (key) { |
||||
|
@case ('enable') { |
||||
|
@if (data) { |
||||
|
<nz-badge nzStatus="success" nzText="启用"></nz-badge> |
||||
|
} @else { |
||||
|
<nz-badge nzStatus="error" nzText="禁用"></nz-badge> |
||||
|
} |
||||
|
} |
||||
|
@case ('roleName') { |
||||
|
@if (data) { |
||||
|
<nz-tag>{{ data }}</nz-tag> |
||||
|
} @else { |
||||
|
- |
||||
|
} |
||||
|
} |
||||
|
@default { |
||||
|
{{ data }} |
||||
|
} |
||||
|
} |
||||
|
</ng-template> |
||||
|
</app-server-paginated-table> |
||||
|
</div> |
||||
|
</div> |
||||
|
</nz-card> |
||||
@ -0,0 +1,3 @@ |
|||||
|
.group { |
||||
|
border-right: 1px solid var(--border-color); |
||||
|
} |
||||
@ -0,0 +1,223 @@ |
|||||
|
import { Component, HostListener, OnInit, OnDestroy } from '@angular/core' |
||||
|
import { lastValueFrom } from 'rxjs' |
||||
|
import { NzTreeNodeOptions } from 'ng-zorro-antd/tree' |
||||
|
import { NzModalService } from 'ng-zorro-antd/modal' |
||||
|
import { FormControl, FormGroup } from '@angular/forms' |
||||
|
import { NzMessageService } from 'ng-zorro-antd/message' |
||||
|
import { format } from 'date-fns' |
||||
|
import { ApiService, UserGroupTreeItem } from 'app/services' |
||||
|
import { SharedModule } from 'app/shared/shared.module' |
||||
|
import { ContactGroupComponent } from '../contact-group/contact-group.component' |
||||
|
import { ContactGroupFormComponent } from '../contact-group-form/contact-group-form.component' |
||||
|
import { AnyObject, TableOption } from 'app/components/server-paginated-table' |
||||
|
import { UserFormComponent } from 'app/components/user-form/user-form.component' |
||||
|
import { RoleListDTO, UserDTO } from 'app/services/api.dto' |
||||
|
import { ContactService } from '../contact.service' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-contact-list', |
||||
|
standalone: true, |
||||
|
imports: [SharedModule, ContactGroupComponent, ContactGroupFormComponent], |
||||
|
templateUrl: './contact-list.component.html', |
||||
|
styleUrl: './contact-list.component.less', |
||||
|
providers: [ContactService], |
||||
|
}) |
||||
|
export class ContactListComponent implements OnInit, OnDestroy { |
||||
|
constructor( |
||||
|
private api: ApiService, |
||||
|
private modal: NzModalService, |
||||
|
private msg: NzMessageService, |
||||
|
private contact: ContactService, |
||||
|
) {} |
||||
|
|
||||
|
height = 'auto' |
||||
|
|
||||
|
groups: NzTreeNodeOptions[] = [] |
||||
|
|
||||
|
tableOption = new TableOption(this.fetchData.bind(this), { |
||||
|
manual: true, |
||||
|
}) |
||||
|
|
||||
|
queryForm = new FormGroup({ |
||||
|
userName: new FormControl(), |
||||
|
enable: new FormControl(), |
||||
|
name: new FormControl(), |
||||
|
createTime: new FormControl(), |
||||
|
}) |
||||
|
|
||||
|
roleList: RoleListDTO[] = [] |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.initTableOption() |
||||
|
this.onResize() |
||||
|
this.getUserGroupTree() |
||||
|
this.getRoleList() |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy(): void {} |
||||
|
|
||||
|
initTableOption() { |
||||
|
this.tableOption |
||||
|
.setColumn([ |
||||
|
{ key: 'username', title: '用户名' }, |
||||
|
{ key: 'name', title: '姓名' }, |
||||
|
{ key: 'enable', title: '状态' }, |
||||
|
{ key: 'roleName', title: '角色' }, |
||||
|
{ key: 'remark', title: '说明' }, |
||||
|
{ key: 'createTime', title: '创建时间' }, |
||||
|
]) |
||||
|
.setRowOperate([ |
||||
|
{ title: '启用', onClick: this.updateStatus.bind(this), visible: (v) => v.id !== '1' && !v.enable }, |
||||
|
{ title: '禁用', onClick: this.updateStatus.bind(this), visible: (v) => v.id !== '1' && v.enable }, |
||||
|
{ |
||||
|
title: '重置密码', |
||||
|
onClick: this.resetPassword.bind(this), |
||||
|
premissions: [], |
||||
|
visible: (v) => v.id !== '1', |
||||
|
}, |
||||
|
{ |
||||
|
title: '编辑', |
||||
|
onClick: this.handleCreateUser.bind(this), |
||||
|
premissions: [], |
||||
|
visible: (v) => v.id !== '1', |
||||
|
}, |
||||
|
{ |
||||
|
title: '删除', |
||||
|
onClick: this.deleteUser.bind(this), |
||||
|
premissions: [], |
||||
|
visible: (v) => v.id !== '1', |
||||
|
}, |
||||
|
]) |
||||
|
} |
||||
|
|
||||
|
@HostListener('window:resize') |
||||
|
onResize() { |
||||
|
this.height = innerHeight - 100 + 'px' |
||||
|
} |
||||
|
|
||||
|
fetchData(p: {}, q: AnyObject) { |
||||
|
let query = Object.create(null) |
||||
|
const activeGroup = this.contact.contactGroup.value |
||||
|
if (Array.isArray(q['createTime'])) { |
||||
|
const createTimeStart = q['createTime']?.[0] |
||||
|
const createTimeEnd = q['createTime']?.[1] |
||||
|
|
||||
|
q['createTimeStart'] = createTimeStart ? format(new Date(createTimeStart), 'yyyy-MM-dd HH:mm:ss') : '' |
||||
|
q['createTimeEnd'] = createTimeEnd ? format(new Date(createTimeEnd), 'yyyy-MM-dd HH:mm:ss') : '' |
||||
|
} |
||||
|
query = { ...q, groupId: activeGroup?.selecedKeys[0] } |
||||
|
return this.api.getUserPage(p, query) |
||||
|
} |
||||
|
|
||||
|
handleGroupSelect() { |
||||
|
this.tableOption.ref?.search() |
||||
|
} |
||||
|
|
||||
|
updateStatus(v: UserDTO) { |
||||
|
this.modal.confirm({ |
||||
|
nzTitle: '警告', |
||||
|
nzContent: `是否要${v.enable ? '禁用' : '启用'}该用户?`, |
||||
|
nzOnOk: async () => { |
||||
|
await lastValueFrom(this.api.updateUserStatus(v.id)) |
||||
|
this.msg.success('操作成功') |
||||
|
this.tableOption.ref.reload() |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
getUserGroupTree() { |
||||
|
this.api.getUserGroupTree().subscribe((res) => { |
||||
|
this.groups = res |
||||
|
this.contact.initState(res) |
||||
|
if (res.length > 0) { |
||||
|
this.tableOption.ref?.reload() |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
getRoleList() { |
||||
|
this.api.getAllRole().subscribe((res) => { |
||||
|
this.roleList = res.data |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
showGroupForm(group?: UserGroupTreeItem, edit = false) { |
||||
|
this.modal.create({ |
||||
|
nzTitle: edit ? '编辑部门' : '新增部门', |
||||
|
nzContent: ContactGroupFormComponent, |
||||
|
nzData: edit ? group : { parentId: group?.id }, |
||||
|
nzOnOk: async (e) => { |
||||
|
const vals = e.getValues() |
||||
|
if (vals) { |
||||
|
await lastValueFrom(this.api.saveUserGroup(vals as UserGroupTreeItem)) |
||||
|
this.msg.success('保存成功') |
||||
|
this.getUserGroupTree() |
||||
|
return true |
||||
|
} |
||||
|
return false |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
deleteUserGroup(id: string) { |
||||
|
this.modal.confirm({ |
||||
|
nzTitle: '警告', |
||||
|
nzContent: '是否要删除该分组?', |
||||
|
nzOkDanger: true, |
||||
|
nzOnOk: async () => { |
||||
|
await lastValueFrom(this.api.deleteUserGroup(id)) |
||||
|
this.msg.success('删除成功') |
||||
|
this.getUserGroupTree() |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
deleteUser(v: UserDTO) { |
||||
|
this.modal.confirm({ |
||||
|
nzTitle: '警告', |
||||
|
nzContent: '是否要删除该用户?', |
||||
|
nzOkDanger: true, |
||||
|
nzOnOk: async () => { |
||||
|
await lastValueFrom(this.api.deleteUser(v.id)) |
||||
|
this.msg.success('删除成功') |
||||
|
this.getUserGroupTree() |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
resetPassword(v: UserDTO) { |
||||
|
this.modal.confirm({ |
||||
|
nzTitle: '警告', |
||||
|
nzContent: '是否要将该用户的密码重置为jwkj?', |
||||
|
nzOnOk: async () => { |
||||
|
await lastValueFrom(this.api.changeUserPassword(v.id, 'jwkj')) |
||||
|
this.msg.success('重置成功') |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
handleCreateUser(user?: UserDTO) { |
||||
|
this.modal.create({ |
||||
|
nzTitle: user ? '编辑用户' : '添加用户', |
||||
|
nzContent: UserFormComponent, |
||||
|
nzWidth: 720, |
||||
|
nzData: { |
||||
|
roleList: this.roleList, |
||||
|
value: user, |
||||
|
}, |
||||
|
nzOnOk: async (e) => { |
||||
|
const vals = e.getValues() |
||||
|
if (vals) { |
||||
|
const activeGroup = this.contact.contactGroup.value |
||||
|
await lastValueFrom( |
||||
|
this.api.saveUser({ ...(user ?? {}), ...vals, groupId: activeGroup.selecedKeys[0] }), |
||||
|
) |
||||
|
this.tableOption.ref?.reload() |
||||
|
this.msg.success('保存成功') |
||||
|
return true |
||||
|
} |
||||
|
return false |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
import { Injectable, OnDestroy } from '@angular/core' |
||||
|
import { Persist, Utils } from 'app/utils' |
||||
|
import { NzTreeNodeOptions } from 'ng-zorro-antd/tree' |
||||
|
|
||||
|
@Injectable() |
||||
|
export class ContactService implements OnDestroy { |
||||
|
constructor() {} |
||||
|
|
||||
|
ngOnDestroy(): void { |
||||
|
this.contactGroup.clear() |
||||
|
} |
||||
|
|
||||
|
contactGroup = new Persist<{ selecedKeys: string[]; expandedKeys: string[] }>(sessionStorage, 'contactGroup', { |
||||
|
selecedKeys: [], |
||||
|
expandedKeys: [], |
||||
|
}) |
||||
|
|
||||
|
public get selecedKeys(): string[] { |
||||
|
return this.contactGroup.value.selecedKeys |
||||
|
} |
||||
|
|
||||
|
public get expandedKeys(): string[] { |
||||
|
return this.contactGroup.value.expandedKeys |
||||
|
} |
||||
|
|
||||
|
initState(treeData: NzTreeNodeOptions[]) { |
||||
|
if (treeData.length === 0) { |
||||
|
return |
||||
|
} |
||||
|
let selecedKeys = this.selecedKeys |
||||
|
let expandedKeys = this.expandedKeys |
||||
|
const flatData: NzTreeNodeOptions[] = Utils.treeToFlatList(treeData) |
||||
|
if (!flatData.some((s) => selecedKeys.includes(s.key))) { |
||||
|
selecedKeys = [] |
||||
|
} |
||||
|
if (selecedKeys.length === 0) { |
||||
|
selecedKeys = [treeData[0].key] as string[] |
||||
|
} else { |
||||
|
expandedKeys = this.setExpandedKeysBySelecedKeys(treeData, selecedKeys) |
||||
|
} |
||||
|
this.contactGroup.update(() => { |
||||
|
return { |
||||
|
expandedKeys: [...new Set(expandedKeys)], |
||||
|
selecedKeys, |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
setExpandedKeysBySelecedKeys(flatData: NzTreeNodeOptions[], selecedKeys: string[]) { |
||||
|
const res: string[] = [] |
||||
|
|
||||
|
let parentId = flatData.find((f) => selecedKeys.includes(f.key))?.['parentId'] |
||||
|
while (parentId) { |
||||
|
res.push(parentId) |
||||
|
parentId = flatData.find((f) => f.key === parentId)?.['parentId'] |
||||
|
} |
||||
|
return this.expandedKeys.concat(res) |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,83 @@ |
|||||
|
<nz-card [nzBordered]="false"> |
||||
|
<form nz-form class="w-[800px]" [formGroup]="formGroup" (ngSubmit)="onSubmit()"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzRequired nzSpan="4"> 角色名称 </nz-form-label> |
||||
|
<nz-form-control> |
||||
|
<input nz-input placeholder="请输入角色名称" formControlName="roleName" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
|
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzRequired nzSpan="4"> 权限 </nz-form-label> |
||||
|
<nz-form-control> |
||||
|
<nz-spin [nzSpinning]="permissionsLoading"> |
||||
|
<div class="bordered min-h-[20px]"> |
||||
|
<ul class="permissions"> |
||||
|
@for (p of permissions; track $index) { |
||||
|
<li> |
||||
|
<div |
||||
|
nz-checkbox |
||||
|
[nzChecked]="checked.has(p.id)" |
||||
|
(nzCheckedChange)="onPermissionChange($event, p.id)" |
||||
|
[nzValue]="p.id" |
||||
|
> |
||||
|
{{ p.name }} |
||||
|
</div> |
||||
|
</li> |
||||
|
} |
||||
|
</ul> |
||||
|
<!-- <nz-collapse nzGhost> |
||||
|
@for (item of permissions; track $index) { |
||||
|
<nz-collapse-panel [nzHeader]="item.name" [nzExtra]="nzExtraTpl"> |
||||
|
<ng-template #nzExtraTpl> |
||||
|
<label |
||||
|
nz-checkbox |
||||
|
[nzIndeterminate]="checkedAll.get(item.id)?.nzIndeterminate" |
||||
|
[nzChecked]="checkedAll.get(item.id)?.nzChecked" |
||||
|
(click)="$event.stopPropagation()" |
||||
|
(nzCheckedChange)="checkAllChange($event, item)" |
||||
|
> |
||||
|
全选 |
||||
|
</label> |
||||
|
</ng-template> |
||||
|
<div nz-row [nzGutter]="[12, 12]"> |
||||
|
@for (p of item.children; track $index) { |
||||
|
<div nz-col nzSpan="8"> |
||||
|
<label |
||||
|
nz-checkbox |
||||
|
[nzChecked]="checked.has(p.id)" |
||||
|
[nzValue]="p.id" |
||||
|
(nzCheckedChange)="onPermissionChange($event, p.id, item)" |
||||
|
> |
||||
|
{{ p.name }} |
||||
|
</label> |
||||
|
</div> |
||||
|
} @empty { |
||||
|
<div nz-col nzSpan="24"> |
||||
|
<nz-empty></nz-empty> |
||||
|
</div> |
||||
|
} |
||||
|
</div> |
||||
|
</nz-collapse-panel> |
||||
|
} |
||||
|
</nz-collapse> --> |
||||
|
</div> |
||||
|
</nz-spin> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzSpan="4"> 说明 </nz-form-label> |
||||
|
<nz-form-control> |
||||
|
<textarea nz-input formControlName="remark" placeholder="请输入说明"></textarea> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
<nz-form-item> |
||||
|
<nz-form-control nzOffset="4"> |
||||
|
<nz-space> |
||||
|
<button nz-button nzType="primary" *nzSpaceItem>保存</button> |
||||
|
<button nz-button *nzSpaceItem [routerLink]="['/', 'role']">取消</button> |
||||
|
</nz-space> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</form> |
||||
|
</nz-card> |
||||
@ -0,0 +1,11 @@ |
|||||
|
.permissions { |
||||
|
li { |
||||
|
display: flex; |
||||
|
padding: 8px 24px; |
||||
|
|
||||
|
&:not(:last-child) { |
||||
|
|
||||
|
border-bottom: 1px solid var(--border-color); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,146 @@ |
|||||
|
import { finalize } from 'rxjs' |
||||
|
import { Component, OnInit } from '@angular/core' |
||||
|
import { FormControl, FormGroup } from '@angular/forms' |
||||
|
import { ActivatedRoute, Router } from '@angular/router' |
||||
|
import { ApiService } from 'app/services' |
||||
|
import { PermissionDTO } from 'app/services/api.dto' |
||||
|
import { SharedModule } from 'app/shared/shared.module' |
||||
|
import { FormValidators } from 'app/utils' |
||||
|
import { NzMessageService } from 'ng-zorro-antd/message' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-role-form', |
||||
|
standalone: true, |
||||
|
imports: [SharedModule], |
||||
|
templateUrl: './role-form.component.html', |
||||
|
styleUrl: './role-form.component.less', |
||||
|
}) |
||||
|
export class RoleFormComponent implements OnInit { |
||||
|
constructor( |
||||
|
private api: ApiService, |
||||
|
private msg: NzMessageService, |
||||
|
private route: ActivatedRoute, |
||||
|
private router: Router, |
||||
|
// private modal: NzModalService,
|
||||
|
) {} |
||||
|
|
||||
|
id: string | null = null |
||||
|
|
||||
|
permissions: PermissionDTO[] = [] |
||||
|
|
||||
|
formGroup = new FormGroup({ |
||||
|
roleName: new FormControl('', [FormValidators.required('请输入角色名称'), FormValidators.maxLength(30)]), |
||||
|
remark: new FormControl('', [FormValidators.maxLength(30)]), |
||||
|
}) |
||||
|
|
||||
|
checkedAll = new Map<string, { nzIndeterminate: boolean; nzChecked: boolean }>() |
||||
|
|
||||
|
checked = new Set<string>() |
||||
|
|
||||
|
permissionsLoading = false |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.getPermissions() |
||||
|
const id = this.route.snapshot.paramMap.get('id') |
||||
|
if (id && id !== 'create') { |
||||
|
this.id = id |
||||
|
this.getRoleInfo() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
getPermissions() { |
||||
|
this.permissionsLoading = true |
||||
|
this.api |
||||
|
.getAllPermission() |
||||
|
.pipe( |
||||
|
finalize(() => { |
||||
|
this.permissionsLoading = false |
||||
|
}), |
||||
|
) |
||||
|
.subscribe((res) => { |
||||
|
this.permissions = res.data |
||||
|
this.updateCheckedAll() |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
getRoleInfo() { |
||||
|
this.api.getRoleById(this.id!).subscribe((res) => { |
||||
|
this.formGroup.patchValue(res.data) |
||||
|
this.checked = new Set(res.data.authorityId) |
||||
|
this.updateCheckedAll() |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 权限之间的联动 |
||||
|
*/ |
||||
|
roleLinkage = new Map<string, string[]>([ |
||||
|
// ['2', ['2', '9', '15']],
|
||||
|
// ['9', ['2', '9', '15']],
|
||||
|
// ['15', ['2', '9', '15']],
|
||||
|
// ['20', ['20', '25']],
|
||||
|
// ['25', ['20', '25']],
|
||||
|
]) |
||||
|
|
||||
|
onPermissionChange(checked: boolean, id: string) { |
||||
|
const checkedIds = this.roleLinkage.get(id) ?? [id] |
||||
|
checkedIds.forEach((i) => { |
||||
|
if (checked) { |
||||
|
this.checked.add(i) |
||||
|
} else { |
||||
|
this.checked.delete(i) |
||||
|
} |
||||
|
}) |
||||
|
this.updateCheckedAll() |
||||
|
} |
||||
|
|
||||
|
checkAllChange(checked: boolean, p: PermissionDTO) { |
||||
|
p.children?.forEach((permission) => { |
||||
|
if (checked) { |
||||
|
this.checked.add(permission.id) |
||||
|
} else { |
||||
|
this.checked.delete(permission.id) |
||||
|
} |
||||
|
}) |
||||
|
this.updateCheckedAll() |
||||
|
} |
||||
|
|
||||
|
updateCheckedAll() { |
||||
|
this.permissions.forEach((g) => { |
||||
|
let nzChecked = true |
||||
|
let nzIndeterminate = false |
||||
|
g.children?.forEach((permission) => { |
||||
|
if (this.checked.has(permission.id)) { |
||||
|
nzIndeterminate = true |
||||
|
} else { |
||||
|
nzChecked = false |
||||
|
} |
||||
|
}) |
||||
|
this.checkedAll.set(g.id, { |
||||
|
nzChecked, |
||||
|
nzIndeterminate: nzChecked ? false : nzIndeterminate, |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
onSubmit() { |
||||
|
if (FormValidators.validateFormGroup(this.formGroup)) { |
||||
|
if (this.checked.size === 0) { |
||||
|
this.msg.error('请选择权限') |
||||
|
return |
||||
|
} |
||||
|
const { value } = this.formGroup |
||||
|
this.api |
||||
|
.saveRole({ |
||||
|
id: this.id, |
||||
|
roleName: value.roleName!, |
||||
|
remark: value.remark as string, |
||||
|
authorityId: Array.from(this.checked), |
||||
|
}) |
||||
|
.subscribe(() => { |
||||
|
this.msg.success('保存成功') |
||||
|
this.router.navigate(['/role/list']) |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
<nz-card [nzBordered]="false"> |
||||
|
<app-server-paginated-table [options]="tableOption" [formGroup]="queryForm"> |
||||
|
<div *appTableAction class="flex items-center justify-between"> |
||||
|
<div class="flex-1"> |
||||
|
<nz-space> |
||||
|
<button |
||||
|
nz-button |
||||
|
nzType="primary" |
||||
|
type="button" |
||||
|
[routerLink]="['/', 'role', 'form', 'create']" |
||||
|
*nzSpaceItem |
||||
|
> |
||||
|
添加角色 |
||||
|
</button> |
||||
|
</nz-space> |
||||
|
</div> |
||||
|
<!-- <div> |
||||
|
<nz-input-group nzSearch [nzSuffix]="suffixIconSearch"> |
||||
|
<input type="text" nz-input placeholder="请输入角色名称" formControlName="roleName" /> |
||||
|
</nz-input-group> |
||||
|
<ng-template #suffixIconSearch> |
||||
|
<span nz-icon nzType="search"></span> |
||||
|
</ng-template> |
||||
|
</div> --> |
||||
|
</div> |
||||
|
</app-server-paginated-table> |
||||
|
</nz-card> |
||||
@ -0,0 +1,72 @@ |
|||||
|
import { lastValueFrom } from 'rxjs' |
||||
|
import { Component, OnInit } from '@angular/core' |
||||
|
import { FormControl, FormGroup } from '@angular/forms' |
||||
|
import { Router } from '@angular/router' |
||||
|
import { TableOption } from 'app/components/server-paginated-table' |
||||
|
import { ApiService } from 'app/services' |
||||
|
import { RoleListDTO } from 'app/services/api.dto' |
||||
|
import { SharedModule } from 'app/shared/shared.module' |
||||
|
import { NzModalService } from 'ng-zorro-antd/modal' |
||||
|
import { NzMessageService } from 'ng-zorro-antd/message' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-role-list', |
||||
|
standalone: true, |
||||
|
imports: [SharedModule], |
||||
|
templateUrl: './role-list.component.html', |
||||
|
styleUrl: './role-list.component.less', |
||||
|
}) |
||||
|
export class RoleListComponent implements OnInit { |
||||
|
constructor( |
||||
|
private api: ApiService, |
||||
|
private modal: NzModalService, |
||||
|
private router: Router, |
||||
|
private msg: NzMessageService, |
||||
|
) {} |
||||
|
|
||||
|
tableOption = new TableOption(this.fetchData.bind(this)) |
||||
|
|
||||
|
queryForm = new FormGroup({ |
||||
|
roleName: new FormControl(), |
||||
|
}) |
||||
|
|
||||
|
permissions = [] |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.initTable() |
||||
|
} |
||||
|
|
||||
|
initTable() { |
||||
|
this.tableOption |
||||
|
.setColumn([ |
||||
|
{ key: 'roleName', title: '角色名称' }, |
||||
|
{ key: 'remark', title: '说明' }, |
||||
|
{ key: 'createTime', title: '创建时间' }, |
||||
|
]) |
||||
|
.setRowOperate([ |
||||
|
{ title: '编辑', onClick: this.edit.bind(this), visible: (v) => v.id !== '1' }, |
||||
|
{ title: '删除', onClick: this.deleteItem.bind(this), visible: (v) => v.id !== '1' }, |
||||
|
]) |
||||
|
} |
||||
|
|
||||
|
fetchData(p: {}, q: {}) { |
||||
|
return this.api.getRolePage(p, q) |
||||
|
} |
||||
|
|
||||
|
edit(role: RoleListDTO) { |
||||
|
this.router.navigate(['/', 'role', 'form', role.id]) |
||||
|
} |
||||
|
|
||||
|
deleteItem(role: RoleListDTO) { |
||||
|
this.modal.confirm({ |
||||
|
nzTitle: '警告', |
||||
|
nzContent: '是否要删除该角色?', |
||||
|
nzOkDanger: true, |
||||
|
nzOnOk: async () => { |
||||
|
await lastValueFrom(this.api.deleteRole(role.id)) |
||||
|
this.msg.success('删除成功') |
||||
|
this.tableOption.ref?.reload() |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
<div nz-row nzGutter="12"> |
||||
|
<div nz-col nzSpan="6"> |
||||
|
<nz-card [nzBordered]="false"> |
||||
|
<div> |
||||
|
<nz-statistic [nzValue]="counter.totalClient" [nzTitle]="'今日预约'"></nz-statistic> |
||||
|
</div> |
||||
|
</nz-card> |
||||
|
</div> |
||||
|
<div nz-col nzSpan="18"> |
||||
|
<nz-card [nzBordered]="false"> |
||||
|
<div nz-row nzGutter="24"> |
||||
|
<div nz-col nzSpan="8"> |
||||
|
<nz-statistic [nzValue]="counter.totalClient" [nzTitle]="'会员'"></nz-statistic> |
||||
|
</div> |
||||
|
<div nz-col nzSpan="8"> |
||||
|
<nz-statistic [nzValue]="counter.totalProduct" [nzTitle]="'会员卡'"></nz-statistic> |
||||
|
</div> |
||||
|
<div nz-col nzSpan="8"> |
||||
|
<nz-statistic [nzValue]="counter.totalLicense" [nzTitle]="'教练'"></nz-statistic> |
||||
|
</div> |
||||
|
</div> |
||||
|
</nz-card> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="mt-4"> |
||||
|
<nz-card [nzBordered]="false" nzTitle="近30天办卡"> |
||||
|
<div class="h-80" #chart1Tpl></div> |
||||
|
</nz-card> |
||||
|
</div> |
||||
|
|
||||
|
<div class="mt-4 mb-4"> |
||||
|
<nz-card [nzBordered]="false" nzTitle="近30天到期会员"> |
||||
|
<nz-table nzTemplateMode nzSize="small" [nzBordered]="true"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th>实体</th> |
||||
|
<th>产品</th> |
||||
|
<!-- <th>版本</th> --> |
||||
|
<th>到期时间</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
@for (p of expiringSoon; track $index) { |
||||
|
<tr> |
||||
|
<td>{{ p.clientName }}</td> |
||||
|
<td>{{ p.productName }}</td> |
||||
|
<!-- <td></td> --> |
||||
|
<td> |
||||
|
{{ p.endTime * 1000 | date: 'yyyy-MM-dd HH:mm:ss' }} |
||||
|
</td> |
||||
|
</tr> |
||||
|
} @empty { |
||||
|
<tr> |
||||
|
<td [colSpan]="4"> |
||||
|
<nz-empty nzNotFoundContent="没有近30天到期的授权"></nz-empty> |
||||
|
</td> |
||||
|
</tr> |
||||
|
} |
||||
|
</tbody> |
||||
|
</nz-table> |
||||
|
</nz-card> |
||||
|
</div> |
||||
@ -0,0 +1,195 @@ |
|||||
|
import { AfterViewInit, Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core' |
||||
|
import { init, EChartsType } from 'echarts' |
||||
|
import { SharedModule } from '../../shared/shared.module' |
||||
|
import { ApiService } from 'app/services' |
||||
|
import { ConsoleClientTopDTO, ConsoleCountDTO, ConsoleExpireDTO } from 'app/services/api.dto' |
||||
|
|
||||
|
const antvColor = [ |
||||
|
'#5B8FF9', |
||||
|
'#61DDAA', |
||||
|
'#65789B', |
||||
|
'#F6BD16', |
||||
|
'#7262FD', |
||||
|
'#78D3F8', |
||||
|
'#9661BC', |
||||
|
'#F6903D', |
||||
|
'#008685', |
||||
|
'#F08BB4', |
||||
|
] |
||||
|
|
||||
|
@Component({ |
||||
|
standalone: true, |
||||
|
imports: [SharedModule], |
||||
|
selector: 'app-dashboard', |
||||
|
templateUrl: './dashboard.component.html', |
||||
|
styleUrls: ['./dashboard.component.less'], |
||||
|
}) |
||||
|
export class DashboardComponent implements OnInit, AfterViewInit { |
||||
|
constructor(private api: ApiService) {} |
||||
|
|
||||
|
@ViewChild('chart1Tpl') chart1!: ElementRef<HTMLDivElement> |
||||
|
|
||||
|
@ViewChild('chart2Tpl') chart2!: ElementRef<HTMLDivElement> |
||||
|
|
||||
|
echartRef1?: EChartsType |
||||
|
|
||||
|
echartRef2?: EChartsType |
||||
|
|
||||
|
counter: ConsoleCountDTO = { totalClient: 0, totalLicense: 0, totalProduct: 0 } |
||||
|
|
||||
|
expiringSoon: ConsoleExpireDTO[] = [] |
||||
|
|
||||
|
clientTopTen: ConsoleClientTopDTO[] = [] |
||||
|
|
||||
|
@HostListener('window:resize') |
||||
|
onResize() { |
||||
|
// this.echartRef1?.resize();
|
||||
|
this.echartRef2?.resize() |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
// this.api.getDashboardCounter().subscribe((res) => {
|
||||
|
// this.counter = res.data
|
||||
|
// })
|
||||
|
// this.api.getDashboardExpire().subscribe((res) => {
|
||||
|
// this.expiringSoon = res.data
|
||||
|
// })
|
||||
|
// this.api.getDashboardClientTop().subscribe((res) => {
|
||||
|
// this.clientTopTen = res.data
|
||||
|
// this.initChart2()
|
||||
|
// })
|
||||
|
} |
||||
|
|
||||
|
ngAfterViewInit(): void { |
||||
|
// this.initChart1();
|
||||
|
} |
||||
|
|
||||
|
initChart1() { |
||||
|
const el = this.chart1.nativeElement |
||||
|
if (el) { |
||||
|
this.echartRef1 = init(el) |
||||
|
this.echartRef1.setOption({ |
||||
|
color: antvColor, |
||||
|
tooltip: { |
||||
|
trigger: 'axis', |
||||
|
axisPointer: { |
||||
|
type: 'cross', |
||||
|
label: { |
||||
|
backgroundColor: '#6a7985', |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
legend: { |
||||
|
data: ['晶未智慧安全平台', 'NG-WAF', 'DLP', 'API'], |
||||
|
}, |
||||
|
|
||||
|
grid: { |
||||
|
left: '10px', |
||||
|
right: '10px', |
||||
|
bottom: '3%', |
||||
|
containLabel: true, |
||||
|
}, |
||||
|
xAxis: [ |
||||
|
{ |
||||
|
type: 'category', |
||||
|
boundaryGap: false, |
||||
|
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], |
||||
|
}, |
||||
|
], |
||||
|
yAxis: [ |
||||
|
{ |
||||
|
type: 'value', |
||||
|
}, |
||||
|
], |
||||
|
series: [ |
||||
|
{ |
||||
|
name: '晶未智慧安全平台', |
||||
|
type: 'line', |
||||
|
smooth: true, |
||||
|
areaStyle: { |
||||
|
opacity: 0.2, |
||||
|
}, |
||||
|
emphasis: { |
||||
|
focus: 'series', |
||||
|
}, |
||||
|
data: [620, 332, 401, 534, 190, 330, 410], |
||||
|
}, |
||||
|
{ |
||||
|
name: 'NG-WAF', |
||||
|
type: 'line', |
||||
|
smooth: true, |
||||
|
areaStyle: { |
||||
|
opacity: 0.2, |
||||
|
}, |
||||
|
emphasis: { |
||||
|
focus: 'series', |
||||
|
}, |
||||
|
data: [220, 182, 191, 234, 290, 330, 310], |
||||
|
}, |
||||
|
{ |
||||
|
name: 'API', |
||||
|
type: 'line', |
||||
|
smooth: true, |
||||
|
areaStyle: { |
||||
|
opacity: 0.2, |
||||
|
}, |
||||
|
emphasis: { |
||||
|
focus: 'series', |
||||
|
}, |
||||
|
data: [55, 612, 491, 34, 90, 200, 411], |
||||
|
}, |
||||
|
{ |
||||
|
name: 'DLP', |
||||
|
type: 'line', |
||||
|
smooth: true, |
||||
|
areaStyle: { |
||||
|
opacity: 0.2, |
||||
|
}, |
||||
|
emphasis: { |
||||
|
focus: 'series', |
||||
|
}, |
||||
|
data: [20, 162, 291, 134, 390, 300, 10], |
||||
|
}, |
||||
|
], |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
initChart2() { |
||||
|
const el = this.chart2.nativeElement |
||||
|
if (el) { |
||||
|
this.echartRef2 = init(el) |
||||
|
this.echartRef2.setOption({ |
||||
|
color: antvColor, |
||||
|
tooltip: { |
||||
|
trigger: 'axis', |
||||
|
axisPointer: { |
||||
|
type: 'cross', |
||||
|
label: { |
||||
|
backgroundColor: '#6a7985', |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
|
||||
|
grid: { |
||||
|
left: '10px', |
||||
|
right: '10px', |
||||
|
bottom: '3%', |
||||
|
containLabel: true, |
||||
|
}, |
||||
|
xAxis: { |
||||
|
type: 'category', |
||||
|
data: this.clientTopTen.map((i) => i.clientName), |
||||
|
}, |
||||
|
yAxis: { |
||||
|
type: 'value', |
||||
|
}, |
||||
|
series: [ |
||||
|
{ |
||||
|
data: this.clientTopTen.map((i) => i.count), |
||||
|
type: 'bar', |
||||
|
}, |
||||
|
], |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,101 @@ |
|||||
|
@if (entity) { |
||||
|
<div class="mb-4"> |
||||
|
<nz-card [nzBordered]="false" nzTitle="实体详情" [nzExtra]="nzExtraTpl"> |
||||
|
<ng-template #nzExtraTpl> |
||||
|
<nz-space> |
||||
|
<button *nzSpaceItem nz-button [routerLink]="['/', 'entity']">返回</button> |
||||
|
</nz-space> |
||||
|
</ng-template> |
||||
|
|
||||
|
<nz-descriptions [nzColumn]="3" class="ml-16"> |
||||
|
<nz-descriptions-item nzTitle="实体名称"> |
||||
|
{{ entity.name }} |
||||
|
</nz-descriptions-item> |
||||
|
<nz-descriptions-item nzTitle="联系方式"> |
||||
|
{{ entity.contact ?? '-' }} |
||||
|
</nz-descriptions-item> |
||||
|
<!-- <nz-descriptions-item nzTitle="地址"> |
||||
|
{{ entity.address ?? '-' }} |
||||
|
</nz-descriptions-item> --> |
||||
|
<nz-descriptions-item nzTitle="说明">{{ entity.remark || '-' }}</nz-descriptions-item> |
||||
|
</nz-descriptions> |
||||
|
</nz-card> |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
<div class="mb-4"> |
||||
|
<nz-card [nzBordered]="false" nzTitle="授权记录" [nzExtra]="licenseExtraTpl"> |
||||
|
<ng-template #licenseExtraTpl> |
||||
|
<nz-space> |
||||
|
<button *nzSpaceItem nz-button nzType="primary" (click)="authorize()">新增授权</button> |
||||
|
</nz-space> |
||||
|
</ng-template> |
||||
|
<div> |
||||
|
<div class="mb-3"> |
||||
|
<nz-space nzAlign="center"> |
||||
|
<div *nzSpaceItem>状态:</div> |
||||
|
<div *nzSpaceItem> |
||||
|
<nz-select |
||||
|
nzPlaceHolder="请选择状态" |
||||
|
class="w-64" |
||||
|
[(ngModel)]="status" |
||||
|
(ngModelChange)="onStatusChange()" |
||||
|
> |
||||
|
<nz-option [nzValue]="-1" nzLabel="全部"></nz-option> |
||||
|
<nz-option [nzValue]="0" nzLabel="无效"></nz-option> |
||||
|
<nz-option [nzValue]="1" nzLabel="生效中"></nz-option> |
||||
|
</nz-select> |
||||
|
</div> |
||||
|
</nz-space> |
||||
|
</div> |
||||
|
<nz-table #table [nzData]="visibleList" [nzShowPagination]="false"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th>授权产品</th> |
||||
|
<th>操作人</th> |
||||
|
<th>生效时间</th> |
||||
|
<th>失效时间</th> |
||||
|
<th>状态</th> |
||||
|
<th>创建时间</th> |
||||
|
<th>操作</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
@for (item of table.data; track $index) { |
||||
|
<tr> |
||||
|
<td> |
||||
|
{{ item.productName }} |
||||
|
</td> |
||||
|
<td> |
||||
|
{{ item.username }} |
||||
|
</td> |
||||
|
<td> |
||||
|
{{ item.startTime * 1000 | date: 'yyyy-MM-dd HH:mm:ss' }} |
||||
|
</td> |
||||
|
<td> |
||||
|
{{ item.endTime ? (item.endTime * 1000 | date: 'yyyy-MM-dd HH:mm:ss') : '永久' }} |
||||
|
</td> |
||||
|
<td> |
||||
|
@if (item.status) { |
||||
|
<nz-badge nzStatus="processing" nzText="生效中"> </nz-badge> |
||||
|
} @else { |
||||
|
<nz-badge nzStatus="default" nzText="无效"></nz-badge> |
||||
|
} |
||||
|
</td> |
||||
|
<td> |
||||
|
{{ item.createTime }} |
||||
|
</td> |
||||
|
<td> |
||||
|
<nz-space> |
||||
|
<button *nzSpaceItem nz-button nzType="link" (click)="copy(item.license)"> |
||||
|
复制授权码 |
||||
|
</button> |
||||
|
</nz-space> |
||||
|
</td> |
||||
|
</tr> |
||||
|
} |
||||
|
</tbody> |
||||
|
</nz-table> |
||||
|
</div> |
||||
|
</nz-card> |
||||
|
</div> |
||||
@ -0,0 +1,105 @@ |
|||||
|
import { Component, OnInit } from '@angular/core' |
||||
|
import { ActivatedRoute } from '@angular/router' |
||||
|
import { ApiService } from 'app/services' |
||||
|
import { AuthorizeItemDTO, EntityDTO, EntityDetailDTO } from 'app/services/api.dto' |
||||
|
import { SharedModule } from 'app/shared/shared.module' |
||||
|
import { Clipboard } from '@angular/cdk/clipboard' |
||||
|
import { NzMessageService } from 'ng-zorro-antd/message' |
||||
|
import { NzModalService } from 'ng-zorro-antd/modal' |
||||
|
import { AuthorizeComponent } from 'app/components/authorize/authorize.component' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-entity-detail', |
||||
|
standalone: true, |
||||
|
imports: [SharedModule], |
||||
|
templateUrl: './entity-detail.component.html', |
||||
|
styleUrl: './entity-detail.component.less', |
||||
|
}) |
||||
|
export class EntityDetailComponent implements OnInit { |
||||
|
constructor( |
||||
|
private api: ApiService, |
||||
|
private route: ActivatedRoute, |
||||
|
private clipboard: Clipboard, |
||||
|
private msg: NzMessageService, |
||||
|
private modal: NzModalService, |
||||
|
) {} |
||||
|
|
||||
|
entity?: EntityDetailDTO |
||||
|
|
||||
|
authorizeData: AuthorizeItemDTO[] = [] |
||||
|
|
||||
|
visibleList: AuthorizeItemDTO[] = [] |
||||
|
|
||||
|
product: { id: string; name: string }[] = [] |
||||
|
|
||||
|
id: string = '' |
||||
|
|
||||
|
status = -1 |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
const id = this.route.snapshot.paramMap.get('id') |
||||
|
if (id) { |
||||
|
this.id = id |
||||
|
this.api.getEntityDetail(id).subscribe((res) => { |
||||
|
this.entity = res.data |
||||
|
}) |
||||
|
this.getEntityAuthorizeList() |
||||
|
} |
||||
|
this.api.getAllProduct().subscribe((res) => { |
||||
|
this.product = res.data |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
getEntityAuthorizeList() { |
||||
|
this.api.getEntityAuthorizeList(this.id).subscribe((res) => { |
||||
|
this.authorizeData = res.data.map((i) => ({ |
||||
|
...i, |
||||
|
status: this.between(i.startTime, i.endTime) ? 1 : 0, |
||||
|
})) |
||||
|
this.onStatusChange() |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
onStatusChange() { |
||||
|
this.visibleList = this.authorizeData.filter((f) => (this.status === -1 ? true : f.status === this.status)) |
||||
|
} |
||||
|
|
||||
|
copy(license: string) { |
||||
|
const pending = this.clipboard.beginCopy(license) |
||||
|
let remainingAttempts = 3 |
||||
|
const attempt = () => { |
||||
|
const result = pending.copy() |
||||
|
if (!result && --remainingAttempts) { |
||||
|
setTimeout(attempt) |
||||
|
} else { |
||||
|
this.msg.success('复制成功') |
||||
|
pending.destroy() |
||||
|
} |
||||
|
} |
||||
|
attempt() |
||||
|
} |
||||
|
|
||||
|
between(start: number, end: number): boolean { |
||||
|
if (end === 0) { |
||||
|
return true |
||||
|
} |
||||
|
const now = Date.now() |
||||
|
return start * 1000 <= now && now <= end * 1000 |
||||
|
} |
||||
|
|
||||
|
authorize() { |
||||
|
const ref = this.modal.create({ |
||||
|
nzTitle: '添加授权', |
||||
|
nzContent: AuthorizeComponent, |
||||
|
nzWidth: 800, |
||||
|
nzData: { |
||||
|
product: this.product, |
||||
|
entity: this.entity, |
||||
|
}, |
||||
|
nzFooter: null, |
||||
|
}) |
||||
|
ref.afterClose.subscribe(() => { |
||||
|
this.getEntityAuthorizeList() |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,83 @@ |
|||||
|
<nz-card [nzBordered]="false"> |
||||
|
<app-server-paginated-table |
||||
|
#tableTpl |
||||
|
[renderColumn]="renderColumnsTpl" |
||||
|
[formGroup]="queryForm" |
||||
|
[simpleSearch]="true" |
||||
|
[options]="table" |
||||
|
> |
||||
|
<ng-template #renderColumnsTpl let-data let-key="key" let-row="row"> |
||||
|
@switch (key) { |
||||
|
@case ('name') { |
||||
|
<a [routerLink]="['/', 'entity', row.id, 'detail']">{{ data }}</a> |
||||
|
} |
||||
|
@default { |
||||
|
{{ data }} |
||||
|
} |
||||
|
} |
||||
|
</ng-template> |
||||
|
|
||||
|
<div *appTableForm nz-row [nzGutter]="[24, 24]"> |
||||
|
<div nz-col nzSpan="8"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzFlex="80px"> 实体名称 </nz-form-label> |
||||
|
<nz-form-control nzSpan="18"> |
||||
|
<input nz-input placeholder="请输入实体名称" formControlName="name" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</div> |
||||
|
<div nz-col nzSpan="12"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzFlex="80px"> 创建时间 </nz-form-label> |
||||
|
<nz-form-control nzSpan="18"> |
||||
|
<!-- <nz-range-picker nzShowTime formControlName="createTime"></nz-range-picker> --> |
||||
|
<app-range-picker [showTime]="true" formControlName="createTime"></app-range-picker> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div *appTableAction class="flex"> |
||||
|
<div> |
||||
|
<nz-space> |
||||
|
<button *nzSpaceItem nzType="primary" nz-button (click)="create()">添加实体</button> |
||||
|
</nz-space> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- <div action>Custom Modal action</div> --> |
||||
|
</app-server-paginated-table> |
||||
|
</nz-card> |
||||
|
|
||||
|
<ng-template #entityFormTpl> |
||||
|
<form [formGroup]="createForm" nz-form [nzLayout]="'vertical'"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzRequired> 实体名称 </nz-form-label> |
||||
|
<nz-form-control [nzErrorTip]="formErrorTipsTpl"> |
||||
|
<input nz-input placeholder="请输入实体名称" formControlName="name" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
|
||||
|
<nz-form-item> |
||||
|
<nz-form-label> 联系方式 </nz-form-label> |
||||
|
<nz-form-control [nzErrorTip]="formErrorTipsTpl"> |
||||
|
<input nz-input placeholder="请输入联系方式" formControlName="contact" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
<!-- <nz-form-item> |
||||
|
<nz-form-label> 地址 </nz-form-label> |
||||
|
<nz-form-control [nzErrorTip]="formErrorTipsTpl"> |
||||
|
<input nz-input placeholder="请输入地址" formControlName="address" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> --> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label> 说明 </nz-form-label> |
||||
|
<nz-form-control [nzErrorTip]="formErrorTipsTpl"> |
||||
|
<textarea nz-input placeholder="请输入说明" formControlName="remark"></textarea> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</form> |
||||
|
</ng-template> |
||||
|
|
||||
|
<ng-template #formErrorTipsTpl let-control> |
||||
|
<form-error-tips [control]="control"></form-error-tips> |
||||
|
</ng-template> |
||||
@ -0,0 +1,132 @@ |
|||||
|
import { Component, ElementRef, OnInit, TemplateRef, ViewChild } from '@angular/core' |
||||
|
import { FormControl, FormGroup } from '@angular/forms' |
||||
|
import { NzModalService } from 'ng-zorro-antd/modal' |
||||
|
import { ApiService } from 'app/services' |
||||
|
import { AnyObject, ServerPaginatedTableComponent, TableOption } from 'app/components/server-paginated-table' |
||||
|
import { SharedModule } from 'app/shared/shared.module' |
||||
|
import { FormValidators } from 'app/utils' |
||||
|
import { lastValueFrom } from 'rxjs' |
||||
|
import { EntityDTO, ProductDTO } from 'app/services/api.dto' |
||||
|
import { NzMessageService } from 'ng-zorro-antd/message' |
||||
|
import { format } from 'date-fns' |
||||
|
import { AuthorizeComponent } from 'app/components/authorize/authorize.component' |
||||
|
|
||||
|
@Component({ |
||||
|
standalone: true, |
||||
|
imports: [SharedModule], |
||||
|
selector: 'app-entity-list', |
||||
|
templateUrl: './entity-list.component.html', |
||||
|
styleUrls: ['./entity-list.component.less'], |
||||
|
}) |
||||
|
export class EntityListComponent implements OnInit { |
||||
|
constructor( |
||||
|
private api: ApiService, |
||||
|
private modal: NzModalService, |
||||
|
private msg: NzMessageService, |
||||
|
) {} |
||||
|
|
||||
|
@ViewChild('entityFormTpl') entityFormTpl!: TemplateRef<{}> |
||||
|
|
||||
|
table = new TableOption(this.fetchData.bind(this), [{ key: 'name', title: '实体名称' }], [], { |
||||
|
cacheUrls: ['/dashboard'], |
||||
|
}) |
||||
|
|
||||
|
queryForm = new FormGroup({ |
||||
|
name: new FormControl(''), |
||||
|
createTime: new FormControl(), |
||||
|
}) |
||||
|
|
||||
|
createForm = new FormGroup({ |
||||
|
name: new FormControl('', [FormValidators.required('请输入实体名称'), FormValidators.maxLength(30)]), |
||||
|
// secretKey: new FormControl('', [FormValidators.required('请输入序列号')]),
|
||||
|
contact: new FormControl('', FormValidators.maxLength(30)), |
||||
|
// address: new FormControl('', FormValidators.maxLength(200)),
|
||||
|
remark: new FormControl('', FormValidators.maxLength(200)), |
||||
|
}) |
||||
|
|
||||
|
product: { id: string; name: string }[] = [] |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.table |
||||
|
.setColumn([ |
||||
|
{ key: 'name', title: '实体名称' }, |
||||
|
{ key: 'contact', title: '联系方式' }, |
||||
|
// { key: 'address', title: '地址' },
|
||||
|
{ key: 'remark', title: '说明' }, |
||||
|
{ key: 'createTime', title: '创建时间' }, |
||||
|
]) |
||||
|
.setRowOperate([ |
||||
|
{ title: '添加授权', premissions: [], onClick: this.authorize.bind(this) }, |
||||
|
{ title: '编辑', onClick: this.create.bind(this) }, |
||||
|
{ title: '删除', onClick: this.deleteItem.bind(this) }, |
||||
|
]) |
||||
|
this.api.getAllProduct().subscribe((res) => { |
||||
|
this.product = res.data |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
fetchData(p: {}, q: AnyObject) { |
||||
|
if (Array.isArray(q['createTime'])) { |
||||
|
const createTimeStart = q['createTime']?.[0] |
||||
|
const createTimeEnd = q['createTime']?.[1] |
||||
|
|
||||
|
q['createTimeStart'] = createTimeStart ? format(new Date(createTimeStart), 'yyyy-MM-dd HH:mm:ss') : '' |
||||
|
q['createTimeEnd'] = createTimeEnd ? format(new Date(createTimeEnd), 'yyyy-MM-dd HH:mm:ss') : '' |
||||
|
} |
||||
|
return this.api.getEntityPage(p, q) |
||||
|
} |
||||
|
|
||||
|
onSearch() {} |
||||
|
|
||||
|
authorize(v: EntityDTO) { |
||||
|
this.modal.create({ |
||||
|
nzTitle: '添加授权', |
||||
|
nzContent: AuthorizeComponent, |
||||
|
nzWidth: 800, |
||||
|
nzData: { |
||||
|
product: this.product, |
||||
|
entity: v, |
||||
|
}, |
||||
|
nzFooter: null, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
create(entity?: EntityDTO) { |
||||
|
if (entity) { |
||||
|
this.createForm.patchValue(entity) |
||||
|
this.createForm.get('secretKey')?.disable() |
||||
|
} else { |
||||
|
this.createForm.get('secretKey')?.enable() |
||||
|
} |
||||
|
this.modal.create({ |
||||
|
nzTitle: entity ? '编辑实体' : '添加实体', |
||||
|
nzContent: this.entityFormTpl, |
||||
|
nzOnOk: async () => { |
||||
|
if (FormValidators.validateFormGroup(this.createForm)) { |
||||
|
await lastValueFrom(this.api.saveEntity({ ...(entity ?? {}), ...this.createForm.value })) |
||||
|
this.msg.success('保存成功') |
||||
|
this.table.ref?.reload() |
||||
|
this.createForm.reset() |
||||
|
return true |
||||
|
} |
||||
|
return false |
||||
|
}, |
||||
|
nzOnCancel: () => { |
||||
|
this.createForm.reset() |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
deleteItem(entity: EntityDTO) { |
||||
|
this.modal.confirm({ |
||||
|
nzTitle: '警告', |
||||
|
nzContent: '是否要删除该实体?', |
||||
|
nzOkDanger: true, |
||||
|
nzOnOk: async () => { |
||||
|
await lastValueFrom(this.api.deleteEntity(entity.id)) |
||||
|
this.msg.success('删除成功') |
||||
|
this.table.ref?.reload() |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,5 @@ |
|||||
|
<nz-card [nzBordered]="false" class="h-screen"> |
||||
|
<div class="flex items-center justify-center"> |
||||
|
<nz-result nzStatus="403"></nz-result> |
||||
|
</div> |
||||
|
</nz-card> |
||||
@ -0,0 +1,11 @@ |
|||||
|
import { Component } from '@angular/core' |
||||
|
import { SharedModule } from 'app/shared/shared.module' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-forbidden', |
||||
|
standalone: true, |
||||
|
imports: [SharedModule], |
||||
|
templateUrl: './forbidden.component.html', |
||||
|
styleUrl: './forbidden.component.less', |
||||
|
}) |
||||
|
export class ForbiddenComponent {} |
||||
@ -0,0 +1,82 @@ |
|||||
|
<nz-card [nzBordered]="false"> |
||||
|
<app-server-paginated-table |
||||
|
#tableTpl |
||||
|
[options]="tableOption" |
||||
|
[formGroup]="queryForm" |
||||
|
[simpleSearch]="false" |
||||
|
[renderColumn]="renderColumnsTpl" |
||||
|
> |
||||
|
<ng-template #renderColumnsTpl let-data let-key="key" let-row="row"> |
||||
|
@switch (key) { |
||||
|
@case ('content') { |
||||
|
<span> |
||||
|
@switch (row.operation) { |
||||
|
@case ('insert') { |
||||
|
<nz-tag nzColor="success">新增</nz-tag> |
||||
|
} |
||||
|
@case ('update') { |
||||
|
<nz-tag nzColor="blue">编辑</nz-tag> |
||||
|
} |
||||
|
@case ('delete') { |
||||
|
<nz-tag nzColor="error">删除</nz-tag> |
||||
|
} |
||||
|
@case ('login') { |
||||
|
<nz-tag nzColor="gold">登录</nz-tag> |
||||
|
} |
||||
|
} |
||||
|
</span> |
||||
|
<span> {{ data }} </span> |
||||
|
} |
||||
|
|
||||
|
@default { |
||||
|
{{ data }} |
||||
|
} |
||||
|
} |
||||
|
</ng-template> |
||||
|
<div *appTableForm nz-row [nzGutter]="[24, 24]"> |
||||
|
<div nz-col nzSpan="8"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzSpan="6"> 操作人 </nz-form-label> |
||||
|
<nz-form-control nzSpan="18"> |
||||
|
<input nz-input placeholder="请输入操作人" formControlName="username" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</div> |
||||
|
|
||||
|
<div nz-col nzSpan="8"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzSpan="6"> 操作类型 </nz-form-label> |
||||
|
<nz-form-control nzSpan="18"> |
||||
|
<nz-select nzPlaceHolder="请选择操作类型" formControlName="operation" nzAllowClear> |
||||
|
<nz-option [nzValue]="'insert'" [nzLabel]="'新增'"></nz-option> |
||||
|
<nz-option [nzValue]="'update'" [nzLabel]="'编辑'"></nz-option> |
||||
|
<nz-option [nzValue]="'delete'" [nzLabel]="'删除'"></nz-option> |
||||
|
<nz-option [nzValue]="'login'" [nzLabel]="'登录'"></nz-option> |
||||
|
</nz-select> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</div> |
||||
|
|
||||
|
<div nz-col nzSpan="8"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzSpan="6"> 操作内容 </nz-form-label> |
||||
|
<nz-form-control nzSpan="18"> |
||||
|
<input nz-input placeholder="请输入操作内容" formControlName="content" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</div> |
||||
|
|
||||
|
<div nz-col nzSpan="8"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzSpan="6"> 创建时间 </nz-form-label> |
||||
|
<nz-form-control nzSpan="18"> |
||||
|
<!-- <nz-range-picker class="w-full" formControlName="createTime"></nz-range-picker> --> |
||||
|
<app-range-picker [showTime]="true" formControlName="createTime"></app-range-picker> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</div> |
||||
|
</div> |
||||
|
</app-server-paginated-table> |
||||
|
</nz-card> |
||||
|
|
||||
|
<ng-template #createTpl> </ng-template> |
||||
@ -0,0 +1,87 @@ |
|||||
|
import { AfterViewInit, Component, ElementRef, OnInit, TemplateRef, ViewChild } from '@angular/core' |
||||
|
import { FormControl, FormGroup } from '@angular/forms' |
||||
|
import { ApiService } from 'app/services/api.service' |
||||
|
import { |
||||
|
AnyObject, |
||||
|
ServerPaginatedTableComponent, |
||||
|
ServerPaginatedTableService, |
||||
|
TableOption, |
||||
|
} from 'app/components/server-paginated-table' |
||||
|
import { SharedModule } from 'app/shared/shared.module' |
||||
|
import { NzModalService } from 'ng-zorro-antd/modal' |
||||
|
import { format } from 'date-fns' |
||||
|
|
||||
|
@Component({ |
||||
|
standalone: true, |
||||
|
imports: [SharedModule], |
||||
|
selector: 'app-log', |
||||
|
templateUrl: './log.component.html', |
||||
|
styleUrls: ['./log.component.less'], |
||||
|
}) |
||||
|
export class LogComponent implements OnInit, AfterViewInit { |
||||
|
constructor( |
||||
|
private api: ApiService, |
||||
|
private modal: NzModalService, |
||||
|
private table: ServerPaginatedTableService, |
||||
|
) {} |
||||
|
|
||||
|
@ViewChild('shadowLineTpl') shadowLineRef!: ElementRef<HTMLCanvasElement> |
||||
|
|
||||
|
tableOption = new TableOption(this.fetchData.bind(this)) |
||||
|
|
||||
|
queryForm = new FormGroup({ |
||||
|
operation: new FormControl(), |
||||
|
username: new FormControl(), |
||||
|
content: new FormControl(), |
||||
|
|
||||
|
createTime: new FormControl(), |
||||
|
}) |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
// this.table.run();
|
||||
|
this.tableOption.setConfig({ |
||||
|
cacheUrls: ['/dashboard'], |
||||
|
}) |
||||
|
this.tableOption.setColumn([ |
||||
|
{ |
||||
|
key: 'username', |
||||
|
title: '操作人', |
||||
|
}, |
||||
|
{ |
||||
|
key: 'content', |
||||
|
title: '操作内容', |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
key: 'createTime', |
||||
|
title: '创建时间', |
||||
|
}, |
||||
|
]) |
||||
|
} |
||||
|
|
||||
|
ngAfterViewInit(): void {} |
||||
|
|
||||
|
fetchData(p: {}, q: AnyObject) { |
||||
|
if (Array.isArray(q['createTime'])) { |
||||
|
const createTimeStart = q['createTime']?.[0] |
||||
|
const createTimeEnd = q['createTime']?.[1] |
||||
|
|
||||
|
q['createTimeStart'] = createTimeStart ? format(new Date(createTimeStart), 'yyyy-MM-dd HH:mm:ss') : '' |
||||
|
q['createTimeEnd'] = createTimeEnd ? format(new Date(createTimeEnd), 'yyyy-MM-dd HH:mm:ss') : '' |
||||
|
} |
||||
|
return this.api.getSysLog(p, q) |
||||
|
} |
||||
|
|
||||
|
create(nzContent: TemplateRef<{}>) { |
||||
|
this.modal.create({ |
||||
|
nzTitle: '添加授权', |
||||
|
nzContent, |
||||
|
nzWidth: 720, |
||||
|
nzOnOk: async () => { |
||||
|
this.tableOption.ref?.reload() |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
onSearch() {} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
<div class="flex items-center justify-center fixed inset-0 z-10 login-page"> |
||||
|
<div class="bg fixed inset-0"></div> |
||||
|
<div class="w-[450px] relative z-10 login-form p-8 bg-white rounded-md shadow-2xl"> |
||||
|
<div class="px-4"> |
||||
|
<div class="text-center"> |
||||
|
<h1>Back to sea</h1> |
||||
|
<p class="text-lg mb-6">登录</p> |
||||
|
</div> |
||||
|
<form nz-form [formGroup]="loginForm" class="mt-12"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-control [nzErrorTip]="formErrorTipsTpl"> |
||||
|
<nz-input-group [nzPrefix]="prefixTemplateUser" nzSize="large"> |
||||
|
<input nz-input nzSize="large" placeholder="用户名" formControlName="username" /> |
||||
|
</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> |
||||
|
|
||||
|
<ng-template #formErrorTipsTpl let-control> |
||||
|
<form-error-tips [control]="control"></form-error-tips> |
||||
|
</ng-template> |
||||
@ -0,0 +1,20 @@ |
|||||
|
.login-page { |
||||
|
|
||||
|
|
||||
|
.bg { |
||||
|
background-image: url(/assets/images/login-bg.jpg); |
||||
|
background-repeat: no-repeat; |
||||
|
background-size: cover; |
||||
|
filter: blur(1px); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
.login-form { |
||||
|
|
||||
|
background-color: #fff; |
||||
|
background-color: rgba(255, 255, 255, .7); |
||||
|
-webkit-backdrop-filter: blur(10px); |
||||
|
backdrop-filter: blur(10px); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
import { Component } from '@angular/core' |
||||
|
import { FormControl, FormGroup } from '@angular/forms' |
||||
|
import { SharedModule } from '../../shared/shared.module' |
||||
|
import { FormValidators } from 'app/utils' |
||||
|
import { ApiService, LocalHttpInterceptorService } from 'app/services' |
||||
|
import { NzMessageService } from 'ng-zorro-antd/message' |
||||
|
import { Router } from '@angular/router' |
||||
|
import { finalize } from 'rxjs' |
||||
|
|
||||
|
@Component({ |
||||
|
standalone: true, |
||||
|
imports: [SharedModule], |
||||
|
selector: 'app-login', |
||||
|
templateUrl: './login.component.html', |
||||
|
styleUrls: ['./login.component.less'], |
||||
|
}) |
||||
|
export class LoginComponent { |
||||
|
constructor( |
||||
|
private api: ApiService, |
||||
|
private msg: NzMessageService, |
||||
|
private local: LocalHttpInterceptorService, |
||||
|
private router: Router, |
||||
|
) {} |
||||
|
|
||||
|
loading = false |
||||
|
|
||||
|
loginForm = new FormGroup({ |
||||
|
username: new FormControl('', [FormValidators.required('请输入用户名')]), |
||||
|
password: new FormControl('', [FormValidators.required('请输入密码')]), |
||||
|
}) |
||||
|
|
||||
|
onLogin() { |
||||
|
if (FormValidators.validateFormGroup(this.loginForm)) { |
||||
|
this.loading = true |
||||
|
this.router.navigate(['/']) |
||||
|
return |
||||
|
this.api |
||||
|
.login(this.loginForm.value) |
||||
|
.pipe( |
||||
|
finalize(() => { |
||||
|
this.loading = false |
||||
|
}), |
||||
|
) |
||||
|
.subscribe((res) => { |
||||
|
this.msg.success('登录成功') |
||||
|
this.local.setAccess({ |
||||
|
access_token: res.data, |
||||
|
}) |
||||
|
localStorage.setItem('add', res.data) |
||||
|
this.router.navigate(['/']) |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,5 @@ |
|||||
|
<nz-card [nzBordered]="false" class="h-screen"> |
||||
|
<div class="flex items-center justify-center"> |
||||
|
<nz-result nzStatus="404" nzTitle="此页面未找到。"></nz-result> |
||||
|
</div> |
||||
|
</nz-card> |
||||
@ -0,0 +1,11 @@ |
|||||
|
import { Component } from '@angular/core' |
||||
|
import { SharedModule } from 'app/shared/shared.module' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-notfound', |
||||
|
standalone: true, |
||||
|
imports: [SharedModule], |
||||
|
templateUrl: './notfound.component.html', |
||||
|
styleUrl: './notfound.component.less', |
||||
|
}) |
||||
|
export class NotfoundComponent {} |
||||
@ -0,0 +1,43 @@ |
|||||
|
<div> |
||||
|
<nz-card [nzBordered]="false" nzTitle="产品详情" [nzExtra]="nzExtraTpl"> |
||||
|
<ng-template #nzExtraTpl> |
||||
|
<nz-space> |
||||
|
<button *nzSpaceItem nz-button [routerLink]="['/', 'product']">返回</button> |
||||
|
</nz-space> |
||||
|
</ng-template> |
||||
|
|
||||
|
@if (detail(); as info) { |
||||
|
<nz-descriptions [nzColumn]="2" class="ml-16"> |
||||
|
<nz-descriptions-item nzTitle="产品ID"> |
||||
|
{{ info.productId }} |
||||
|
</nz-descriptions-item> |
||||
|
<nz-descriptions-item nzTitle="产品名称"> |
||||
|
{{ info.name }} |
||||
|
</nz-descriptions-item> |
||||
|
|
||||
|
<nz-descriptions-item nzTitle="说明">{{ info.remark || '-' }}</nz-descriptions-item> |
||||
|
</nz-descriptions> |
||||
|
} |
||||
|
</nz-card> |
||||
|
</div> |
||||
|
<!-- <div class="mt-4"> |
||||
|
<nz-card [nzBordered]="false" nzTitle="版本管理" [nzExtra]="versionExtraTpl"> |
||||
|
<ng-template #versionExtraTpl> |
||||
|
<nz-space> |
||||
|
<button *nzSpaceItem nz-button nzType="primary" (click)="createVersion()">添加</button> |
||||
|
</nz-space> |
||||
|
</ng-template> |
||||
|
<app-server-paginated-table [options]="tableOption"></app-server-paginated-table> |
||||
|
</nz-card> |
||||
|
</div> --> |
||||
|
|
||||
|
<ng-template #versionFormTpl> |
||||
|
<form nz-form [formGroup]="formGroup"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzRequired>版本号</nz-form-label> |
||||
|
<nz-form-control> |
||||
|
<input nz-input formControlName="name" placeholder="请输入版本号" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</form> |
||||
|
</ng-template> |
||||
@ -0,0 +1,84 @@ |
|||||
|
import { TemplateRef } from '@angular/core' |
||||
|
import { ViewChild } from '@angular/core' |
||||
|
import { Component, OnInit, signal, computed, effect } from '@angular/core' |
||||
|
import { FormControl, FormGroup } from '@angular/forms' |
||||
|
import { ActivatedRoute, Router } from '@angular/router' |
||||
|
import { TableOption } from 'app/components/server-paginated-table' |
||||
|
import { ApiService } from 'app/services' |
||||
|
import { VersionDTO } from 'app/services/api.dto' |
||||
|
import { SharedModule } from 'app/shared/shared.module' |
||||
|
import { FormValidators } from 'app/utils' |
||||
|
import { NzMessageService } from 'ng-zorro-antd/message' |
||||
|
import { NzModalService } from 'ng-zorro-antd/modal' |
||||
|
import { lastValueFrom } from 'rxjs' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-product-detail', |
||||
|
standalone: true, |
||||
|
imports: [SharedModule], |
||||
|
templateUrl: './product-detail.component.html', |
||||
|
styleUrl: './product-detail.component.less', |
||||
|
}) |
||||
|
export class ProductDetailComponent implements OnInit { |
||||
|
constructor( |
||||
|
private api: ApiService, |
||||
|
private router: Router, |
||||
|
private route: ActivatedRoute, |
||||
|
private modal: NzModalService, |
||||
|
private msg: NzMessageService, |
||||
|
) {} |
||||
|
|
||||
|
@ViewChild('versionFormTpl') versionFormTpl!: TemplateRef<{}> |
||||
|
|
||||
|
tableOption = new TableOption(this.fetchData.bind(this)) |
||||
|
|
||||
|
formGroup = new FormGroup({ |
||||
|
name: new FormControl('', [FormValidators.required('请输入产品版本号')]), |
||||
|
}) |
||||
|
|
||||
|
detail = signal({ |
||||
|
productId: '', |
||||
|
name: '', |
||||
|
remark: '', |
||||
|
}) |
||||
|
|
||||
|
id!: string |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.id = this.route.snapshot.paramMap.get('id')! |
||||
|
this.api.getProductDetail(this.id).subscribe((res) => { |
||||
|
this.detail.set(res.data) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
fetchData(p: {}, q: {}) { |
||||
|
return this.api.getProductVersionPage(p, { ...q, productId: this.id }) |
||||
|
} |
||||
|
|
||||
|
createVersion(version?: VersionDTO) { |
||||
|
this.modal.create({ |
||||
|
nzTitle: '添加版本', |
||||
|
nzContent: this.versionFormTpl, |
||||
|
nzOnOk: async () => { |
||||
|
if (FormValidators.validateFormGroup(this.formGroup)) { |
||||
|
await lastValueFrom(this.api.saveVersion({ ...(version ?? {}), ...this.formGroup.value })) |
||||
|
this.msg.success('保存成功') |
||||
|
this.tableOption.ref?.reload() |
||||
|
} |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
deleteItem(v: VersionDTO) { |
||||
|
this.modal.confirm({ |
||||
|
nzTitle: '警告', |
||||
|
nzContent: '是否要删除该版本?', |
||||
|
nzOkDanger: true, |
||||
|
nzOnOk: async () => { |
||||
|
await lastValueFrom(this.api.deleteVersion(v.id, this.id)) |
||||
|
this.msg.success('删除成功') |
||||
|
this.tableOption.ref?.reload() |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
<nz-card [nzBordered]="false"> |
||||
|
<app-server-paginated-table |
||||
|
#tableTpl |
||||
|
[renderColumn]="renderColumnsTpl" |
||||
|
[formGroup]="queryForm" |
||||
|
[simpleSearch]="true" |
||||
|
[options]="table" |
||||
|
> |
||||
|
<ng-template #renderColumnsTpl let-data let-key="key" let-row="row"> |
||||
|
@switch (key) { |
||||
|
@case ('name') { |
||||
|
<a [routerLink]="['/', 'product', row.id, 'detail']"> |
||||
|
{{ data }} |
||||
|
</a> |
||||
|
} |
||||
|
@default { |
||||
|
{{ data }} |
||||
|
} |
||||
|
} |
||||
|
</ng-template> |
||||
|
|
||||
|
<div *appTableForm nz-row [nzGutter]="[24, 24]"> |
||||
|
<div nz-col nzSpan="8"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzFlex="80px">产品名称</nz-form-label> |
||||
|
<nz-form-control nzSpan="18"> |
||||
|
<input nz-input placeholder="请输入产品名称" formControlName="name" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</div> |
||||
|
<div nz-col nzSpan="12"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzFlex="80px">创建时间</nz-form-label> |
||||
|
<nz-form-control nzSpan="18"> |
||||
|
<!-- <nz-range-picker nzShowTime formControlName="createTime"></nz-range-picker> --> |
||||
|
<app-range-picker [showTime]="true" formControlName="createTime"></app-range-picker> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div *appTableAction class="flex"> |
||||
|
<div> |
||||
|
<nz-space> |
||||
|
<button *nzSpaceItem nzType="primary" nz-button (click)="create()">添加产品</button> |
||||
|
</nz-space> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- <div action>Custom Modal action</div> --> |
||||
|
</app-server-paginated-table> |
||||
|
</nz-card> |
||||
|
|
||||
|
<ng-template #productFormTpl> |
||||
|
<form [formGroup]="createForm" nz-form [nzLayout]="'vertical'"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzRequired> 产品名称 </nz-form-label> |
||||
|
<nz-form-control [nzErrorTip]="formErrorTipsTpl"> |
||||
|
<input nz-input placeholder="请输入产品名称" formControlName="name" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
|
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzRequired> 产品ID </nz-form-label> |
||||
|
<nz-form-control [nzErrorTip]="formErrorTipsTpl"> |
||||
|
<input nz-input placeholder="请输入产品ID" formControlName="productId" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
|
||||
|
<nz-form-item> |
||||
|
<nz-form-label> 产品说明 </nz-form-label> |
||||
|
<nz-form-control [nzErrorTip]="formErrorTipsTpl"> |
||||
|
<textarea nz-input placeholder="请输入产品说明" formControlName="remark"></textarea> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</form> |
||||
|
</ng-template> |
||||
|
|
||||
|
<ng-template #formErrorTipsTpl let-control> |
||||
|
<form-error-tips [control]="control"></form-error-tips> |
||||
|
</ng-template> |
||||
@ -0,0 +1,104 @@ |
|||||
|
import { Component, ElementRef, OnInit, TemplateRef, ViewChild } from '@angular/core' |
||||
|
import { FormControl, FormGroup } from '@angular/forms' |
||||
|
import { NzModalService } from 'ng-zorro-antd/modal' |
||||
|
import { ApiService } from 'app/services' |
||||
|
import { AnyObject, ServerPaginatedTableComponent, TableOption } from 'app/components/server-paginated-table' |
||||
|
import { SharedModule } from 'app/shared/shared.module' |
||||
|
import { FormValidators } from 'app/utils' |
||||
|
import { lastValueFrom } from 'rxjs' |
||||
|
import { ProductDTO } from 'app/services/api.dto' |
||||
|
import { NzMessageService } from 'ng-zorro-antd/message' |
||||
|
import { format } from 'date-fns' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-product-list', |
||||
|
standalone: true, |
||||
|
imports: [SharedModule], |
||||
|
templateUrl: './product-list.component.html', |
||||
|
styleUrl: './product-list.component.less', |
||||
|
}) |
||||
|
export class ProductListComponent { |
||||
|
constructor( |
||||
|
private api: ApiService, |
||||
|
private modal: NzModalService, |
||||
|
private msg: NzMessageService, |
||||
|
) {} |
||||
|
|
||||
|
@ViewChild('productFormTpl') productFormTpl!: TemplateRef<{}> |
||||
|
|
||||
|
table = new TableOption( |
||||
|
this.fetchData.bind(this), |
||||
|
[ |
||||
|
{ key: 'name', title: '产品名称' }, |
||||
|
{ key: 'productId', title: '产品ID' }, |
||||
|
{ key: 'remark', title: '产品说明', width: '500px' }, |
||||
|
{ key: 'createTime', title: '创建时间' }, |
||||
|
], |
||||
|
[], |
||||
|
) |
||||
|
|
||||
|
queryForm = new FormGroup({ |
||||
|
name: new FormControl(''), |
||||
|
createTime: new FormControl(), |
||||
|
}) |
||||
|
|
||||
|
createForm = new FormGroup({ |
||||
|
name: new FormControl('', [FormValidators.required('请输入产品名称'), FormValidators.maxLength(30)]), |
||||
|
productId: new FormControl('', [FormValidators.required('请输入产品ID'), FormValidators.maxLength(30)]), |
||||
|
remark: new FormControl('', [FormValidators.maxLength(200)]), |
||||
|
}) |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.table.setRowOperate([ |
||||
|
{ title: '编辑', onClick: this.create.bind(this) }, |
||||
|
{ title: '删除', onClick: this.deleteItem.bind(this) }, |
||||
|
]) |
||||
|
} |
||||
|
|
||||
|
fetchData(p: {}, q: AnyObject) { |
||||
|
if (Array.isArray(q['createTime'])) { |
||||
|
const createTimeStart = q['createTime']?.[0] |
||||
|
const createTimeEnd = q['createTime']?.[1] |
||||
|
|
||||
|
q['createTimeStart'] = createTimeStart ? format(new Date(createTimeStart), 'yyyy-MM-dd HH:mm:ss') : '' |
||||
|
q['createTimeEnd'] = createTimeEnd ? format(new Date(createTimeEnd), 'yyyy-MM-dd HH:mm:ss') : '' |
||||
|
} |
||||
|
return this.api.getProductPage(p, q) |
||||
|
} |
||||
|
|
||||
|
onSearch() {} |
||||
|
|
||||
|
create(product?: ProductDTO) { |
||||
|
if (product) { |
||||
|
this.createForm.patchValue(product) |
||||
|
} |
||||
|
this.modal.create({ |
||||
|
nzTitle: product ? '编辑产品' : '添加产品', |
||||
|
nzContent: this.productFormTpl, |
||||
|
nzOnOk: async () => { |
||||
|
if (FormValidators.validateFormGroup(this.createForm)) { |
||||
|
await lastValueFrom(this.api.saveProduct({ ...(product ?? {}), ...this.createForm.value })) |
||||
|
this.msg.success('保存成功') |
||||
|
this.table.ref?.reload() |
||||
|
this.createForm.reset() |
||||
|
} |
||||
|
}, |
||||
|
nzOnCancel: () => { |
||||
|
this.createForm.reset() |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
deleteItem(product: ProductDTO) { |
||||
|
this.modal.confirm({ |
||||
|
nzTitle: '警告', |
||||
|
nzContent: '是否要删除该产品?', |
||||
|
nzOkDanger: true, |
||||
|
nzOnOk: async () => { |
||||
|
await lastValueFrom(this.api.deleteProduct(product.id)) |
||||
|
this.msg.success('删除成功') |
||||
|
this.table.ref?.reload() |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
<div class="w-[600px] pt-6"> |
||||
|
<form nz-form [formGroup]="formGroup" (ngSubmit)="onSubmit()"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzSpan="4" nzRequired> 用户名 </nz-form-label> |
||||
|
<nz-form-control nzSpan="16"> |
||||
|
<input nz-input placeholder="请输入用户名" formControlName="username" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzSpan="4" nzRequired> 新密码 </nz-form-label> |
||||
|
<nz-form-control nzSpan="16"> |
||||
|
<input nz-input type="password" placeholder="请输入新密码" formControlName="password" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzSpan="4" nzRequired> 确认密码 </nz-form-label> |
||||
|
<nz-form-control nzSpan="16"> |
||||
|
<input nz-input type="password" placeholder="请再次输入密码" formControlName="confirmPassword" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
|
||||
|
<nz-form-item> |
||||
|
<nz-form-control nzOffset="4"> |
||||
|
<button nz-button nzType="primary">保存</button> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</form> |
||||
|
</div> |
||||
@ -0,0 +1,46 @@ |
|||||
|
import { Component } from '@angular/core' |
||||
|
import { SharedModule } from '../../shared/shared.module' |
||||
|
import { FormControl, FormGroup } from '@angular/forms' |
||||
|
import { FormValidators } from 'app/utils' |
||||
|
import { ApiService, LocalHttpInterceptorService } from 'app/services' |
||||
|
import { NzMessageService } from 'ng-zorro-antd/message' |
||||
|
|
||||
|
@Component({ |
||||
|
standalone: true, |
||||
|
imports: [SharedModule], |
||||
|
selector: 'app-profile-account', |
||||
|
templateUrl: './profile-account.component.html', |
||||
|
styleUrls: ['./profile-account.component.less'], |
||||
|
}) |
||||
|
export class ProfileAccountComponent { |
||||
|
constructor( |
||||
|
private local: LocalHttpInterceptorService, |
||||
|
private api: ApiService, |
||||
|
private msg: NzMessageService, |
||||
|
) {} |
||||
|
|
||||
|
formGroup = new FormGroup({ |
||||
|
id: new FormControl(this.api.authInfo.id), |
||||
|
username: new FormControl({ value: this.api.authInfo.username, disabled: true }, [ |
||||
|
FormValidators.required('请输入用户名'), |
||||
|
]), |
||||
|
password: new FormControl('', [FormValidators.required('请输入密码')]), |
||||
|
confirmPassword: new FormControl('', [FormValidators.required('请再次输入密码')]), |
||||
|
}) |
||||
|
|
||||
|
onSubmit() { |
||||
|
if (FormValidators.validateFormGroup(this.formGroup)) { |
||||
|
if (this.formGroup.value.confirmPassword !== this.formGroup.value.password) { |
||||
|
this.msg.error('两次输入密码不一致') |
||||
|
return |
||||
|
} |
||||
|
const v = this.formGroup.getRawValue() |
||||
|
this.api.changePassword(v).subscribe(() => { |
||||
|
this.msg.success('修改成功,请重新登录').onClose.subscribe(() => { |
||||
|
this.local.removeAccess() |
||||
|
window.location.href = '/login' |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
<div class="w-[600px] pt-6"> |
||||
|
<form nz-form [formGroup]="formGroup" (ngSubmit)="onSubmit()"> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzSpan="4" nzRequired>姓名</nz-form-label> |
||||
|
<nz-form-control nzSpan="16" [nzErrorTip]="formErrorTipsTpl"> |
||||
|
<input nz-input placeholder="请输入姓名" formControlName="name" /> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
|
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzSpan="4">备注</nz-form-label> |
||||
|
<nz-form-control nzSpan="16"> |
||||
|
<textarea nz-input placeholder="请输入备注" formControlName="remark"></textarea> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
<!-- <nz-form-item> |
||||
|
<nz-form-label nzSpan="4">登录限制</nz-form-label> |
||||
|
<nz-form-control nzSpan="16"> |
||||
|
<nz-switch></nz-switch> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
<nz-form-item> |
||||
|
<nz-form-label nzSpan="4">限制规则</nz-form-label> |
||||
|
<nz-form-control nzSpan="16"> |
||||
|
<div> |
||||
|
<nz-input-group nzAddOnBefore="登录失败达到" nzAddOnAfter="次"> |
||||
|
<input type="number" nz-input /> |
||||
|
</nz-input-group> |
||||
|
</div> |
||||
|
<div class="mt-2"> |
||||
|
<ng-template #nzAddOnAfterTpl> |
||||
|
<nz-select class="!w-20"> |
||||
|
<nz-option nzValue="m" nzLabel="分钟"></nz-option> |
||||
|
<nz-option nzValue="h" nzLabel="小时"></nz-option> |
||||
|
<nz-option nzValue="d" nzLabel="天"></nz-option> |
||||
|
</nz-select> |
||||
|
</ng-template> |
||||
|
<nz-input-group nzAddOnBefore="锁定账号" [nzAddOnAfter]="nzAddOnAfterTpl"> |
||||
|
<input type="number" nz-input /> |
||||
|
</nz-input-group> |
||||
|
</div> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> --> |
||||
|
<nz-form-item> |
||||
|
<nz-form-control nzOffset="4"> |
||||
|
<button nz-button nzType="primary">保存</button> |
||||
|
</nz-form-control> |
||||
|
</nz-form-item> |
||||
|
</form> |
||||
|
|
||||
|
<ng-template #formErrorTipsTpl let-control> |
||||
|
<form-error-tips [control]="control"></form-error-tips> |
||||
|
</ng-template> |
||||
|
</div> |
||||
@ -0,0 +1,52 @@ |
|||||
|
import { Component, OnDestroy, OnInit } from '@angular/core' |
||||
|
import { SharedModule } from '../../shared/shared.module' |
||||
|
import { ApiService } from 'app/services' |
||||
|
import { UserFormComponent } from 'app/components/user-form/user-form.component' |
||||
|
import { FormBuilder, FormControl, FormGroup } from '@angular/forms' |
||||
|
import { FormValidators } from 'app/utils' |
||||
|
import { NzMessageService } from 'ng-zorro-antd/message' |
||||
|
import { Subscription } from 'rxjs' |
||||
|
|
||||
|
@Component({ |
||||
|
standalone: true, |
||||
|
imports: [SharedModule, UserFormComponent], |
||||
|
selector: 'app-profile-basic', |
||||
|
templateUrl: './profile-basic.component.html', |
||||
|
styleUrls: ['./profile-basic.component.less'], |
||||
|
}) |
||||
|
export class ProfileBasicComponent implements OnInit, OnDestroy { |
||||
|
constructor( |
||||
|
public api: ApiService, |
||||
|
private fb: FormBuilder, |
||||
|
private msg: NzMessageService, |
||||
|
) {} |
||||
|
|
||||
|
formGroup!: FormGroup |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.formGroup = this.fb.group({ |
||||
|
name: new FormControl(this.api.authInfo.name, [FormValidators.required('请输入姓名')]), |
||||
|
remark: new FormControl(this.api.authInfo.remark), |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy(): void {} |
||||
|
|
||||
|
public getValues() { |
||||
|
let values = null |
||||
|
if (FormValidators.validateFormGroup(this.formGroup)) { |
||||
|
values = this.formGroup.value |
||||
|
} |
||||
|
return values |
||||
|
} |
||||
|
|
||||
|
onSubmit() { |
||||
|
const vals = this.getValues() |
||||
|
if (vals) { |
||||
|
this.api.saveUser({ ...this.api.authInfo, ...vals }).subscribe(() => { |
||||
|
this.msg.success('保存成功') |
||||
|
this.api.getAuthInfo(true).subscribe(() => {}) |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
<div> |
||||
|
<nz-card [nzBordered]="false"> |
||||
|
<nz-descriptions [nzColumn]="2" class="ml-16"> |
||||
|
<nz-descriptions-item nzTitle="用户名"> |
||||
|
{{ api.authInfo.username }} |
||||
|
</nz-descriptions-item> |
||||
|
<nz-descriptions-item nzTitle="姓名"> |
||||
|
{{ api.authInfo.name }} |
||||
|
</nz-descriptions-item> |
||||
|
<nz-descriptions-item nzTitle="角色"> |
||||
|
{{ api.authInfo.roleName }} |
||||
|
</nz-descriptions-item> |
||||
|
<nz-descriptions-item nzTitle="状态"> |
||||
|
@if (api.authInfo.enable) { |
||||
|
<nz-badge nzStatus="success" nzText="启用"></nz-badge> |
||||
|
} @else { |
||||
|
<nz-badge nzStatus="errot" nzText="禁用"></nz-badge> |
||||
|
} |
||||
|
</nz-descriptions-item> |
||||
|
<nz-descriptions-item nzTitle="备注">{{ api.authInfo.remark || '-' }}</nz-descriptions-item> |
||||
|
</nz-descriptions> |
||||
|
</nz-card> |
||||
|
</div> |
||||
|
<div class="mt-4"> |
||||
|
<nz-card [nzBordered]="false"> |
||||
|
<div class="nav-bar"> |
||||
|
<ul> |
||||
|
<li class="cursor-pointer" [routerLink]="['/profile/basic']" routerLinkActive="active">基础信息</li> |
||||
|
<li class="cursor-pointer" [routerLink]="['/profile/account']" routerLinkActive="active">账号密码</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
<div class="py-4"> |
||||
|
<router-outlet></router-outlet> |
||||
|
</div> |
||||
|
</nz-card> |
||||
|
</div> |
||||
@ -0,0 +1,18 @@ |
|||||
|
.nav-bar { |
||||
|
ul { |
||||
|
display: flex; |
||||
|
|
||||
|
li { |
||||
|
padding: 6px 24px; |
||||
|
margin-right: 12px; |
||||
|
font-size: 14px; |
||||
|
border-radius: 20px; |
||||
|
font-weight: bold; |
||||
|
|
||||
|
&.active { |
||||
|
background-color: #f2f3f5; |
||||
|
color: var(--primary); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue