Pārlūkot izejas kodu

fix: 提交进度页面

Mickey Mike 1 nedēļu atpakaļ
vecāks
revīzija
d372921405

+ 0 - 1
components.d.ts

@@ -13,7 +13,6 @@ declare module 'vue' {
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
     ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
     ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
-    ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     Panel: typeof import('./src/components/Panel.vue')['default']

+ 7 - 0
src/assets/images/u1404.svg

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="67px" height="70px" xmlns="http://www.w3.org/2000/svg">
+  <g transform="matrix(1 0 0 1 -80 -171 )">
+    <path d="M 81 172  L 146 172  L 146 240  L 81 240  L 81 172  Z " fill-rule="nonzero" fill="#ffffff" stroke="none" fill-opacity="0" />
+    <path d="M 80.5 171.5  L 146.5 171.5  L 146.5 240.5  L 80.5 240.5  L 80.5 171.5  Z " stroke-width="1" stroke-dasharray="3,1" stroke="#5bdda6" fill="none" stroke-opacity="0.349019607843137" stroke-dashoffset="0.5" />
+  </g>
+</svg>

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 6 - 0
src/assets/images/u1407.svg


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 6 - 0
src/assets/images/u1420.svg


+ 4 - 0
src/components/Chart/index.vue

@@ -15,6 +15,8 @@ import {
   GridComponent,
   DatasetComponent,
   TransformComponent,
+  DataZoomComponent,
+  LegendComponent,
 } from 'echarts/components';
 // 标签自动布局、全局过渡动画等特性
 import { LabelLayout, UniversalTransition } from 'echarts/features';
@@ -35,6 +37,8 @@ echarts.use([
   LineChart,
   PieChart,
   BarChart,
+  DataZoomComponent,
+  LegendComponent,
 ]);
 
 const props = defineProps<{

+ 390 - 0
src/views/process/ProjectMilestone.vue

@@ -0,0 +1,390 @@
+<template>
+  <div class="pm-root" :style="{ width: width + 'px', height: height + 'px' }">
+    <!-- 背景遮罩 -->
+    <div class="bg-layer"></div>
+
+    <!-- 顶部 header -->
+    <div class="header">
+      <div class="title-left">
+        <!-- <span class="triangle">▶</span> -->
+        <span class="title">项目里程碑</span>
+      </div>
+      <div class="years-right">
+        <div v-for="y in headerYears" :key="y" class="year">{{ y }}</div>
+      </div>
+    </div>
+
+    <!-- project row (left project name / right months labels) -->
+    <div class="project-row">
+      <div class="project-left">
+        <div class="project-name">{{ projectName }}</div>
+      </div>
+      <div class="months-right">
+        <div class="months-grid">
+          <div
+            v-for="(m, idx) in monthLabels"
+            :key="m"
+            class="month-cell"
+            :style="{ width: monthWidth + 'px' }"
+          >
+            {{ m }}
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- SVG timeline -->
+    <div class="svg-wrap" ref="svgWrapRef">
+      <svg :width="svgWidth" :height="svgHeight" xmlns="http://www.w3.org/2000/svg">
+        <defs>
+          <!-- arrow marker -->
+          <marker
+            id="arrowHead"
+            markerWidth="10"
+            markerHeight="10"
+            refX="5"
+            refY="5"
+            orient="auto"
+            markerUnits="strokeWidth"
+          >
+            <path d="M0,0 L10,5 L0,10 z" fill="#7fd7ff" />
+          </marker>
+
+          <!-- star symbol -->
+          <symbol id="starSym" viewBox="0 0 24 24">
+            <path d="M12 2.6l2.4 5 5.5.8-4 3.9.9 5.5L12 16.6 6.2 18.8l.9-5.5L3 9.3l5.5-.8z" />
+          </symbol>
+
+          <!-- subtle vertical grid line -->
+          <linearGradient id="barGrad" x1="0" x2="1">
+            <stop offset="0" stop-color="#59baff" />
+            <stop offset="1" stop-color="#86e9ff" />
+          </linearGradient>
+        </defs>
+
+        <!-- vertical month lines -->
+        <g v-for="(m, i) in monthsArr" :key="'v' + i">
+          <line
+            :x1="leftCol + i * monthWidth + monthWidth / 2"
+            y1="0"
+            :x2="leftCol + i * monthWidth + monthWidth / 2"
+            :y2="svgHeight"
+            stroke="rgba(255,255,255,0.03)"
+            stroke-width="1"
+          />
+        </g>
+
+        <!-- rows: bars, nodes, labels -->
+        <g v-for="(row, rIdx) in rows" :key="'row' + rIdx">
+          <!-- horizontal baseline -->
+          <line
+            :x1="leftCol"
+            :y1="rowY(rIdx)"
+            :x2="svgWidth - rightPadding"
+            :y2="rowY(rIdx)"
+            stroke="rgba(255,255,255,0.04)"
+            stroke-width="1"
+          />
+
+          <!-- duration rect -->
+          <rect
+            v-if="row.start !== null && row.end !== null"
+            :x="xOfMonth(row.start) - monthWidth / 2 + 2"
+            :y="rowY(rIdx) - barH / 2"
+            :width="(row.end - row.start + 1) * monthWidth - 4"
+            :height="barH"
+            rx="8"
+            :fill="'rgba(63,150,200,0.18)'"
+            stroke="rgba(127,210,255,0.9)"
+          />
+
+          <!-- arrow polygon at end -->
+          <polygon
+            v-if="row.end !== null"
+            :points="arrowAt(row.end, rowY(rIdx), monthWidth)"
+            fill="#7fd7ff"
+          />
+
+          <!-- nodes -->
+          <g
+            v-for="(n, ni) in row.nodes"
+            :key="'n' + rIdx + '-' + ni"
+            :transform="`translate(${xOfMonth(n.month)}, ${rowY(rIdx)})`"
+          >
+            <template v-if="n.type === 'circle'">
+              <circle r="7" :fill="n.color || '#8feaff'" stroke="#ffffff" stroke-width="1.2" />
+            </template>
+            <template v-else-if="n.type === 'dot'">
+              <circle r="5" :fill="n.color || '#ff6b6b'" stroke="#ffffff" stroke-width="1" />
+            </template>
+            <template v-else-if="n.type === 'star'">
+              <use
+                href="#starSym"
+                x="-10"
+                y="-10"
+                width="20"
+                height="20"
+                :fill="n.color || '#ffd36b'"
+              />
+            </template>
+
+            <!-- node label below -->
+            <text x="0" :y="labelY" text-anchor="middle" class="node-label">{{ n.label }}</text>
+          </g>
+        </g>
+
+        <!-- left column labels (stage names) -->
+        <g>
+          <rect :x="0" :y="0" :width="leftCol" :height="svgHeight" fill="rgba(6,18,30,0.45)" />
+          <g v-for="(row, rIdx) in rows" :key="'label' + rIdx">
+            <text :x="leftPad" :y="rowY(rIdx) + 6" class="left-label">{{ row.label }}</text>
+          </g>
+        </g>
+      </svg>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, ref } from 'vue';
+
+/**
+ * Props
+ */
+const props = defineProps({
+  width: { type: Number, default: 880 },
+  height: { type: Number, default: 500 },
+  projectName: { type: String, default: '科技生态园' },
+  monthsStart: { type: String, default: '2023-09' },
+  monthsEnd: { type: String, default: '2025-05' },
+});
+
+/** Layout constants */
+const leftCol = 160; // 左侧列宽
+const rightPadding = 24;
+const topPadding = 8;
+const bottomPadding = 10;
+const headerH = 48;
+const subHeaderH = 36;
+const rowH = 72;
+const barH = 14;
+const leftPad = 20;
+const labelY = 22;
+
+const svgWidth = computed(() => props.width);
+const svgHeight = computed(() => {
+  // compute visual SVG height area for timeline (excluding header/subheader)
+  const rowsCount = rows.length;
+  return topPadding + rowsCount * rowH + bottomPadding;
+});
+
+/** build months array inclusive */
+function buildMonths(start: string, end: string) {
+  const [sy, sm] = start.split('-').map(Number);
+  const [ey, em] = end.split('-').map(Number);
+  const arr: string[] = [];
+  let y = sy,
+    m = sm;
+  while (y < ey || (y === ey && m <= em)) {
+    arr.push(`${y}-${String(m).padStart(2, '0')}`);
+    m++;
+    if (m > 12) {
+      m = 1;
+      y++;
+    }
+  }
+  return arr;
+}
+
+const monthsArr = computed(() => buildMonths(props.monthsStart, props.monthsEnd));
+const monthWidth = computed(() => {
+  const usable = props.width - leftCol - rightPadding;
+  return Math.max(36, Math.floor(usable / monthsArr.value.length)); // 最少宽度以防变形
+});
+const monthLabels = computed(() =>
+  monthsArr.value.map((s) => {
+    const [y, m] = s.split('-');
+    return `${Number(m)}`;
+  }),
+);
+
+// header years
+const headerYears = computed(() => {
+  const set = new Set<number>();
+  monthsArr.value.forEach((m) => set.add(Number(m.split('-')[0])));
+  return Array.from(set);
+});
+
+/** rows data (造假数据,但按你图的时间与节点摆放来高度还原) */
+const rows = [
+  {
+    label: '项目设计',
+    start: 0, // month index in monthsArr
+    end: 8,
+    nodes: [
+      { month: 0, type: 'circle', label: '招标\n2023/09' },
+      { month: 2, type: 'dot', label: '招标\n2023/11' },
+    ],
+  },
+  {
+    label: '项目报建',
+    start: 1,
+    end: 6,
+    nodes: [{ month: 1, type: 'circle', label: '招标\n2023/10' }],
+  },
+  {
+    label: '项目采购',
+    start: 0,
+    end: 12,
+    nodes: [
+      { month: 1, type: 'circle', label: '招标\n2023/10' },
+      { month: 4, type: 'dot', label: '节点\n2024/01' },
+    ],
+  },
+  {
+    label: '土建工程',
+    start: 2,
+    end: 15,
+    nodes: [
+      { month: 6, type: 'dot', label: '招标\n2024/03' },
+      { month: 11, type: 'star', label: '★\n2024/11' },
+    ],
+  },
+  {
+    label: '洁净工程',
+    start: 5,
+    end: 20,
+    nodes: [{ month: 12, type: 'star', label: '★\n2025/01' }],
+  },
+];
+
+/** helpers to compute coordinates */
+function xOfMonth(monthIndex: number) {
+  // center of month cell
+  return leftCol + monthIndex * monthWidth.value + monthWidth.value / 2;
+}
+function rowY(rIdx: number) {
+  return topPadding + rIdx * rowH + rowH / 2;
+}
+function arrowAt(endMonth: number, centerY: number, mW: number) {
+  // small right-pointing triangle right after end of bar
+  const xEnd = leftCol + (endMonth + 1) * mW - 6;
+  const cx = xEnd + 10;
+  const y1 = centerY - 8;
+  const y2 = centerY;
+  const y3 = centerY + 8;
+  return `${xEnd},${y1} ${cx},${y2} ${xEnd},${y3}`;
+}
+
+const svgWrapRef = ref<HTMLElement | null>(null);
+</script>
+
+<style scoped lang="less">
+.pm-root {
+  position: relative;
+  font-family: 'Microsoft Yahei', Arial, sans-serif;
+  color: #cfeffd;
+  border-radius: 6px;
+  overflow: hidden;
+  background: rgba(29, 66, 110, 0.6);
+}
+
+/* faint decorative background */
+.bg-layer {
+  position: absolute;
+  inset: 0;
+  background-image: radial-gradient(rgba(80, 170, 220, 0.03) 1px, transparent 1px);
+  background-size: 80px 80px;
+  z-index: 0;
+  pointer-events: none;
+}
+
+/* header */
+.header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  height: 36px;
+  z-index: 2;
+}
+.title-left {
+  width: 145px;
+}
+.triangle {
+  color: #39e6ff;
+  font-weight: 700;
+}
+.title {
+  font-size: 18px;
+  font-weight: 700;
+  color: #bff8ff;
+}
+.years-right {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+  justify-content: space-between;
+  width: calc(100% - 145px);
+}
+.year {
+  background: linear-gradient(180deg, rgba(18, 80, 120, 0.18), rgba(6, 30, 50, 0.12));
+  padding: 4px 8px;
+  border-radius: 4px;
+  color: #aef0ff;
+  font-weight: 600;
+  border: 1px solid rgba(160, 230, 255, 0.06);
+}
+
+/* project row */
+.project-row {
+  display: flex;
+  z-index: 2;
+  align-items: center;
+}
+.project-left {
+  width: 160px;
+}
+.project-name {
+  color: #8feaff;
+  font-weight: 700;
+  font-size: 15px;
+  padding-top: 2px;
+}
+.months-right {
+  flex: 1;
+  overflow: hidden;
+}
+.months-grid {
+  display: flex;
+  gap: 0;
+  align-items: center;
+}
+.month-cell {
+  text-align: center;
+  font-size: 12px;
+  color: rgba(200, 230, 255, 0.8);
+  padding: 4px 0;
+  border-right: 1px solid rgba(255, 255, 255, 0.02);
+}
+
+/* svg area */
+.svg-wrap {
+  z-index: 1;
+  box-sizing: border-box;
+}
+.left-label {
+  fill: #8feaff;
+  font-weight: 700;
+  font-size: 13px;
+}
+.node-label {
+  fill: rgba(200, 230, 255, 0.94);
+  font-size: 11px;
+  white-space: pre;
+}
+
+/* ensure svg text has smooth antialiasing */
+svg {
+  display: block;
+}
+</style>

+ 512 - 4
src/views/process/index.vue

@@ -1,13 +1,521 @@
 <template>
-  <div>
-    进度
+  <div class="w-full h-full relative flex justify-between items-center">
+    <!-- 左侧面板 -->
+    <Panel>
+      <CardTitle>项目倒计时</CardTitle>
+
+      <div class="w-full flex flex-row gap-10px pt-10px mb-10px">
+        <div class="flex flex-col">
+          <div class="p-5px flex flex-row justify-center items-center bg-[#348bc74c] w-200px">
+            <div class="backgroundImages w-70px h-70px flex justify-center items-center">
+              <img src="@/assets/images/u1407.svg" />
+            </div>
+            <div class="ml-10px">
+              <div class="text-15px color-[#ffffffb2]">合同开工日期</div>
+              <div class="text-20px mt-10px">2024-10-01</div>
+            </div>
+          </div>
+          <div
+            class="p-5px flex flex-row justify-center items-center bg-[#348bc74c] mt-10px w-200px"
+          >
+            <div class="backgroundImages w-70px h-70px flex justify-center items-center">
+              <img src="@/assets/images/u1420.svg" />
+            </div>
+            <div class="ml-10px">
+              <div class="text-15px color-[#ffffffb2]">合同开工日期</div>
+              <div class="text-20px mt-10px">2024-10-01</div>
+            </div>
+          </div>
+        </div>
+        <div class="bg-[#348bc74c] w-211px flex flex-col justify-center items-center">
+          <DigitalFlop :config="config5" />
+          <div class="text-17px color-[#ffffffb2]">剩余天数</div>
+        </div>
+      </div>
+      <CardTitle>WBS分解</CardTitle>
+      <div class="text-white h-350px w-full pt-10px left-bottom-board">
+        <ScrollBoard :config="config1" @mouseover="mouseoverHandler" @click="clickHandler" />
+      </div>
+
+      <CardTitle>CWS分解</CardTitle>
+      <div class="w-full h-260px flex gap-10px p-10px box-border justify-between items-center">
+        <Chart :options="chartOptions" />
+      </div>
+    </Panel>
+
+    <!-- 中间区域 -->
+    <div
+      class="h-full box-border pr-10px pl-10px middle-content flex-1 flex flex-col gap-10px justify-center"
+    >
+      <div class="text-white h-500px w-full center-board">
+        <ProjectMilestone />
+      </div>
+    </div>
+
+    <!-- 右侧面板 -->
+    <Panel>
+      <div class="bg-[#348bc74c] w-full h-150px flex flex-col justify-center items-center">
+        <DigitalFlop :config="config5" style="height: 80px; width: 100%" />
+        <div class="text-32px color-[#ffffffb2]">实际AC</div>
+      </div>
+      <CardTitle>单体完成比率统计</CardTitle>
+      <div class="text-white h-350px w-full center-board right-top-board">
+        <Chart :options="chartOptionsRightTop" />
+      </div>
+
+      <CardTitle>单体-分部完成比率统计</CardTitle>
+      <div class="w-full box-border p-10px h-350px">
+        <Chart :options="chartOptionsRightBottom" />
+      </div>
+    </Panel>
   </div>
 </template>
 
 <script setup lang="ts">
+import { reactive, onMounted, ref } from 'vue';
+import { ScrollBoard, DigitalFlop } from '@kjgl77/datav-vue3';
+import Panel from '@/components/Panel.vue';
+import CardTitle from '@/components/CardTitle.vue';
+import Chart from '@/components/Chart/index.vue';
+import type { EChartsOption } from 'echarts';
+import ProjectMilestone from './ProjectMilestone.vue';
+
+const config1 = reactive({
+  header: ['单体', '当前状态', '所属分类'],
+  data: [
+    ['高处作业安全', '253', '123'],
+    ['高处作业安全', '253', '123'],
+    ['高处作业安全', '253', '123'],
+    ['高处作业安全', '253', '123'],
+    ['高处作业安全', '253', '123'],
+    ['高处作业安全', '253', '123'],
+    ['高处作业安全', '253', '123'],
+    ['高处作业安全', '253', '123'],
+  ],
+  headerBGC: '#348bc763',
+  evenRowBGC: 'rgba(29, 66, 110, 0.6)',
+  oddRowBGC: 'rgba(52, 139, 199, 0.0980392156862745)',
+  align: ['center'],
+  index: true,
+  columnWidth: [50],
+  rowNum: 6,
+  headerHeight: 35,
+  waitTime: 50005000,
+});
+
+const config5 = reactive({
+  number: [1132],
+  content: '{nt}%',
+});
+
+setInterval(() => {
+  config5.number = config5.number.map((num) => num + 100);
+}, 3000);
+
+let scrollInterval: ReturnType<typeof setInterval>;
+let currentZoom = { start: 0, end: 50 };
+
+const chartData = ref({
+  plan: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120],
+  actual: [12, 22, 28, 38, 55, 65, 72, 85, 95, 108, 115, 125],
+  actual1: [12, 22, 28, 38, 55, 65, 72, 85, 95, 108, 115, 125],
+});
+
+const chartOptions = ref<EChartsOption>({
+  tooltip: {
+    trigger: 'item',
+    textStyle: {
+      color: '#000',
+    },
+  },
+  xAxis: {
+    type: 'category',
+    data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+    axisLabel: {
+      color: '#fff',
+    },
+  },
+  yAxis: {
+    type: 'value',
+    axisLabel: {
+      color: '#fff',
+    },
+  },
+  series: [
+    {
+      name: '单体',
+      data: chartData.value.plan,
+      type: 'bar',
+      itemStyle: {
+        color: 'rgba(42, 254, 255, 1)',
+      },
+      label: {
+        show: true,
+        position: 'inside',
+        color: '#fff',
+        fontSize: 10,
+      },
+    },
+    {
+      name: '状态',
+      data: chartData.value.actual,
+      type: 'bar',
+      itemStyle: {
+        color: 'rgba(225, 189, 97, 1)',
+      },
+      label: {
+        show: true,
+        position: 'inside',
+        color: '#fff',
+        fontSize: 10,
+      },
+    },
+    {
+      name: '分类',
+      data: chartData.value.actual1,
+      type: 'bar',
+      itemStyle: {
+        color: 'rgba(0, 189, 97, 1)',
+      },
+      label: {
+        show: true,
+        position: 'inside',
+        color: '#fff',
+        fontSize: 10,
+      },
+    },
+  ],
+  legend: {
+    data: ['单体', '状态', '分类'],
+    top: '10px',
+    right: '10px',
+    textStyle: {
+      color: '#fff',
+    },
+    itemWidth: 20,
+    itemHeight: 10,
+    icon: 'rect',
+  },
+  grid: {
+    top: '15%',
+    left: '3%',
+    right: '4%',
+    bottom: '3%',
+    containLabel: true,
+  },
+  dataZoom: [
+    {
+      show: false,
+      type: 'slider',
+      start: currentZoom.start,
+      end: currentZoom.end,
+    },
+  ],
+});
+
+const startAutoScroll = () => {
+  scrollInterval = setInterval(() => {
+    if (currentZoom.end >= 100) {
+      currentZoom.start = 0;
+      currentZoom.end = 50;
+    } else {
+      currentZoom.start += 2;
+      currentZoom.end += 2;
+    }
+
+    chartOptions.value = {
+      ...chartOptions.value,
+      dataZoom: [
+        {
+          show: false,
+          type: 'slider',
+          start: currentZoom.start,
+          end: currentZoom.end,
+        },
+      ],
+    };
+  }, 2000); // 每2秒更新一次
+};
 
+// 右侧顶部柱状图
+let scrollIntervalRightTop: ReturnType<typeof setInterval>;
+let currentZoomeRightTop = { start: 0, end: 50 };
+const chartDataRightTop = ref([30, 50, 70, 90, 60, 80, 100, 120, 140, 160, 180, 200]);
+const chartOptionsRightTop = ref<EChartsOption>({
+  tooltip: {
+    trigger: 'item',
+    textStyle: { color: '#000' },
+  },
+  xAxis: {
+    type: 'value',
+    axisLabel: { color: '#fff' },
+  },
+  yAxis: {
+    type: 'category',
+    data: [
+      '项目A',
+      '项目B',
+      '项目C',
+      '项目D',
+      '项目E',
+      '项目F',
+      '项目G',
+      '项目H',
+      '项目I',
+      '项目J',
+      '项目K',
+      '项目L',
+    ],
+    axisLabel: { color: '#fff' },
+  },
+  series: [
+    {
+      name: '进度',
+      type: 'bar',
+      data: chartDataRightTop.value,
+      barWidth: 16,
+      itemStyle: {
+        color: 'rgba(42, 254, 255, 1)',
+        borderRadius: [4, 4, 4, 4],
+      },
+      label: {
+        show: true,
+        position: 'right',
+        color: '#fff',
+        fontSize: 10,
+      },
+    },
+  ],
+  grid: {
+    top: '5%',
+    left: '3%',
+    right: '4%',
+    bottom: '3%',
+    containLabel: true,
+  },
+  dataZoom: [
+    {
+      show: false,
+      type: 'slider',
+      yAxisIndex: 0,
+      start: currentZoom.start,
+      end: currentZoom.end,
+    },
+  ],
+});
+
+const startAutoScrollRightTop = () => {
+  clearInterval(scrollIntervalRightTop);
+  scrollIntervalRightTop = setInterval(() => {
+    if (currentZoomeRightTop.end >= 100) {
+      currentZoomeRightTop.start = 0;
+      currentZoomeRightTop.end = 50;
+    } else {
+      currentZoomeRightTop.start += 2;
+      currentZoomeRightTop.end += 2;
+    }
+
+    chartOptionsRightTop.value = {
+      ...chartOptionsRightTop.value,
+      dataZoom: [
+        {
+          show: false,
+          type: 'slider',
+          yAxisIndex: 0,
+          start: currentZoomeRightTop.start,
+          end: currentZoomeRightTop.end,
+        },
+      ],
+    };
+  }, 2000);
+};
+
+// 右侧底部
+
+let scrollIntervalRightBottom: ReturnType<typeof setInterval>;
+let currentZoomeRightBottom = { start: 0, end: 50 };
+const chartDataRightBottom = ref([30, 50, 70, 90, 60, 80, 100, 120, 140, 160, 180, 200]);
+const chartOptionsRightBottom = ref<EChartsOption>({
+  tooltip: {
+    trigger: 'item',
+    textStyle: { color: '#000' },
+  },
+  xAxis: {
+    type: 'value',
+    axisLabel: { color: '#fff' },
+  },
+  yAxis: {
+    type: 'category',
+    data: [
+      '项目A',
+      '项目B',
+      '项目C',
+      '项目D',
+      '项目E',
+      '项目F',
+      '项目G',
+      '项目H',
+      '项目I',
+      '项目J',
+      '项目K',
+      '项目L',
+    ],
+    axisLabel: { color: '#fff' },
+  },
+  series: [
+    {
+      name: '进度',
+      type: 'bar',
+      data: chartDataRightBottom.value,
+      barWidth: 16,
+      itemStyle: {
+        color: 'rgba(42, 254, 255, 1)',
+        borderRadius: [4, 4, 4, 4],
+      },
+      label: {
+        show: true,
+        position: 'right',
+        color: '#fff',
+        fontSize: 10,
+      },
+    },
+  ],
+  grid: {
+    top: '5%',
+    left: '3%',
+    right: '4%',
+    bottom: '3%',
+    containLabel: true,
+  },
+  dataZoom: [
+    {
+      show: false,
+      type: 'slider',
+      yAxisIndex: 0,
+      start: currentZoom.start,
+      end: currentZoom.end,
+    },
+  ],
+});
+
+const startAutoScrollRightBottom = () => {
+  clearInterval(scrollIntervalRightBottom);
+  scrollIntervalRightBottom = setInterval(() => {
+    if (currentZoomeRightBottom.end >= 100) {
+      currentZoomeRightBottom.start = 0;
+      currentZoomeRightBottom.end = 50;
+    } else {
+      currentZoomeRightBottom.start += 2;
+      currentZoomeRightBottom.end += 2;
+    }
+
+    chartOptionsRightBottom.value = {
+      ...chartOptionsRightBottom.value,
+      dataZoom: [
+        {
+          show: false,
+          type: 'slider',
+          yAxisIndex: 0,
+          start: currentZoomeRightBottom.start,
+          end: currentZoomeRightBottom.end,
+        },
+      ],
+    };
+  }, 2000);
+};
+
+// 模拟数据更新
+setInterval(() => {
+  chartData.value.plan = chartData.value.plan.map((item) => item + Math.floor(Math.random() * 10));
+  chartData.value.actual = chartData.value.actual.map(
+    (item) => item + Math.floor(Math.random() * 10),
+  );
+  chartData.value.actual1 = chartData.value.actual1.map(
+    (item) => item + Math.floor(Math.random() * 10),
+  );
+
+  chartDataRightTop.value = chartDataRightTop.value.map((v) => v + Math.floor(Math.random() * 10));
+  chartDataRightBottom.value = chartDataRightBottom.value.map(
+    (v) => v + Math.floor(Math.random() * 10),
+  );
+}, 5000); // 每5秒更新一次数据
+
+onMounted(() => {
+  startAutoScroll();
+  startAutoScrollRightTop();
+  startAutoScrollRightBottom();
+});
+
+const mouseoverHandler = (e: any) => {
+  console.log(e);
+};
+
+const clickHandler = (e: any) => {
+  console.log(e);
+};
 </script>
 
-<style lang="scss" scoped>
+<style lang="less" scoped>
+:deep(*) {
+  .el-descriptions__body {
+    background-color: transparent;
+  }
+  .el-descriptions__label {
+    color: #fff;
+  }
+  .el-descriptions__content {
+    color: #ccc;
+  }
+}
+.backgroundImages {
+  background: url(@/assets/images/u1404.svg) center center no-repeat;
+}
+:deep(.left-bottom-board .dv-scroll-board .header) {
+  height: 35px;
+}
+:deep(.left-bottom-board .dv-scroll-board .header-item) {
+  font-size: 17px !important;
+  color: #31e4cf !important;
+}
+
+:deep(.left-board .dv-scroll-board .header) {
+  height: 30px !important;
+  color: #31e4cf;
+}
+:deep(.left-board .row-item) {
+  font-size: 17px;
+  font-weight: 400;
+}
+:deep(.left-board .header-item) {
+  font-weight: 400;
+  font-size: 17px;
+}
 
-</style>
+:deep(.center-board .dv-scroll-board .header) {
+  height: 48px !important;
+  color: #fff;
+  font-size: 20px;
+  font-weight: 700;
+}
+:deep(.left-bottom-board .dv-scroll-board .rows .ceil) {
+  font-size: 17px;
+  font-weight: 400;
+}
+:deep(.center-board .dv-scroll-board .rows .ceil) {
+  font-size: 16px;
+  color: rgba(247, 247, 247, 0.698039215686274);
+}
+:deep(.dv-scroll-ranking-board .ranking-info .rank) {
+  width: auto !important;
+  margin-right: 10px;
+}
+:deep(.dv-scroll-ranking-board .ranking-column) {
+  height: 14px;
+  border: none;
+  background-color: rgba(232, 232, 232, 0.27843137254902);
+}
+:deep(.dv-scroll-ranking-board .ranking-column .inside-column) {
+  height: 100%;
+  background-color: rgba(115, 235, 233, 1);
+}
+</style>