Просмотр исходного кода

feat: left part of workbench

Signed-off-by: carlos <568187512@qq.com>
carlos 3 месяцев назад
Родитель
Сommit
e1725819bb
34 измененных файлов с 763 добавлено и 5 удалено
  1. 36 0
      src/app/pages/manager/workbench/my/hazard-remediation/hazard-remediation.component.html
  2. 6 0
      src/app/pages/manager/workbench/my/hazard-remediation/hazard-remediation.component.less
  3. 23 0
      src/app/pages/manager/workbench/my/hazard-remediation/hazard-remediation.component.spec.ts
  4. 35 0
      src/app/pages/manager/workbench/my/hazard-remediation/hazard-remediation.component.ts
  5. 39 0
      src/app/pages/manager/workbench/my/listing/list-item/list-item.component.html
  6. 40 0
      src/app/pages/manager/workbench/my/listing/list-item/list-item.component.less
  7. 23 0
      src/app/pages/manager/workbench/my/listing/list-item/list-item.component.spec.ts
  8. 25 0
      src/app/pages/manager/workbench/my/listing/list-item/list-item.component.ts
  9. 28 0
      src/app/pages/manager/workbench/my/listing/listing.component.html
  10. 0 0
      src/app/pages/manager/workbench/my/listing/listing.component.less
  11. 23 0
      src/app/pages/manager/workbench/my/listing/listing.component.spec.ts
  12. 55 0
      src/app/pages/manager/workbench/my/listing/listing.component.ts
  13. 21 0
      src/app/pages/manager/workbench/my/my-flow/my-flow-item/my-flow-item.component.html
  14. 51 0
      src/app/pages/manager/workbench/my/my-flow/my-flow-item/my-flow-item.component.less
  15. 23 0
      src/app/pages/manager/workbench/my/my-flow/my-flow-item/my-flow-item.component.spec.ts
  16. 20 0
      src/app/pages/manager/workbench/my/my-flow/my-flow-item/my-flow-item.component.ts
  17. 6 1
      src/app/pages/manager/workbench/my/my-flow/my-flow.component.html
  18. 15 1
      src/app/pages/manager/workbench/my/my-flow/my-flow.component.ts
  19. 42 0
      src/app/pages/manager/workbench/my/my-plan/my-plan.component.html
  20. 3 0
      src/app/pages/manager/workbench/my/my-plan/my-plan.component.less
  21. 23 0
      src/app/pages/manager/workbench/my/my-plan/my-plan.component.spec.ts
  22. 31 0
      src/app/pages/manager/workbench/my/my-plan/my-plan.component.ts
  23. 6 2
      src/app/pages/manager/workbench/my/my.component.html
  24. 4 1
      src/app/pages/manager/workbench/my/my.component.ts
  25. 1 0
      src/app/pages/manager/workbench/my/risk-statistics/risk-statistics.component.html
  26. 0 0
      src/app/pages/manager/workbench/my/risk-statistics/risk-statistics.component.less
  27. 23 0
      src/app/pages/manager/workbench/my/risk-statistics/risk-statistics.component.spec.ts
  28. 12 0
      src/app/pages/manager/workbench/my/risk-statistics/risk-statistics.component.ts
  29. 17 0
      src/app/shared/workbench-tabs/workbench-tabs.component.html
  30. 40 0
      src/app/shared/workbench-tabs/workbench-tabs.component.less
  31. 23 0
      src/app/shared/workbench-tabs/workbench-tabs.component.spec.ts
  32. 49 0
      src/app/shared/workbench-tabs/workbench-tabs.component.ts
  33. 14 0
      src/assets/icons/emoji-sad.svg
  34. 6 0
      src/styles/workbench.less

+ 36 - 0
src/app/pages/manager/workbench/my/hazard-remediation/hazard-remediation.component.html

@@ -0,0 +1,36 @@
+<div class="workbench-panel" style="height: 153px">
+  <div class="workbench-panel-header" style="height: 43px">
+    <span class="header-title" style="line-height: 40px">隐患整治</span>
+  </div>
+  <div class="grid grid-cols-2 px-4 leading-loose py-1">
+    @for (item of items; track item) {
+      <div class="flex">
+        <div class="text-[#666666] pl-4">{{ item.label }}:</div>
+        <div
+          class="underline underline-offset-2 pr-[43px]"
+          [class.cursor-pointer]="item.count > 0"
+          [class.hover:!text-primary]="item.count > 0"
+          [style.color]="item.count > 0 ? '#3083E6' : '#979797 '"
+        >
+          {{ item.count ? item.count : '' }}{{ item.count > 0 ? '起' : '无' }}
+        </div>
+      </div>
+    }
+  </div>
+  <div class="px-8 relative" *ngIf="reminder.visible">
+    <div class="alert-reminder" @scaleInOut>
+      <div class="flex items-center justify-between">
+        <span nz-icon nzType="icons:emoji-sad" class="text-lg mr-4"></span>
+        <span class="flex-1 text-left">{{ reminder.content }}</span>
+        <span class="ml-4 pr-2">
+          <span
+            nz-icon
+            nzType="close"
+            class="cursor-pointer hover:scale-125 transition-transform"
+            (click)="handleReminderClose()"
+          ></span>
+        </span>
+      </div>
+    </div>
+  </div>
+</div>

+ 6 - 0
src/app/pages/manager/workbench/my/hazard-remediation/hazard-remediation.component.less

@@ -0,0 +1,6 @@
+.alert-reminder {
+  background: #ffdbdb;
+  border-radius: 8px;
+  padding: 6px 8px;
+  color: #f32d2d;
+}

+ 23 - 0
src/app/pages/manager/workbench/my/hazard-remediation/hazard-remediation.component.spec.ts

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

+ 35 - 0
src/app/pages/manager/workbench/my/hazard-remediation/hazard-remediation.component.ts

@@ -0,0 +1,35 @@
+import { Component } from '@angular/core';
+import { horizontalInOut, scaleInOut } from '../../../../../common.animation';
+import { CommonNzModule } from '../../../../../common.nz.module';
+
+@Component({
+  selector: 'workbench-hazard-remediation',
+  standalone: true,
+  imports: [CommonNzModule],
+  templateUrl: './hazard-remediation.component.html',
+  styleUrl: './hazard-remediation.component.less',
+  animations: [horizontalInOut, scaleInOut],
+})
+export class HazardRemediationComponent {
+  reminder = {
+    visible: false,
+    content: '2起隐患治理今日到期呀~',
+  };
+
+  items = [
+    { label: '重大隐患治理', count: 0 },
+    { label: 'A类隐患治理', count: 1 },
+    { label: 'B类隐患治理', count: 1 },
+    { label: 'C类隐患治理', count: 0 },
+  ];
+
+  ngAfterViewInit() {
+    setTimeout(() => {
+      this.reminder.visible = true;
+    }, 1000);
+  }
+
+  handleReminderClose() {
+    this.reminder.visible = false;
+  }
+}

+ 39 - 0
src/app/pages/manager/workbench/my/listing/list-item/list-item.component.html

@@ -0,0 +1,39 @@
+<div class="list-item">
+  <div class="list-level">
+    <span class="level-ball">{{ item.level }}</span>
+  </div>
+  <div>
+    <div class="list-title">{{ item.name }}</div>
+    <div class="list-category">
+      <span class="list-label">{{ item.category }}</span>
+      <span class="list-separator"> - </span>
+      <span class="list-value">{{ item.type }}</span>
+    </div>
+  </div>
+  <div>
+    <div>
+      <span class="list-label">场所/设备:</span>
+      <span class="list-value">{{ item.spot }}</span>
+      <span class="list-separator"> - </span>
+      <span class="list-value">{{ item.equipment }}</span>
+    </div>
+    <div>
+      <span class="list-label">可能导致结果:</span>
+      <span class="list-value">{{ item.result }}</span>
+    </div>
+  </div>
+  <div>
+    <div>
+      <span class="list-label">措施事实部门:</span>
+      <span class="list-value">{{ item.department }}</span>
+    </div>
+    <div>
+      <span class="list-label">责任人:</span>
+      <span class="list-value">{{ item.responsible }}</span>
+    </div>
+  </div>
+  <div>
+    <button nz-button nzType="primary" class="precaution-button">查看详情</button>
+    <button nz-button nzType="primary" class="precaution-secondary ml-8">评价</button>
+  </div>
+</div>

+ 40 - 0
src/app/pages/manager/workbench/my/listing/list-item/list-item.component.less

@@ -0,0 +1,40 @@
+.list-item {
+  display: flex;
+  margin-left: 24px;
+  margin-right: 24px;
+  justify-content: space-between;
+  height: 80px;
+  align-items: center;
+  border-bottom: 1px solid #f3f3f3;
+}
+.list-level {
+  .level-ball {
+    display: inline-block;
+    width: 48px;
+    height: 48px;
+    border-radius: 50%;
+    background-color: #3083e6;
+    color: #fff;
+    text-align: center;
+    line-height: 48px;
+    font-size: 16px;
+    font-weight: 500;
+  }
+}
+.list-title {
+  font-weight: 500;
+  font-size: 16px;
+
+  color: #131523;
+}
+.list-label {
+  font-size: 14px;
+  font-weight: 400;
+  color: #191b29cc;
+}
+
+.list-value {
+  font-size: 14px;
+  color: #06060a;
+  font-weight: 500;
+}

+ 23 - 0
src/app/pages/manager/workbench/my/listing/list-item/list-item.component.spec.ts

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

+ 25 - 0
src/app/pages/manager/workbench/my/listing/list-item/list-item.component.ts

@@ -0,0 +1,25 @@
+import { Component, Input } from '@angular/core';
+import { CommonNzModule } from '../../../../../../common.nz.module';
+
+export interface ListItem {
+  name: string;
+  category: string;
+  type: string;
+  level: string;
+  spot: string;
+  result: string;
+  equipment: string;
+  department: string;
+  responsible: string;
+}
+
+@Component({
+  selector: 'workbench-list-item',
+  standalone: true,
+  imports: [CommonNzModule],
+  templateUrl: './list-item.component.html',
+  styleUrl: './list-item.component.less',
+})
+export class ListItemComponent {
+  @Input() item!: ListItem;
+}

+ 28 - 0
src/app/pages/manager/workbench/my/listing/listing.component.html

@@ -0,0 +1,28 @@
+<div class="workbench-panel">
+  <div class="workbench-panel-header flex justify-between">
+    <div class="header-button-group">
+      @for (tab of tabs; track tab) {
+        <span class="button-item" [class.active]="activeTab === tab" (click)="activeTab = tab">{{ tab }}</span>
+      }
+    </div>
+    <div class="flex items-center">
+      <nz-radio-group [(ngModel)]="type" (ngModelChange)="changeType($event)">
+        @for (item of typeOptions; track $index) {
+          <label nz-radio [nzValue]="item.value" class="mr-8 last:mr-0">{{ item.label }}</label>
+        }
+      </nz-radio-group>
+    </div>
+  </div>
+  <div class="workbench-panel-body">
+    <workbench-tabs
+      [tabs]="levelOptions"
+      [(activeTab)]="level"
+      (activeTabChange)="changeLevel($event)"
+    ></workbench-tabs>
+    <div class="overflow-y-auto custom-scroll-bar" style="max-height: calc(453px - 54px - 47px)">
+      @for (item of list; track $index) {
+        <workbench-list-item [item]="item"></workbench-list-item>
+      }
+    </div>
+  </div>
+</div>

+ 0 - 0
src/app/pages/manager/workbench/my/listing/listing.component.less


+ 23 - 0
src/app/pages/manager/workbench/my/listing/listing.component.spec.ts

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

+ 55 - 0
src/app/pages/manager/workbench/my/listing/listing.component.ts

@@ -0,0 +1,55 @@
+import { Component } from '@angular/core';
+import { CommonNzModule } from '../../../../../common.nz.module';
+import { WorkbenchTabsComponent } from '../../../../../shared/workbench-tabs/workbench-tabs.component';
+import { ListItem, ListItemComponent } from './list-item/list-item.component';
+
+const getMockItem = (index: number): ListItem => {
+  return {
+    name: `XXX${index + 1}风险名称`,
+    category: '八防措施',
+    type: '触电伤害',
+    level: 'R2',
+    spot: '6号线',
+    equipment: '接触网',
+    result: '人身伤亡',
+    department: 'XXX部门',
+    responsible: '王甜甜',
+  };
+};
+
+@Component({
+  selector: 'workbench-my-listing',
+  standalone: true,
+  imports: [CommonNzModule, WorkbenchTabsComponent, ListItemComponent],
+  templateUrl: './listing.component.html',
+  styleUrl: './listing.component.less',
+})
+export class ListingComponent {
+  tabs = ['风险清单', '隐患清单'];
+  activeTab = this.tabs[0];
+
+  typeOptions = [
+    { label: '处置中问题', value: 'processing' },
+    { label: '所有问题', value: 'all' },
+  ];
+  type = this.typeOptions[0].value;
+
+  levelOptions: Option[] = [
+    { label: '全部', value: 'all' },
+    { label: 'R1', value: 'R1' },
+    { label: 'R2', value: 'R2' },
+    { label: 'R2*', value: 'R2*' },
+    { label: 'R3', value: 'R3' },
+    { label: 'R4', value: 'R4' },
+  ];
+  level: string | number = this.levelOptions[0].value;
+
+  changeLevel(value: string | number) {
+    this.level = value;
+  }
+  changeType(value: string) {
+    this.type = value;
+  }
+
+  list: ListItem[] = Array.from({ length: 10 }).map((_, index) => getMockItem(index));
+}

+ 21 - 0
src/app/pages/manager/workbench/my/my-flow/my-flow-item/my-flow-item.component.html

@@ -0,0 +1,21 @@
+<div class="flow-item">
+  <div class="flex pt-[6px] pb-[2px]">
+    <div class="flow-item-category">
+      <span>{{ data.category }}</span>
+    </div>
+    <div class="flow-item-title flex-1">{{ data.title }}</div>
+    <div class="flow-item-status">
+      <span>{{ data.status }}</span>
+    </div>
+  </div>
+  <div class="flex justify-between border-bottom pt-[2px] pb-[6px]">
+    <div class="flow-item-creator">
+      <span class="label">发起人:</span>
+      <span>{{ data.creator }}</span>
+    </div>
+    <div class="flow-item-time">
+      <span class="label">发起时间:</span>
+      <span>{{ data.time }}</span>
+    </div>
+  </div>
+</div>

+ 51 - 0
src/app/pages/manager/workbench/my/my-flow/my-flow-item/my-flow-item.component.less

@@ -0,0 +1,51 @@
+.flow-item {
+  // display: flex;
+  // justify-content: space-between;
+  color: #242731;
+  padding: 0 32px;
+  transition: all 0.3s;
+  &:hover {
+    cursor: pointer;
+    background: rgba(101, 162, 235, 0.1);
+  }
+  .flow-item-category {
+    margin-right: 24px;
+    span {
+      display: inline-block;
+      background: #3083e6;
+      border-radius: 10px;
+      color: #fff;
+      padding: 2px 8px;
+      font-weight: 400;
+      font-size: 12px;
+      color: #ffffff;
+      line-height: 17px;
+    }
+  }
+  .flow-item-title {
+    font-weight: 500;
+    font-size: 16px;
+    line-height: 21px;
+  }
+  .flow-item-status {
+    margin-left: 24px;
+    span {
+      display: inline-block;
+      padding: 2px 8px;
+      background: rgba(101, 162, 235, 0.34);
+      border-radius: 12px;
+      font-weight: 400;
+      font-size: 12px;
+      color: #3083e6;
+      line-height: 17px;
+    }
+  }
+
+  .border-bottom {
+    border-bottom: 1px solid #f3f3f3;
+  }
+
+  .label {
+    color: #666666;
+  }
+}

+ 23 - 0
src/app/pages/manager/workbench/my/my-flow/my-flow-item/my-flow-item.component.spec.ts

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

+ 20 - 0
src/app/pages/manager/workbench/my/my-flow/my-flow-item/my-flow-item.component.ts

@@ -0,0 +1,20 @@
+import { Component, Input } from '@angular/core';
+
+export interface Flow {
+  category: string;
+  title: string;
+  time: string;
+  status: string;
+  creator: string;
+}
+
+@Component({
+  selector: 'workbench-my-flow-item',
+  standalone: true,
+  imports: [],
+  templateUrl: './my-flow-item.component.html',
+  styleUrl: './my-flow-item.component.less',
+})
+export class MyFlowItemComponent {
+  @Input() data!: Flow;
+}

+ 6 - 1
src/app/pages/manager/workbench/my/my-flow/my-flow.component.html

@@ -1,4 +1,4 @@
-<div class="workbench-panel px-8" style="height: 356px">
+<div class="workbench-panel" style="height: 356px">
   <div class="workbench-panel-header">
     <div class="header-button-group">
       @for (tab of tabs; track tab) {
@@ -6,4 +6,9 @@
       }
     </div>
   </div>
+  <div class="workbench-panel-body overflow-y-auto custom-scroll-bar" style="height: calc(100% - 54px)">
+    @for (flow of flows; track flow) {
+      <workbench-my-flow-item [data]="flow"></workbench-my-flow-item>
+    }
+  </div>
 </div>

+ 15 - 1
src/app/pages/manager/workbench/my/my-flow/my-flow.component.ts

@@ -1,9 +1,21 @@
 import { Component } from '@angular/core';
+import { Flow, MyFlowItemComponent } from './my-flow-item/my-flow-item.component';
+
+const getMockFlow = (): Flow => {
+  // 获取mock数据
+  return {
+    category: '隐患',
+    title: 'XX隐患治理上报',
+    time: '2024-09-20 10:00',
+    status: '待审批',
+    creator: '高鹏', // 发起人
+  };
+};
 
 @Component({
   selector: 'workbench-my-flow',
   standalone: true,
-  imports: [],
+  imports: [MyFlowItemComponent],
   templateUrl: './my-flow.component.html',
   styleUrl: './my-flow.component.less',
 })
@@ -11,4 +23,6 @@ export class MyFlowComponent {
   // 当前选中的tab
   activeTab = '待处理';
   tabs = ['待处理', '抄送我的', '我发起的'];
+
+  flows: Flow[] = Array.from({ length: 10 }).map(() => getMockFlow());
 }

+ 42 - 0
src/app/pages/manager/workbench/my/my-plan/my-plan.component.html

@@ -0,0 +1,42 @@
+<div class="workbench-panel mb-5" style="height: 184px">
+  <div class="workbench-panel-header">
+    <div class="header-button-group">
+      @for (tab of tabs; track tab) {
+        <span class="button-item" [class.active]="activeTab === tab" (click)="activeTab = tab">{{ tab }}</span>
+      }
+    </div>
+  </div>
+  <div class="workbench-panel-body overflow-y-auto custom-scroll-bar" style="height: calc(100% - 54px)">
+    @for (item of items; track item) {
+      <ng-container
+        *ngTemplateOutlet="
+          planItem;
+          context: {
+            $implicit: {
+              title: item.label,
+              count: item.count,
+            },
+          }
+        "
+      ></ng-container>
+    }
+  </div>
+</div>
+
+<ng-template #planItem let-plan>
+  <div class="text-[#666666]">
+    <div class="flex justify-between mx-8 border-bottom h-[43px] leading-[43px]">
+      <div class="text-[#666666]">
+        {{ plan.title }}
+      </div>
+      <div
+        class="underline underline-offset-2 pr-[43px]"
+        [class.cursor-pointer]="plan.count > 0"
+        [class.hover:!text-primary]="plan.count > 0"
+        [style.color]="plan.count > 0 ? '#3083E6' : '#979797 '"
+      >
+        {{ plan.count ? plan.count : '' }}{{ plan.count > 0 ? '起' : '无' }}
+      </div>
+    </div>
+  </div>
+</ng-template>

+ 3 - 0
src/app/pages/manager/workbench/my/my-plan/my-plan.component.less

@@ -0,0 +1,3 @@
+.border-bottom {
+  border-bottom: 1px solid #f3f3f3;
+}

+ 23 - 0
src/app/pages/manager/workbench/my/my-plan/my-plan.component.spec.ts

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

+ 31 - 0
src/app/pages/manager/workbench/my/my-plan/my-plan.component.ts

@@ -0,0 +1,31 @@
+import { Component } from '@angular/core';
+import { CommonNzModule } from '../../../../../common.nz.module';
+
+export interface Plan {
+  title: string;
+  time: string;
+  status: string;
+}
+
+@Component({
+  selector: 'workbench-my-plan',
+  standalone: true,
+  imports: [CommonNzModule],
+  templateUrl: './my-plan.component.html',
+  styleUrl: './my-plan.component.less',
+})
+export class MyPlanComponent {
+  // 当前选中的tab
+  activeTab = '今日计划';
+  tabs = ['今日计划', '周计划', '月计划'];
+  items = [
+    { label: '日常隐患排查', count: 0 },
+    { label: '综合性隐患排查', count: 1 },
+    { label: '其他隐患排查', count: 0 },
+  ];
+  // plans = {
+  //   daily: 0,
+  //   comprehensive: 0,
+  //   other: 0,
+  // };
+}

+ 6 - 2
src/app/pages/manager/workbench/my/my.component.html

@@ -1,12 +1,16 @@
 <div class="flex">
   <div class="flex-1">
     <workbench-my-work></workbench-my-work>
-    <div class="flex-1">
+    <div class="flex flex-1 gap-x-4">
       <div class="w-1/2 py-4">
         <workbench-my-flow></workbench-my-flow>
       </div>
-      <div class="w-1/2"></div>
+      <div class="w-1/2 py-4">
+        <workbench-my-plan></workbench-my-plan>
+        <workbench-hazard-remediation></workbench-hazard-remediation>
+      </div>
     </div>
+    <workbench-my-listing></workbench-my-listing>
   </div>
   <div class="" style="width: 600px">
     <div>你的工作</div>

+ 4 - 1
src/app/pages/manager/workbench/my/my.component.ts

@@ -1,11 +1,14 @@
 import { Component } from '@angular/core';
+import { HazardRemediationComponent } from './hazard-remediation/hazard-remediation.component';
+import { ListingComponent } from './listing/listing.component';
 import { MyFlowComponent } from './my-flow/my-flow.component';
+import { MyPlanComponent } from './my-plan/my-plan.component';
 import { MyWorkComponent } from './my-work/my-work.component';
 
 @Component({
   selector: 'manager-workbench-my',
   standalone: true,
-  imports: [MyWorkComponent, MyFlowComponent],
+  imports: [MyWorkComponent, MyFlowComponent, MyPlanComponent, HazardRemediationComponent, ListingComponent],
   templateUrl: './my.component.html',
   styleUrl: './my.component.less',
 })

+ 1 - 0
src/app/pages/manager/workbench/my/risk-statistics/risk-statistics.component.html

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

+ 0 - 0
src/app/pages/manager/workbench/my/risk-statistics/risk-statistics.component.less


+ 23 - 0
src/app/pages/manager/workbench/my/risk-statistics/risk-statistics.component.spec.ts

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

+ 12 - 0
src/app/pages/manager/workbench/my/risk-statistics/risk-statistics.component.ts

@@ -0,0 +1,12 @@
+import { Component } from '@angular/core';
+
+@Component({
+  selector: 'app-risk-statistics',
+  standalone: true,
+  imports: [],
+  templateUrl: './risk-statistics.component.html',
+  styleUrl: './risk-statistics.component.less'
+})
+export class RiskStatisticsComponent {
+
+}

+ 17 - 0
src/app/shared/workbench-tabs/workbench-tabs.component.html

@@ -0,0 +1,17 @@
+<div
+  #tabsRef
+  class="workbench-tabs"
+  [style.--active-width]="activeWidth"
+  [style.--active-offset-left]="activeOffsetLeft"
+>
+  @for (tab of tabs; track tab.value) {
+    <span
+      class="button-item"
+      [class.active]="activeTab === tab.value"
+      [attr.data-value]="tab.value"
+      (click)="handleChange(tab)"
+    >
+      {{ tab.label }}
+    </span>
+  }
+</div>

+ 40 - 0
src/app/shared/workbench-tabs/workbench-tabs.component.less

@@ -0,0 +1,40 @@
+.workbench-tabs {
+  display: flex;
+  align-items: center;
+  height: 47px;
+  border-bottom: 1px solid #f3f3f3;
+  margin-left: 24px;
+  margin-right: 24px;
+  font-size: 14px;
+  position: relative;
+
+  &:before {
+    content: '';
+    position: absolute;
+    left: var(--active-offset-left);
+    bottom: 0;
+    height: 4px;
+    border-radius: 2px;
+    background: var(--ant-primary-color);
+    width: var(--active-width);
+    transition:
+      left 0.2s ease-in-out,
+      width 0.2s ease-in-out;
+  }
+}
+
+.button-item {
+  cursor: pointer;
+  margin-right: 24px;
+  min-width: 60px;
+  text-align: center;
+  color: #72767c;
+  transition: color 0.2s ease-in-out;
+  &:hover {
+    color: var(--ant-primary-color);
+  }
+  &.active {
+    font-weight: 500;
+    color: var(--ant-primary-color);
+  }
+}

+ 23 - 0
src/app/shared/workbench-tabs/workbench-tabs.component.spec.ts

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

+ 49 - 0
src/app/shared/workbench-tabs/workbench-tabs.component.ts

@@ -0,0 +1,49 @@
+import { CommonModule } from '@angular/common';
+import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
+
+@Component({
+  selector: 'workbench-tabs',
+  standalone: true,
+  imports: [CommonModule],
+  templateUrl: './workbench-tabs.component.html',
+  styleUrl: './workbench-tabs.component.less',
+})
+export class WorkbenchTabsComponent {
+  @ViewChild('tabsRef') tabsRef!: ElementRef<HTMLDivElement>;
+  @Input() tabs: Option[] = [];
+  @Input() activeTab: string | number = '';
+  @Output() activeTabChange = new EventEmitter<string | number>();
+
+  activeWidth = '0px'; // 48px
+  activeOffsetLeft = '0px';
+
+  ngAfterViewInit() {
+    this.handleChange(this.tabs.find(tab => tab.value === this.activeTab)!);
+  }
+  handleChange(tab: Option) {
+    this.activeTab = tab.value;
+    this.activeTabChange.emit(tab.value);
+    this.activeOffsetLeft = this.getActiveOffsetLeft() + 'px';
+    this.activeWidth = this.getActiveWidth() + 'px';
+  }
+
+  findActiveTab() {
+    const activeTab = this.tabsRef.nativeElement.querySelector(`[data-value="${this.activeTab}"]`) as HTMLSpanElement;
+    return activeTab;
+  }
+  getActiveWidth() {
+    const activeTab = this.findActiveTab();
+    if (!activeTab) return 0;
+    const width = activeTab.offsetWidth;
+    if (width < 60) return 48;
+    return width;
+  }
+  getActiveOffsetLeft() {
+    const activeTab = this.findActiveTab();
+    if (!activeTab) return 0;
+    const offset = activeTab.offsetLeft;
+    const width = activeTab.offsetWidth;
+    if (width < 60) return offset + 6;
+    return offset;
+  }
+}

Разница между файлами не показана из-за своего большого размера
+ 14 - 0
src/assets/icons/emoji-sad.svg


+ 6 - 0
src/styles/workbench.less

@@ -5,7 +5,13 @@
 
   .workbench-panel-header {
     height: 54px;
+    margin: 0 24px;
     border-bottom: 1px solid #f3f3f3;
+    .header-title {
+      font-size: 20px;
+      font-weight: 500;
+      line-height: 53px;
+    }
     .header-button-group {
       display: flex;
       align-items: center;

Некоторые файлы не были показаны из-за большого количества измененных файлов