From 9d485123b8c947f61c94aee67a0ceec8953a510d Mon Sep 17 00:00:00 2001
From: yaowanxin <3588231647@qq.com>
Date: 星期五, 30 一月 2026 17:36:56 +0800
Subject: [PATCH] 用印管理、规章制度管理页面添加分页功能

---
 src/views/reportAnalysis/productionAnalysis/components/center-bottom.vue |  351 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 351 insertions(+), 0 deletions(-)

diff --git a/src/views/reportAnalysis/productionAnalysis/components/center-bottom.vue b/src/views/reportAnalysis/productionAnalysis/components/center-bottom.vue
new file mode 100644
index 0000000..46a870a
--- /dev/null
+++ b/src/views/reportAnalysis/productionAnalysis/components/center-bottom.vue
@@ -0,0 +1,351 @@
+<template>
+  <div>
+    <PanelHeader title="鐢熶骇璁㈠崟瀹屾垚杩涘害" />
+    <div class="main-panel">
+      <div class="panel-item-customers">
+        <CarouselCards :items="cardItems" :visible-count="4" />
+        <div
+          class="progress-table-container"
+          ref="progressTableRef"
+          style="margin-top: 0px;"
+          @scroll="handleTableScroll"
+        >
+          <table class="progress-table">
+            <thead>
+              <tr>
+                <th>鐢熶骇璁㈠崟鍙�</th>
+                <th>浜у搧鍚嶇О</th>
+                <th>瑙勬牸</th>
+                <th>闇�姹傛暟閲�</th>
+                <th>瀹屾垚鏁伴噺</th>
+                <th>瀹屾垚杩涘害</th>
+              </tr>
+            </thead>
+            <tbody>
+              <tr
+                v-for="(item, index) in progressTableData"
+                :key="index"
+                :ref="(el) => setRowRef(el, index)"
+                :class="{ 'row-under-header': isRowUnderHeader(index) }"
+              >
+                <td>{{ item.npsNo || '-' }}</td>
+                <td>{{ item.productCategory || '-' }}</td>
+                <td>{{ item.specificationModel || '-' }}</td>
+                <td>{{ item.quantity || 0 }}</td>
+                <td>{{ item.completeQuantity || 0 }}</td>
+                <td>
+                  <el-progress
+                    :percentage="calculateProgress(item)"
+                    :color="progressColor(calculateProgress(item))"
+                    :status="calculateProgress(item) >= 100 ? 'success' : ''"
+                    :stroke-width="8"
+                  />
+                </td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
+import { getProgressStatistics } from '@/api/viewIndex.js'
+import PanelHeader from './PanelHeader.vue'
+import CarouselCards from './CarouselCards.vue'
+
+const progressTableRef = ref(null)
+const progressTableScrollTimer = ref(null)
+const tableScrollTimeout = ref(null)
+const tableRowRefs = ref([])
+const rowsUnderHeader = ref(new Set())
+
+// 璁㈠崟缁熻瀵硅薄
+const orderStatisticsObject = ref({
+  totalOrderCount: 0,
+  uncompletedOrderCount: 0,
+  partialCompletedOrderCount: 0,
+  completedOrderCount: 0,
+})
+
+// 杞挱鍗$墖鏁版嵁锛堢敱 orderStatisticsObject 鍚屾锛�
+const cardItems = ref([])
+
+// 鐢熶骇璁㈠崟瀹屾垚杩涘害琛ㄦ牸鏁版嵁
+const progressTableData = ref([])
+
+// 璁$畻瀹屾垚杩涘害鐧惧垎姣�
+const calculateProgress = (item) => {
+  if (!item) return 0
+  if (item.completionStatus !== undefined && item.completionStatus !== null) {
+    const percentage = Number(item.completionStatus)
+    if (isNaN(percentage)) return 0
+    return Math.min(Math.max(Math.round(percentage), 0), 100)
+  }
+  if (!item.quantity || item.quantity === 0) return 0
+  const percentage = ((item.completeQuantity || 0) / item.quantity) * 100
+  return Math.min(Math.max(Math.round(percentage), 0), 100)
+}
+
+// 鏍规嵁杩涘害鐧惧垎姣旇繑鍥為鑹�
+const progressColor = (percentage) => {
+  const p = percentage || 0
+  if (p < 30) return '#f56c6c'
+  if (p < 50) return '#e6a23c'
+  if (p < 80) return '#409eff'
+  return '#67c23a'
+}
+
+const setRowRef = (el, index) => {
+  if (el) {
+    tableRowRefs.value[index] = el
+  }
+}
+
+const isRowUnderHeader = (index) => rowsUnderHeader.value.has(index)
+
+const handleTableScroll = () => {
+  const tableContainer = progressTableRef.value
+  if (!tableContainer) return
+  const thead = tableContainer.querySelector('thead')
+  if (!thead) return
+  const theadHeight = thead.offsetHeight
+  const containerRect = tableContainer.getBoundingClientRect()
+  const containerTop = containerRect.top
+  const theadBottom = containerTop + theadHeight
+  rowsUnderHeader.value.clear()
+  tableRowRefs.value.forEach((row, index) => {
+    if (row) {
+      const rowRect = row.getBoundingClientRect()
+      const rowTop = rowRect.top
+      const rowBottom = rowRect.bottom
+      if (rowTop < theadBottom && rowBottom > containerTop) {
+        rowsUnderHeader.value.add(index)
+      }
+    }
+  })
+  if (tableScrollTimeout.value) clearTimeout(tableScrollTimeout.value)
+  tableScrollTimeout.value = setTimeout(() => {
+    rowsUnderHeader.value.clear()
+  }, 150)
+}
+
+const initProgressTableScroll = () => {
+  const tableContainer = progressTableRef.value
+  if (!tableContainer) return
+  if (progressTableScrollTimer.value) {
+    cancelAnimationFrame(progressTableScrollTimer.value)
+    progressTableScrollTimer.value = null
+  }
+  if (tableContainer._pauseTimer) {
+    clearInterval(tableContainer._pauseTimer)
+    tableContainer._pauseTimer = null
+  }
+  const tbody = tableContainer.querySelector('tbody')
+  if (!tbody) return
+  const originalCount = progressTableData.value.length
+  const allRows = Array.from(tbody.querySelectorAll('tr'))
+  if (allRows.length > originalCount) {
+    for (let i = originalCount; i < allRows.length; i++) {
+      allRows[i].remove()
+    }
+  }
+  const scrollItems = Array.from(tbody.querySelectorAll('tr'))
+  if (scrollItems.length === 0) return
+  const originalItemCount = scrollItems.length
+  const thead = tableContainer.querySelector('thead')
+  const theadHeight = thead ? thead.offsetHeight : 40
+  const containerHeight = tableContainer.clientHeight
+  const visibleHeight = containerHeight - theadHeight
+  const itemHeight = scrollItems[0]?.offsetHeight || 40
+  const totalContentHeight = itemHeight * originalItemCount
+  if (totalContentHeight <= visibleHeight) return
+  const cloneCount = Math.ceil(visibleHeight / itemHeight) + 2
+  for (let i = 0; i < cloneCount; i++) {
+    const clone = scrollItems[i % originalItemCount].cloneNode(true)
+    tbody.appendChild(clone)
+  }
+  let scrollPosition = 0
+  const scrollSpeed = 1.5
+  const pauseTime = 3000
+  let isPaused = false
+  let lastTimestamp = 0
+  function scrollAnimation(timestamp) {
+    if (!lastTimestamp) lastTimestamp = timestamp
+    const deltaTime = timestamp - lastTimestamp
+    lastTimestamp = timestamp
+    if (!isPaused) {
+      scrollPosition += scrollSpeed * (deltaTime / 16)
+      const maxScroll = itemHeight * originalItemCount
+      if (scrollPosition >= maxScroll) {
+        scrollPosition = 0
+        tableContainer.scrollTop = 0
+      } else {
+        tableContainer.scrollTop = scrollPosition
+      }
+    }
+    progressTableScrollTimer.value = requestAnimationFrame(scrollAnimation)
+  }
+  progressTableScrollTimer.value = requestAnimationFrame(scrollAnimation)
+  const pauseTimer = setInterval(() => {
+    isPaused = !isPaused
+  }, pauseTime)
+  tableContainer._pauseTimer = pauseTimer
+}
+
+const progressStatisticsInfo = () => {
+  getProgressStatistics()
+    .then((res) => {
+      if (!res || !res.data) return
+      const obj = {
+        totalOrderCount: res.data.totalOrderCount || 0,
+        uncompletedOrderCount: res.data.uncompletedOrderCount || 0,
+        partialCompletedOrderCount: res.data.partialCompletedOrderCount || 0,
+        completedOrderCount: res.data.completedOrderCount || 0,
+      }
+      orderStatisticsObject.value = obj
+      cardItems.value = [
+        { label: '鎬昏鍗曟暟', value: obj.totalOrderCount, unit: '浠�' },
+        { label: '鏈畬鎴愯鍗曟暟', value: obj.uncompletedOrderCount, unit: '浠�' },
+        { label: '閮ㄥ垎瀹屾垚璁㈠崟鏁�', value: obj.partialCompletedOrderCount, unit: '浠�' },
+        { label: '宸插畬鎴愯鍗曟暟', value: obj.completedOrderCount, unit: '浠�' },
+      ]
+      progressTableData.value = res.data.completedOrderDetails || []
+      tableRowRefs.value = []
+      rowsUnderHeader.value.clear()
+      nextTick(() => {
+        initProgressTableScroll()
+      })
+    })
+    .catch((err) => {
+      console.error('鑾峰彇鐢熶骇璁㈠崟瀹屾垚杩涘害缁熻澶辫触:', err)
+    })
+}
+
+onMounted(() => {
+  progressStatisticsInfo()
+})
+
+onBeforeUnmount(() => {
+  if (progressTableScrollTimer.value) {
+    cancelAnimationFrame(progressTableScrollTimer.value)
+  }
+  if (tableScrollTimeout.value) clearTimeout(tableScrollTimeout.value)
+  const tableContainer = progressTableRef.value
+  if (tableContainer?._pauseTimer) {
+    clearInterval(tableContainer._pauseTimer)
+  }
+})
+</script>
+
+<style scoped>
+.main-panel {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+.panel-item-customers {
+  border: 1px solid #1a58b0;
+  padding: 18px;
+  width: 100%;
+  height: 428px;
+}
+
+.progress-table-container {
+  height: 320px;
+  overflow-y: auto;
+  overflow-x: hidden;
+  margin-top: 10px;
+  scrollbar-width: none;
+  -ms-overflow-style: none;
+}
+
+.progress-table-container::-webkit-scrollbar {
+  display: none;
+}
+
+.progress-table {
+  width: 100%;
+  border-collapse: collapse;
+  color: #b8c8e0;
+  font-size: 12px;
+  table-layout: fixed;
+}
+
+.progress-table thead {
+  position: sticky;
+  top: 0;
+  background-color: rgba(26, 88, 176, 0.9);
+  z-index: 10;
+}
+
+.progress-table th {
+  padding: 8px 6px;
+  text-align: left;
+  font-weight: 500;
+  border-bottom: 1px solid rgba(184, 200, 224, 0.3);
+  color: #b8c8e0;
+  font-size: 12px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.progress-table th:nth-child(1) {
+  width: 15%;
+}
+
+.progress-table th:nth-child(2) {
+  width: 15%;
+}
+
+.progress-table th:nth-child(3) {
+  width: 15%;
+}
+
+.progress-table th:nth-child(4) {
+  width: 12%;
+}
+
+.progress-table th:nth-child(5) {
+  width: 12%;
+}
+
+.progress-table th:nth-child(6) {
+  width: 31%;
+}
+
+.progress-table td {
+  padding: 8px 6px;
+  border-bottom: 1px solid rgba(184, 200, 224, 0.1);
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  font-size: 12px;
+  transition: opacity 0.3s ease;
+}
+
+.progress-table tbody tr:hover {
+  background-color: rgba(184, 200, 224, 0.1);
+}
+
+.progress-table tbody tr.row-under-header {
+  opacity: 0.5;
+}
+
+.progress-table :deep(.el-progress) {
+  width: 100%;
+}
+
+.progress-table :deep(.el-progress-bar__outer) {
+  background-color: rgba(184, 200, 224, 0.2);
+}
+
+.progress-table :deep(.el-progress__text) {
+  color: #b8c8e0;
+  font-size: 11px;
+}
+</style>

--
Gitblit v1.9.3