Selaa lähdekoodia

feat: plan overview

Signed-off-by: carlos <568187512@qq.com>
carlos 2 kuukautta sitten
vanhempi
commit
1a662a5bf7

+ 1 - 1
src/app/classes/risk-item.model.ts

@@ -28,7 +28,7 @@ export class RiskItemModel {
     return this.basic.getEquipmentName(equipmentId);
   }
   getJobName(jobId: number) {
-    return this.basic.getJobName(jobId);
+    return this.basic.getJobNameWithDepartment(jobId);
   }
   getConsequenceName(consequenceId: number) {
     return this.basic.getConsequenceName(consequenceId);

+ 79 - 1
src/app/pages/manager/hazard/inspection-plan/inspection-plan.component.html

@@ -1 +1,79 @@
-<p>inspection-plan works!</p>
+<div class="flex justify-between">
+  <div class="headline pl-2">排查计划</div>
+  <div class="pr-4">
+    <button nz-button class="precaution-secondary" nzType="primary" style="border-radius: 12px">创建计划</button>
+  </div>
+</div>
+<div class="flex">
+  <div class="overview-panel flex-1 flex h-[140px]">
+    <div class="w-[36%] r-border px-[3%]">
+      <div class="semi-annual-switch">
+        <div class="arrow" (click)="handleSemiAnnualChange('left')">
+          @if (leftArrowVisible) {
+            <span nz-icon nzType="icons:material-left-arrow" class="text-2xl cursor-pointer hover:text-primary"></span>
+          }
+        </div>
+        <div class="text">
+          {{ semiAnnualText }}
+        </div>
+        <div class="arrow" (click)="handleSemiAnnualChange('right')">
+          @if (rightArrowVisible) {
+            <span nz-icon nzType="icons:material-right-arrow" class="text-2xl cursor-pointer hover:text-primary"></span>
+          }
+        </div>
+      </div>
+      <div class="pb-2">
+        @for (row of firstSection; track $index) {
+          <div class="plan-type" [class.active]="currentPlanKey === row.key" (click)="handlePlanTypeChange(row.key)">
+            <span class="section-label">{{ row.label }}:</span>
+            <span class="unit-number">{{ row.value + '起' }}</span>
+          </div>
+        }
+      </div>
+    </div>
+    <div class="w-[64%] flex">
+      <div class="flex flex-col justify-between py-4 w-1/2 pl-[6%] pr-[4%]">
+        @for (row of secondSection; track $index) {
+          <div class="flex items-center h-1/3 justify-between">
+            <span class="section-label">{{ row.label }}:</span>
+            <span class="link-number text-left flex-1 pl-[20%]">{{ row.value + '%' }}</span>
+          </div>
+        }
+      </div>
+      <div class="flex flex-col py-6 w-1/2 pr-[8%] pl-[2%]">
+        <div class="flex justify-between">
+          <div class="section-label">排查中发现隐患:</div>
+          <div class="flex justify-between text-[#333333] font-medium w-[40%]">
+            <div>
+              <div class="pb-4">重大:{{ discovery.significant }}</div>
+              <div>A类:{{ discovery.a }}</div>
+            </div>
+            <div>
+              <div class="pb-4">B类:{{ discovery.b }}</div>
+              <div>C类:{{ discovery.c }}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div class="overview-panel ml-5 w-[30%] pt-[13px]">
+    <div class="notifications">
+      @for (item of notifications; track $index) {
+        <div class="notification-item">
+          <div class="type">
+            <div
+              class="type-tag"
+              [style.color]="getNotificationTypeColor(item.type)"
+              [style.background-color]="getNotificationTypeBgColor(item.type)"
+            >
+              {{ getNotificationTypeText(item.type) }}
+            </div>
+          </div>
+          <div class="title">{{ item.title }}</div>
+          <div class="time pl-4">{{ item.createDate }}</div>
+        </div>
+      }
+    </div>
+  </div>
+</div>

+ 113 - 0
src/app/pages/manager/hazard/inspection-plan/inspection-plan.component.less

@@ -0,0 +1,113 @@
+.headline {
+  font-weight: 500;
+  font-size: 20px;
+  color: #000000;
+  line-height: 28px;
+}
+
+.overview-panel {
+  background: #ffffff;
+  box-shadow:
+    0px 0px 8px 0px rgba(223, 223, 223, 0.5),
+    0px 3px 4px 0px rgba(0, 0, 0, 0.04);
+  border-radius: 20px;
+}
+.r-border {
+  position: relative;
+  &:after {
+    content: '';
+    position: absolute;
+    display: block;
+    height: calc(100% - 32px);
+    right: 0;
+    top: 16px;
+    width: 1px;
+    background-color: rgba(151, 151, 151, 0.2);
+  }
+}
+
+.section-label {
+  font-weight: 400;
+  font-size: 14px;
+  color: #666666;
+}
+.link-number {
+  font-weight: 500;
+  font-size: 14px;
+  color: var(--deep-blue);
+  text-decoration: underline;
+}
+.unit-number {
+  font-weight: 500;
+  font-size: 14px;
+  color: #333333;
+  text-decoration: underline;
+}
+
+.plan-type {
+  display: flex;
+  justify-content: space-between;
+  border-radius: 8px;
+  background: transparent;
+  line-height: 32px;
+  padding: 0 8px;
+  transition: all 0.3s;
+  cursor: pointer;
+  &:hover {
+    background: rgba(41, 83, 232, 0.08);
+  }
+  &.active {
+    background: rgba(41, 83, 232, 0.16);
+  }
+}
+
+.semi-annual-switch {
+  display: flex;
+  padding: 10px 10% 0;
+  font-weight: 500;
+  font-size: 16px;
+  color: #333333;
+  line-height: 22px;
+  user-select: none;
+  .text {
+    flex: 1;
+    text-align: center;
+    user-select: none;
+  }
+  .arrow {
+    min-width: 24px;
+    user-select: none;
+  }
+}
+.notifications {
+  padding: 0 24px;
+}
+.notification-item {
+  text-wrap: nowrap;
+  display: flex;
+  flex-wrap: nowrap;
+  align-items: center;
+  margin-bottom: 8px;
+  &:last-child {
+    margin-bottom: 0;
+  }
+
+  .type-tag {
+    padding: 0 8px;
+    border-radius: 2px;
+    font-size: 14px;
+    line-height: 22px;
+  }
+
+  .title {
+    color: #4e5969;
+    font-size: 14px;
+    padding: 0 12px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+  .time {
+    font-size: 12px;
+    color: #999999;
+  }
+}

+ 99 - 2
src/app/pages/manager/hazard/inspection-plan/inspection-plan.component.ts

@@ -1,12 +1,109 @@
 import { Component } from '@angular/core';
+import { CommonNzModule } from '../../../../common.nz.module';
+import { PlanCardComponent } from './plan-card/plan-card.component';
+
+const notificationConfig = [
+  { type: 1, text: '通知', color: '#165DFF', backgroundColor: '#E8F3FF' },
+  { type: 2, text: '消息', color: '#0FC6C2', backgroundColor: '#E8FFFB' },
+];
 
 @Component({
   selector: 'app-inspection-plan',
   standalone: true,
-  imports: [],
+  imports: [CommonNzModule, PlanCardComponent],
   templateUrl: './inspection-plan.component.html',
-  styleUrl: './inspection-plan.component.less'
+  styleUrl: './inspection-plan.component.less',
 })
 export class InspectionPlanComponent {
+  semiAnnualOptions = [
+    { label: '2023上半年度', value: 202301 },
+    { label: '2023下半年度', value: 202306 },
+    { label: '2024上半年度', value: 202401 },
+    { label: '2024下半年度', value: 202406 },
+    { label: '2025上半年度', value: 202501 },
+  ];
+  firstSection = [
+    { label: '日常隐患排查计划', value: 3, key: 'normal' },
+    { label: '专项隐患排查计划', value: 6, key: 'special' },
+    { label: '其他隐患排查计划', value: 9, key: 'other' },
+  ];
+  secondSection = [
+    { label: '计划站点排查覆盖', value: 65 },
+    { label: '其他场所排查覆盖', value: 100 },
+    { label: '计划人员排查占比', value: 95 },
+  ];
+  discovery = {
+    significant: 1,
+    a: 2,
+    b: 3,
+    c: 5,
+  };
+  notifications = [
+    {
+      type: 1,
+      title:
+        '当前产品试用期即将截止,在这段宝贵的试用时光里,您想必已经充分领略到了它的独特魅力与卓越性能。它犹如一位得力助手,在您的工作与生活中默默耕耘,助力您轻松跨越一道道难关,高效完成各项任务。无论是处理复杂的文档、分析海量的数据,还是畅享流畅的娱乐体验,它都表现得游刃有余',
+      createDate: '2024/6/16',
+    },
+    {
+      type: 1,
+      title:
+        '1月新系统升级计划通知,为了给您提供更优质、高效、稳定的服务体验,提升系统性能与功能,我们计划于 1 月进行系统升级。现将相关事宜详细通知如下:',
+      createDate: '2024/10/16',
+    },
+    {
+      type: 2,
+      title:
+        '新增内容已经通过审核,详情如下:此次审核涵盖了文本、图像、音频等多方面元素,确保了新增内容在质量与合规性上均达到了既定标准。文本部分,语言表达流畅自然,逻辑严谨清晰,无论是专业术语的运用还是普通语句的叙述,都经过了仔细的斟酌与校对,避免了歧义与错误信息的出现。',
+      createDate: '2024/11/4',
+    },
+    {
+      type: 1,
+      title:
+        '1月新系统升级计划通知为了给您提供更优质、高效、稳定的服务体验,提升系统性能与功能,我们计划于 1 月进行系统升级。现将相关事宜详细通知如下:',
+      createDate: '2024/11/16',
+    },
+  ];
+  currentPlanKey = 'special';
+  semiAnnualKey = 202406;
+  handlePlanTypeChange(type: string) {
+    this.currentPlanKey = type;
+  }
+  handleSemiAnnualChange(direction: 'left' | 'right') {
+    const index = this.currentSemiAnnualOptionIndex;
+    if (direction === 'left') {
+      if (!this.leftArrowVisible) return;
+    }
+    if (direction === 'right') {
+      if (!this.rightArrowVisible) return;
+    }
+    this.semiAnnualKey = this.semiAnnualOptions[index + (direction === 'left' ? -1 : 1)]?.value || 0;
+  }
+
+  getNotificationTypeText(t: number) {
+    return notificationConfig.find(n => n.type === t)?.text || '未知';
+  }
+  getNotificationTypeColor(t: number) {
+    return notificationConfig.find(n => n.type === t)?.color || '';
+  }
+  getNotificationTypeBgColor(t: number) {
+    return notificationConfig.find(n => n.type === t)?.backgroundColor || '';
+  }
 
+  get currentSemiAnnualOptionIndex() {
+    return this.semiAnnualOptions.findIndex(se => se.value === this.semiAnnualKey);
+  }
+  get semiAnnualText() {
+    return this.semiAnnualOptions.find(se => se.value === this.semiAnnualKey)?.label || '';
+  }
+  get leftArrowVisible() {
+    const index = this.currentSemiAnnualOptionIndex;
+    if (index === -1) return false;
+    return !!this.semiAnnualOptions[index - 1];
+  }
+  get rightArrowVisible() {
+    const index = this.currentSemiAnnualOptionIndex;
+    if (index === -1) return false;
+    return !!this.semiAnnualOptions[index + 1];
+  }
 }

+ 1 - 0
src/app/pages/manager/hazard/inspection-plan/plan-card/plan-card.component.html

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

+ 0 - 0
src/app/pages/manager/hazard/inspection-plan/plan-card/plan-card.component.less


+ 23 - 0
src/app/pages/manager/hazard/inspection-plan/plan-card/plan-card.component.spec.ts

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

+ 12 - 0
src/app/pages/manager/hazard/inspection-plan/plan-card/plan-card.component.ts

@@ -0,0 +1,12 @@
+import { Component, Input } from '@angular/core';
+
+@Component({
+  selector: 'plan-card',
+  standalone: true,
+  imports: [],
+  templateUrl: './plan-card.component.html',
+  styleUrl: './plan-card.component.less',
+})
+export class PlanCardComponent {
+  @Input() data!: Hazard.Plan;
+}

+ 6 - 2
src/app/pages/manager/layout/header/header.component.html

@@ -47,11 +47,15 @@
     </div>
     <div>
       <span class="text-[#8f8f8f]">责任部门:</span>
-      <span class="text-[#333333]">{{ user?.department }}</span>
+      <span class="text-[#333333]">{{ departmentText }}</span>
     </div>
     <div>
       <span class="text-[#8f8f8f]">责任场所:</span>
-      <span class="text-[#333333]">{{ user?.line }} - {{ user?.job }}</span>
+      <span class="text-[#333333]">{{ user?.line }}</span>
+    </div>
+    <div>
+      <span class="text-[#8f8f8f]">责任岗位:</span>
+      <span class="text-[#333333]"> {{ jobText }}</span>
     </div>
   </div>
 </div>

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

@@ -86,4 +86,14 @@ export class HeaderComponent {
       .filter(Boolean)
       .join(',');
   }
+  get departmentText() {
+    const id = this.user?.department;
+    if (!id) return '';
+    return this.basic.getDepartmentName(id);
+  }
+  get jobText() {
+    const id = this.user?.job;
+    if (!id) return '';
+    return this.basic.getJobName(id);
+  }
 }

+ 6 - 4
src/app/pages/manager/layout/sidebar/navigation/navigation.component.ts

@@ -65,10 +65,12 @@ export class NavigationComponent {
       const secondaryRoute = this.findSecondaryRoute();
       if (secondaryRoute) {
         this.paths =
-          secondaryRoute.children?.map(child => ({
-            ...child,
-            path: `/manager/${secondaryRoute?.path}/${child.path}`,
-          })) || [];
+          secondaryRoute.children
+            ?.map(child => ({
+              ...child,
+              path: `/manager/${secondaryRoute?.path}/${child.path}`,
+            }))
+            .filter(p => !p.redirectTo) || [];
       }
     });
   }

+ 1 - 1
src/app/pages/manager/risk/risk-bank/risk-item-form/risk-item-form.component.ts

@@ -126,7 +126,7 @@ export class RiskItemFormComponent {
     this.options.job = this.basic.jobs
       .filter(j => this.currentCategory!.departments.includes(j.department))
       .map(item => ({
-        label: this.basic.getJobName(item.id),
+        label: this.basic.getJobNameWithDepartment(item.id),
         value: item.id,
       }));
   }

+ 1 - 1
src/app/pages/manager/user/user-table/user-table.component.ts

@@ -47,7 +47,7 @@ export class UserTableComponent {
     }
   }
   getJobName(jobId: number) {
-    return this.basic.jobs.find(j => j.id === jobId)?.name;
+    return this.basic.getJobName(jobId);
   }
   getDepartmentName(departmentId: number) {
     return this.basic.departments.find(d => d.id === departmentId)?.name;

+ 6 - 1
src/app/services/basic.service.ts

@@ -103,7 +103,7 @@ export class BasicDataService {
   getDepartmentName(departmentId: number) {
     return this.departments.find(eq => eq.id === departmentId)?.name;
   }
-  getJobName(jobId: number) {
+  getJobNameWithDepartment(jobId: number) {
     const jobDto = this.jobs.find(job => job.id === jobId);
     if (!jobDto) return '';
     const departmentId = jobDto?.department;
@@ -111,6 +111,11 @@ export class BasicDataService {
     if (!departmentName) return jobDto.name;
     return `${jobDto.name}(${departmentName})`;
   }
+  getJobName(jobId: number) {
+    const jobDto = this.jobs.find(job => job.id === jobId);
+    if (!jobDto) return '';
+    return jobDto.name || '';
+  }
   getConsequenceName(consequenceId: number) {
     return this.consequences.find(con => con.id === consequenceId)?.value;
   }

+ 1 - 0
src/assets/icons/material-left-arrow.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="M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6l6 6l1.41-1.41z" fill="currentColor"></path></svg>

+ 1 - 0
src/assets/icons/material-right-arrow.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="M8.59 16.59L13.17 12L8.59 7.41L10 6l6 6l-6 6l-1.41-1.41z" fill="currentColor"></path></svg>

+ 11 - 0
src/types/hazard.d.ts

@@ -13,4 +13,15 @@ declare namespace Hazard {
     responsibleDepartments: number[];
     images: string[];
   }
+
+  interface Plan {
+    id: number;
+    type: number;
+    state: number;
+    name: string;
+    startDate: string;
+    endDate: string;
+    createDate: string;
+    department: number;
+  }
 }