gongchunyi
2026-05-28 23fc9eb0f2a9d3822cf21894ff25f551751f7d4e
src/views/reportAnalysis/productionAnalysis/components/center-bottom.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,362 @@
<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, inject, watch } 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 stopProgressTableScroll = () => {
  if (progressTableScrollTimer.value) {
    cancelAnimationFrame(progressTableScrollTimer.value)
    progressTableScrollTimer.value = null
  }
  const tableContainer = progressTableRef.value
  if (tableContainer?._pauseTimer) {
    clearInterval(tableContainer._pauseTimer)
    tableContainer._pauseTimer = null
  }
}
const initProgressTableScroll = () => {
  const tableContainer = progressTableRef.value
  if (!tableContainer) return
  stopProgressTableScroll()
  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) => {
      stopProgressTableScroll()
      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)
    })
}
const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
if (dataDashboardRefreshTick) {
  watch(dataDashboardRefreshTick, () => {
    progressStatisticsInfo()
  })
}
onMounted(() => {
  progressStatisticsInfo()
})
onBeforeUnmount(() => {
  stopProgressTableScroll()
  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>