Browse Source

Merge branch 'dev' of http://www.lj2.top:3000/precaution/precaution-frontend into dev

mickey135 3 months ago
parent
commit
679388107f
96 changed files with 2225 additions and 195 deletions
  1. 2 2
      package.json
  2. 1 1
      proxy.prod.conf.json
  3. 1 1
      proxy.stage.conf.json
  4. 0 1
      src/app/pages/login/login.component.html
  5. 29 18
      src/app/pages/login/login.component.ts
  6. 0 0
      src/app/pages/manager/basic/basic.component.less
  7. 23 0
      src/app/pages/manager/basic/basic.component.spec.ts
  8. 11 0
      src/app/pages/manager/basic/basic.component.ts
  9. 80 0
      src/app/pages/manager/basic/basic.route.ts
  10. 1 0
      src/app/pages/manager/basic/department/department.component.html
  11. 0 0
      src/app/pages/manager/basic/department/department.component.less
  12. 23 0
      src/app/pages/manager/basic/department/department.component.spec.ts
  13. 41 0
      src/app/pages/manager/basic/department/department.component.ts
  14. 126 0
      src/app/pages/manager/basic/equipment/equipment.component.html
  15. 0 0
      src/app/pages/manager/basic/equipment/equipment.component.less
  16. 23 0
      src/app/pages/manager/basic/equipment/equipment.component.spec.ts
  17. 128 0
      src/app/pages/manager/basic/equipment/equipment.component.ts
  18. 45 0
      src/app/pages/manager/basic/equipment/form/form.component.html
  19. 0 0
      src/app/pages/manager/basic/equipment/form/form.component.less
  20. 22 0
      src/app/pages/manager/basic/equipment/form/form.component.spec.ts
  21. 82 0
      src/app/pages/manager/basic/equipment/form/form.component.ts
  22. 0 0
      src/app/pages/manager/basic/line/line.component.html
  23. 0 0
      src/app/pages/manager/basic/line/line.component.less
  24. 22 0
      src/app/pages/manager/basic/line/line.component.spec.ts
  25. 41 0
      src/app/pages/manager/basic/line/line.component.ts
  26. 1 0
      src/app/pages/manager/basic/operation/operation.component.html
  27. 0 0
      src/app/pages/manager/basic/operation/operation.component.less
  28. 23 0
      src/app/pages/manager/basic/operation/operation.component.spec.ts
  29. 41 0
      src/app/pages/manager/basic/operation/operation.component.ts
  30. 1 0
      src/app/pages/manager/basic/post-group/post-group.component.html
  31. 0 0
      src/app/pages/manager/basic/post-group/post-group.component.less
  32. 22 0
      src/app/pages/manager/basic/post-group/post-group.component.spec.ts
  33. 35 0
      src/app/pages/manager/basic/post-group/post-group.component.ts
  34. 1 0
      src/app/pages/manager/basic/post/post.component.html
  35. 0 0
      src/app/pages/manager/basic/post/post.component.less
  36. 22 0
      src/app/pages/manager/basic/post/post.component.spec.ts
  37. 35 0
      src/app/pages/manager/basic/post/post.component.ts
  38. 1 0
      src/app/pages/manager/basic/type/type.component.html
  39. 0 0
      src/app/pages/manager/basic/type/type.component.less
  40. 22 0
      src/app/pages/manager/basic/type/type.component.spec.ts
  41. 42 0
      src/app/pages/manager/basic/type/type.component.ts
  42. 40 0
      src/app/pages/manager/basic/unit/form/form.component.html
  43. 0 0
      src/app/pages/manager/basic/unit/form/form.component.less
  44. 22 0
      src/app/pages/manager/basic/unit/form/form.component.spec.ts
  45. 80 0
      src/app/pages/manager/basic/unit/form/form.component.ts
  46. 134 0
      src/app/pages/manager/basic/unit/unit.component.html
  47. 0 0
      src/app/pages/manager/basic/unit/unit.component.less
  48. 22 0
      src/app/pages/manager/basic/unit/unit.component.spec.ts
  49. 118 0
      src/app/pages/manager/basic/unit/unit.component.ts
  50. 5 5
      src/app/pages/manager/knowledge/bank/bank.component.ts
  51. 12 26
      src/app/pages/manager/knowledge/bank/risk-item-list/risk-item-list.component.html
  52. 0 35
      src/app/pages/manager/knowledge/bank/risk-item-list/risk-item-list.component.less
  53. 8 1
      src/app/pages/manager/knowledge/bank/risk-item-list/risk-item-list.component.ts
  54. 55 0
      src/app/pages/manager/knowledge/doc/doc-import/doc-import.component.html
  55. 0 0
      src/app/pages/manager/knowledge/doc/doc-import/doc-import.component.less
  56. 23 0
      src/app/pages/manager/knowledge/doc/doc-import/doc-import.component.spec.ts
  57. 92 0
      src/app/pages/manager/knowledge/doc/doc-import/doc-import.component.ts
  58. 54 0
      src/app/pages/manager/knowledge/doc/doc-table/doc-table.component.html
  59. 0 0
      src/app/pages/manager/knowledge/doc/doc-table/doc-table.component.less
  60. 23 0
      src/app/pages/manager/knowledge/doc/doc-table/doc-table.component.spec.ts
  61. 83 0
      src/app/pages/manager/knowledge/doc/doc-table/doc-table.component.ts
  62. 33 1
      src/app/pages/manager/knowledge/doc/doc.component.html
  63. 7 0
      src/app/pages/manager/knowledge/doc/doc.component.less
  64. 93 4
      src/app/pages/manager/knowledge/doc/doc.component.ts
  65. 8 8
      src/app/pages/manager/knowledge/list/list.component.ts
  66. 5 0
      src/app/pages/manager/layout/header/header.component.ts
  67. 11 0
      src/app/pages/manager/manager.routes.ts
  68. 9 1
      src/app/services/api.service.ts
  69. 16 0
      src/app/services/apis/auth.ts
  70. 58 0
      src/app/services/apis/basic.ts
  71. 14 0
      src/app/services/apis/knowledge.ts
  72. 6 4
      src/app/services/auth.service.ts
  73. 28 0
      src/app/services/basic.service.ts
  74. 2 2
      src/app/services/setting.service.ts
  75. 3 0
      src/app/services/storage.service.ts
  76. 6 6
      src/app/shared/data-select/user-search.component.ts
  77. 10 0
      src/app/shared/filter-button-group/filter-button-group.component.html
  78. 32 0
      src/app/shared/filter-button-group/filter-button-group.component.less
  79. 23 0
      src/app/shared/filter-button-group/filter-button-group.component.spec.ts
  80. 19 0
      src/app/shared/filter-button-group/filter-button-group.component.ts
  81. 1 1
      src/app/shared/simple-form-page/simple-form-page.component.ts
  82. 1 0
      src/assets/icons/database.svg
  83. 1 0
      src/assets/icons/db.svg
  84. 11 0
      src/assets/icons/docx.svg
  85. 1 0
      src/assets/icons/equipment.svg
  86. 1 0
      src/assets/icons/line.svg
  87. 1 0
      src/assets/icons/post-group.svg
  88. 1 0
      src/assets/icons/transit.svg
  89. 1 0
      src/assets/icons/type.svg
  90. 1 0
      src/assets/icons/unit.svg
  91. 1 0
      src/assets/icons/user-fill.svg
  92. 8 2
      src/styles/custom.less
  93. 47 0
      src/types/auth.d.ts
  94. 32 0
      src/types/basic.d.ts
  95. 16 0
      src/types/knowledge.d.ts
  96. 0 76
      src/types/user.d.ts

+ 2 - 2
package.json

@@ -4,8 +4,8 @@
   "scripts": {
     "ng": "ng",
     "start": "ng serve --port 4201",
-    "start:prod": "ng serve --open --proxy-config=proxy.prod.conf.json",
-    "start:stage": "ng serve --open --proxy-config=proxy.stage.conf.json",
+    "start:prod": "ng serve --open --port 4201 --proxy-config=proxy.prod.conf.json",
+    "start:stage": "ng serve --open --port 4201 --proxy-config=proxy.stage.conf.json",
     "format": "prettier --write \"src/**/*.(ts|html|less)\"",
     "build": "ng build",
     "watch": "ng build --watch --configuration development",

+ 1 - 1
proxy.prod.conf.json

@@ -1,6 +1,6 @@
 {
   "/api": {
-    "target": "http://sa.leadinvr.com/",
+    "target": "https://precaution-check2.stage.leadinvr.com",
     "secure": false,
     "changeOrigin": true
   }

+ 1 - 1
proxy.stage.conf.json

@@ -1,6 +1,6 @@
 {
   "/api": {
-    "target": "https://precaution-check2.stage.leadinvr.com",
+    "target": "https://safety-check2.stage.leadinvr.com",
     "secure": false,
     "changeOrigin": true
   }

+ 0 - 1
src/app/pages/login/login.component.html

@@ -40,7 +40,6 @@
                       placeholder="密码"
                       formControlName="password"
                       class="bg-[transparent] !pl-[10px] !pr-[10px] text-white"
-                      (keyup.enter)="submitForm()"
                     />
                   </nz-input-group>
 

+ 29 - 18
src/app/pages/login/login.component.ts

@@ -2,6 +2,7 @@ import { Component } from '@angular/core';
 import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
 import { Router } from '@angular/router';
 import { NzMessageService } from 'ng-zorro-antd/message';
+import { Md5 } from 'ts-md5';
 import { CommonNzModule } from '../../common.nz.module';
 import { AuthService } from '../../services/auth.service';
 import { SettingService } from '../../services/setting.service';
@@ -38,10 +39,12 @@ export class LoginComponent {
   initUserInfo() {
     const username = this.storage.getString('username');
     const password = this.storage.getString('password');
+    const remember = this.storage.getBoolean('remember');
     if (username !== undefined && password !== undefined) {
       this.validateForm.setValue({
         userName: username,
         password: password,
+        remember: remember || false,
       });
     }
   }
@@ -59,24 +62,32 @@ export class LoginComponent {
     this.loading = true;
     const username = this.validateForm.get('userName')?.value;
     let password = this.validateForm.get('password')?.value;
-    // const resp = await this.auth
-    //   .login({ username, password: Md5.hashStr(password) })
-    //   .catch(e => {
-    //     this.message.error('用户名或密码错误');
-    //     throw e;
-    //   })
-    //   .finally(() => {
-    //     this.loading = false;
-    //   });
-    // this.storage.setString('username', username);
-    // this.storage.setString('password', password);
+    const remember = this.validateForm.get('remember')?.value;
+    const resp = await this.auth
+      .login({ username, password: Md5.hashStr(password) })
+      .catch(e => {
+        this.message.error('用户名或密码错误');
+        throw e;
+      })
+      .finally(() => {
+        this.loading = false;
+      });
+    if (remember) {
+      this.storage.setString('username', username);
+      this.storage.setString('password', password);
+      this.storage.setBoolean('remember', remember);
+    } else {
+      // this.storage.remove('username');
+      // this.storage.remove('password');
+      this.storage.remove('remember');
+    }
     // this.storage.setNumber('id', resp.changePassword);
-    // this.storage.addUser({
-    //   id: username,
-    //   name: username,
-    //   password: password,
-    // });
-    // this.storage.token = resp.token;
+    this.storage.addUser({
+      id: username,
+      name: username,
+      password: password,
+    });
+    this.storage.token = resp.token;
     await this.afterLogin();
   }
   private async afterLogin() {
@@ -84,7 +95,7 @@ export class LoginComponent {
     const { url } = this.router;
     const l = new URL(location.origin + url);
     const form = l.searchParams.get('from');
-    const nvUrl = form ? form : '/manager/task';
+    const nvUrl = form ? form : '/manager/workbench/my';
     location.replace(nvUrl);
   }
 }

+ 0 - 0
src/app/pages/manager/basic/basic.component.less


+ 23 - 0
src/app/pages/manager/basic/basic.component.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { BasicComponent } from './basic.component';
+
+describe('BasicComponent', () => {
+  let component: BasicComponent;
+  let fixture: ComponentFixture<BasicComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [BasicComponent]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(BasicComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 11 - 0
src/app/pages/manager/basic/basic.component.ts

@@ -0,0 +1,11 @@
+import { Component } from '@angular/core';
+import { RouterOutlet } from '@angular/router';
+
+@Component({
+  selector: 'app-basic',
+  standalone: true,
+  imports: [RouterOutlet],
+  template: `<router-outlet></router-outlet>`,
+  styleUrl: './basic.component.less',
+})
+export class BasicComponent {}

+ 80 - 0
src/app/pages/manager/basic/basic.route.ts

@@ -0,0 +1,80 @@
+import { Routes } from '@angular/router';
+
+export const basicRoutes: Routes = [
+  {
+    path: 'line',
+    loadComponent: () => import('./line/line.component').then(m => m.LineComponent),
+    data: {
+      title: '线路',
+      icon: 'icons:line',
+      weight: 1,
+    },
+  },
+  {
+    path: 'type',
+    loadComponent: () => import('./type/type.component').then(m => m.TypeComponent),
+    data: {
+      animation: 'TypePage',
+      title: '生产类型',
+      icon: 'icons:type',
+      weight: 2,
+    },
+  },
+  {
+    path: 'unit',
+    loadComponent: () => import('./unit/unit.component').then(m => m.UnitComponent),
+    data: {
+      animation: 'UnitPage',
+      title: '生产单元',
+      icon: 'icons:unit',
+      weight: 3,
+    },
+  },
+  {
+    path: 'post',
+    loadComponent: () => import('./post/post.component').then(m => m.PostComponent),
+    data: {
+      animation: 'PostPage',
+      title: '岗位',
+      icon: 'icons:user-fill',
+      weight: 4,
+    },
+  },
+  {
+    path: 'post-group',
+    loadComponent: () => import('./post-group/post-group.component').then(m => m.PostGroupComponent),
+    data: {
+      animation: 'PostGroupPage',
+      title: '岗位组',
+      icon: 'icons:post-group',
+      weight: 5,
+    },
+  },
+  {
+    path: 'operation',
+    loadComponent: () => import('./operation/operation.component').then(m => m.OperationComponent),
+    data: {
+      title: '作业',
+      icon: 'icons:transit',
+      weight: 6,
+    },
+  },
+  {
+    path: 'equipment',
+    loadComponent: () => import('./equipment/equipment.component').then(m => m.EquipmentComponent),
+    data: {
+      title: '设备',
+      icon: 'icons:equipment',
+      weight: 7,
+    },
+  },
+  {
+    path: 'department',
+    loadComponent: () => import('./department/department.component').then(m => m.DepartmentComponent),
+    data: {
+      title: '部门',
+      icon: 'icons:post-group',
+      weight: 8,
+    },
+  },
+];

+ 1 - 0
src/app/pages/manager/basic/department/department.component.html

@@ -0,0 +1 @@
+<p>department works!</p>

+ 0 - 0
src/app/pages/manager/basic/department/department.component.less


+ 23 - 0
src/app/pages/manager/basic/department/department.component.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DepartmentComponent } from './department.component';
+
+describe('DepartmentComponent', () => {
+  let component: DepartmentComponent;
+  let fixture: ComponentFixture<DepartmentComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [DepartmentComponent]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(DepartmentComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 41 - 0
src/app/pages/manager/basic/department/department.component.ts

@@ -0,0 +1,41 @@
+import { Component } from '@angular/core';
+import { CommonNzModule } from '../../../../common.nz.module';
+import { Column, SimpleFormPageComponent } from '../../../../shared/simple-form-page/simple-form-page.component';
+
+@Component({
+  selector: 'app-department',
+  standalone: true,
+  imports: [SimpleFormPageComponent, CommonNzModule],
+  templateUrl: '../../../../shared/simple-form-page/simple-form-page.component.html',
+  styleUrl: './department.component.less',
+})
+export class DepartmentComponent extends SimpleFormPageComponent<BasicData.Predefine> {
+  override showSearchForm = false;
+  override editable = false;
+  override keyWord: string = '部门';
+  override key: string = 'value';
+  override columns: Column[] = [
+    { key: 'seq', label: '序号' },
+    { key: 'value', label: '部门名称' },
+  ];
+  override inputFields = [{ label: '部门名称', key: 'value', initValue: '' }];
+  override async fetchList() {
+    const list = await this.api.basic.getPredefineList('department');
+    this.list = list;
+  }
+  override async onAdd(data: BasicData.Predefine) {
+    return this.api.basic
+      .addPredefine({
+        ...data,
+        type: 'department',
+      })
+      .then(() => {
+        this.message.success('添加成功');
+      });
+  }
+  override async onDelete(data: BasicData.Predefine) {
+    return this.api.basic.deletePredefine('department', data.value).then(() => {
+      this.message.success('删除成功');
+    });
+  }
+}

+ 126 - 0
src/app/pages/manager/basic/equipment/equipment.component.html

@@ -0,0 +1,126 @@
+<div class="flex justify-between py-4 items-center">
+  <div class="page-headline-font pl-8">{{ keyWord }}</div>
+  <div class="pr-8 flex">
+    <form nz-form nzLayout="inline" [formGroup]="validateForm">
+      <nz-space>
+        @for (item of inputFields; track item.key) {
+          <nz-form-item *nzSpaceItem>
+            <nz-form-label>{{ item.label }}</nz-form-label>
+            <nz-form-control>
+              <input
+                nz-input
+                class="precaution-input"
+                [placeholder]="'请输入' + item.label"
+                [formControlName]="item.key"
+                (ngModelChange)="searchFormChange(item.key)"
+              />
+            </nz-form-control>
+          </nz-form-item>
+        }
+        <nz-form-item *nzSpaceItem>
+          <nz-form-label>线路</nz-form-label>
+          <nz-form-control>
+            <nz-select
+              class="precaution-select"
+              nzDropdownClassName="precaution-select-dropdown"
+              nzAllowClear
+              style="min-width: 144px"
+              formControlName="line"
+              nzPlaceHolder="请选择线路"
+              (ngModelChange)="searchFormChange('line')"
+            >
+              @for (item of lines; track item.value) {
+                <nz-option [nzValue]="item.value" [nzLabel]="item.value"></nz-option>
+              }
+            </nz-select>
+          </nz-form-control>
+        </nz-form-item>
+        <nz-form-item *nzSpaceItem>
+          <nz-form-label>生产单元</nz-form-label>
+          <nz-form-control>
+            <nz-select
+              class="precaution-select"
+              nzDropdownClassName="precaution-select-dropdown"
+              nzAllowClear
+              style="min-width: 144px"
+              formControlName="unit"
+              nzPlaceHolder="请选择生产单元"
+              (ngModelChange)="searchFormChange('unit')"
+            >
+              @for (item of units; track item.name) {
+                <nz-option [nzValue]="item.name" [nzLabel]="item.name"></nz-option>
+              }
+            </nz-select>
+          </nz-form-control>
+        </nz-form-item>
+      </nz-space>
+    </form>
+
+    <button class="precaution-secondary ml-2" nz-button (click)="onReset()">重置</button>
+    <button class="precaution-button ml-6" nz-button nzType="primary" (click)="handleAdd()">
+      <span nz-icon nzType="plus"></span>
+      {{ '新增' + keyWord }}
+    </button>
+  </div>
+</div>
+
+<div class="px-6">
+  <nz-table
+    class="safety-table"
+    #table
+    [nzData]="filterList"
+    nzShowPagination
+    nzPaginationType="small"
+    [nzFrontPagination]="false"
+    [nzTotal]="pageConfig.total"
+    [nzPageIndex]="pageConfig.current + 1"
+    (nzPageIndexChange)="onCurrentPageChange($event - 1)"
+  >
+    <thead>
+      <tr>
+        @for (item of columns; track $index) {
+          <th
+            [nzShowSort]="item.sortable"
+            [nzSortOrder]="item.sortOrder || null"
+            [nzSortDirections]="sortDirections"
+            (nzSortOrderChange)="handleSort(item, $event)"
+            [nzWidth]="item.width || null"
+          >
+            {{ item.label }}
+          </th>
+        }
+        <th>操作</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr *ngFor="let data of table.data">
+        @for (item of columns; track $index) {
+          <td>
+            <!-- @if (item.type === 'line') {
+              <ng-container *ngTemplateOutlet="lineTpl; context: { $implicit: data }"></ng-container>
+            } @else if (item.type === 'type') {
+              <ng-container *ngTemplateOutlet="typeTpl; context: { $implicit: data }"></ng-container>
+            } @else { -->
+            <span>{{ getValue(data, item.key) }}</span>
+            <!-- } -->
+          </td>
+        }
+
+        <td>
+          <button class="safety-button px-4" nz-button nzSize="small" nzType="primary" (click)="handleEdit(data)">
+            编辑
+          </button>
+          <button
+            class="safety-secondary px-4 ml-2"
+            nz-button
+            nzSize="small"
+            nzType="primary"
+            (click)="handleDelete(data)"
+          >
+            删除
+          </button>
+        </td>
+      </tr>
+    </tbody>
+  </nz-table>
+</div>

+ 0 - 0
src/app/pages/manager/basic/equipment/equipment.component.less


+ 23 - 0
src/app/pages/manager/basic/equipment/equipment.component.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { EquipmentComponent } from './equipment.component';
+
+describe('EquipmentComponent', () => {
+  let component: EquipmentComponent;
+  let fixture: ComponentFixture<EquipmentComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [EquipmentComponent]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(EquipmentComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 128 - 0
src/app/pages/manager/basic/equipment/equipment.component.ts

@@ -0,0 +1,128 @@
+import { Component } from '@angular/core';
+import { CommonNzModule } from '../../../../common.nz.module';
+import { Column, SimpleFormPageComponent } from '../../../../shared/simple-form-page/simple-form-page.component';
+import { EquipmentFormComponent, EquipmentFormModalData } from './form/form.component';
+
+@Component({
+  selector: 'app-equipment',
+  standalone: true,
+  imports: [CommonNzModule],
+  templateUrl: './equipment.component.html',
+  styleUrl: './equipment.component.less',
+})
+export class EquipmentComponent extends SimpleFormPageComponent<BasicData.Equipment> {
+  filterList: BasicData.Equipment[] = [];
+  override keyWord: string = '设备';
+  override key: string = 'name';
+  override columns: Column[] = [
+    {
+      key: 'id',
+      label: '编号',
+      // sortOrder: 'ascend',
+      // sortable: true,
+      width: '80px',
+    },
+    { key: 'name', label: '设备名称' },
+    { key: 'unit', label: '生产单元' },
+    { key: 'line', label: '线路' },
+  ];
+  override inputFields = [{ label: '设备名称', key: 'equipment', initValue: '' }];
+  override validateForm = this.fb.group({
+    equipment: this.fb.control<string | undefined>(undefined),
+    line: this.fb.control<string | undefined>(undefined),
+    unit: this.fb.control<string | undefined>(undefined),
+  });
+  override ngOnInit(): void {
+    super.ngOnInit();
+  }
+  override async fetchList() {
+    const list = (await this.api.basic.getEquipmentList()).filter(item =>
+      this.lines.map(l => l.value).includes(item.line)
+    );
+    this.list = list;
+    this.resetFilterList();
+  }
+  override async onEdit(data: Partial<BasicData.Equipment>) {
+    return this.api.basic.updateEquipment(data).then(() => {
+      this.message.success('编辑成功');
+    });
+  }
+  override async onAdd(data: Partial<BasicData.Equipment>) {
+    return this.api.basic.addEquipment(data).then(() => {
+      this.message.success('新增成功');
+    });
+  }
+  override async onDelete(data: BasicData.Equipment): Promise<void> {
+    return this.api.basic.deletedEquipment(data.id).then(() => {
+      this.message.success('删除成功');
+    });
+  }
+  override createModal(type: 'add' | 'edit', data?: BasicData.Equipment): void {
+    const name = data ? this.getValue(data, this.key) : '';
+    this.modal.create<EquipmentFormComponent, EquipmentFormModalData>({
+      nzClassName: 'precaution-modal',
+      nzTitle: type === 'add' ? '新增' + this.keyWord : '编辑' + this.keyWord + ' - ' + name,
+      nzOkText: type === 'add' ? '新增' : '修改',
+      nzContent: EquipmentFormComponent,
+      nzWidth: 500,
+      nzViewContainerRef: this.viewContainerRef,
+      nzData: {
+        keyWord: this.keyWord,
+        type,
+        params: {
+          name,
+          id: data ? this.getValue(data, 'id') : undefined,
+          line: data ? this.getValue(data, 'line') : undefined,
+          unit: data ? this.getValue(data, 'unit') : undefined,
+        },
+        lines: this.lines,
+      },
+      nzOnOk: async instance => {
+        const res = await instance.submit();
+        if (type === 'add') {
+          await this.onAdd(res!);
+          this.fetchList();
+        } else {
+          await this.onEdit({
+            ...data!,
+            ...res!,
+          });
+          this.onSearch();
+        }
+      },
+    });
+  }
+  searchFormChange(key: string) {
+    if (key === 'line') {
+      this.validateForm.get('unit')?.reset();
+    }
+    this.resetFilterList();
+  }
+  resetFilterList() {
+    const { equipment, line, unit } = this.validateForm.value;
+    this.filterList = this.list.filter(item => {
+      let isMatch = true;
+      if (equipment) {
+        isMatch = item.name.includes(equipment);
+      }
+      if (isMatch && line) {
+        isMatch = item.line === line;
+      }
+      if (isMatch && unit) {
+        isMatch = item.unit === unit;
+      }
+      return isMatch;
+    });
+  }
+  get lines() {
+    return this.basic.lines || [];
+  }
+  get types() {
+    return this.basic.types || [];
+  }
+  get units() {
+    const line = this.validateForm.value.line;
+    if (!line) return [];
+    return this.basic.units?.filter(item => item.line === line) || [];
+  }
+}

+ 45 - 0
src/app/pages/manager/basic/equipment/form/form.component.html

@@ -0,0 +1,45 @@
+<form nz-form nzLayout="horizontal" [formGroup]="validateForm">
+  <nz-form-item>
+    <nz-form-label nzRequired style="width: 90px">{{ nzModalData.keyWord }}名称</nz-form-label>
+    <nz-form-control [nzErrorTip]="'请输入' + nzModalData.keyWord + '名称'">
+      <input
+        nz-input
+        class="precaution-input"
+        [placeholder]="'请输入' + nzModalData.keyWord + '名称'"
+        formControlName="name"
+      />
+    </nz-form-control>
+  </nz-form-item>
+  <nz-form-item>
+    <nz-form-label style="width: 90px" nzRequired>线路</nz-form-label>
+    <nz-form-control [nzErrorTip]="'请选择线路'">
+      <nz-select
+        class="precaution-select"
+        nzDropdownClassName="precaution-select-dropdown"
+        style="min-width: 144px"
+        formControlName="line"
+        nzPlaceHolder="请选择线路"
+      >
+        @for (item of lineList; track item) {
+          <nz-option [nzValue]="item.value" [nzLabel]="item.value"></nz-option>
+        }
+      </nz-select>
+    </nz-form-control>
+  </nz-form-item>
+  <nz-form-item nzRequired>
+    <nz-form-label style="width: 90px" nzRequired>生产单元</nz-form-label>
+    <nz-form-control [nzErrorTip]="'请选择生产单元'">
+      <nz-select
+        class="precaution-select"
+        nzDropdownClassName="precaution-select-dropdown"
+        style="min-width: 144px"
+        formControlName="unit"
+        nzPlaceHolder="请选择生产单元"
+      >
+        @for (item of unitList; track item.name) {
+          <nz-option [nzValue]="item.name" [nzLabel]="item.name"></nz-option>
+        }
+      </nz-select>
+    </nz-form-control>
+  </nz-form-item>
+</form>

+ 0 - 0
src/app/pages/manager/basic/equipment/form/form.component.less


+ 22 - 0
src/app/pages/manager/basic/equipment/form/form.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { FormComponent } from './form.component';
+
+describe('FormComponent', () => {
+  let component: FormComponent;
+  let fixture: ComponentFixture<FormComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [FormComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(FormComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 82 - 0
src/app/pages/manager/basic/equipment/form/form.component.ts

@@ -0,0 +1,82 @@
+import { Component, inject } from '@angular/core';
+import { NonNullableFormBuilder, Validators } from '@angular/forms';
+import { NZ_MODAL_DATA } from 'ng-zorro-antd/modal';
+import { CommonNzModule } from '../../../../../common.nz.module';
+import { BasicDataService } from '../../../../../services/basic.service';
+
+export interface EquipmentFormModalData {
+  keyWord: string;
+  type: 'add' | 'edit';
+  params: {
+    id?: number;
+    name?: string;
+    line?: string;
+    unit?: string;
+  };
+  lines: BasicData.Predefine[];
+}
+
+@Component({
+  selector: 'equipment-form',
+  standalone: true,
+  imports: [CommonNzModule],
+  templateUrl: './form.component.html',
+  styleUrl: './form.component.less',
+})
+export class EquipmentFormComponent {
+  readonly nzModalData: EquipmentFormModalData = inject(NZ_MODAL_DATA);
+  validateForm = this.fb.group({
+    name: this.fb.control<string>('', [Validators.required]),
+    id: this.fb.control<number>(0),
+    line: this.fb.control<string>('', [Validators.required]),
+    unit: this.fb.control<string>('', [Validators.required]),
+  });
+  loading = false;
+  constructor(
+    private fb: NonNullableFormBuilder,
+    private basic: BasicDataService
+  ) {}
+  ngOnInit() {
+    this.init();
+    this.basic.fetchBasicData();
+  }
+  async init() {
+    if (this.nzModalData.type === 'edit') {
+      const { name, id, line, unit } = this.nzModalData.params;
+      this.validateForm.patchValue({
+        name,
+        id,
+        line,
+        unit,
+      });
+    }
+  }
+  async submit() {
+    if (this.loading) return;
+    if (!this.validateForm.valid) {
+      Object.values(this.validateForm.controls).forEach(control => {
+        if (control.invalid) {
+          control.markAsDirty();
+          control.updateValueAndValidity({ onlySelf: true });
+        }
+      });
+      return Promise.reject();
+    }
+    this.loading = true;
+    const values = this.validateForm.value;
+    return Promise.resolve({
+      name: values.name!,
+      id: values.id,
+      line: values.line,
+      unit: values.unit,
+    });
+  }
+  get lineList() {
+    return this.nzModalData.lines || [];
+  }
+  get unitList() {
+    const line = this.validateForm.value.line;
+    if (!line) return [];
+    return this.basic.units?.filter(item => item.line === line) || [];
+  }
+}

+ 0 - 0
src/app/pages/manager/basic/line/line.component.html


+ 0 - 0
src/app/pages/manager/basic/line/line.component.less


+ 22 - 0
src/app/pages/manager/basic/line/line.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LineComponent } from './line.component';
+
+describe('LineComponent', () => {
+  let component: LineComponent;
+  let fixture: ComponentFixture<LineComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [LineComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(LineComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 41 - 0
src/app/pages/manager/basic/line/line.component.ts

@@ -0,0 +1,41 @@
+import { Component } from '@angular/core';
+import { CommonNzModule } from '../../../../common.nz.module';
+import { Column, SimpleFormPageComponent } from '../../../../shared/simple-form-page/simple-form-page.component';
+
+@Component({
+  selector: 'app-line',
+  standalone: true,
+  imports: [SimpleFormPageComponent, CommonNzModule],
+  templateUrl: '../../../../shared/simple-form-page/simple-form-page.component.html',
+  styleUrl: './line.component.less',
+})
+export class LineComponent extends SimpleFormPageComponent<BasicData.Predefine> {
+  override showSearchForm = false;
+  override editable = false;
+  override keyWord: string = '线路';
+  override key: string = 'value';
+  override columns: Column[] = [
+    { key: 'seq', label: '序号' },
+    { key: 'value', label: '线路名称' },
+  ];
+  override inputFields = [{ label: '线路名称', key: 'value', initValue: '' }];
+  override async fetchList() {
+    const list = await this.api.basic.getPredefineList('line');
+    this.list = list;
+  }
+  override async onAdd(data: BasicData.Predefine) {
+    return this.api.basic
+      .addPredefine({
+        ...data,
+        type: 'line',
+      })
+      .then(() => {
+        this.message.success('添加成功');
+      });
+  }
+  override async onDelete(data: BasicData.Predefine) {
+    return this.api.basic.deletePredefine('line', data.value).then(() => {
+      this.message.success('删除成功');
+    });
+  }
+}

+ 1 - 0
src/app/pages/manager/basic/operation/operation.component.html

@@ -0,0 +1 @@
+<p>operation works!</p>

+ 0 - 0
src/app/pages/manager/basic/operation/operation.component.less


+ 23 - 0
src/app/pages/manager/basic/operation/operation.component.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { OperationComponent } from './operation.component';
+
+describe('OperationComponent', () => {
+  let component: OperationComponent;
+  let fixture: ComponentFixture<OperationComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [OperationComponent]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(OperationComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 41 - 0
src/app/pages/manager/basic/operation/operation.component.ts

@@ -0,0 +1,41 @@
+import { Component } from '@angular/core';
+import { CommonNzModule } from '../../../../common.nz.module';
+import { Column, SimpleFormPageComponent } from '../../../../shared/simple-form-page/simple-form-page.component';
+
+@Component({
+  selector: 'app-operation',
+  standalone: true,
+  imports: [SimpleFormPageComponent, CommonNzModule],
+  templateUrl: '../../../../shared/simple-form-page/simple-form-page.component.html',
+  styleUrl: './operation.component.less',
+})
+export class OperationComponent extends SimpleFormPageComponent<BasicData.Predefine> {
+  override showSearchForm = false;
+  override editable = false;
+  override keyWord: string = '作业';
+  override key: string = 'value';
+  override columns: Column[] = [
+    { key: 'seq', label: '序号' },
+    { key: 'value', label: '作业名称' },
+  ];
+  override inputFields = [{ label: '作业名称', key: 'value', initValue: '' }];
+  override async fetchList() {
+    const list = await this.api.basic.getPredefineList('operation');
+    this.list = list;
+  }
+  override async onAdd(data: BasicData.Predefine) {
+    return this.api.basic
+      .addPredefine({
+        ...data,
+        type: 'operation',
+      })
+      .then(() => {
+        this.message.success('添加成功');
+      });
+  }
+  override async onDelete(data: BasicData.Predefine) {
+    return this.api.basic.deletePredefine('operation', data.value).then(() => {
+      this.message.success('删除成功');
+    });
+  }
+}

+ 1 - 0
src/app/pages/manager/basic/post-group/post-group.component.html

@@ -0,0 +1 @@
+<p>post-group works!</p>

+ 0 - 0
src/app/pages/manager/basic/post-group/post-group.component.less


+ 22 - 0
src/app/pages/manager/basic/post-group/post-group.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PostGroupComponent } from './post-group.component';
+
+describe('PostGroupComponent', () => {
+  let component: PostGroupComponent;
+  let fixture: ComponentFixture<PostGroupComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [PostGroupComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(PostGroupComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 35 - 0
src/app/pages/manager/basic/post-group/post-group.component.ts

@@ -0,0 +1,35 @@
+import { Component } from '@angular/core';
+import { CommonNzModule } from '../../../../common.nz.module';
+import { Column, SimpleFormPageComponent } from '../../../../shared/simple-form-page/simple-form-page.component';
+
+@Component({
+  selector: 'app-post-group',
+  standalone: true,
+  imports: [SimpleFormPageComponent, CommonNzModule],
+  templateUrl: '../../../../shared/simple-form-page/simple-form-page.component.html',
+  styleUrl: './post-group.component.less',
+})
+export class PostGroupComponent extends SimpleFormPageComponent<BasicData.Predefine> {
+  override keyWord: string = '岗位组';
+  override editable = false;
+  override showSearchForm = false;
+  override key: string = 'value';
+  override columns: Column[] = [
+    { key: 'seq', label: '序号' },
+    { key: 'value', label: '岗位组名称' },
+  ];
+  override inputFields = [{ label: '岗位组名称', key: 'value', initValue: '' }];
+  override async fetchList() {
+    const list = await this.api.basic.getPredefineList('job-group');
+    this.list = list;
+  }
+  override async onAdd(data: BasicData.Predefine) {
+    return this.api.basic.addPredefine({
+      ...data,
+      type: 'job-group',
+    });
+  }
+  override async onDelete(data: BasicData.Predefine) {
+    return this.api.basic.deletePredefine('job-group', data.value);
+  }
+}

+ 1 - 0
src/app/pages/manager/basic/post/post.component.html

@@ -0,0 +1 @@
+<p>post works!</p>

+ 0 - 0
src/app/pages/manager/basic/post/post.component.less


+ 22 - 0
src/app/pages/manager/basic/post/post.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PostComponent } from './post.component';
+
+describe('PostComponent', () => {
+  let component: PostComponent;
+  let fixture: ComponentFixture<PostComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [PostComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(PostComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 35 - 0
src/app/pages/manager/basic/post/post.component.ts

@@ -0,0 +1,35 @@
+import { Component } from '@angular/core';
+import { CommonNzModule } from '../../../../common.nz.module';
+import { Column, SimpleFormPageComponent } from '../../../../shared/simple-form-page/simple-form-page.component';
+
+@Component({
+  selector: 'app-post',
+  standalone: true,
+  imports: [SimpleFormPageComponent, CommonNzModule],
+  templateUrl: '../../../../shared/simple-form-page/simple-form-page.component.html',
+  styleUrl: './post.component.less',
+})
+export class PostComponent extends SimpleFormPageComponent<BasicData.Predefine> {
+  override keyWord: string = '岗位';
+  override showSearchForm = false;
+  override editable = false;
+  override key: string = 'value';
+  override columns: Column[] = [
+    { key: 'seq', label: '序号' },
+    { key: 'value', label: '岗位名称' },
+  ];
+  override inputFields = [{ label: '岗位名称', key: 'value', initValue: '' }];
+  override async fetchList() {
+    const list = await this.api.basic.getPredefineList('job');
+    this.list = list;
+  }
+  override async onAdd(data: BasicData.Predefine) {
+    return this.api.basic.addPredefine({
+      ...data,
+      type: 'job',
+    });
+  }
+  override async onDelete(data: BasicData.Predefine) {
+    return this.api.basic.deletePredefine('job', data.value);
+  }
+}

+ 1 - 0
src/app/pages/manager/basic/type/type.component.html

@@ -0,0 +1 @@
+<p>type works!</p>

+ 0 - 0
src/app/pages/manager/basic/type/type.component.less


+ 22 - 0
src/app/pages/manager/basic/type/type.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TypeComponent } from './type.component';
+
+describe('TypeComponent', () => {
+  let component: TypeComponent;
+  let fixture: ComponentFixture<TypeComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [TypeComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(TypeComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 42 - 0
src/app/pages/manager/basic/type/type.component.ts

@@ -0,0 +1,42 @@
+import { Component } from '@angular/core';
+import { CommonNzModule } from '../../../../common.nz.module';
+import { Column, SimpleFormPageComponent } from '../../../../shared/simple-form-page/simple-form-page.component';
+
+@Component({
+  selector: 'app-type',
+  standalone: true,
+  imports: [CommonNzModule],
+  templateUrl: '../../../../shared/simple-form-page/simple-form-page.component.html',
+  styleUrl: './type.component.less',
+})
+export class TypeComponent extends SimpleFormPageComponent<BasicData.Predefine> {
+  override showSearchForm = false;
+  override editable = false;
+  override keyWord: string = '车站类型';
+  override key: string = 'value';
+  override columns: Column[] = [
+    { key: 'seq', label: '序号' },
+    { key: 'value', label: '车站类型' },
+  ];
+
+  override async fetchList() {
+    const list = await this.api.basic.getPredefineList('product');
+    this.list = list;
+  }
+  override async onAdd(data: BasicData.Predefine) {
+    return this.api.basic
+      .addPredefine({
+        seq: 0,
+        type: 'product',
+        value: data.value,
+      })
+      .then(() => {
+        this.message.success('添加成功');
+      });
+  }
+  override async onDelete(data: BasicData.Predefine) {
+    return this.api.basic.deletePredefine('product', data.value).then(() => {
+      this.message.success('删除成功');
+    });
+  }
+}

+ 40 - 0
src/app/pages/manager/basic/unit/form/form.component.html

@@ -0,0 +1,40 @@
+<form nz-form nzLayout="horizontal" [formGroup]="validateForm">
+  <nz-form-item>
+    <nz-form-label nzRequired style="width: 90px">{{ nzModalData.keyWord }}</nz-form-label>
+    <nz-form-control [nzErrorTip]="'请输入' + nzModalData.keyWord">
+      <input nz-input class="precaution-input" [placeholder]="'请输入' + nzModalData.keyWord" formControlName="name" />
+    </nz-form-control>
+  </nz-form-item>
+  <nz-form-item>
+    <nz-form-label style="width: 90px" nzRequired>线路</nz-form-label>
+    <nz-form-control [nzErrorTip]="'请选择线路'">
+      <nz-select
+        class="precaution-select"
+        nzDropdownClassName="precaution-select-dropdown"
+        style="min-width: 144px"
+        formControlName="line"
+        nzPlaceHolder="请选择线路"
+      >
+        @for (item of lineList; track item) {
+          <nz-option [nzValue]="item.value" [nzLabel]="item.value"></nz-option>
+        }
+      </nz-select>
+    </nz-form-control>
+  </nz-form-item>
+  <nz-form-item>
+    <nz-form-label style="width: 90px" nzRequired>类型</nz-form-label>
+    <nz-form-control [nzErrorTip]="'请选择类型'">
+      <nz-select
+        class="precaution-select"
+        nzDropdownClassName="precaution-select-dropdown"
+        style="min-width: 144px"
+        formControlName="type"
+        nzPlaceHolder="请选择类型"
+      >
+        @for (item of typeList; track item) {
+          <nz-option [nzValue]="item.value" [nzLabel]="item.value"></nz-option>
+        }
+      </nz-select>
+    </nz-form-control>
+  </nz-form-item>
+</form>

+ 0 - 0
src/app/pages/manager/basic/unit/form/form.component.less


+ 22 - 0
src/app/pages/manager/basic/unit/form/form.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { FormComponent } from './form.component';
+
+describe('FormComponent', () => {
+  let component: FormComponent;
+  let fixture: ComponentFixture<FormComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [FormComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(FormComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 80 - 0
src/app/pages/manager/basic/unit/form/form.component.ts

@@ -0,0 +1,80 @@
+import { Component, inject } from '@angular/core';
+import { NonNullableFormBuilder, Validators } from '@angular/forms';
+import { NZ_MODAL_DATA } from 'ng-zorro-antd/modal';
+import { CommonNzModule } from '../../../../../common.nz.module';
+import { BasicDataService } from '../../../../../services/basic.service';
+
+export interface UnitFormModalData {
+  keyWord: string;
+  type: 'add' | 'edit';
+  params: {
+    id?: number;
+    name?: string;
+    line?: string;
+    type?: string;
+  };
+  lines: BasicData.Predefine[];
+}
+
+@Component({
+  selector: 'unit-form',
+  standalone: true,
+  imports: [CommonNzModule],
+  templateUrl: './form.component.html',
+  styleUrl: './form.component.less',
+})
+export class UnitFormComponent {
+  readonly nzModalData: UnitFormModalData = inject(NZ_MODAL_DATA);
+  validateForm = this.fb.group({
+    name: this.fb.control<string>('', [Validators.required]),
+    id: this.fb.control<number>(0),
+    line: this.fb.control<string>('', [Validators.required]),
+    type: this.fb.control<string>('', [Validators.required]),
+  });
+  loading = false;
+  constructor(
+    private fb: NonNullableFormBuilder,
+    private basic: BasicDataService
+  ) {}
+  ngOnInit() {
+    this.init();
+    this.basic.fetchBasicData();
+  }
+  async init() {
+    if (this.nzModalData.type === 'edit') {
+      const { name, id, line, type } = this.nzModalData.params;
+      this.validateForm.patchValue({
+        name,
+        id,
+        line,
+        type,
+      });
+    }
+  }
+  async submit() {
+    if (this.loading) return;
+    if (!this.validateForm.valid) {
+      Object.values(this.validateForm.controls).forEach(control => {
+        if (control.invalid) {
+          control.markAsDirty();
+          control.updateValueAndValidity({ onlySelf: true });
+        }
+      });
+      return Promise.reject();
+    }
+    this.loading = true;
+    const values = this.validateForm.value;
+    return Promise.resolve({
+      name: values.name!,
+      id: values.id,
+      line: values.line,
+      type: values.type,
+    });
+  }
+  get lineList() {
+    return this.nzModalData.lines || [];
+  }
+  get typeList() {
+    return this.basic.types || [];
+  }
+}

+ 134 - 0
src/app/pages/manager/basic/unit/unit.component.html

@@ -0,0 +1,134 @@
+<div class="flex justify-between py-4 items-center">
+  <div class="page-headline-font pl-8">{{ keyWord }}</div>
+  <div class="pr-8 flex">
+    <form nz-form nzLayout="inline" [formGroup]="validateForm">
+      <nz-space>
+        @for (item of inputFields; track item.key) {
+          <nz-form-item *nzSpaceItem>
+            <nz-form-label>{{ item.label }}</nz-form-label>
+            <nz-form-control>
+              <input
+                nz-input
+                class="precaution-input"
+                [placeholder]="'请输入' + item.label"
+                [formControlName]="item.key"
+                (ngModelChange)="searchFormChange()"
+              />
+            </nz-form-control>
+          </nz-form-item>
+        }
+        <nz-form-item *nzSpaceItem>
+          <nz-form-label>生产类型</nz-form-label>
+          <nz-form-control>
+            <nz-select
+              class="precaution-select"
+              nzDropdownClassName="precaution-select-dropdown"
+              nzAllowClear
+              style="min-width: 144px"
+              formControlName="type"
+              nzPlaceHolder="请选择生产类型"
+              (ngModelChange)="searchFormChange()"
+            >
+              @for (item of types; track item.value) {
+                <nz-option [nzValue]="item.value" [nzLabel]="item.value"></nz-option>
+              }
+            </nz-select>
+          </nz-form-control>
+        </nz-form-item>
+        <nz-form-item *nzSpaceItem>
+          <nz-form-label>线路</nz-form-label>
+          <nz-form-control>
+            <nz-select
+              class="precaution-select"
+              nzDropdownClassName="precaution-select-dropdown"
+              nzAllowClear
+              style="min-width: 144px"
+              formControlName="line"
+              nzPlaceHolder="请选择线路"
+              (ngModelChange)="searchFormChange()"
+            >
+              @for (item of lines; track item.value) {
+                <nz-option [nzValue]="item.value" [nzLabel]="item.value"></nz-option>
+              }
+            </nz-select>
+          </nz-form-control>
+        </nz-form-item>
+      </nz-space>
+    </form>
+
+    <button class="precaution-secondary ml-2" nz-button (click)="onReset()">重置</button>
+    <button class="precaution-button ml-6" nz-button nzType="primary" (click)="handleAdd()">
+      <span nz-icon nzType="plus"></span>
+      {{ '新增' + keyWord }}
+    </button>
+  </div>
+</div>
+
+<div class="px-6">
+  <nz-table
+    class="safety-table"
+    #table
+    [nzData]="filterList"
+    nzShowPagination
+    nzPaginationType="small"
+    [nzFrontPagination]="false"
+    [nzTotal]="pageConfig.total"
+    [nzPageIndex]="pageConfig.current + 1"
+    (nzPageIndexChange)="onCurrentPageChange($event - 1)"
+  >
+    <thead>
+      <tr>
+        @for (item of columns; track $index) {
+          <th
+            [nzShowSort]="item.sortable"
+            [nzSortOrder]="item.sortOrder || null"
+            [nzSortDirections]="sortDirections"
+            (nzSortOrderChange)="handleSort(item, $event)"
+            [nzWidth]="item.width || null"
+          >
+            {{ item.label }}
+          </th>
+        }
+        <th>操作</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr *ngFor="let data of table.data">
+        @for (item of columns; track $index) {
+          <td>
+            <!-- @if (item.type === 'line') {
+              <ng-container *ngTemplateOutlet="lineTpl; context: { $implicit: data }"></ng-container>
+            } @else if (item.type === 'type') {
+              <ng-container *ngTemplateOutlet="typeTpl; context: { $implicit: data }"></ng-container>
+            } @else { -->
+            <span>{{ getValue(data, item.key) }}</span>
+            <!-- } -->
+          </td>
+        }
+
+        <td>
+          <button class="safety-button px-4" nz-button nzSize="small" nzType="primary" (click)="handleEdit(data)">
+            编辑
+          </button>
+          <button
+            class="safety-secondary px-4 ml-2"
+            nz-button
+            nzSize="small"
+            nzType="primary"
+            (click)="handleDelete(data)"
+          >
+            删除
+          </button>
+        </td>
+      </tr>
+    </tbody>
+  </nz-table>
+</div>
+
+<!-- <ng-template #lineTpl let-data>
+  <nz-tag class="rounded" [nzColor]="'cyan'">{{ getTagText(data, 'line_id') }}</nz-tag>
+</ng-template>
+
+<ng-template #typeTpl let-data>
+  <nz-tag class="rounded" [nzColor]="'cyan'">{{ getTagText(data, 'type_id') }}</nz-tag>
+</ng-template> -->

+ 0 - 0
src/app/pages/manager/basic/unit/unit.component.less


+ 22 - 0
src/app/pages/manager/basic/unit/unit.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { UnitComponent } from './unit.component';
+
+describe('UnitComponent', () => {
+  let component: UnitComponent;
+  let fixture: ComponentFixture<UnitComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [UnitComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(UnitComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 118 - 0
src/app/pages/manager/basic/unit/unit.component.ts

@@ -0,0 +1,118 @@
+import { Component } from '@angular/core';
+import { CommonNzModule } from '../../../../common.nz.module';
+import { Column, SimpleFormPageComponent } from '../../../../shared/simple-form-page/simple-form-page.component';
+import { UnitFormComponent, UnitFormModalData } from './form/form.component';
+
+@Component({
+  selector: 'app-unit',
+  standalone: true,
+  imports: [CommonNzModule],
+  templateUrl: './unit.component.html',
+  styleUrl: './unit.component.less',
+})
+export class UnitComponent extends SimpleFormPageComponent<BasicData.Unit> {
+  filterList: BasicData.Unit[] = [];
+  override keyWord: string = '生产单元';
+  override key: string = 'name';
+  override columns: Column[] = [
+    {
+      key: 'id',
+      label: '编号',
+      // sortOrder: 'ascend',
+      // sortable: true,
+      width: '80px',
+    },
+    { key: 'name', label: '生产单元' },
+    { key: 'line', label: '线路' },
+    { key: 'type', label: '生产类型' },
+  ];
+  override inputFields = [{ label: '生产单元', key: 'unit', initValue: '' }];
+  override validateForm = this.fb.group({
+    unit: this.fb.control<string | undefined>(undefined),
+    line: this.fb.control<string | undefined>(undefined),
+    type: this.fb.control<string | undefined>(undefined),
+  });
+  override ngOnInit(): void {
+    super.ngOnInit();
+  }
+  override async fetchList() {
+    const list = (await this.api.basic.getUnitList()).filter(item => this.lines.map(l => l.value).includes(item.line));
+    this.list = list;
+    this.resetFilterList();
+  }
+  override async onEdit(data: Partial<BasicData.Unit>) {
+    return this.api.basic.updateUnit(data).then(() => {
+      this.message.success('编辑成功');
+    });
+  }
+  override async onAdd(data: Partial<BasicData.Unit>) {
+    return this.api.basic.addUnit(data).then(() => {
+      this.message.success('新增成功');
+    });
+  }
+  override async onDelete(data: BasicData.Unit): Promise<void> {
+    return this.api.basic.deletedUnit(data.id).then(() => {
+      this.message.success('删除成功');
+    });
+  }
+  override createModal(type: 'add' | 'edit', data?: BasicData.Unit): void {
+    const name = data ? this.getValue(data, this.key) : '';
+    this.modal.create<UnitFormComponent, UnitFormModalData>({
+      nzClassName: 'precaution-modal',
+      nzTitle: type === 'add' ? '新增' + this.keyWord : '编辑' + this.keyWord + ' - ' + name,
+      nzOkText: type === 'add' ? '新增' : '修改',
+      nzContent: UnitFormComponent,
+      nzWidth: 500,
+      nzViewContainerRef: this.viewContainerRef,
+      nzData: {
+        keyWord: this.keyWord,
+        type,
+        params: {
+          name,
+          id: data ? this.getValue(data, 'id') : undefined,
+          line: data ? this.getValue(data, 'line') : undefined,
+          type: data ? this.getValue(data, 'type') : undefined,
+        },
+        lines: this.lines,
+      },
+      nzOnOk: async instance => {
+        const res = await instance.submit();
+        if (type === 'add') {
+          await this.onAdd(res!);
+          this.fetchList();
+        } else {
+          await this.onEdit({
+            ...data!,
+            ...res!,
+          });
+          this.onSearch();
+        }
+      },
+    });
+  }
+  searchFormChange() {
+    this.resetFilterList();
+  }
+  resetFilterList() {
+    const { unit, line, type } = this.validateForm.value;
+    this.filterList = this.list.filter(item => {
+      let isMatch = true;
+      if (unit) {
+        isMatch = item.name.includes(unit);
+      }
+      if (isMatch && line) {
+        isMatch = item.line === line;
+      }
+      if (isMatch && type) {
+        isMatch = item.type === type;
+      }
+      return isMatch;
+    });
+  }
+  get lines() {
+    return this.basic.lines || [];
+  }
+  get types() {
+    return this.basic.types || [];
+  }
+}

+ 5 - 5
src/app/pages/manager/knowledge/bank/bank.component.ts

@@ -17,16 +17,16 @@ export class BankComponent {
       label: '风险知识',
       value: 'risk-knowledge',
     },
-    {
-      label: '企业录入情况',
-      value: 'enterprise-input',
-    },
+    // {
+    //   label: '企业录入情况',
+    //   value: 'enterprise-input',
+    // },
     {
       label: '更新纪录',
       value: 'update-record',
     },
   ];
-  activeTab = this.tabs[2].value;
+  activeTab = this.tabs[0].value;
 
   changeTab(tab: string) {
     this.activeTab = tab;

+ 12 - 26
src/app/pages/manager/knowledge/bank/risk-item-list/risk-item-list.component.html

@@ -2,7 +2,6 @@
   <div class="risk-item-header">
     <div class="title">XXXXX公司风险项列表</div>
     <div>
-      <button nz-button nzType="primary" class="precaution-secondary" style="border-radius: 12px">创建风险项</button>
       <button
         nz-button
         nzType="primary"
@@ -15,31 +14,18 @@
     </div>
   </div>
   <div class="risk-item-filter">
-    <div class="filter-item">
-      <label class="label">风险类别: </label>
-      <div class="flex-1">
-        @for (category of categories; track category.value) {
-          <div
-            class="button-item"
-            [class.active]="activeCategory === category.value"
-            (click)="changeCategory(category.value)"
-          >
-            {{ category.label }}
-          </div>
-        }
-      </div>
-    </div>
-
-    <div class="filter-item">
-      <label class="label">风险等级: </label>
-      <div class="flex-1">
-        @for (level of levels; track level.value) {
-          <div class="button-item" [class.active]="activeLevel === level.value" (click)="changeLevel(level.value)">
-            {{ level.label }}
-          </div>
-        }
-      </div>
-    </div>
+    <filter-button-group
+      label="风险类别"
+      [options]="categories"
+      [activeValue]="activeCategory"
+      (change)="changeCategory($event)"
+    ></filter-button-group>
+    <filter-button-group
+      label="风险等级"
+      [options]="levels"
+      [activeValue]="activeLevel"
+      (change)="changeLevel($event)"
+    ></filter-button-group>
   </div>
   <app-risk-item-table></app-risk-item-table>
 </div>

+ 0 - 35
src/app/pages/manager/knowledge/bank/risk-item-list/risk-item-list.component.less

@@ -10,38 +10,3 @@
     padding-bottom: 16px;
   }
 }
-
-.risk-item-filter {
-  .filter-item {
-    display: flex;
-    gap: 10px;
-    align-items: flex-start;
-    justify-content: flex-start;
-    margin-bottom: 16px;
-  }
-  .label {
-    line-height: 32px;
-  }
-  .button-item {
-    display: inline-block;
-    border-radius: 10px;
-    padding: 0 18px;
-    height: 32px;
-    line-height: 32px;
-    color: rgba(102, 102, 102, 0.667);
-    margin-right: 8px;
-    cursor: pointer;
-    transition: all 0.3s;
-    &:hover {
-      background-color: var(--ant-primary-1);
-    }
-
-    &:last-child {
-      margin-right: 0;
-    }
-    &.active {
-      background-color: var(--deep-blue);
-      color: #ffffff;
-    }
-  }
-}

+ 8 - 1
src/app/pages/manager/knowledge/bank/risk-item-list/risk-item-list.component.ts

@@ -1,6 +1,7 @@
 import { Component } from '@angular/core';
 import { CommonNzModule } from '../../../../../common.nz.module';
 import { CustomDrawerComponent } from '../../../../../shared/custom-drawer/custom-drawer.component';
+import { FilterButtonGroupComponent } from '../../../../../shared/filter-button-group/filter-button-group.component';
 import { RiskItemFormComponent } from '../risk-item-form/risk-item-form.component';
 import { RiskItemTableComponent } from '../risk-item-table/risk-item-table.component';
 
@@ -26,7 +27,13 @@ const levels = ['R1', 'R2', 'R2*', 'R3', 'R4'];
 @Component({
   selector: 'risk-item-list',
   standalone: true,
-  imports: [CommonNzModule, RiskItemTableComponent, CustomDrawerComponent, RiskItemFormComponent],
+  imports: [
+    CommonNzModule,
+    RiskItemTableComponent,
+    CustomDrawerComponent,
+    RiskItemFormComponent,
+    FilterButtonGroupComponent,
+  ],
   templateUrl: './risk-item-list.component.html',
   styleUrl: './risk-item-list.component.less',
 })

+ 55 - 0
src/app/pages/manager/knowledge/doc/doc-import/doc-import.component.html

@@ -0,0 +1,55 @@
+<nz-spin [nzSpinning]="importing" nzSize="large">
+  @if (!this.validateForm.valid) {
+    <nz-alert class="rounded-alert" nzType="warning" nzMessage="导入注意事项:  风险类别,风险等级是必选项!"></nz-alert>
+  }
+
+  <form class="py-2" nz-form nzLayout="horizontal" [formGroup]="validateForm">
+    <nz-form-item style="margin: 4px 0 4px 0">
+      <nz-form-label nzSpan="5">风险类别</nz-form-label>
+      <nz-form-control nzSpan="16">
+        <nz-select
+          class="precaution-select"
+          nzDropdownClassName="precaution-select-dropdown"
+          [style.minWidth]="'150px'"
+          formControlName="category"
+          nzPlaceHolder="请选择"
+        >
+          @for (item of categories; track item.value) {
+            <nz-option [nzValue]="item.value" [nzLabel]="item.label"></nz-option>
+          }
+        </nz-select>
+      </nz-form-control>
+    </nz-form-item>
+    <nz-form-item style="margin: 4px 0 4px 0">
+      <nz-form-label nzSpan="5">风险等级</nz-form-label>
+      <nz-form-control nzSpan="16">
+        <nz-select
+          class="precaution-select"
+          nzDropdownClassName="precaution-select-dropdown"
+          [style.minWidth]="'150px'"
+          formControlName="level"
+          nzPlaceHolder="请选择"
+        >
+          @for (item of levels; track item.value) {
+            <nz-option [nzValue]="item.value" [nzLabel]="item.label"></nz-option>
+          }
+        </nz-select>
+      </nz-form-control>
+    </nz-form-item>
+  </form>
+
+  <div class="pt-4 pb-6 text-right flex justify-end">
+    <nz-upload
+      [nzAction]="'/doc/import?category=' + validateForm.value.category + '&level=' + validateForm.value.level"
+      nzAccept=".docx,.pdf"
+      [nzShowUploadList]="false"
+      (nzChange)="handleImportStatusChange($event)"
+    >
+      <button class="precaution-button ml-4 mr-2" nzType="primary" nz-button [disabled]="!validateForm.valid">
+        <span nz-icon nzType="upload"></span>
+        导入文档
+      </button>
+    </nz-upload>
+    <button class="precaution-secondary" nz-button (click)="handleReset()">重置</button>
+  </div>
+</nz-spin>

+ 0 - 0
src/app/pages/manager/knowledge/doc/doc-import/doc-import.component.less


+ 23 - 0
src/app/pages/manager/knowledge/doc/doc-import/doc-import.component.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DocImportComponent } from './doc-import.component';
+
+describe('DocImportComponent', () => {
+  let component: DocImportComponent;
+  let fixture: ComponentFixture<DocImportComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [DocImportComponent]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(DocImportComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 92 - 0
src/app/pages/manager/knowledge/doc/doc-import/doc-import.component.ts

@@ -0,0 +1,92 @@
+import { Component, inject } from '@angular/core';
+import { NonNullableFormBuilder, Validators } from '@angular/forms';
+import { NzMessageService } from 'ng-zorro-antd/message';
+import { NZ_MODAL_DATA } from 'ng-zorro-antd/modal';
+import { NzUploadChangeParam } from 'ng-zorro-antd/upload';
+import { CommonNzModule } from '../../../../../common.nz.module';
+
+export interface DocImportModalData {
+  onImportSuccess: () => void; // Add this callback
+  categories: Option[];
+  levels: Option[];
+  category: number | string;
+  level: number | string;
+}
+
+@Component({
+  selector: 'doc-import',
+  standalone: true,
+  imports: [CommonNzModule],
+  templateUrl: './doc-import.component.html',
+  styleUrl: './doc-import.component.less',
+})
+export class DocImportComponent {
+  readonly nzModalData: DocImportModalData = inject(NZ_MODAL_DATA);
+  importing: boolean = false;
+
+  // repeatWords: string[] = [];
+  constructor(
+    private message: NzMessageService,
+    private fb: NonNullableFormBuilder
+  ) {}
+  validateForm = this.fb.group({
+    category: this.fb.control<number | string | undefined>(undefined, {
+      validators: [Validators.required],
+    }),
+    level: this.fb.control<number | string | undefined>(undefined, {
+      validators: [Validators.required],
+    }),
+  });
+  selectFields: {
+    label: string;
+    key: string;
+    minWidth?: string;
+    options: Option[];
+  }[] = [
+    // { label: '风险类别', key: 'category', options: [], minWidth: '150px' },
+    // { label: '风险等级', key: 'level', options: [], minWidth: '150px' },
+  ];
+
+  ngOnInit() {
+    console.log(this.nzModalData.category, this.nzModalData.level);
+    this.validateForm.patchValue({
+      category: this.nzModalData.category,
+      level: this.nzModalData.level,
+    });
+  }
+  handleImportStatusChange(event: NzUploadChangeParam) {
+    if (event.type === 'start') {
+      this.importing = true;
+    }
+    if (event.type === 'success') {
+      if (event.file.response?.code !== 0) {
+        this.message.error('导入失败: ' + event.file.response?.message);
+        this.importing = false;
+        return;
+      }
+      this.message.success('导入成功');
+      this.importing = false;
+
+      // Call the callback function to refresh the list in the parent component
+      if (this.nzModalData.onImportSuccess) {
+        this.nzModalData.onImportSuccess();
+      }
+    } else if (event.type === 'error') {
+      if (event.file.error.error?.message) {
+        this.message.error('导入失败: ' + event.file.error.error.message);
+      } else {
+        this.message.error('导入失败');
+      }
+      this.importing = false;
+    }
+  }
+  handleReset() {
+    this.validateForm.reset();
+  }
+  get categories() {
+    return this.nzModalData.categories;
+  }
+  get levels() {
+    return this.nzModalData.levels;
+  }
+}

+ 54 - 0
src/app/pages/manager/knowledge/doc/doc-table/doc-table.component.html

@@ -0,0 +1,54 @@
+<nz-table
+  class="precaution-table"
+  #table
+  [nzData]="list"
+  nzShowPagination
+  nzPaginationType="small"
+  [nzFrontPagination]="false"
+  [nzTotal]="pageConfig.total"
+  [nzPageIndex]="pageConfig.page + 1"
+  (nzPageIndexChange)="onCurrentPageChange($event - 1)"
+  [nzLoading]="loading"
+>
+  <thead>
+    <tr>
+      <th>文档名称</th>
+      <th>更新日期</th>
+      <th>发布部门</th>
+      <th [nzRight]="'0px'">操作</th>
+    </tr>
+  </thead>
+  <tbody>
+    @for (data of table.data; track $index) {
+      <tr>
+        <td>
+          <span nz-icon nzType="icons:docx" class="mr-2"></span>
+          <span>{{ data.name }}</span>
+        </td>
+        <td>{{ data.updateDate }}</td>
+        <td>{{ data.department }}</td>
+
+        <td [nzRight]="'0px'">
+          <nz-upload
+            [nzAction]="'/doc/update?id=' + data.id"
+            nzAccept=".docx,.pdf"
+            [nzShowUploadList]="false"
+            (nzChange)="handleImportStatusChange($event)"
+          >
+            <button class="precaution-button ml-4 mr-2" nzType="primary" nzSize="small" nz-button>更新文档</button>
+          </nz-upload>
+          <button
+            class="precaution-secondary px-2 ml-4"
+            nz-button
+            nzSize="small"
+            nzType="primary"
+            nzDanger
+            (click)="onRemove(data)"
+          >
+            删除
+          </button>
+        </td>
+      </tr>
+    }
+  </tbody>
+</nz-table>

+ 0 - 0
src/app/pages/manager/knowledge/doc/doc-table/doc-table.component.less


+ 23 - 0
src/app/pages/manager/knowledge/doc/doc-table/doc-table.component.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DocTableComponent } from './doc-table.component';
+
+describe('DocTableComponent', () => {
+  let component: DocTableComponent;
+  let fixture: ComponentFixture<DocTableComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [DocTableComponent]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(DocTableComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 83 - 0
src/app/pages/manager/knowledge/doc/doc-table/doc-table.component.ts

@@ -0,0 +1,83 @@
+import { Component, EventEmitter, Input, Output } from '@angular/core';
+import { NzMessageService } from 'ng-zorro-antd/message';
+import { NzModalService } from 'ng-zorro-antd/modal';
+import { NzUploadChangeParam } from 'ng-zorro-antd/upload';
+import { CommonNzModule } from '../../../../../common.nz.module';
+import { ApiService } from '../../../../../services/api.service';
+
+@Component({
+  selector: 'doc-table',
+  standalone: true,
+  imports: [CommonNzModule],
+  templateUrl: './doc-table.component.html',
+  styleUrl: './doc-table.component.less',
+})
+export class DocTableComponent {
+  importing = false;
+  loading = false;
+  @Input() pageConfig: IPageConfig = {
+    page: 0,
+    total: 0,
+    size: 10,
+  };
+  @Input() list: Knowledge.Doc[] = [];
+  @Output() pageChange = new EventEmitter<number>();
+
+  constructor(
+    private message: NzMessageService,
+    private modal: NzModalService,
+    private api: ApiService
+  ) {}
+
+  onCurrentPageChange(page: number) {
+    this.pageConfig.page = page;
+    this.pageChange.emit(page);
+  }
+
+  onRemove(data: Knowledge.Doc) {
+    this.list = this.list.filter(item => item.id !== data.id);
+    this.modal.confirm({
+      nzClassName: 'precaution-modal',
+      nzTitle: '删除文档',
+      nzContent: `确定要删除【${data.name}】吗?`,
+      nzOkText: '确定',
+      nzOkDanger: true,
+      nzOnOk: async () => {
+        await this.api.knowledge
+          .deleteDoc(data.id)
+          .then(() => {
+            this.message.success('删除成功');
+            this.pageChange.emit(this.pageConfig.page);
+          })
+          .catch(err => {
+            this.message.error(err.message);
+            throw err;
+          });
+      },
+    });
+  }
+
+  handleImportStatusChange(event: NzUploadChangeParam) {
+    if (event.type === 'start') {
+      this.importing = true;
+    }
+    if (event.type === 'success') {
+      if (event.file.response?.code !== 0) {
+        this.message.error('更新失败: ' + event.file.response?.message);
+        this.importing = false;
+        return;
+      }
+      this.message.success('更新成功');
+      this.importing = false;
+
+      this.pageChange.emit(this.pageConfig.page);
+    } else if (event.type === 'error') {
+      if (event.file.error.error?.message) {
+        this.message.error('更新失败: ' + event.file.error.error.message);
+      } else {
+        this.message.error('更新失败');
+      }
+      this.importing = false;
+    }
+  }
+}

+ 33 - 1
src/app/pages/manager/knowledge/doc/doc.component.html

@@ -1 +1,33 @@
-<p>doc works!</p>
+<div class="knowledge-panel">
+  <div class="knowledge-header">
+    <div class="title">风险文档</div>
+  </div>
+  <div class="knowledge-content">
+    <div class="risk-item-filter">
+      <filter-button-group
+        label="风险类别"
+        [options]="docCategories"
+        [activeValue]="category"
+        (change)="changeCategory($event)"
+      ></filter-button-group>
+      <filter-button-group
+        label="风险等级"
+        [options]="levelOptions"
+        [activeValue]="level"
+        (change)="changeLevel($event)"
+      ></filter-button-group>
+    </div>
+
+    <div class="flex justify-between pb-4">
+      <div>
+        <span class="title-text">文档库</span>
+      </div>
+      <div>
+        <button nz-button nzType="primary" class="precaution-button" style="border-radius: 12px" (click)="onImport()">
+          上传文档
+        </button>
+      </div>
+    </div>
+    <doc-table [list]="list" [pageConfig]="pageConfig" (pageChange)="onPageChange($event)"></doc-table>
+  </div>
+</div>

+ 7 - 0
src/app/pages/manager/knowledge/doc/doc.component.less

@@ -0,0 +1,7 @@
+.title-text {
+  font-weight: 500;
+  font-size: 20px;
+  color: #333333;
+  line-height: 28px;
+  text-shadow: 0px 0px 8px rgba(223, 223, 223, 0.5);
+}

+ 93 - 4
src/app/pages/manager/knowledge/doc/doc.component.ts

@@ -1,12 +1,101 @@
-import { Component } from '@angular/core';
+import { Component, ViewContainerRef } from '@angular/core';
+import { NzModalService } from 'ng-zorro-antd/modal';
+import { CommonNzModule } from '../../../../common.nz.module';
+import { ApiService } from '../../../../services/api.service';
+import { FilterButtonGroupComponent } from '../../../../shared/filter-button-group/filter-button-group.component';
+import { DocImportComponent, DocImportModalData } from './doc-import/doc-import.component';
+import { DocTableComponent } from './doc-table/doc-table.component';
+
+const getMockItem = (id: number): Knowledge.Doc => ({
+  id,
+  name: `文档${id}`,
+  department: `部门${id}`,
+  updateDate: '2024/9/9',
+});
 
 @Component({
-  selector: 'app-doc',
+  selector: 'knowledge-doc',
   standalone: true,
-  imports: [],
+  imports: [CommonNzModule, FilterButtonGroupComponent, DocTableComponent],
   templateUrl: './doc.component.html',
-  styleUrl: './doc.component.less'
+  styleUrl: './doc.component.less',
 })
 export class DocComponent {
+  docCategories = [
+    { label: '管理规定', value: 1 },
+    { label: '规程', value: 2 },
+    { label: '作业指导', value: 3 },
+    { label: '应急预案', value: 4 },
+    { label: '教育培训', value: 5 },
+  ];
+  levelOptions = [
+    { label: '集团级', value: 1 },
+    { label: '公司级', value: 2 },
+  ];
+  category = this.docCategories[0].value;
+  level = this.levelOptions[0].value;
+
+  pageConfig: IPageConfig = {
+    page: 0,
+    total: 0,
+    size: 10,
+  };
+  list: Knowledge.Doc[] = Array.from({ length: 10 }, (_, index) => getMockItem(index + 1));
+
+  constructor(
+    private modal: NzModalService,
+    private viewContainerRef: ViewContainerRef,
+    private api: ApiService
+  ) {}
+
+  ngOnInit() {
+    this.fetchList();
+  }
+
+  changeCategory(value: number | string) {
+    this.category = value as number;
+    this.fetchList();
+  }
+  changeLevel(value: number | string) {
+    this.level = value as number;
+    this.fetchList();
+  }
+
+  onPageChange(page: number) {
+    this.pageConfig.page = page;
+    this.fetchList();
+  }
+
+  onImport() {
+    this.modal.create<DocImportComponent, DocImportModalData>({
+      nzClassName: 'precaution-modal',
+      nzTitle: '文档 - 导入',
+      nzContent: DocImportComponent,
+      nzWidth: 500,
+      nzBodyStyle: { paddingBottom: '0px' },
+      nzViewContainerRef: this.viewContainerRef,
+      nzData: {
+        categories: this.docCategories,
+        levels: this.levelOptions,
+        category: this.category,
+        level: this.level,
+        onImportSuccess: () => {
+          this.onSearch();
+        },
+      },
+      nzFooter: null,
+    });
+  }
 
+  onSearch() {
+    console.log('search');
+    this.fetchList();
+  }
+  async fetchList() {
+    const result = await this.api.knowledge.getDocList({
+      category: this.category,
+      level: this.level,
+    });
+    console.log(result);
+  }
 }

+ 8 - 8
src/app/pages/manager/knowledge/list/list.component.ts

@@ -15,14 +15,14 @@ export class ListComponent {
       label: '风险知识',
       value: 'risk-knowledge',
     },
-    {
-      label: '企业录入情况',
-      value: 'enterprise-input',
-    },
-    {
-      label: '更新纪录',
-      value: 'update-record',
-    },
+    // {
+    //   label: '企业录入情况',
+    //   value: 'enterprise-input',
+    // },
+    // {
+    //   label: '更新纪录',
+    //   value: 'update-record',
+    // },
   ];
   activeTab = this.tabs[0].value;
 

+ 5 - 0
src/app/pages/manager/layout/header/header.component.ts

@@ -50,6 +50,11 @@ export class HeaderComponent {
       label: '隐患管理',
       icon: 'assets/images/nav/potential-hazard.png',
     },
+    {
+      path: '/manager/basic-data',
+      label: '基础数据管理',
+      icon: 'assets/images/nav/potential-hazard.png',
+    },
   ];
   constructor(
     readonly setting: SettingService,

+ 11 - 0
src/app/pages/manager/manager.routes.ts

@@ -1,4 +1,5 @@
 import { Routes } from '@angular/router';
+import { basicRoutes } from './basic/basic.route';
 import { knowledgeRoutes } from './knowledge/knowledge.route';
 import { workbenchRoutes } from './workbench/workbench.route';
 
@@ -23,5 +24,15 @@ export const managerRoutes: Routes = [
       weight: 0,
     },
   },
+  {
+    path: 'basic-data',
+    loadComponent: () => import('./basic/basic.component').then(m => m.BasicComponent),
+    children: basicRoutes,
+    data: {
+      title: '基础数据管理',
+      icon: 'user',
+      weight: 0,
+    },
+  },
   { path: '', pathMatch: 'full', redirectTo: 'workbench' },
 ];

+ 9 - 1
src/app/services/api.service.ts

@@ -1,10 +1,18 @@
 import { Injectable } from '@angular/core';
 
+import { AuthApi } from './apis/auth';
+import { BasicApi } from './apis/basic';
+import { KnowledgeApi } from './apis/knowledge';
 import { HttpService } from './http.service';
 
 @Injectable({ providedIn: 'root' })
 export class ApiService {
-  constructor(public readonly http: HttpService) {}
+  constructor(
+    public readonly http: HttpService,
+    public readonly basic: BasicApi,
+    public readonly auth: AuthApi,
+    public readonly knowledge: KnowledgeApi
+  ) {}
 
   getAllUrls(): string[] {
     const ret: string[] = [];

+ 16 - 0
src/app/services/apis/auth.ts

@@ -0,0 +1,16 @@
+import { Injectable } from '@angular/core';
+import { Post, requestDone } from '../http.decorators';
+import { _BaseApi } from './_base';
+
+@Injectable({ providedIn: 'root' })
+export class AuthApi extends _BaseApi {
+  @Post('/user/login')
+  async login(data: Auth.LoginParams): Promise<Auth.LoginResponse> {
+    requestDone(data);
+  }
+
+  @Post('/auth/logout')
+  async logout(): Promise<void> {
+    requestDone();
+  }
+}

+ 58 - 0
src/app/services/apis/basic.ts

@@ -0,0 +1,58 @@
+import { Injectable } from '@angular/core';
+import { Get, Post, requestDone } from '../http.decorators';
+import { _BaseApi } from './_base';
+
+@Injectable({ providedIn: 'root' })
+export class BasicApi extends _BaseApi {
+  @Get('/basic/predefine/all', false, ['type'])
+  async getPredefineList(type: string): Promise<BasicData.Predefine[]> {
+    requestDone();
+  }
+  @Post('/basic/predefine/add')
+  async addPredefine(data: Omit<BasicData.Predefine, 'id'>): Promise<void> {
+    requestDone(data);
+  }
+  async deletePredefine(type: string, value: string): Promise<void> {
+    return this.http.delete('/basic/predefine/remove', { type, value });
+  }
+
+  async deleteType(name: string): Promise<void> {
+    return this.http.delete('/basic/product/remove-type', { name });
+  }
+
+  @Get('/basic/product/units')
+  async getUnitList(): Promise<BasicData.Unit[]> {
+    requestDone();
+  }
+  @Post('/basic/product/update-unit')
+  async updateUnit(data: Partial<BasicData.Unit>): Promise<void> {
+    requestDone(data);
+  }
+  @Post('/basic/product/add-unit')
+  async addUnit(data: Partial<BasicData.Unit>): Promise<void> {
+    requestDone(data);
+  }
+  async deletedUnit(id: number): Promise<void> {
+    return this.http.delete('/basic/product/remove-unit', { id });
+  }
+
+  @Get('/basic/equipment/all')
+  async getEquipmentList(): Promise<BasicData.Equipment[]> {
+    requestDone();
+  }
+  @Post('/basic/equipment/update')
+  async updateEquipment(data: Partial<BasicData.Equipment>): Promise<void> {
+    requestDone(data);
+  }
+  @Post('/basic/equipment/add')
+  async addEquipment(data: Partial<BasicData.Equipment>): Promise<void> {
+    requestDone(data);
+  }
+  async deletedEquipment(id: number): Promise<void> {
+    return this.http.delete('/basic/equipment/remove', { id });
+  }
+
+  async removeDepartment(departmentName: string, companyName: string): Promise<void> {
+    return this.http.delete('/hr/department/remove', { department: departmentName, company: companyName });
+  }
+}

+ 14 - 0
src/app/services/apis/knowledge.ts

@@ -0,0 +1,14 @@
+import { Injectable } from '@angular/core';
+import { Post, requestDone } from '../http.decorators';
+import { _BaseApi } from './_base';
+
+@Injectable({ providedIn: 'root' })
+export class KnowledgeApi extends _BaseApi {
+  @Post('/knowledge/doc/list')
+  async getDocList(data: Knowledge.GetDocListParams): Promise<Knowledge.GetDocListResponse> {
+    requestDone(data);
+  }
+  async deleteDoc(id: number) {
+    return this.http.post('/knowledge/doc/delete', { id });
+  }
+}

+ 6 - 4
src/app/services/auth.service.ts

@@ -18,8 +18,10 @@ export class AuthService {
     return this.token != undefined;
   }
 
-  async login(): Promise<UserApi.User> {
-    const result = {} as UserApi.User;
+  async login(params: Auth.LoginParams): Promise<Auth.LoginResponse> {
+    const result = await this.api.auth.login(params).catch(err => {
+      throw err;
+    });
     await this.afterLogin(result);
     return result;
   }
@@ -35,8 +37,8 @@ export class AuthService {
     return true;
   }
 
-  private async afterLogin(result: UserApi.User) {
-    this.storage.token = result.name;
+  private async afterLogin(result: Auth.LoginResponse) {
+    this.storage.token = result.token;
     const autoLogin = await this.autoLogin();
     if (!autoLogin) {
       return undefined;

+ 28 - 0
src/app/services/basic.service.ts

@@ -6,11 +6,38 @@ import { ApiService } from './api.service';
   providedIn: 'root',
 })
 export class BasicDataService {
+  lines: BasicData.Predefine[] = [];
+  types: BasicData.Predefine[] = [];
+  jobs: BasicData.Predefine[] = [];
+  units: BasicData.Unit[] = [];
   private _initialized = false;
   $fetched = new Subject<void>();
   constructor(private readonly api: ApiService) {}
+  async fetchBasicData() {
+    return Promise.all([this.fetchLines(), this.fetchTypes(), this.fetchJobs(), this.fetchUnits()]).then(() => {
+      setTimeout(() => {
+        this.$fetched.next();
+      }, 100);
+    });
+  }
+  async fetchUnits() {
+    this.units = await this.api.basic.getUnitList();
+  }
+  async fetchLines() {
+    this.lines = await this.api.basic.getPredefineList('line');
+  }
+  async fetchTypes() {
+    this.types = await this.api.basic.getPredefineList('product');
+  }
+  async fetchJobs() {
+    this.jobs = await this.api.basic.getPredefineList('job');
+  }
   clear() {
     this._initialized = false;
+    this.lines = [];
+    this.types = [];
+    this.jobs = [];
+    this.units = [];
   }
 
   async init(): Promise<boolean> {
@@ -24,6 +51,7 @@ export class BasicDataService {
   }
 
   private async onInit() {
+    await this.fetchBasicData();
     this._initialized = true;
     return true;
   }

+ 2 - 2
src/app/services/setting.service.ts

@@ -7,7 +7,7 @@ import { StorageService } from './storage.service';
   providedIn: 'root',
 })
 export class SettingService {
-  user?: UserApi.User;
+  user?: Auth.User;
 
   get userCompany() {
     return this.user?.company || '';
@@ -52,7 +52,7 @@ export class SettingService {
     // this.user = info;
   }
 
-  updateUser(data: UserApi.ModifyUserInfo) {
+  updateUser(data: Auth.ModifyUserInfo) {
     // return this.api.user.modifyMyInfo(data);
   }
 }

+ 3 - 0
src/app/services/storage.service.ts

@@ -32,6 +32,9 @@ export class StorageService {
     this._cacheToken = undefined;
     this.setString('token', '');
   }
+  remove(key: string) {
+    localStorage.removeItem(this.getRealKey(key));
+  }
   setString(key: string, data: string): void {
     key = this.getRealKey(key);
     if (!data || data.length <= 0) {

+ 6 - 6
src/app/shared/data-select/user-search.component.ts

@@ -12,20 +12,20 @@ import { CommonNzModule } from '../../common.nz.module';
 })
 export class UserSearchComponent {
   @Input() hiddenContent = false;
-  @Input() userList: UserApi.User[] = [];
+  @Input() userList: Auth.User[] = [];
   loading = false;
-  sources: UserApi.User[] = [];
-  dataMap: Map<string, UserApi.User[]> = new Map();
+  sources: Auth.User[] = [];
+  dataMap: Map<string, Auth.User[]> = new Map();
   checkedDto: Record<string, boolean> = {};
 
   search = {
     text: '',
   };
-  filteredList: UserApi.User[] = [];
+  filteredList: Auth.User[] = [];
   capitals: string[] = [];
   list: {
     w: string;
-    items: UserApi.User[];
+    items: Auth.User[];
   }[] = [];
 
   private subject$ = new Subject<void>();
@@ -178,7 +178,7 @@ export class UserSearchComponent {
     this.onItemCheckedChange();
   }
 
-  additionalLabel(user: UserApi.User) {
+  additionalLabel(user: Auth.User) {
     const { job } = user;
     return job ? `(${job})` : '';
   }

+ 10 - 0
src/app/shared/filter-button-group/filter-button-group.component.html

@@ -0,0 +1,10 @@
+<div class="filter-item">
+  <label class="label">{{ label }}: </label>
+  <div class="flex-1">
+    @for (o of options; track o.value) {
+      <div class="button-item" [class.active]="activeValue === o.value" (click)="handleChange(o.value)">
+        {{ o.label }}
+      </div>
+    }
+  </div>
+</div>

+ 32 - 0
src/app/shared/filter-button-group/filter-button-group.component.less

@@ -0,0 +1,32 @@
+.filter-item {
+  display: flex;
+  gap: 10px;
+  align-items: flex-start;
+  justify-content: flex-start;
+  margin-bottom: 16px;
+}
+.label {
+  line-height: 32px;
+}
+.button-item {
+  display: inline-block;
+  border-radius: 10px;
+  padding: 0 18px;
+  height: 32px;
+  line-height: 32px;
+  color: rgba(102, 102, 102, 0.667);
+  margin-right: 8px;
+  cursor: pointer;
+  transition: all 0.3s;
+  &:hover {
+    background-color: var(--ant-primary-1);
+  }
+
+  &:last-child {
+    margin-right: 0;
+  }
+  &.active {
+    background-color: var(--deep-blue);
+    color: #ffffff;
+  }
+}

+ 23 - 0
src/app/shared/filter-button-group/filter-button-group.component.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { FilterButtonGroupComponent } from './filter-button-group.component';
+
+describe('FilterButtonGroupComponent', () => {
+  let component: FilterButtonGroupComponent;
+  let fixture: ComponentFixture<FilterButtonGroupComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [FilterButtonGroupComponent]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(FilterButtonGroupComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 19 - 0
src/app/shared/filter-button-group/filter-button-group.component.ts

@@ -0,0 +1,19 @@
+import { Component, EventEmitter, Input, Output } from '@angular/core';
+
+@Component({
+  selector: 'filter-button-group',
+  standalone: true,
+  imports: [],
+  templateUrl: './filter-button-group.component.html',
+  styleUrl: './filter-button-group.component.less',
+})
+export class FilterButtonGroupComponent<T = any> {
+  @Input() label = '';
+  @Input() options: Option<T>[] = [];
+  @Input() activeValue: T | null = null;
+  @Output() change = new EventEmitter<T>();
+
+  handleChange(value: T) {
+    this.change.emit(value);
+  }
+}

+ 1 - 1
src/app/shared/simple-form-page/simple-form-page.component.ts

@@ -26,7 +26,7 @@ export interface Column {
   templateUrl: './simple-form-page.component.html',
   styleUrl: './simple-form-page.component.less',
 })
-export class SimpleFormPageComponent<T = BaseData.Predefine> {
+export class SimpleFormPageComponent<T = BasicData.Predefine> {
   list: T[] = [];
   sortDirections: NzTableSortOrder[] = ['ascend', 'descend', null];
   @Input() keyWord = '';

+ 1 - 0
src/assets/icons/database.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><g fill="none"><path d="M12 10c4.418 0 8-1.79 8-4s-3.582-4-8-4s-8 1.79-8 4s3.582 4 8 4zm6.328.17A7.61 7.61 0 0 0 20 9.053V18c0 2.21-3.582 4-8 4s-8-1.79-8-4V9.053a7.61 7.61 0 0 0 1.672 1.117C7.37 11.018 9.608 11.5 12 11.5c2.392 0 4.63-.482 6.328-1.33z" fill="currentColor"></path></g></svg>

+ 1 - 0
src/assets/icons/db.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 448 512"><path d="M448 73.143v45.714C448 159.143 347.667 192 224 192S0 159.143 0 118.857V73.143C0 32.857 100.333 0 224 0s224 32.857 224 73.143zM448 176v102.857C448 319.143 347.667 352 224 352S0 319.143 0 278.857V176c48.125 33.143 136.208 48.572 224 48.572S399.874 209.143 448 176zm0 160v102.857C448 479.143 347.667 512 224 512S0 479.143 0 438.857V336c48.125 33.143 136.208 48.572 224 48.572S399.874 369.143 448 336z" fill="currentColor"></path></svg>

+ 11 - 0
src/assets/icons/docx.svg

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>编组备份 7</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="风险知识-风险文档" transform="translate(-357.000000, -499.000000)" fill="#009994" fill-rule="nonzero">
+            <g id="编组备份-2" transform="translate(357.000000, 499.000000)">
+                <path d="M10.5,1.40929557 L13.5,1.40929557 C13.7761406,1.40929557 14,1.63096523 14,1.90440474 L14,11.8039888 C14,12.0774283 13.7761406,12.2990979 13.5,12.2990979 L10.5,12.2990979 L10.5,13.2133479 C10.5,13.4867256 10.2761406,13.7083333 10,13.7083333 C9.97634375,13.7083333 9.95271875,13.7066778 9.92929688,13.7033513 L0.429296875,12.3598107 C0.18296875,12.3249674 0,12.1161241 0,11.8698073 L0,1.83858617 C0,1.59226935 0.18296875,1.38342612 0.429296875,1.34858281 L9.92929688,0.00504219827 C10.2026563,-0.0336382054 10.4559219,0.154425917 10.4949687,0.425049493 C10.4983281,0.448242263 10.5,0.471620699 10.5,0.495045552 L10.5,1.40929557 Z M10.5,2.39928182 L10.5,11.3091117 L13,11.3091117 L13,2.39928182 L10.5,2.39928182 Z M1.125,2.37522261 L1.125,11.3331709 L9.375,12.4999266 L9.375,1.20846692 L1.125,2.37522261 Z M4.75,4.25549251 L5.75,4.25549251 L6.75,7.71970946 L7.75,4.25549251 L8.75,4.25549251 L7.25,9.45181794 L6.25,9.45181794 L5.25,5.98760098 L4.25,9.45181794 L4.2496875,9.45181794 L4.25,9.45290099 L3.25,9.45290099 L1.75,4.25549251 L2.75,4.25549251 L3.74989062,7.72006532 L4.75,4.25549251 Z" id="形状"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 1 - 0
src/assets/icons/equipment.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32"><path d="M32 26v-2h-2.101a4.968 4.968 0 0 0-.732-1.753l1.49-1.49l-1.414-1.414l-1.49 1.49A4.968 4.968 0 0 0 26 20.101V18h-2v2.101a4.968 4.968 0 0 0-1.753.732l-1.49-1.49l-1.414 1.414l1.49 1.49A4.968 4.968 0 0 0 20.101 24H18v2h2.101a4.968 4.968 0 0 0 .732 1.753l-1.49 1.49l1.414 1.414l1.49-1.49a4.968 4.968 0 0 0 1.753.732V32h2v-2.101a4.968 4.968 0 0 0 1.753-.732l1.49 1.49l1.414-1.414l-1.49-1.49A4.968 4.968 0 0 0 29.899 26zm-7 2a3 3 0 1 1 3-3a3.003 3.003 0 0 1-3 3z" fill="currentColor"></path><circle cx="7" cy="20" r="2" fill="currentColor"></circle><path d="M14 20a4 4 0 1 1 4-4a4.012 4.012 0 0 1-4 4zm0-6a2 2 0 1 0 2 2a2.006 2.006 0 0 0-2-2z" fill="currentColor"></path><circle cx="21" cy="12" r="2" fill="currentColor"></circle><path d="M13.02 28.271L3 22.427V9.574l11-6.416l11.496 6.706l1.008-1.728l-12-7a1 1 0 0 0-1.008 0l-12 7A1 1 0 0 0 1 9v14a1 1 0 0 0 .496.864L12.013 30z" fill="currentColor"></path></svg>

+ 1 - 0
src/assets/icons/line.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class=" iconify iconify--maki" width="1em" height="1em" viewBox="0 0 15 15"><path fill="currentColor" d="M5.5 0s-.75 0-1 1L3 6.5V10c0 1 1 1 1 1h7s1 0 1-1V6.5L10.5 1c-.273-1-1-1-1-1zm1 1.5h2s.536 0 .75 1L10 6c.215 1.002-1 1-1 1H6s-1.215.002-1-1l.75-3.5c.214-1 .75-1 .75-1M5 8a1 1 0 1 1 0 2a1 1 0 0 1 0-2m1.75 0h1.5a.25.25 0 1 1 0 .5h-1.5a.25.25 0 1 1 0-.5M10 8a1 1 0 1 1 0 2a1 1 0 0 1 0-2m-5.875 4L3 15h1.5l.375-1h5.25l.375 1H12l-1.125-3h-1.5l.375 1h-4.5l.375-1z"></path></svg>

+ 1 - 0
src/assets/icons/post-group.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class=" iconify iconify--material-symbols" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M0 18v-1.575q0-1.075 1.1-1.75T4 14q.325 0 .625.013t.575.062q-.35.525-.525 1.1t-.175 1.2V18zm6 0v-1.625q0-.8.438-1.463t1.237-1.162T9.588 13T12 12.75q1.325 0 2.438.25t1.912.75t1.225 1.163t.425 1.462V18zm13.5 0v-1.625q0-.65-.162-1.225t-.488-1.075q.275-.05.563-.062T20 14q1.8 0 2.9.663t1.1 1.762V18zM4 13q-.825 0-1.412-.587T2 11q0-.85.588-1.425T4 9q.85 0 1.425.575T6 11q0 .825-.575 1.413T4 13m16 0q-.825 0-1.412-.587T18 11q0-.85.588-1.425T20 9q.85 0 1.425.575T22 11q0 .825-.575 1.413T20 13m-8-1q-1.25 0-2.125-.875T9 9q0-1.275.875-2.137T12 6q1.275 0 2.138.863T15 9q0 1.25-.862 2.125T12 12"></path></svg>

+ 1 - 0
src/assets/icons/transit.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><path d="M12 2c-4 0-8 .5-8 4v9.5C4 17.43 5.57 19 7.5 19L6 20v1h12v-1l-1.5-1c1.93 0 3.5-1.57 3.5-3.5V6c0-3.5-3.58-4-8-4zM8.5 16c-.83 0-1.5-.67-1.5-1.5S7.67 13 8.5 13s1.5.67 1.5 1.5S9.33 16 8.5 16zm2.5-6H6V7h5v3zm4.5 6c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5s1.5.67 1.5 1.5s-.67 1.5-1.5 1.5zm2.5-6h-5V7h5v3z" fill="currentColor"></path></svg>

+ 1 - 0
src/assets/icons/type.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class=" iconify iconify--lucide" width="1em" height="1em" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7V4h16v3M9 20h6M12 4v16"></path></svg>

+ 1 - 0
src/assets/icons/unit.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class=" iconify iconify--material-symbols" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M8 9V7h8v2zM7 23q-.825 0-1.412-.587T5 21V3q0-.825.588-1.412T7 1h10q.825 0 1.413.588T19 3v18q0 .825-.587 1.413T17 23zm0-5h10V6H7z"></path></svg>

+ 1 - 0
src/assets/icons/user-fill.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512"><path d="M256 288c79.5 0 144-64.5 144-144S335.5 0 256 0S112 64.5 112 144s64.5 144 144 144zm128 32h-55.1c-22.2 10.2-46.9 16-72.9 16s-50.6-5.8-72.9-16H128C57.3 320 0 377.3 0 448v16c0 26.5 21.5 48 48 48h416c26.5 0 48-21.5 48-48v-16c0-70.7-57.3-128-128-128z" fill="currentColor"></path></svg>

+ 8 - 2
src/styles/custom.less

@@ -112,7 +112,7 @@ textarea {
   border-radius: 4px;
   display: inline-flex;
   align-items: center;
-  --ant-error-color: #fa6400;
+  --ant-error-color: #f43f5e;
 }
 .precaution-secondary {
   border-radius: 4px;
@@ -121,7 +121,7 @@ textarea {
   --ant-primary-color-active: #839dfd;
   --ant-error-color: #ffd9d9;
   &.ant-btn-dangerous {
-    color: #ff0000;
+    color: #f43f5e;
   }
   &:hover,
   &:active {
@@ -188,6 +188,11 @@ textarea {
         border-bottom: 1px solid rgba(151, 151, 151, 0.3);
         padding: 16px;
       }
+      &:last-child {
+        td {
+          border-bottom: none;
+        }
+      }
     }
   }
   nz-pagination {
@@ -296,6 +301,7 @@ textarea {
   }
 }
 .precaution-modal {
+  --ant-error-color: #ff0000;
   .ant-modal-header {
     border-radius: 8px 8px 0 0;
   }

+ 47 - 0
src/types/auth.d.ts

@@ -0,0 +1,47 @@
+declare namespace Auth {
+  interface LoginParams {
+    username: string;
+    password: string;
+  }
+  interface LoginResponse {
+    token: string;
+  }
+  /**
+   * 修改当前用户 RequestParams
+   */
+  interface ModifyUserInfo {
+    id: number;
+    email?: string; // 邮箱
+    name?: string;
+    phone?: string;
+    password?: string;
+    newpassword?: string;
+    confirmnewpassword?: string;
+  }
+
+  interface UserList {
+    items: User[];
+    total: number;
+  }
+  interface User {
+    id: string;
+    department: string;
+    company: string;
+    job: string;
+    line: string;
+    jobGroup: string;
+    createDate: string;
+    roles: string[];
+    status: number;
+    empno: string;
+    startingDate: string;
+    gender: string;
+    name: string;
+    icon: string;
+    email: string; // 邮箱
+    phone: string;
+    gender: string;
+    startingDate: string;
+    loginDate: string;
+  }
+}

+ 32 - 0
src/types/basic.d.ts

@@ -0,0 +1,32 @@
+declare namespace BasicData {
+  interface Predefine {
+    id: number;
+    type: string;
+    value: string;
+    seq: number;
+  }
+
+  interface PostList {
+    data: Post[];
+    total: number;
+  }
+
+  interface PostGroupList {
+    data: PostGroup[];
+    total: number;
+  }
+
+  interface Unit {
+    id: number;
+    name: string;
+    line: string;
+    type: string;
+  }
+
+  interface Equipment {
+    id: number;
+    name: string;
+    line: string;
+    unit: string;
+  }
+}

+ 16 - 0
src/types/knowledge.d.ts

@@ -0,0 +1,16 @@
+declare namespace Knowledge {
+  interface Doc {
+    id: number;
+    name: string;
+    department: string;
+    updateDate: string;
+  }
+  interface GetDocListParams {
+    category: number;
+    level: number;
+  }
+  interface GetDocListResponse {
+    items: Doc[];
+    total: number;
+  }
+}

+ 0 - 76
src/types/user.d.ts

@@ -1,76 +0,0 @@
-declare namespace UserApi {
-  /**
-   * 修改当前用户 RequestParams
-   */
-  interface ModifyUserInfo {
-    id: number;
-    email?: string; // 邮箱
-    name?: string;
-    phone?: string;
-    password?: string;
-    newpassword?: string;
-    confirmnewpassword?: string;
-  }
-
-  interface UserList {
-    items: User[];
-    total: number;
-  }
-  interface User {
-    id: string;
-    department: string;
-    company: string;
-    job: string;
-    line: string;
-    jobGroup: string;
-    createDate: string;
-    roles: string[];
-    status: number;
-    empno: string;
-    startingDate: string;
-    gender: string;
-    name: string;
-    icon: string;
-    email: string; // 邮箱
-    phone: string;
-    gender: string;
-    startingDate: string;
-    loginDate: string;
-  }
-
-  // interface User {
-  //   count: number;
-  //   department: number;
-  //   id: number;
-  //   job: number;
-  //   username: string;
-  //   email: string; // 邮箱
-  //   last_login_time?: string;
-  //   name: string;
-  //   path: string;
-  //   phone: string;
-  //   roles: string;
-  //   roleName: string;
-  //   status: number;
-  //   update_time: string;
-  //   create_time: string;
-  //   delete_time: string;
-  //   avatar: string; // 头像
-  // }
-
-  interface UserRequestParams extends User {
-    roles: number[];
-  }
-
-  interface SimpleUser {
-    id: number;
-    name: string;
-    status: number; // 1表示角色启用,-1表示删除
-  }
-
-  interface SimpleRole {
-    id: number;
-    name: string;
-    status: number; // 1表示角色启用,0表示禁用
-  }
-}