Browse Source

first commit

main
kely 2 years ago
commit
a3d0b27f55
  1. 106
      web-admin-app/.gitignore
  2. 8
      web-admin-app/.prettierrc
  3. 27
      web-admin-app/README.md
  4. 120
      web-admin-app/angular.json
  5. 13413
      web-admin-app/package-lock.json
  6. 47
      web-admin-app/package.json
  7. 17
      web-admin-app/proxy/devserver.js
  8. 2
      web-admin-app/proxy/proxy.conf.js
  9. 2
      web-admin-app/proxy/proxy.conf.remote.js
  10. 1
      web-admin-app/src/app/app.component.html
  11. 4
      web-admin-app/src/app/app.component.less
  12. 11
      web-admin-app/src/app/app.component.ts
  13. 68
      web-admin-app/src/app/app.config.ts
  14. 245
      web-admin-app/src/app/app.routes.ts
  15. 32
      web-admin-app/src/app/components/app-page/app-page.component.html
  16. 6
      web-admin-app/src/app/components/app-page/app-page.component.less
  17. 68
      web-admin-app/src/app/components/app-page/app-page.component.ts
  18. 36
      web-admin-app/src/app/components/form-error-tips/form-error-tips.component.html
  19. 0
      web-admin-app/src/app/components/form-error-tips/form-error-tips.component.less
  20. 28
      web-admin-app/src/app/components/form-error-tips/form-error-tips.component.ts
  21. 82
      web-admin-app/src/app/components/header/header.component.html
  22. 32
      web-admin-app/src/app/components/header/header.component.less
  23. 41
      web-admin-app/src/app/components/header/header.component.ts
  24. 5
      web-admin-app/src/app/components/index.ts
  25. 4
      web-admin-app/src/app/components/layout/layout.component.html
  26. 15
      web-admin-app/src/app/components/layout/layout.component.less
  27. 14
      web-admin-app/src/app/components/layout/layout.component.ts
  28. 29
      web-admin-app/src/app/components/range-picker/range-picker.component.html
  29. 0
      web-admin-app/src/app/components/range-picker/range-picker.component.less
  30. 96
      web-admin-app/src/app/components/range-picker/range-picker.component.ts
  31. 17
      web-admin-app/src/app/components/server-paginated-table/date-query/date-query.component.html
  32. 12
      web-admin-app/src/app/components/server-paginated-table/date-query/date-query.component.less
  33. 14
      web-admin-app/src/app/components/server-paginated-table/date-query/date-query.component.ts
  34. 6
      web-admin-app/src/app/components/server-paginated-table/index.ts
  35. 8
      web-admin-app/src/app/components/server-paginated-table/query-item/query-item.component.html
  36. 7
      web-admin-app/src/app/components/server-paginated-table/query-item/query-item.component.less
  37. 10
      web-admin-app/src/app/components/server-paginated-table/query-item/query-item.component.ts
  38. 164
      web-admin-app/src/app/components/server-paginated-table/server-paginated-table.component.html
  39. 90
      web-admin-app/src/app/components/server-paginated-table/server-paginated-table.component.less
  40. 359
      web-admin-app/src/app/components/server-paginated-table/server-paginated-table.component.ts
  41. 57
      web-admin-app/src/app/components/server-paginated-table/server-paginated-table.module.ts
  42. 95
      web-admin-app/src/app/components/server-paginated-table/server-paginated-table.service.ts
  43. 9
      web-admin-app/src/app/components/server-paginated-table/table-action.directive.ts
  44. 9
      web-admin-app/src/app/components/server-paginated-table/table-form.directive.ts
  45. 78
      web-admin-app/src/app/components/user-form/user-form.component.html
  46. 0
      web-admin-app/src/app/components/user-form/user-form.component.less
  47. 46
      web-admin-app/src/app/components/user-form/user-form.component.ts
  48. 20
      web-admin-app/src/app/guards/auth.guard.ts
  49. 1
      web-admin-app/src/app/guards/index.ts
  50. 12
      web-admin-app/src/app/pages copy/contacts/contact-group-form/contact-group-form.component.html
  51. 0
      web-admin-app/src/app/pages copy/contacts/contact-group-form/contact-group-form.component.less
  52. 38
      web-admin-app/src/app/pages copy/contacts/contact-group-form/contact-group-form.component.ts
  53. 31
      web-admin-app/src/app/pages copy/contacts/contact-group/contact-group.component.html
  54. 0
      web-admin-app/src/app/pages copy/contacts/contact-group/contact-group.component.less
  55. 83
      web-admin-app/src/app/pages copy/contacts/contact-group/contact-group.component.ts
  56. 97
      web-admin-app/src/app/pages copy/contacts/contact-list/contact-list.component.html
  57. 3
      web-admin-app/src/app/pages copy/contacts/contact-list/contact-list.component.less
  58. 223
      web-admin-app/src/app/pages copy/contacts/contact-list/contact-list.component.ts
  59. 59
      web-admin-app/src/app/pages copy/contacts/contact.service.ts
  60. 83
      web-admin-app/src/app/pages copy/contacts/role-form/role-form.component.html
  61. 11
      web-admin-app/src/app/pages copy/contacts/role-form/role-form.component.less
  62. 146
      web-admin-app/src/app/pages copy/contacts/role-form/role-form.component.ts
  63. 27
      web-admin-app/src/app/pages copy/contacts/role-list/role-list.component.html
  64. 0
      web-admin-app/src/app/pages copy/contacts/role-list/role-list.component.less
  65. 72
      web-admin-app/src/app/pages copy/contacts/role-list/role-list.component.ts
  66. 63
      web-admin-app/src/app/pages copy/dashboard/dashboard.component.html
  67. 0
      web-admin-app/src/app/pages copy/dashboard/dashboard.component.less
  68. 195
      web-admin-app/src/app/pages copy/dashboard/dashboard.component.ts
  69. 101
      web-admin-app/src/app/pages copy/entity-detail/entity-detail.component.html
  70. 0
      web-admin-app/src/app/pages copy/entity-detail/entity-detail.component.less
  71. 105
      web-admin-app/src/app/pages copy/entity-detail/entity-detail.component.ts
  72. 83
      web-admin-app/src/app/pages copy/entity-list/entity-list.component.html
  73. 0
      web-admin-app/src/app/pages copy/entity-list/entity-list.component.less
  74. 132
      web-admin-app/src/app/pages copy/entity-list/entity-list.component.ts
  75. 5
      web-admin-app/src/app/pages copy/forbidden/forbidden.component.html
  76. 0
      web-admin-app/src/app/pages copy/forbidden/forbidden.component.less
  77. 11
      web-admin-app/src/app/pages copy/forbidden/forbidden.component.ts
  78. 82
      web-admin-app/src/app/pages copy/log/log.component.html
  79. 0
      web-admin-app/src/app/pages copy/log/log.component.less
  80. 87
      web-admin-app/src/app/pages copy/log/log.component.ts
  81. 54
      web-admin-app/src/app/pages copy/login/login.component.html
  82. 20
      web-admin-app/src/app/pages copy/login/login.component.less
  83. 54
      web-admin-app/src/app/pages copy/login/login.component.ts
  84. 5
      web-admin-app/src/app/pages copy/notfound/notfound.component.html
  85. 0
      web-admin-app/src/app/pages copy/notfound/notfound.component.less
  86. 11
      web-admin-app/src/app/pages copy/notfound/notfound.component.ts
  87. 43
      web-admin-app/src/app/pages copy/product-detail/product-detail.component.html
  88. 0
      web-admin-app/src/app/pages copy/product-detail/product-detail.component.less
  89. 84
      web-admin-app/src/app/pages copy/product-detail/product-detail.component.ts
  90. 80
      web-admin-app/src/app/pages copy/product-list/product-list.component.html
  91. 0
      web-admin-app/src/app/pages copy/product-list/product-list.component.less
  92. 104
      web-admin-app/src/app/pages copy/product-list/product-list.component.ts
  93. 28
      web-admin-app/src/app/pages copy/profile-account/profile-account.component.html
  94. 0
      web-admin-app/src/app/pages copy/profile-account/profile-account.component.less
  95. 46
      web-admin-app/src/app/pages copy/profile-account/profile-account.component.ts
  96. 54
      web-admin-app/src/app/pages copy/profile-basic/profile-basic.component.html
  97. 0
      web-admin-app/src/app/pages copy/profile-basic/profile-basic.component.less
  98. 52
      web-admin-app/src/app/pages copy/profile-basic/profile-basic.component.ts
  99. 36
      web-admin-app/src/app/pages copy/profile/profile.component.html
  100. 18
      web-admin-app/src/app/pages copy/profile/profile.component.less

106
web-admin-app/.gitignore

@ -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

8
web-admin-app/.prettierrc

@ -0,0 +1,8 @@
{
"singleQuote": true,
"trailingComma": "all",
"useTabs": true,
"tabWidth": 4,
"semi": false,
"printWidth": 120
}

27
web-admin-app/README.md

@ -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.

120
web-admin-app/angular.json

@ -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
}
}

13413
web-admin-app/package-lock.json

File diff suppressed because it is too large

47
web-admin-app/package.json

@ -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"
}
}

17
web-admin-app/proxy/devserver.js

@ -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

2
web-admin-app/proxy/proxy.conf.js

@ -0,0 +1,2 @@
const devProxy = require('./devserver')
module.exports = devProxy()

2
web-admin-app/proxy/proxy.conf.remote.js

@ -0,0 +1,2 @@
const devProxy = require("./devserver");
module.exports = devProxy(true);

1
web-admin-app/src/app/app.component.html

@ -0,0 +1 @@
<router-outlet></router-outlet>

4
web-admin-app/src/app/app.component.less

@ -0,0 +1,4 @@
:host {
display: block;
height: 100%;
}

11
web-admin-app/src/app/app.component.ts

@ -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 {}

68
web-admin-app/src/app/app.config.ts

@ -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' },
],
}

245
web-admin-app/src/app/app.routes.ts

@ -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',
},
]

32
web-admin-app/src/app/components/app-page/app-page.component.html

@ -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>

6
web-admin-app/src/app/components/app-page/app-page.component.less

@ -0,0 +1,6 @@
:host {
@apply p-3;
height: 100%;
display: flex;
flex-direction: column;
}

68
web-admin-app/src/app/components/app-page/app-page.component.ts

@ -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
}
}

36
web-admin-app/src/app/components/form-error-tips/form-error-tips.component.html

@ -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
web-admin-app/src/app/components/form-error-tips/form-error-tips.component.less

28
web-admin-app/src/app/components/form-error-tips/form-error-tips.component.ts

@ -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 {}
}

82
web-admin-app/src/app/components/header/header.component.html

@ -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>

32
web-admin-app/src/app/components/header/header.component.less

@ -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;
}
}
}
}

41
web-admin-app/src/app/components/header/header.component.ts

@ -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('退出成功')
},
})
}
}

5
web-admin-app/src/app/components/index.ts

@ -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'

4
web-admin-app/src/app/components/layout/layout.component.html

@ -0,0 +1,4 @@
<app-header></app-header>
<main class="app-width mx-auto">
<router-outlet></router-outlet>
</main>

15
web-admin-app/src/app/components/layout/layout.component.less

@ -0,0 +1,15 @@
:host {
display: block;
height: 100%;
}
.app-width {
::ng-deep {
router-outlet+* {
display: block;
width: 100%;
height: 100%;
// overflow: hidden;
}
}
}

14
web-admin-app/src/app/components/layout/layout.component.ts

@ -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 {
}

29
web-admin-app/src/app/components/range-picker/range-picker.component.html

@ -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
web-admin-app/src/app/components/range-picker/range-picker.component.less

96
web-admin-app/src/app/components/range-picker/range-picker.component.ts

@ -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 {}
}

17
web-admin-app/src/app/components/server-paginated-table/date-query/date-query.component.html

@ -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>

12
web-admin-app/src/app/components/server-paginated-table/date-query/date-query.component.less

@ -0,0 +1,12 @@
::ng-deep {
.inner-date-picker {
.ant-popover-inner-content {
padding: 0;
}
.ant-picker-dropdown {
padding: 0;
}
}
}

14
web-admin-app/src/app/components/server-paginated-table/date-query/date-query.component.ts

@ -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> = []
}

6
web-admin-app/src/app/components/server-paginated-table/index.ts

@ -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'

8
web-admin-app/src/app/components/server-paginated-table/query-item/query-item.component.html

@ -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>

7
web-admin-app/src/app/components/server-paginated-table/query-item/query-item.component.less

@ -0,0 +1,7 @@
.query-control {
::ng-deep {
input {
width: 100px;
}
}
}

10
web-admin-app/src/app/components/server-paginated-table/query-item/query-item.component.ts

@ -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
}

164
web-admin-app/src/app/components/server-paginated-table/server-paginated-table.component.html

@ -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>

90
web-admin-app/src/app/components/server-paginated-table/server-paginated-table.component.less

@ -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);
}
}
}

359
web-admin-app/src/app/components/server-paginated-table/server-paginated-table.component.ts

@ -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
}
}

57
web-admin-app/src/app/components/server-paginated-table/server-paginated-table.module.ts

@ -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 {}

95
web-admin-app/src/app/components/server-paginated-table/server-paginated-table.service.ts

@ -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
}
}

9
web-admin-app/src/app/components/server-paginated-table/table-action.directive.ts

@ -0,0 +1,9 @@
import { Directive, TemplateRef } from "@angular/core";
@Directive({
selector: "[appTableAction]",
exportAs: "appTableAction",
})
export class TableActionDirective {
constructor(public templateRef: TemplateRef<{}>) {}
}

9
web-admin-app/src/app/components/server-paginated-table/table-form.directive.ts

@ -0,0 +1,9 @@
import { Directive, TemplateRef } from "@angular/core";
@Directive({
selector: "[appTableForm]",
exportAs: "appTableForm",
})
export class TableFormDirective {
constructor(public templateRef: TemplateRef<{}>) {}
}

78
web-admin-app/src/app/components/user-form/user-form.component.html

@ -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
web-admin-app/src/app/components/user-form/user-form.component.less

46
web-admin-app/src/app/components/user-form/user-form.component.ts

@ -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
}
}

20
web-admin-app/src/app/guards/auth.guard.ts

@ -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))
}

1
web-admin-app/src/app/guards/index.ts

@ -0,0 +1 @@
export * from "./auth.guard";

12
web-admin-app/src/app/pages copy/contacts/contact-group-form/contact-group-form.component.html

@ -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
web-admin-app/src/app/pages copy/contacts/contact-group-form/contact-group-form.component.less

38
web-admin-app/src/app/pages copy/contacts/contact-group-form/contact-group-form.component.ts

@ -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
}
}

31
web-admin-app/src/app/pages copy/contacts/contact-group/contact-group.component.html

@ -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
web-admin-app/src/app/pages copy/contacts/contact-group/contact-group.component.less

83
web-admin-app/src/app/pages copy/contacts/contact-group/contact-group.component.ts

@ -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)
}
}

97
web-admin-app/src/app/pages copy/contacts/contact-list/contact-list.component.html

@ -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>

3
web-admin-app/src/app/pages copy/contacts/contact-list/contact-list.component.less

@ -0,0 +1,3 @@
.group {
border-right: 1px solid var(--border-color);
}

223
web-admin-app/src/app/pages copy/contacts/contact-list/contact-list.component.ts

@ -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
},
})
}
}

59
web-admin-app/src/app/pages copy/contacts/contact.service.ts

@ -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)
}
}

83
web-admin-app/src/app/pages copy/contacts/role-form/role-form.component.html

@ -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>

11
web-admin-app/src/app/pages copy/contacts/role-form/role-form.component.less

@ -0,0 +1,11 @@
.permissions {
li {
display: flex;
padding: 8px 24px;
&:not(:last-child) {
border-bottom: 1px solid var(--border-color);
}
}
}

146
web-admin-app/src/app/pages copy/contacts/role-form/role-form.component.ts

@ -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'])
})
}
}
}

27
web-admin-app/src/app/pages copy/contacts/role-list/role-list.component.html

@ -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
web-admin-app/src/app/pages copy/contacts/role-list/role-list.component.less

72
web-admin-app/src/app/pages copy/contacts/role-list/role-list.component.ts

@ -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()
},
})
}
}

63
web-admin-app/src/app/pages copy/dashboard/dashboard.component.html

@ -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
web-admin-app/src/app/pages copy/dashboard/dashboard.component.less

195
web-admin-app/src/app/pages copy/dashboard/dashboard.component.ts

@ -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',
},
],
})
}
}
}

101
web-admin-app/src/app/pages copy/entity-detail/entity-detail.component.html

@ -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
web-admin-app/src/app/pages copy/entity-detail/entity-detail.component.less

105
web-admin-app/src/app/pages copy/entity-detail/entity-detail.component.ts

@ -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()
})
}
}

83
web-admin-app/src/app/pages copy/entity-list/entity-list.component.html

@ -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
web-admin-app/src/app/pages copy/entity-list/entity-list.component.less

132
web-admin-app/src/app/pages copy/entity-list/entity-list.component.ts

@ -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()
},
})
}
}

5
web-admin-app/src/app/pages copy/forbidden/forbidden.component.html

@ -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
web-admin-app/src/app/pages copy/forbidden/forbidden.component.less

11
web-admin-app/src/app/pages copy/forbidden/forbidden.component.ts

@ -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 {}

82
web-admin-app/src/app/pages copy/log/log.component.html

@ -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
web-admin-app/src/app/pages copy/log/log.component.less

87
web-admin-app/src/app/pages copy/log/log.component.ts

@ -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() {}
}

54
web-admin-app/src/app/pages copy/login/login.component.html

@ -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>

20
web-admin-app/src/app/pages copy/login/login.component.less

@ -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);
}
}

54
web-admin-app/src/app/pages copy/login/login.component.ts

@ -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(['/'])
})
}
}
}

5
web-admin-app/src/app/pages copy/notfound/notfound.component.html

@ -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
web-admin-app/src/app/pages copy/notfound/notfound.component.less

11
web-admin-app/src/app/pages copy/notfound/notfound.component.ts

@ -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 {}

43
web-admin-app/src/app/pages copy/product-detail/product-detail.component.html

@ -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
web-admin-app/src/app/pages copy/product-detail/product-detail.component.less

84
web-admin-app/src/app/pages copy/product-detail/product-detail.component.ts

@ -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()
},
})
}
}

80
web-admin-app/src/app/pages copy/product-list/product-list.component.html

@ -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
web-admin-app/src/app/pages copy/product-list/product-list.component.less

104
web-admin-app/src/app/pages copy/product-list/product-list.component.ts

@ -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()
},
})
}
}

28
web-admin-app/src/app/pages copy/profile-account/profile-account.component.html

@ -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
web-admin-app/src/app/pages copy/profile-account/profile-account.component.less

46
web-admin-app/src/app/pages copy/profile-account/profile-account.component.ts

@ -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'
})
})
}
}
}

54
web-admin-app/src/app/pages copy/profile-basic/profile-basic.component.html

@ -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
web-admin-app/src/app/pages copy/profile-basic/profile-basic.component.less

52
web-admin-app/src/app/pages copy/profile-basic/profile-basic.component.ts

@ -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(() => {})
})
}
}
}

36
web-admin-app/src/app/pages copy/profile/profile.component.html

@ -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>

18
web-admin-app/src/app/pages copy/profile/profile.component.less

@ -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…
Cancel
Save