Kaynağa Gözat

概览弹窗调整

肖栋 1 hafta önce
ebeveyn
işleme
13aeeaf7ce
1 değiştirilmiş dosya ile 661 ekleme ve 50 silme
  1. 661 50
      src/pages/home/overview.vue

+ 661 - 50
src/pages/home/overview.vue

@@ -24,7 +24,12 @@
               <div class="page-overview-card-metric">
                 <span
                   class="page-overview-card-num"
+                  :class="{
+                    'page-overview-card-num--clickable':
+                      card.key === 'warning' || card.key === 'delay',
+                  }"
                   :style="{ color: card.color }"
+                  @click="onSummaryNumClick(card)"
                 >{{ card.value }}</span>
                 <span v-if="card.unit" class="page-overview-card-unit">{{
                   card.unit
@@ -35,10 +40,15 @@
               </div>
             </div>
           </div>
-          <icon-svg
-            :name="card.icon"
-            size="32px"
-          />
+          <span
+            class="page-overview-card-icon"
+            :class="{ 'page-overview-card-icon--shake': card.key === 'delay' }"
+          >
+            <icon-svg
+              :name="card.icon"
+              size="32px"
+            />
+          </span>
         </div>
 
         <div v-if="card.phases?.length" class="page-overview-card-bottom">
@@ -102,9 +112,9 @@
             <tr>
               <th class="col-name">项目名称</th>
               <th class="col-progress">整体进度</th>
-              <th class="col-phase">可行性研究</th>
-              <th class="col-phase">基本设计</th>
-              <th class="col-phase">详细设计/建造安装</th>
+              <th class="col-phase col-phase--feasibility">可行性研究</th>
+              <th class="col-phase col-phase--basic">基本设计</th>
+              <th class="col-phase col-phase--detail">详细设计/建造安装</th>
               <th class="col-status">预警</th>
               <th class="col-status">滞后</th>
             </tr>
@@ -140,7 +150,7 @@
                         :key="`plan-${idx}`"
                         class="page-overview-table-gantt-seg"
                         :class="`is-${seg.type}`"
-                        :style="{ flex: seg.flex }"
+                        :style="{ flex: `${seg.flex} 1 0%` }"
                       >
                         <span v-if="seg.label">{{ seg.label }}</span>
                       </div>
@@ -148,15 +158,75 @@
                   </div>
                   <div class="page-overview-table-gantt-row is-actual">
                     <template v-if="item.progress > 0">
-                      <div
+                      <template
                         v-for="(seg, idx) in item.timeline.actual"
                         :key="`actual-${idx}`"
-                        class="page-overview-table-gantt-seg"
-                        :class="`is-${seg.type}`"
-                        :style="{ flex: seg.flex }"
                       >
-                        <span v-if="seg.label">{{ seg.label }}</span>
-                      </div>
+                        <div
+                          v-if="seg.popover"
+                          class="page-overview-table-gantt-seg-host"
+                          :style="{ flex: `${seg.flex} 1 0%` }"
+                        >
+                          <el-popover
+                            placement="bottom"
+                            trigger="hover"
+                            :show-arrow="true"
+                            :width="200"
+                            :offset="6"
+                            popper-class="page-overview-gantt-popper"
+                          >
+                            <template #reference>
+                              <div
+                                class="page-overview-table-gantt-seg"
+                                :class="`is-${seg.type}`"
+                                tabindex="0"
+                              >
+                                <span v-if="seg.label">{{ seg.label }}</span>
+                              </div>
+                            </template>
+                            <div class="page-overview-gantt-tip">
+                              <div class="page-overview-gantt-tip-name">
+                                {{ seg.popover.name }}
+                              </div>
+                              <div class="page-overview-gantt-tip-line">
+                                计划时间:{{ seg.popover.planTime }}
+                              </div>
+                              <div
+                                v-if="seg.popover.remainDays != null"
+                                class="page-overview-gantt-tip-line"
+                              >
+                                距完成天数:<span
+                                  class="page-overview-gantt-tip-days is-warning"
+                                >{{ seg.popover.remainDays }}</span
+                                >天
+                              </div>
+                              <div
+                                v-else-if="seg.popover.overdueDays != null"
+                                class="page-overview-gantt-tip-line"
+                              >
+                                已逾期天数:<span
+                                  class="page-overview-gantt-tip-days is-delay"
+                                >{{ seg.popover.overdueDays }}</span
+                                >天
+                              </div>
+                              <div
+                                v-else-if="seg.popover.completeTime"
+                                class="page-overview-gantt-tip-line"
+                              >
+                                完成时间:{{ seg.popover.completeTime }}
+                              </div>
+                            </div>
+                          </el-popover>
+                        </div>
+                        <div
+                          v-else
+                          class="page-overview-table-gantt-seg"
+                          :class="`is-${seg.type}`"
+                          :style="{ flex: `${seg.flex} 1 0%` }"
+                        >
+                          <span v-if="seg.label">{{ seg.label }}</span>
+                        </div>
+                      </template>
                     </template>
                   </div>
                 </div>
@@ -181,6 +251,85 @@
       </div>
     </div>
   </div>
+
+  <el-dialog
+    v-model="reportDialogVisible"
+    width="720px"
+    append-to-body
+    align-center
+    class="page-overview-report-dialog"
+  >
+    <template #header>
+      <div class="page-overview-report-dialog-header">
+        <icon-svg
+          :name="
+            reportDialogType === 'warning'
+              ? 'early_warning_report'
+              : 'late_report'
+          "
+          size="20px"
+        />
+        <span class="page-overview-report-dialog-title">
+          {{ reportDialogType === "warning" ? "预警报告" : "滞后报告" }}
+          <span
+            class="page-overview-report-dialog-count"
+            :class="`is-${reportDialogType}`"
+            >({{ reportDialogList.length }})</span
+          >
+        </span>
+      </div>
+    </template>
+
+    <div class="page-overview-report-dialog-body">
+      <div
+        v-for="item in reportDialogList"
+        :key="item.id"
+        class="page-overview-report-item"
+      >
+        <div class="page-overview-report-item-name">{{ item.projectName }}</div>
+        <div class="page-overview-report-item-grid">
+          <div class="page-overview-report-field is-full">
+            <span class="label">报告名称:</span>
+            <span class="value">{{ item.reportName }}</span>
+          </div>
+          <div class="page-overview-report-field">
+            <span class="label">开始时间:</span>
+            <span class="value">{{ item.startTime }}</span>
+          </div>
+          <div class="page-overview-report-field">
+            <span class="label">工期:</span>
+            <span class="value">{{ item.duration }}</span>
+          </div>
+          <div class="page-overview-report-field">
+            <span class="label">计划完成时间:</span>
+            <span class="value">{{ item.planEndTime }}</span>
+          </div>
+          <div class="page-overview-report-field">
+            <span class="label">{{
+              reportDialogType === "warning" ? "距完成天数:" : "已逾期天数:"
+            }}</span>
+            <span class="value" :class="`is-${reportDialogType}`"
+              >{{ item.days }} 天</span
+            >
+          </div>
+          <div class="page-overview-report-field">
+            <span class="label">报告编制单位:</span>
+            <span class="value">{{ item.unit }}</span>
+          </div>
+          <div class="page-overview-report-field">
+            <span class="label">负责人:</span>
+            <span class="value">{{ item.owner }}</span>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <template #footer>
+      <div class="page-overview-report-dialog-footer">
+        <el-button @click="reportDialogVisible = false">关闭</el-button>
+      </div>
+    </template>
+  </el-dialog>
 </template>
 
 <script setup>
@@ -205,6 +354,57 @@ const listFilterOptions = [
   { label: "滞后", value: "delay" },
 ];
 
+const reportDialogVisible = ref(false);
+const reportDialogType = ref("warning");
+const reportDialogList = ref([]);
+
+const warningReportList = [
+  {
+    id: 1,
+    projectName: "垦利10-1油田开发项目",
+    reportName: "可行性研究-环境质量现状调查",
+    startTime: "2026-06-05",
+    duration: "60天",
+    planEndTime: "2026-08-05",
+    days: 2,
+    unit: "樟树市邦厉有限公司",
+    owner: "苏小苏",
+  },
+  {
+    id: 2,
+    projectName: "渤中19-6凝析气田开发项目",
+    reportName: "详细设计/建造安装-通航安全影响咨询",
+    startTime: "2026-05-01",
+    duration: "90天",
+    planEndTime: "2026-08-20",
+    days: 22,
+    unit: "天津海蓝工程咨询有限公司",
+    owner: "李明",
+  },
+];
+
+const delayReportList = [
+  {
+    id: 1,
+    projectName: "锦州25-1油田开发项目",
+    reportName: "可行性研究-通航安全影响咨询",
+    startTime: "2026-05-01",
+    duration: "90天",
+    planEndTime: "2026-07-30",
+    days: 3,
+    unit: "天津海蓝工程咨询有限公司",
+    owner: "王强",
+  },
+];
+
+const onSummaryNumClick = (card) => {
+  if (card.key !== "warning" && card.key !== "delay") return;
+  reportDialogType.value = card.key;
+  reportDialogList.value =
+    card.key === "warning" ? warningReportList : delayReportList;
+  reportDialogVisible.value = true;
+};
+
 const summaryCards = [
   {
     key: "total",
@@ -263,28 +463,139 @@ const summaryCards = [
   },
 ];
 
+const createNormalPopover = (name, planTime = "2026-07-30") => ({
+  name,
+  planTime,
+  completeTime: planTime,
+});
+
 const projectList = [
   {
     id: 1,
     name: "垦利10-1油田开发项目",
-    progress: 0,
-    timeline: { plan: [], actual: [] },
-    warning: 0,
+    progress: 20,
+    timeline: {
+      plan: [
+        { type: "plan", flex: 1 },
+        { type: "empty", flex: 13 },
+      ],
+      actual: [
+        {
+          type: "warning",
+          flex: 1,
+          label: "环境质量现状调查",
+          popover: {
+            name: "环境质量现状调查",
+            planTime: "2026-08-05",
+            remainDays: 2,
+          },
+        },
+        { type: "empty", flex: 13 },
+      ],
+    },
+    warning: 1,
     delay: 0,
   },
   {
     id: 2,
     name: "锦州25-1油田开发项目",
-    progress: 0,
-    timeline: { plan: [], actual: [] },
+    progress: 43,
+    timeline: {
+      plan: [
+        { type: "done", flex: 5 },
+        { type: "plan", flex: 1 },
+        { type: "empty", flex: 8 },
+      ],
+      actual: [
+        {
+          type: "actual",
+          flex: 1,
+          label: "环境质量现状调查",
+          popover: createNormalPopover("环境质量现状调查"),
+        },
+        {
+          type: "actual",
+          flex: 1,
+          label: "渔业资源现状调查",
+          popover: createNormalPopover("渔业资源现状调查"),
+        },
+        {
+          type: "actual",
+          flex: 1,
+          label: "环境风险预评价",
+          popover: createNormalPopover("环境风险预评价"),
+        },
+        {
+          type: "actual",
+          flex: 1,
+          label: "桌面路由方案",
+          popover: createNormalPopover("桌面路由方案"),
+        },
+        {
+          type: "delay",
+          flex: 1,
+          label: "通航安全影响咨询",
+          popover: {
+            name: "通航安全影响咨询",
+            planTime: "2026-07-30",
+            overdueDays: 3,
+          },
+        },
+        {
+          type: "actual",
+          flex: 1,
+          label: "安全预评价",
+          popover: createNormalPopover("安全预评价"),
+        },
+        { type: "empty", flex: 8 },
+      ],
+    },
     warning: 0,
-    delay: 0,
+    delay: 1,
   },
   {
     id: 3,
     name: "渤中19-6凝析气田开发项目",
-    progress: 0,
-    timeline: { plan: [], actual: [] },
+    progress: 100,
+    timeline: {
+      plan: [
+        { type: "done", flex: 1 },
+        { type: "done", flex: 1 },
+        { type: "done", flex: 1 },
+        { type: "done", flex: 1 },
+        { type: "done", flex: 1 },
+        { type: "done", flex: 1 },
+        { type: "done", flex: 1 },
+        { type: "done", flex: 1 },
+        { type: "done", flex: 1 },
+        { type: "done", flex: 1 },
+        { type: "done", flex: 1 },
+        { type: "done", flex: 1 },
+        { type: "done", flex: 1 },
+        { type: "done", flex: 1 },
+      ],
+      actual: [
+        "环境质量现状调查",
+        "环境影响评价",
+        "可行性研究报告",
+        "项目建议书",
+        "核准批复",
+        "总图设计",
+        "工艺设计",
+        "结构设计",
+        "公用工程设计",
+        "基本设计审查",
+        "详细设计",
+        "设备采购",
+        "建造安装",
+        "竣工验收",
+      ].map((label) => ({
+        type: "actual",
+        flex: 1,
+        label,
+        popover: createNormalPopover(label),
+      })),
+    },
     warning: 0,
     delay: 0,
   },
@@ -356,9 +667,100 @@ const projectList = [
 }
 
 .page-overview-card--delay {
+  position: relative;
+  overflow: hidden;
+  isolation: isolate;
   background: #ffffff;
-  border-radius: 4px 4px 4px 4px;
-  border: 1px solid #f54848;
+  border-radius: 4px;
+  border: 1px solid rgba(245, 72, 72, 0.35);
+
+  &::before {
+    content: "";
+    position: absolute;
+    z-index: 0;
+    top: 50%;
+    left: 50%;
+    width: 220%;
+    height: 220%;
+    translate: -50% -50%;
+    background: conic-gradient(
+      from 0deg,
+      transparent 0deg,
+      transparent 300deg,
+      rgba(245, 72, 72, 0.08) 325deg,
+      rgba(245, 72, 72, 0.28) 342deg,
+      #f54848 352deg,
+      #ff8a8a 356deg,
+      transparent 360deg
+    );
+    animation: page-overview-delay-beam 3.5s linear infinite;
+    pointer-events: none;
+  }
+
+  &::after {
+    content: "";
+    position: absolute;
+    inset: 1px;
+    z-index: 1;
+    border-radius: 3px;
+    background: rgba(255, 255, 255, 0.92);
+    pointer-events: none;
+  }
+
+  > * {
+    position: relative;
+    z-index: 2;
+  }
+}
+
+.page-overview-card-icon {
+  display: inline-flex;
+  flex-shrink: 0;
+  line-height: 0;
+}
+
+.page-overview-card-icon--shake {
+  animation: page-overview-delay-shake 3.2s ease-in-out infinite;
+  will-change: transform;
+  transform-origin: 50% 50%;
+}
+
+@keyframes page-overview-delay-beam {
+  to {
+    transform: rotate(1turn);
+  }
+}
+
+@keyframes page-overview-delay-shake {
+  0%,
+  72%,
+  100% {
+    transform: rotate(0deg);
+  }
+
+  75% {
+    transform: rotate(-6deg);
+  }
+
+  78% {
+    transform: rotate(6deg);
+  }
+
+  81% {
+    transform: rotate(-4deg);
+  }
+
+  84% {
+    transform: rotate(4deg);
+  }
+
+  87% {
+    transform: rotate(-2deg);
+  }
+
+  90% {
+    transform: rotate(0deg);
+  }
 }
 
 .page-overview-card-top {
@@ -414,6 +816,10 @@ const projectList = [
   text-align: left;
 }
 
+.page-overview-card-num--clickable {
+  cursor: pointer;
+}
+
 .page-overview-card-unit {
   font-family: PingFang SC, PingFang SC;
   font-weight: 400;
@@ -648,6 +1054,8 @@ const projectList = [
     border-right: 1px solid #dfe3e7;
     border-bottom: 1px solid #dfe3e7;
     border-left: none;
+    padding-top: 16px;
+    padding-bottom: 16px;
   }
 
   td:first-child {
@@ -655,24 +1063,32 @@ const projectList = [
   }
 
   .col-name {
-    width: 18%;
+    width: 14%;
   }
 
   .col-progress {
-    width: 14%;
+    width: 10%;
+  }
+
+  .col-phase--feasibility {
+    width: calc(60% * 5 / 14);
+  }
+
+  .col-phase--basic {
+    width: calc(60% * 5 / 14);
   }
 
-  .col-phase {
-    width: 16%;
+  .col-phase--detail {
+    width: calc(60% * 4 / 14);
   }
 
   .col-status {
-    width: 7%;
+    width: 5%;
   }
 
   td.col-phase {
     position: relative;
-    padding: 8px 12px;
+    padding: 16px 0;
     background: #ffffff;
   }
 
@@ -700,7 +1116,7 @@ const projectList = [
 .page-overview-table-progress {
   display: flex;
   align-items: center;
-  gap: 8px;
+  gap: 4px;
   min-width: 0;
 }
 
@@ -710,13 +1126,13 @@ const projectList = [
   height: 8px;
   overflow: hidden;
   background: #edf2f8;
-  border-radius: 4px;
+  border-radius: 0;
 }
 
 .page-overview-table-progress-bar {
   height: 100%;
   background: #2382e7;
-  border-radius: 4px;
+  border-radius: 0;
 }
 
 .page-overview-table-progress-text {
@@ -737,30 +1153,53 @@ const projectList = [
 .page-overview-table-gantt-row {
   position: relative;
   display: flex;
-  gap: 2px;
+  gap: 0;
   width: 100%;
   height: 16px;
   min-width: 0;
-  background: #edf1f6;
+  box-sizing: border-box;
+  padding: 0 16px;
+  background-color: #edf1f6;
+  background-clip: content-box;
 
   &::before,
   &::after {
     position: absolute;
     top: 0;
     bottom: 0;
-    z-index: 0;
-    width: 0;
-    border-left: 1px solid #dfe3e7;
+    z-index: 2;
+    width: 1px;
+    background: #dfe3e7;
     content: "";
     pointer-events: none;
   }
 
+  /* 与内容区 5 / 5 / 4 分块对齐,分割线 #DFE3E7 */
   &::before {
-    left: 33.333%;
+    left: calc(16px + (100% - 32px) * 5 / 14);
   }
 
   &::after {
-    left: 66.666%;
+    left: calc(16px + (100% - 32px) * 10 / 14);
+  }
+}
+
+.page-overview-table-gantt-seg-host {
+  z-index: 1;
+  display: flex;
+  min-width: 0;
+  height: 100%;
+
+  .page-overview-table-gantt-seg {
+    flex: 1;
+    width: 100%;
+  }
+
+  :deep(.el-tooltip__trigger) {
+    display: flex;
+    width: 100%;
+    min-width: 0;
+    height: 100%;
   }
 }
 
@@ -773,16 +1212,18 @@ const projectList = [
   min-width: 0;
   height: 100%;
   overflow: hidden;
-  border-radius: 2px;
+  border-radius: 0;
   background: transparent;
 
   > span {
     overflow: hidden;
     padding: 0 6px;
-    color: #ffffff;
     font-family: PingFang SC, PingFang SC;
-    font-size: 12px;
-    line-height: 16px;
+    font-weight: 600;
+    font-size: 7px;
+    color: #ffffff;
+    line-height: 10px;
+    text-align: left;
     text-overflow: ellipsis;
     white-space: nowrap;
   }
@@ -817,24 +1258,194 @@ const projectList = [
   align-items: center;
   justify-content: center;
   min-width: 24px;
-  height: 24px;
+  height: 20px;
   padding: 0 8px;
-  border-radius: 4px;
-  font-size: 13px;
+  border-radius: 1px;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 600;
+  font-size: 14px;
+  line-height: 20px;
+  text-align: left;
 
   &.is-zero {
     color: #9aa5b8;
-    background: #f3f5f8;
+    background: #edf1f6;
   }
 
   &.is-warning {
     color: #ff8e38;
-    background: #fff4eb;
+    background: #fff8f3;
   }
 
   &.is-delay {
     color: #f54848;
-    background: #feeeee;
+    background: #fef4f4;
   }
 }
 </style>
+
+<style lang="scss">
+.page-overview-gantt-popper.el-popover.el-popper {
+  padding: 12px 14px;
+  border: 1px solid #e5ebf3;
+  border-radius: 4px;
+  box-shadow: 0 4px 12px rgba(22, 33, 58, 0.08);
+}
+
+.page-overview-gantt-tip {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+  font-family: PingFang SC, PingFang SC;
+  color: #16213a;
+}
+
+.page-overview-gantt-tip-name {
+  font-weight: 600;
+  font-size: 14px;
+  line-height: 20px;
+}
+
+.page-overview-gantt-tip-line {
+  font-weight: 400;
+  font-size: 13px;
+  line-height: 18px;
+  color: #58657c;
+}
+
+.page-overview-gantt-tip-days {
+  font-weight: 600;
+
+  &.is-warning {
+    color: #ff8e38;
+  }
+
+  &.is-delay {
+    color: #f54848;
+  }
+}
+
+.page-overview-report-dialog.el-dialog {
+  border-radius: 4px;
+
+  .el-dialog__header {
+    margin: 0;
+    padding: 16px 20px;
+    border-bottom: 1px solid #e5ebf3;
+  }
+
+  .el-dialog__body {
+    padding: 16px 20px;
+  }
+
+  .el-dialog__footer {
+    padding: 12px 20px 20px;
+  }
+}
+
+.page-overview-report-dialog-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.page-overview-report-dialog-title {
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 600;
+  font-size: 16px;
+  color: #16213a;
+  line-height: 22px;
+}
+
+.page-overview-report-dialog-count {
+  font-weight: 600;
+
+  &.is-warning {
+    color: #ff8e38;
+  }
+
+  &.is-delay {
+    color: #f54848;
+  }
+}
+
+.page-overview-report-dialog-body {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  max-height: 60vh;
+  overflow-y: auto;
+}
+
+.page-overview-report-item {
+  padding: 16px;
+  background: #f6f9fd;
+  border-radius: 4px;
+}
+
+.page-overview-report-item-name {
+  position: relative;
+  margin-bottom: 12px;
+  padding-left: 10px;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 600;
+  font-size: 14px;
+  color: #16213a;
+  line-height: 20px;
+
+  &::before {
+    position: absolute;
+    top: 2px;
+    left: 0;
+    width: 3px;
+    height: 14px;
+    background: #2382e7;
+    border-radius: 1px;
+    content: "";
+  }
+}
+
+.page-overview-report-item-grid {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px 24px;
+}
+
+.page-overview-report-field {
+  display: flex;
+  align-items: baseline;
+  min-width: 160px;
+  font-family: PingFang SC, PingFang SC;
+  font-size: 13px;
+  line-height: 20px;
+
+  &.is-full {
+    width: 100%;
+    min-width: 100%;
+  }
+
+  .label {
+    flex-shrink: 0;
+    color: #9aa5b8;
+  }
+
+  .value {
+    color: #16213a;
+
+    &.is-warning {
+      color: #ff8e38;
+      font-weight: 600;
+    }
+
+    &.is-delay {
+      color: #f54848;
+      font-weight: 600;
+    }
+  }
+}
+
+.page-overview-report-dialog-footer {
+  display: flex;
+  justify-content: center;
+}
+</style>