spring
16 小时以前 b7604dde06685e5c42a6f7d375a26741c7b600fd
fix: 基础大屏页面重构90%
已添加10个文件
已修改1个文件
5643 ■■■■■ 文件已修改
src/assets/BI/玫瑰图边框.png 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/DateTypeSwitch.vue 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/PanelHeader.vue 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/basic/center-bottom.vue 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/basic/center-top.vue 466 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/basic/left-bottom.vue 234 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/basic/left-top.vue 215 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue 299 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue 348 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/index.vue 1769 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/index0.vue 2036 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/õ¹åͼ±ß¿ò.png
src/views/reportAnalysis/dataDashboard/components/DateTypeSwitch.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,94 @@
<template>
  <el-radio-group
    v-model="currentValue"
    class="date-type-switch"
    @change="handleChange"
  >
    <el-radio-button :label="1">周</el-radio-button>
    <el-radio-button :label="2">月</el-radio-button>
    <el-radio-button :label="3">季度</el-radio-button>
  </el-radio-group>
</template>
<script setup>
import { ref, watch } from 'vue'
const props = defineProps({
  modelValue: {
    type: Number,
    default: 1, // é»˜è®¤é€‰ä¸­"周"
  },
})
const emit = defineEmits(['update:modelValue', 'change'])
const currentValue = ref(props.modelValue)
// ç›‘听外部值变化
watch(
  () => props.modelValue,
  (newVal) => {
    currentValue.value = newVal
  }
)
// å¤„理值变化
const handleChange = (value) => {
  emit('update:modelValue', value)
  emit('change', value)
}
</script>
<style scoped>
.date-type-switch {
  display: inline-flex;
}
/* æœªé€‰ä¸­çŠ¶æ€çš„æ ·å¼ */
.date-type-switch :deep(.el-radio-button__inner) {
  background-color: rgba(26, 88, 176, 0.3);
  color: rgba(184, 200, 224, 0.8);
  border-color: rgba(255, 255, 255, 0.2);
  border-radius: 0;
  padding: 6px 20px;
  font-size: 14px;
  transition: all 0.3s;
}
/* ç¬¬ä¸€ä¸ªæŒ‰é’®å·¦ä¾§åœ†è§’ */
.date-type-switch :deep(.el-radio-button:first-child .el-radio-button__inner) {
  border-top-left-radius: 4px;
  border-bottom-left-radius: 4px;
}
/* æœ€åŽä¸€ä¸ªæŒ‰é’®å³ä¾§åœ†è§’ */
.date-type-switch :deep(.el-radio-button:last-child .el-radio-button__inner) {
  border-top-right-radius: 4px;
  border-bottom-right-radius: 4px;
}
/* æŒ‰é’®ä¹‹é—´çš„分隔线 */
.date-type-switch :deep(.el-radio-button:not(:last-child) .el-radio-button__inner) {
  border-right: 1px solid rgba(255, 255, 255, 0.2);
}
/* é€‰ä¸­çŠ¶æ€çš„æ ·å¼ */
.date-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
  background: linear-gradient(180deg, #3378ff 0%, #00a4ed 100%);
  color: #ffffff;
  border-color: rgba(51, 120, 255, 0.8);
  box-shadow: none;
}
/* æ‚¬åœæ•ˆæžœ */
.date-type-switch :deep(.el-radio-button__inner:hover) {
  color: rgba(184, 200, 224, 1);
  border-color: rgba(255, 255, 255, 0.3);
}
/* é€‰ä¸­çŠ¶æ€æ‚¬åœ */
.date-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner:hover) {
  background: linear-gradient(180deg, #4e8aff 0%, #4ee4ff 100%);
  color: #ffffff;
}
</style>
src/views/reportAnalysis/dataDashboard/components/PanelHeader.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
<template>
  <div class="panel-header">
    <span class="panel-title">{{ title }}</span>
  </div>
</template>
<script setup>
defineProps({
  title: {
    type: String,
    required: true,
    default: ''
  }
})
</script>
<style scoped>
.panel-header {
  background-image: url("@/assets/BI/kehuhetongback@2x.png");
  background-size: 100% 100%;
  background-position: center;
  background-repeat: no-repeat;
}
.panel-title {
  width: 100%;
  font-weight: 500;
  font-size: 16px;
  color: #D9ECFF;
  padding-left: 46px;
  line-height: 36px;
}
</style>
src/views/reportAnalysis/dataDashboard/components/basic/center-bottom.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,149 @@
<template>
  <div>
    <PanelHeader title="人员分布" />
    <div class="main-panel panel-item-customers">
      <Echarts
        :chartStyle="chartStyle"
        :legend="pieLegend"
        :series="pieSeries"
        :tooltip="pieTooltip"
        :color="pieColors"
        :options="pieOptions"
        style="height: 320px"
      />
    </div>
  </div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
import { getProgressStatistics } from '@/api/viewIndex.js'
import PanelHeader from '../PanelHeader.vue'
import Echarts from '@/components/Echarts/echarts.vue'
/**
 * @introduction æŠŠæ•°ç»„中key值相同的那一项提取出来,组成一个对象
 * @description è¯¦ç»†æè¿°
 * @param {参数类型} array ä¼ å…¥çš„æ•°ç»„ [{a:"1",b:"2"},{a:"2",b:"3"}]
 * @param {参数类型} key  å±žæ€§å a
 * @return {返回类型说明}
 * @exception [违例类型] [违例类型说明]
 */
function array2obj(array, key) {
  const resObj = {}
  for (let i = 0; i < array.length; i++) {
    resObj[array[i][key]] = array[i]
  }
  return resObj
}
const chartStyle = {
  width: '100%',
  height: '100%',
}
// é¥¼å›¾æ•°æ®ï¼ˆç¤ºä¾‹ï¼‰
const pieDatas = [
  { value: 335, name: '进入区域', percent: '10' },
  { value: 310, name: '区域入侵', percent: '40' },
  { value: 274, name: '人员聚集', percent: '30' },
  { value: 235, name: '越界侦测', percent: '20' },
]
const pieColors = ['#D1E4F5', '#5782F7', '#2F67EF', '#82BAFF']
const pieLegendData = pieDatas.map((d, idx) => ({
  name: d.name,
  icon: 'circle',
  textStyle: {
    fontSize: 18,
    color: pieColors[idx],
  },
}))
const pieObjData = array2obj(pieDatas, 'name')
const pieLegend = {
  orient: 'vertical',
  top: 'center',
  left: '50%',
  itemGap: 30,
  data: pieLegendData,
  formatter: function (name) {
    return `{title|${name}}{value|${pieObjData[name].value}}{unit|人}{percent|${pieObjData[name].percent}}{unit|%}`
  },
  textStyle: {
    rich: {
      value: {
        color: '#43e8fc',
        fontSize: 18,
        fontWeight: 600,
        padding: [0, 10, 0, 30],
      },
      unit: {
        color: '#82baff',
        fontSize: 14,
        fontWeight: 600,
        padding: [0, 50, 0, 0],
      },
      percent: {
        color: '#43e8fc',
        fontSize: 18,
        fontWeight: 600,
        padding: [0, 10, 0, 0],
      },
      title: {
        fontSize: 18,
        padding: [0, 0, 0, 0],
      },
    },
  },
}
const pieTooltip = {
  trigger: 'item',
  formatter: '{a} <br/>{b} : {c} ({d}%)',
}
const pieSeries = ref([
  {
    name: '访问来源',
    type: 'pie',
    radius: '70%',
    center: ['20%', '50%'],
    itemStyle: {
      // ç»™æ¯ä¸ªæ‰‡åŒºåŠ åˆ†éš”ç¼éš™ï¼Œé¢œè‰²å–æ·±è‰²èƒŒæ™¯
      borderColor: '#0a1c3a',
      borderWidth: 4,
    },
    label: {
      show: false
    },
    data: pieDatas,
    roseType: 'radius',
    animationType: 'scale',
    animationEasing: 'elasticOut',
    animationDelay: function () {
      return Math.random() * 200
    },
  },
])
const pieOptions = {
  backgroundColor: 'transparent',
  textStyle: { color: '#B8C8E0' },
}
</script>
<style scoped>
.main-panel {
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.panel-item-customers {
  border: 1px solid #1a58b0;
  padding: 18px;
  width: 100%;
  height: 370px;
}
</style>
src/views/reportAnalysis/dataDashboard/components/basic/center-top.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,466 @@
<template>
  <div>
    <!-- é¡¶éƒ¨ç»Ÿè®¡å¡ç‰‡ -->
    <div class="stats-cards">
      <div class="stat-card">
        <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
        <div class="card-content">
          <span class="card-label">员工总数</span>
          <span class="card-value">{{ totalStaff }}</span>
        </div>
      </div>
      <div class="stat-card">
        <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
        <div class="card-content">
          <span class="card-label">客户总数</span>
          <span class="card-value">{{ totalCustomers }}</span>
        </div>
      </div>
      <div class="stat-card">
        <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
        <div class="card-content">
          <span class="card-label">供应商总数</span>
          <span class="card-value">{{ totalSuppliers }}</span>
        </div>
      </div>
    </div>
    <!-- è®¾å¤‡ç»Ÿè®¡ -->
    <div class="equipment-stats">
      <div class="equipment-header">
        <img
          src="@/assets/BI/shujutongjiicon@2x.png"
          alt="图标"
          class="equipment-icon"
        />
        <span class="equipment-title">设备统计</span>
      </div>
      <div class="equipment-items">
        <div class="equipment-item">
          <span class="equipment-value">{{ equipmentNum }}</span>
          <span class="equipment-label">设备总数</span>
        </div>
        <div class="equipment-item">
          <span class="equipment-value">{{ equipmentRepair }}</span>
          <span class="equipment-label">待维修设备</span>
        </div>
        <div class="equipment-item">
          <span class="equipment-value">{{ equipmentMaintain }}</span>
          <span class="equipment-label">待保养设备</span>
        </div>
        <div class="equipment-item">
          <span class="equipment-value">{{ totalMeasuring }}</span>
          <span class="equipment-label">计量器具总数</span>
        </div>
      </div>
    </div>
    <!-- äº‹ä»¶åç§° -->
    <div class="event-info">
      <div class="event-header">
        <img
          src="@/assets/BI/shijianmingxiicon@2x.png"
          alt="图标"
          class="event-icon"
        />
        <span class="event-title">事件名称</span>
      </div>
      <div class="event-content">
        <ul class="todo-list" v-if="todoList.length > 0" ref="refTodoList">
          <li v-for="item in todoList" :key="item.id">
            <div
              style="
                display: flex;
                flex-direction: column;
                justify-content: space-between;
                width: 100%;
                gap: 20px;
              "
            >
            <div class="todo-division">待办事由:{{ item.approveReason }}</div>
              <div style="display: flex;justify-content: space-between;align-items: center;"
              >
                <div class="todo-title">申请类型:{{ item.approveId }}</div>
                <div class="todo-division">申请部门:{{ item.approveDeptName }}</div>
                <div class="todo-time">{{ item.approveTime }}</div>
              </div>
            </div>
          </li>
        </ul>
        <div v-else style="text-align: center">暂无数据</div>
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
import { homeTodos } from '@/api/viewIndex.js'
import { staffOnJobListPage } from '@/api/personnelManagement/employeeRecord.js'
import { listCustomer } from '@/api/basicData/customerFile.js'
import { listSupplier } from '@/api/basicData/supplierManageFile.js'
import { getLedgerPage } from '@/api/equipmentManagement/ledger.js'
import { getRepairPage } from '@/api/equipmentManagement/repair.js'
import { getUpkeepPage } from '@/api/equipmentManagement/upkeep.js'
import { measuringInstrumentListPage } from '@/api/equipmentManagement/measurementEquipment.js'
// ç»Ÿè®¡æ•°æ®
const totalStaff = ref(0)
const totalCustomers = ref(0)
const totalSuppliers = ref(0)
const equipmentNum = ref(0)
const equipmentRepair = ref(0)
const equipmentMaintain = ref(0)
const totalMeasuring = ref(0)
// å¾…办事项
const todoList = ref([])
const refTodoList = ref(null)
// èŽ·å–å‘˜å·¥ã€å®¢æˆ·ã€ä¾›åº”å•†æ•°é‡
const getNum = () => {
  const params = {
    pageNum: -1,
    pageSize: -1,
  }
  staffOnJobListPage({ ...params, staffState: 1 }).then((res) => {
    totalStaff.value = res.data.total
  })
  listCustomer(params).then((res) => {
    totalCustomers.value = res.total
  })
  listSupplier(params).then((res) => {
    totalSuppliers.value = res.data.total
  })
}
// èŽ·å–è®¾å¤‡ç›¸å…³æ•°é‡
const getLedgerNum = () => {
  const params = {
    pageNum: -1,
    pageSize: -1,
  }
  getLedgerPage(params).then((res) => {
    equipmentNum.value = res.data.total
  })
  getRepairPage({ ...params, status: 0 }).then((res) => {
    equipmentRepair.value = res.data.total
  })
  getUpkeepPage({ ...params, status: 0 }).then((res) => {
    equipmentMaintain.value = res.data.total
  })
  measuringInstrumentListPage(params).then((res) => {
    totalMeasuring.value = res.data.total
  })
}
// åˆå§‹åŒ–待办事项列表滚动功能
const initTodoListScroll = () => {
  const todoListEl = refTodoList.value
  // å¼ºåˆ¶å¯ç”¨æ»šåŠ¨ï¼Œä¸æ£€æŸ¥ä»»ä½•æ¡ä»¶
  if (todoListEl) {
    // åˆ›å»ºä¸€ä¸ªå…‹éš†é¡¹ï¼Œç”¨äºŽå®žçŽ°æ— ç¼æ»šåŠ¨
    const scrollItems = Array.from(todoListEl.querySelectorAll('li'))
    if (scrollItems.length > 0) {
      // ç¡®ä¿æœ‰è¶³å¤Ÿçš„项目用于滚动
      // å¦‚果项目太少,多复制几次以确保滚动效果
      if (scrollItems.length < 4) {
        const originalItems = [...scrollItems]
        for (let i = 0; i < 4; i++) {
          originalItems.forEach((item) => {
            const clone = item.cloneNode(true)
            todoListEl.appendChild(clone)
          })
        }
        // é‡æ–°èŽ·å–æ‰€æœ‰é¡¹ç›®
        scrollItems.push(
          ...Array.from(todoListEl.querySelectorAll('li')).slice(
            scrollItems.length
          )
        )
      }
      const itemHeight = scrollItems[0]?.offsetHeight || 0
      const containerHeight = todoListEl.clientHeight
      const cloneCount = Math.ceil(containerHeight / itemHeight) + 2
      // å…‹éš†å‰å‡ ä¸ªé¡¹ç›®å¹¶æ·»åŠ åˆ°åˆ—è¡¨æœ«å°¾ï¼Œå®žçŽ°æ— ç¼æ»šåŠ¨
      for (let i = 0; i < cloneCount; i++) {
        const clone = scrollItems[i % scrollItems.length].cloneNode(true)
        todoListEl.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) // æ ‡å‡†åŒ–为60fps的速度
          // å½“滚动超过原始内容长度时,重置位置实现无缝滚动
          const maxScroll = Math.max(
            todoListEl.scrollHeight -
              containerHeight -
              cloneCount * itemHeight,
            itemHeight * scrollItems.length
          )
          if (scrollPosition >= maxScroll) {
            scrollPosition = 0
            todoListEl.scrollTop = 0
          } else {
            todoListEl.scrollTop = scrollPosition
          }
        }
        todoListEl._animationFrame = requestAnimationFrame(scrollAnimation)
      }
      // å¯åŠ¨æ»šåŠ¨åŠ¨ç”»
      todoListEl._animationFrame = requestAnimationFrame(scrollAnimation)
      // è®¾ç½®æ»šåЍ-暂停-滚动的循环效果
      const pauseTimer = setInterval(() => {
        isPaused = !isPaused
      }, pauseTime)
      // æ¸…理定时器
      todoListEl._pauseTimer = pauseTimer
    }
  }
}
// å¾…办事项
const todoInfoS = () => {
  homeTodos().then((res) => {
    todoList.value = res.data
    // åœ¨èŽ·å–åˆ°å¾…åŠžäº‹é¡¹æ•°æ®åŽï¼Œåˆå§‹åŒ–æ»šåŠ¨åŠŸèƒ½
    nextTick(() => {
      initTodoListScroll()
    })
  })
}
onMounted(() => {
  getNum()
  getLedgerNum()
  todoInfoS()
})
onBeforeUnmount(() => {
  // æ¸…理待办事项列表的动画和定时器
  const todoListEl = refTodoList.value
  if (todoListEl) {
    if (todoListEl._animationFrame) {
      cancelAnimationFrame(todoListEl._animationFrame)
      todoListEl._animationFrame = null
    }
    if (todoListEl._pauseTimer) {
      clearInterval(todoListEl._pauseTimer)
      todoListEl._pauseTimer = null
    }
  }
})
</script>
<style scoped>
.stats-cards {
  display: flex;
  gap: 30px;
}
.stat-card {
  flex: 1;
  display: flex;
  align-items: center;
  background-image: url('@/assets/BI/border@2x.png');
  background-size: 100% 100%;
  background-position: center;
  background-repeat: no-repeat;
  height: 142px;
}
.card-icon {
  width: 100px;
  height: 100px;
  margin: 20px 20px 0 10px;
}
.card-content {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.card-value {
  font-weight: 500;
  font-size: 40px;
  background: linear-gradient(360deg, #008bfd 0%, #ffffff 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}
.card-label {
  font-weight: 400;
  font-size: 19px;
  color: rgba(208, 231, 255, 0.7);
}
.equipment-stats {
  border: 1px solid #1a58b0;
  padding: 18px;
  height: 240px;
}
.equipment-header {
  font-weight: 500;
  font-size: 21px;
  display: flex;
  border-bottom: 1px solid;
  border-image: linear-gradient(
      270deg,
      rgba(0, 126, 255, 0) 0%,
      rgba(0, 126, 255, 0.4549) 35%,
      #007eff 78%,
      #007eff 100%
    )
    1;
  padding-bottom: 2px;
}
.equipment-title {
  font-weight: 500;
  font-size: 18px;
  background: linear-gradient(360deg, #056dff 0%, #43e8fc 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  line-height: 50px;
}
.equipment-icon {
  width: 50px;
  height: 50px;
}
.equipment-items {
  display: flex;
  justify-content: space-around;
  gap: 30px;
}
.equipment-item {
  text-align: center;
}
.equipment-value {
  display: block;
  font-weight: 500;
  font-size: 40px;
  color: #ffffff;
  width: 120px;
  height: 110px;
  line-height: 110px;
  background-image: url('@/assets/BI/shujutongji@2x.png');
  background-size: 100% 100%;
  background-position: center;
  background-repeat: no-repeat;
  margin-bottom: 8px;
}
.equipment-label {
  font-weight: 500;
  font-size: 16px;
  color: #fffffe;
}
.event-info {
  background-image: url('@/assets/BI/shijianmingchengbeijing@2x.png');
  background-size: 100% 100%;
  background-position: center;
  background-repeat: no-repeat;
  padding: 20px;
  height: 186px;
}
.event-header {
  display: flex;
  align-items: center;
}
.event-icon {
  width: 40px;
  height: 40px;
}
.event-title {
  font-weight: 500;
  font-size: 18px;
  color: #fffffe;
  line-height: 30px;
}
.todo-list {
  list-style: none;
  padding: 0;
  margin: 0;
  height: 120px; /* æŒ‰ç”¨æˆ·è¦æ±‚调整高度 */
  overflow: hidden;
  font-size: 15px;
}
.todo-list li {
  border-radius: 8px;
  margin-bottom: 12px;
  padding: 12px 40px;
  height: 74px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.todo-title {
  font-weight: 400;
  font-size: 16px;
  color: #fffffe;
  position: relative;
}
.todo-division {
  font-weight: 400;
  font-size: 16px;
  color: #fffffe;
  position: relative;
}
.todo-division::before {
  content: '';
  position: absolute;
  left: -20px;
  top: 50%;
  transform: translateY(-50%);
  width: 6px;
  height: 6px;
  background: #498ceb;
  border-radius: 50%;
}
.todo-time {
  font-weight: 400;
  font-size: 16px;
  color: #fffffe;
  background: rgba(24, 93, 190, 0.4);
border-radius: 5px 5px 5px 5px;
padding: 5px 10px;
}
</style>
src/views/reportAnalysis/dataDashboard/components/basic/left-bottom.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,234 @@
<template>
  <div>
    <PanelHeader title="客户营收贡献数值分析" />
    <div class="main-panel panel-item-customers">
      <div class="filters-row">
        <el-select
          v-model="customerValue"
          class="customer-select"
          placeholder="请选择客户"
          clearable
          filterable
          @change="handleFilterChange"
        >
          <el-option
            v-for="item in customerOptions"
            :key="item.value"
            :label="item.label"
            :value="item.value"
          />
        </el-select>
        <DateTypeSwitch v-model="dateType" @change="handleFilterChange" />
      </div>
      <Echarts
          ref="chart"
          :chartStyle="chartStyle"
          :grid="grid"
          :legend="barLegend"
          :series="barSeries1"
          :tooltip="tooltip"
          :xAxis="xAxis1"
          :yAxis="yAxis1"
          :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }"
          style="height: 260px"
        />
    </div>
  </div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import Echarts from '@/components/Echarts/echarts.vue'
import PanelHeader from '../PanelHeader.vue'
import DateTypeSwitch from '../DateTypeSwitch.vue'
import { qualityStatistics } from '@/api/viewIndex.js'
import { listCustomer } from '@/api/basicData/customerFile.js'
const dateType = ref(1) // 1=周 2=月 3=季度
const customerValue = ref(null)
const customerOptions = ref([])
// è´¨æ£€ç»Ÿè®¡å¯¹è±¡
const qualityStatisticsObject = ref({
  supplierNum: 0,
  processNum: 0,
  factoryNum: 0,
})
const chartStyle = {
  width: '100%',
  height: '150%',
}
const grid = {
  left: '3%',
  right: '4%',
  bottom: '3%',
  containLabel: true,
}
const barLegend = {
  show: false,
  textStyle: { color: '#B8C8E0' },
  data: ['营收'],
}
const barSeries1 = ref([
  {
    name: '营收',
    type: 'bar',
    barGap: 0,
    emphasis: {
      focus: 'series',
    },
    itemStyle: {
      color: {
        type: 'linear',
        x: 0,
        y: 1,
        x2: 0,
        y2: 0,
        colorStops: [
          // linear-gradient(360deg, rgba(0,164,237,0) 0%, #4EE4FF 100%)
          { offset: 0, color: 'rgba(0,164,237,0)' },
          { offset: 1, color: '#4EE4FF' },
        ],
      },
    },
    data: [],
  },
])
const tooltip = {
  trigger: 'axis',
  axisPointer: {
    type: 'shadow',
  },
  formatter: function (params) {
    let result = params[0].axisValueLabel + '<br/>'
    params.forEach((item) => {
      result += `<div style="color: #B8C8E0">${item.marker} ${item.seriesName}: ${item.value}</div>`
    })
    return result
  },
}
const xAxis1 = ref([
  {
    type: 'category',
    axisTick: { show: false },
    axisLabel: { color: '#B8C8E0' },
    data: [],
  },
])
const yAxis1 = [
  {
    type: 'value',
    axisLabel: { color: '#B8C8E0' },
  },
]
// è´¨æ£€ç»Ÿè®¡
const qualityStatisticsInfo = () => {
  qualityStatistics()
    .then((res) => {
      // åˆ‡æ¢ç­›é€‰æ¡ä»¶æ—¶ï¼Œå…ˆæ¸…空再填充,避免重复 push
      xAxis1.value[0].data = []
      barSeries1.value[0].data = []
      res.data.item.forEach((item) => {
        xAxis1.value[0].data.push(item.date)
        // è¿™é‡Œæš‚用 supplierNum ä½œä¸ºæŸ±çŠ¶å›¾æ•°å€¼ï¼ˆæŽ¥å£è¿”å›žé‡Œå½“å‰ä¹Ÿæœ‰è¿™ä¸‰ä¸ªå­—æ®µï¼‰
        barSeries1.value[0].data.push(item.supplierNum)
      })
      qualityStatisticsObject.value.supplierNum = res.data.supplierNum
      qualityStatisticsObject.value.processNum = res.data.processNum
      qualityStatisticsObject.value.factoryNum = res.data.factoryNum
    })
    .catch((error) => {
      console.error('获取质检统计失败:', error)
    })
}
const fetchCustomerOptions = async () => {
  try {
    const params = { pageNum: 1, pageSize: 200 }
    const res = await listCustomer(params)
    const records = res?.records || res?.data?.records || res?.rows || []
    customerOptions.value = records.map((r) => ({
      label: r.customerName || r.name || r.customer || '-',
      value: r.id ?? r.customerId ?? r.customerCode ?? r.customerName,
    }))
  } catch (e) {
    // æŽ¥å£å¼‚常时给一组模拟客户,保证UI可用
    customerOptions.value = [
      { label: '华东精密', value: '华东精密' },
      { label: '星辰电子', value: '星辰电子' },
      { label: '启航科技', value: '启航科技' },
      { label: '铭诚制造', value: '铭诚制造' },
      { label: '远景材料', value: '远景材料' },
    ]
  }
}
const handleFilterChange = () => {
  // ç›®å‰ qualityStatistics æŽ¥å£æœªæºå¸¦ç­›é€‰å‚数,这里先统一触发刷新,避免重复数据
  // è‹¥åŽç«¯åŽç»­æ”¯æŒ customerId/type,可在 qualityStatistics() å¤„改为传参
  qualityStatisticsInfo()
}
onMounted(() => {
  fetchCustomerOptions()
  qualityStatisticsInfo()
})
</script>
<style scoped>
.main-panel {
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.filters-row {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
}
.customer-select {
  width: 180px;
}
/* ä¸‹æ‹‰æ¡†é£Žæ ¼ï¼šä¸Ž DateTypeSwitch ä¿æŒä¸€è‡´ï¼ˆæ·±è‰²åŠé€æ˜Žã€æµ…色文字、细边框) */
.customer-select :deep(.el-input__wrapper),
.customer-select :deep(.el-select__wrapper) {
  background-color: rgba(26, 88, 176, 0.3);
  border: 1px solid rgba(255, 255, 255, 0.2);
  box-shadow: none;
}
.customer-select :deep(.el-input__inner) {
  color: rgba(184, 200, 224, 0.9);
}
.customer-select :deep(.el-input__inner::placeholder) {
  color: rgba(184, 200, 224, 0.6);
}
.customer-select :deep(.el-select__caret),
.customer-select :deep(.el-icon) {
  color: rgba(184, 200, 224, 0.8);
}
.panel-item-customers {
  border: 1px solid #1a58b0;
  padding: 18px;
  width: 100%;
  height: 478px;
}
</style>
src/views/reportAnalysis/dataDashboard/components/basic/left-top.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,215 @@
<template>
  <div>
    <PanelHeader title="产品大类" />
    <div class="panel-item-customers">
      <div style="height: 70%">
        <Echarts
          ref="chart"
          :chartStyle="chartStyle"
          :grid="grid"
          :legend="workInProcessBarLegend"
          :series="workInProcessBarSeries"
          :tooltip="tooltip"
          :xAxis="workInProcessXAxis"
          :yAxis="workInProcessYAxis"
          :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }"
          style="height: 100%"
          class="work-in-process-chart"
        />
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import Echarts from '@/components/Echarts/echarts.vue'
import PanelHeader from '../PanelHeader.vue'
import { getWorkInProcessTurnover } from '@/api/viewIndex.js'
// åœ¨åˆ¶å“å‘¨è½¬ç»Ÿè®¡å¯¹è±¡
const workInProcessStatistics = ref({
  totalQuantity: 0,
  avgTurnoverDays: 0,
  turnoverEfficiency: 0,
})
// åœ¨åˆ¶å“å·¥åºæŸ±çŠ¶å›¾é…ç½®
const workInProcessXAxis = ref([
  {
    type: 'category',
    axisTick: { show: false },
    axisLabel: { color: '#B8C8E0' },
    data: [],
  },
])
const workInProcessYAxis = [
  {
    type: 'value',
    axisLabel: { color: '#B8C8E0' },
    name: '',
  },
]
const workInProcessBarLegend = {
  show: false,
  textStyle: { color: '#B8C8E0' },
  data: [],
}
const workInProcessBarSeries = ref([
  {
    name: '在制品数量',
    type: 'bar',
    barWidth: 25,
    barGap: 0,
    emphasis: {
      focus: 'series',
    },
    itemStyle: {
      color: {
        type: 'linear',
        x: 0,
        y: 0,
        x2: 0,
        y2: 1,
        colorStops: [
          { offset: 0, color: '#4EE4FF' },
          { offset: 1, color: '#00A4ED' },
        ],
      },
    },
    label: {
      show: true,
      position: 'top',
      color: '#B8C8E0',
    },
    data: [],
  },
])
const chartStyle = {
  width: '100%',
  height: '115%',
}
const grid = {
  left: '3%',
  right: '4%',
  bottom: '3%',
  containLabel: true,
}
const tooltip = {
  trigger: 'axis',
  axisPointer: {
    type: 'shadow',
  },
  formatter: function (params) {
    let result = params[0].axisValueLabel + '<br/>'
    params.forEach((item) => {
      result += `<div style="color: #B8C8E0">${item.marker} ${item.seriesName}: ${item.value}</div>`
    })
    return result
  },
}
// åœ¨åˆ¶å“å‘¨è½¬ç»Ÿè®¡
const workInProcessTurnoverInfo = () => {
  getWorkInProcessTurnover()
    .then((res) => {
      console.log('在制品周转统计数据:', res)
      if (!res || !res.data) {
        console.warn('在制品周转统计数据为空')
        return
      }
      // ä»ŽæŽ¥å£èŽ·å–ç»Ÿè®¡æ•°æ®
      workInProcessStatistics.value = {
        totalQuantity: res.data.totalOrderCount || 0,
        avgTurnoverDays: res.data.averageTurnoverDays || 0,
        turnoverEfficiency: res.data.turnoverEfficiency || 0,
      }
      // è®¾ç½®å·¥åºæŸ±çŠ¶å›¾æ•°æ®
      // X轴:processDetails (工序详情数组)
      // Y轴:processQuantityDetails (工序数量详情数组)
      if (res.data.processDetails && Array.isArray(res.data.processDetails)) {
        // è®¾ç½®X轴数据(工序名称)
        workInProcessXAxis.value[0].data = res.data.processDetails
      } else {
        workInProcessXAxis.value[0].data = []
      }
      if (
        res.data.processQuantityDetails &&
        Array.isArray(res.data.processQuantityDetails)
      ) {
        // è®¾ç½®Y轴数据(在制品数量)
        workInProcessBarSeries.value[0].data = res.data.processQuantityDetails
      } else {
        workInProcessBarSeries.value[0].data = []
      }
    })
    .catch((error) => {
      console.error('获取在制品周转统计失败:', error)
    })
}
onMounted(() => {
  workInProcessTurnoverInfo()
})
</script>
<style scoped>
.panel-item-customers {
  border: 1px solid #1a58b0;
  padding: 18px;
  width: 100%;
  height: 420px;
}
.quality-cards {
  display: flex;
  gap: 12px;
  width: 100%;
  height: 54px;
  justify-content: space-between;
  align-items: center;
}
.quality-cardSec {
  display: flex;
}
.quality-cardTitle {
  font-weight: 400;
  font-size: 14px;
  color: #ffffff;
  display: flex;
  align-items: flex-start;
  flex-direction: column;
}
.quality-card {
  width: 80px;
  height: 60px;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
.quality-card.one {
  background-image: url('@/assets/BI/yuancailiaoyijianicon@2x.png');
}
.quality-card.two {
  background-image: url('@/assets/BI/guochengyijianicon@2x.png');
}
.quality-card.three {
  background-image: url('@/assets/BI/chuchangyijianicon@2x.png');
}
</style>
src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,299 @@
<template>
  <div>
    <PanelHeader title="客户金额贡献排名" />
    <div class="panel-item-customers">
      <div class="switch-container">
        <DateTypeSwitch v-model="dateType" @change="handleDateTypeChange" />
      </div>
      <Echarts
        ref="chart"
        :chartStyle="chartStyle"
        :grid="grid"
        :legend="{ show: false }"
        :series="series"
        :tooltip="tooltip"
        :xAxis="xAxis"
        :yAxis="yAxis"
        :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }"
        style="height: 360px"
      />
    </div>
  </div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import Echarts from '@/components/Echarts/echarts.vue'
import PanelHeader from '../PanelHeader.vue'
import DateTypeSwitch from '../DateTypeSwitch.vue'
const chartStyle = {
  width: '100%',
  height: '100%',
}
const dateType = ref(1) // 1=周 2=月 3=季度
// é£žæœºå›¾æ ‡ SVG path(与 right-top ä¸€è‡´ï¼‰
const aircraft =
  'path://M107.000,71.000 C104.936,71.000 102.665,70.806 100.273,70.467 C94.592,76.922 86.275,81.000 77.000,81.000 C70.794,81.000 65.020,79.170 60.172,76.029 C66.952,74.165 72.647,69.714 76.173,63.817 C69.821,61.362 64.063,58.593 60.000,56.039 L60.000,52.813 C70.456,53.950 80.723,55.000 83.000,55.000 C88.972,55.000 93.000,53.723 93.000,50.000 C93.000,47.071 89.222,45.000 83.000,45.000 C80.723,45.000 70.456,46.050 60.000,47.187 L60.000,43.989 C64.057,41.431 69.807,38.644 76.168,36.173 C72.641,30.281 66.948,25.834 60.172,23.971 C65.020,20.830 70.794,19.000 77.000,19.000 C86.270,19.000 94.584,23.074 100.265,29.524 C102.647,29.191 104.918,29.000 107.000,29.000 C129.644,29.000 148.000,50.000 148.000,50.000 C148.000,50.000 129.644,71.000 107.000,71.000 ZM113.000,38.000 C106.373,38.000 101.000,43.373 101.000,50.000 C101.000,56.627 106.373,62.000 113.000,62.000 C119.627,62.000 125.000,56.627 125.000,50.000 C125.000,43.373 119.627,38.000 113.000,38.000 ZM113.000,56.000 C109.686,56.000 107.000,53.314 107.000,50.000 C107.000,46.686 109.686,44.000 113.000,44.000 C116.314,44.000 119.000,46.686 119.000,50.000 C119.000,53.314 116.314,56.000 113.000,56.000 ZM110.500,19.000 C109.567,19.000 108.763,18.483 108.334,17.726 C100.231,9.857 89.187,5.000 77.000,5.000 C64.813,5.000 53.769,9.857 45.666,17.726 C45.237,18.483 44.433,19.000 43.500,19.000 C42.119,19.000 41.000,17.881 41.000,16.500 C41.000,15.847 41.256,15.259 41.665,14.813 L41.575,14.718 C50.629,5.628 63.156,-0.000 77.000,-0.000 C90.844,-0.000 103.371,5.628 112.425,14.718 L112.335,14.813 C112.744,15.259 113.000,15.847 113.000,16.500 C113.000,17.881 111.881,19.000 110.500,19.000 ZM53.000,49.484 C61.406,48.626 77.810,47.000 81.345,47.000 C87.353,47.000 91.000,48.243 91.000,50.000 C91.000,52.234 87.111,53.000 81.345,53.000 C77.810,53.000 61.406,51.374 53.000,50.516 L53.000,49.484 ZM53.000,47.000 L9.000,50.000 L53.000,53.000 L53.000,56.000 L-0.000,50.000 L53.000,44.000 L53.000,47.000 ZM43.500,81.000 C44.433,81.000 45.237,81.517 45.666,82.274 C53.769,90.143 64.813,95.000 77.000,95.000 C89.187,95.000 100.231,90.143 108.334,82.274 C108.763,81.517 109.567,81.000 110.500,81.000 C111.881,81.000 113.000,82.119 113.000,83.500 C113.000,84.153 112.744,84.741 112.335,85.187 L112.425,85.282 C103.371,94.372 90.844,100.000 77.000,100.000 C63.156,100.000 50.629,94.372 41.575,85.282 L41.665,85.187 C41.256,84.741 41.000,84.153 41.000,83.500 C41.000,82.119 42.119,81.000 43.500,81.000 Z'
// é¢œè‰²é…ç½®ï¼ˆä¸Ž right-top ä¸€è‡´ï¼‰
const color = {
  0: '#ff5676',
  1: '#ffd83e',
  2: '#fbff94',
  3: '#7daeff',
}
// åŽŸå§‹æ•°æ®ï¼ˆç»Ÿä¸€æˆ { NAME, NUM })
const dataArr = ref([])
// ä»…保留金额最高的 5 æ¡ï¼ˆå¹¶æŒ‰ä»Žå°åˆ°å¤§å±•示,视觉上最高在最下方)
const dataArray = computed(() => {
  const sortedAsc = [...dataArr.value].sort((a, b) => a.NUM - b.NUM)
  return sortedAsc.length > 5 ? sortedAsc.slice(-5) : sortedAsc
})
const total = computed(() => dataArray.value.reduce((sum, v) => sum + Number(v.NUM || 0), 0))
const xdataName = computed(() => dataArray.value.map((v) => v.NAME))
const dataNum = computed(() => {
  return dataArray.value.map((v, i) => {
    const index = dataArray.value.length - i - 1
    const isTop3 = index < 3
    return {
      value: Number(v.NUM),
      itemStyle: {
        color: {
          type: 'linear',
          x: 1,
          y: 0,
          x2: 0,
          y2: 0,
          colorStops: [
            { offset: 0, color: isTop3 ? '#ffdae1' : '#ecf3ff' },
            { offset: 0.07, color: isTop3 ? color[index] : color[3] },
            {
              offset: 1,
              color: isTop3 ? 'rgba(255, 86, 118, .1)' : 'rgba(125,174,255, .1)',
            },
          ],
          global: false,
        },
        barBorderRadius: [0, 20, 20, 0],
      },
      symbol: isTop3 ? aircraft : 'none',
      symbolPosition: 'end',
      symbolSize: [30, 25],
      symbolOffset: [35, 0],
    }
  })
})
const bgData = computed(() => {
  const maxValue = Math.max(0, ...dataNum.value.map((v) => v.value))
  return dataNum.value.map(() => maxValue + 200)
})
const tooltip = computed(() => ({
  trigger: 'axis',
  textStyle: { fontSize: '100%' },
  formatter: function (params) {
    let result = params[0].axisValueLabel + '<br/>'
    params.forEach((item) => {
      result += `<div style="color: #B8C8E0">${item.marker} ${item.seriesName}: ${item.value}</div>`
    })
    return result
  },
}))
const grid = computed(() => ({ top: 0, left: '20%', right: '10%', bottom: 0 }))
const xAxis = computed(() => [
  {
    splitLine: { show: false },
    axisLine: { show: false },
    axisLabel: { show: false },
    axisTick: { show: false },
  },
])
const yAxis = computed(() => [
  {
    type: 'category',
    inverse: false,
    data: xdataName.value,
    axisLabel: {
      formatter: (params) => `{a|${params}}`,
      rich: {
        a: {
          width: 160,
          fontSize: 14,
          color: '#fff',
          padding: [5, 4, 5, 0],
          align: 'right',
        },
      },
    },
    axisLine: { show: false },
    axisTick: { show: false },
    splitLine: { show: false },
  },
  {
    type: 'category',
    data: dataNum.value.map((item) => item.value),
    axisLabel: {
      formatter: (params, index) => {
        const value = typeof params === 'object' ? params.value : params
        const percent = total.value ? ((value / total.value) * 100).toFixed(0) : 0
        const rank = dataArray.value.length - index
        const isTop3 = rank < 4
        return `{a${isTop3 ? rank : ''}|${percent} }{b${isTop3 ? rank : ''}|%}`
      },
      rich: {
        a: { fontSize: 18, color: '#98bfff', verticalAlign: 'bottom' },
        a1: { fontSize: 18, color: '#ff7f97', verticalAlign: 'bottom' },
        a2: { fontSize: 18, color: '#ffce64', verticalAlign: 'bottom' },
        a3: { fontSize: 18, color: '#e8ed66', verticalAlign: 'bottom' },
        b: { fontSize: 12, color: '#98bfff', verticalAlign: 'bottom' },
        b1: { fontSize: 12, color: '#ff7f97', verticalAlign: 'bottom' },
        b2: { fontSize: 12, color: '#ffce64', verticalAlign: 'bottom' },
        b3: { fontSize: 12, color: '#e8ed66', verticalAlign: 'bottom' },
      },
    },
    axisLine: { show: false },
    axisTick: { show: false },
    splitLine: { show: false },
  },
])
const series = computed(() => [
  {
    name: '金额',
    z: 6,
    type: 'pictorialBar',
    data: dataNum.value,
  },
  {
    name: '背景',
    z: 6,
    type: 'bar',
    barWidth: 25,
    itemStyle: {
      color: 'rgba(255,255,255,.1)',
      barBorderRadius: [0, 20, 20, 0],
    },
    data: bgData.value,
  },
  {
    name: '金额渐变',
    type: 'bar',
    barWidth: 25,
    barGap: '-100%',
    itemStyle: {
      color: {
        type: 'linear',
        x: 1,
        y: 0,
        x2: 0,
        y2: 0,
        colorStops: [
          { offset: 0, color: 'rgba(255, 218, 220)' },
          { offset: 0.07, color: 'rgba(255, 86, 118)' },
          { offset: 1, color: 'rgba(255, 86, 118, 0)' },
        ],
        global: false,
      },
      barBorderRadius: [0, 20, 20, 0],
    },
    data: dataNum.value,
  },
])
const normalizeItem = (item) => {
  const name =
    item?.NAME ??
    item?.name ??
    item?.customerName ??
    item?.customer ??
    item?.label ??
    '-'
  const num =
    item?.NUM ??
    item?.num ??
    item?.value ??
    item?.amount ??
    item?.money ??
    0
  return { NAME: String(name), NUM: Number(num) || 0 }
}
const getMockListByType = (type) => {
  // æ¨¡æ‹Ÿå‡æ•°æ®ï¼ˆé‡‘额贡献排名)
  // type: 1=周 2=月 3=季度
  if (type === 2) {
    return [
      { NAME: '华东精密', NUM: 5120000 },
      { NAME: '星辰电子', NUM: 3860000 },
      { NAME: '启航科技', NUM: 2720000 },
      { NAME: '铭诚制造', NUM: 2160000 },
      { NAME: '远景材料', NUM: 1430000 },
      { NAME: '德润贸易', NUM: 910000 },
      { NAME: '宏达配套', NUM: 680000 },
    ]
  }
  if (type === 3) {
    return [
      { NAME: '华东精密', NUM: 16800000 },
      { NAME: '星辰电子', NUM: 12960000 },
      { NAME: '启航科技', NUM: 9720000 },
      { NAME: '铭诚制造', NUM: 7560000 },
      { NAME: '远景材料', NUM: 5430000 },
      { NAME: '德润贸易', NUM: 3910000 },
      { NAME: '宏达配套', NUM: 2680000 },
    ]
  }
  return [
    { NAME: '华东精密', NUM: 1280000 },
    { NAME: '星辰电子', NUM: 860000 },
    { NAME: '启航科技', NUM: 720000 },
    { NAME: '铭诚制造', NUM: 560000 },
    { NAME: '远景材料', NUM: 430000 },
    { NAME: '德润贸易', NUM: 310000 },
    { NAME: '宏达配套', NUM: 180000 },
  ]
}
const setMockData = (type) => {
  dataArr.value = getMockListByType(type).map(normalizeItem)
}
const handleDateTypeChange = () => {
  setMockData(dateType.value)
}
onMounted(() => {
  setMockData(dateType.value)
})
</script>
<style scoped>
.panel-item-customers {
  border: 1px solid #1a58b0;
  padding: 18px;
  width: 100%;
  height: 449px;
}
.switch-container {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 16px;
}
</style>
src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,348 @@
<template>
  <div>
    <PanelHeader title="供应商采购排名" />
    <div class="panel-item-customers">
      <div class="switch-container">
        <DateTypeSwitch v-model="radio1" @change="handleDateTypeChange" />
      </div>
      <Echarts
        ref="chart"
        :chartStyle="chartStyle"
        :grid="grid"
        :series="series"
        :tooltip="tooltip"
        :xAxis="xAxis"
        :yAxis="yAxis"
        :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }"
        style="height: 360px"
      />
    </div>
  </div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import Echarts from '@/components/Echarts/echarts.vue'
import PanelHeader from '../PanelHeader.vue'
import DateTypeSwitch from '../DateTypeSwitch.vue'
import { statisticsReceivablePayable } from '@/api/viewIndex.js'
const chartStyle = {
  width: '100%',
  height: '100%',
}
const radio1 = ref(1)
// é£žæœºå›¾æ ‡ SVG path
const aircraft =
  'path://M107.000,71.000 C104.936,71.000 102.665,70.806 100.273,70.467 C94.592,76.922 86.275,81.000 77.000,81.000 C70.794,81.000 65.020,79.170 60.172,76.029 C66.952,74.165 72.647,69.714 76.173,63.817 C69.821,61.362 64.063,58.593 60.000,56.039 L60.000,52.813 C70.456,53.950 80.723,55.000 83.000,55.000 C88.972,55.000 93.000,53.723 93.000,50.000 C93.000,47.071 89.222,45.000 83.000,45.000 C80.723,45.000 70.456,46.050 60.000,47.187 L60.000,43.989 C64.057,41.431 69.807,38.644 76.168,36.173 C72.641,30.281 66.948,25.834 60.172,23.971 C65.020,20.830 70.794,19.000 77.000,19.000 C86.270,19.000 94.584,23.074 100.265,29.524 C102.647,29.191 104.918,29.000 107.000,29.000 C129.644,29.000 148.000,50.000 148.000,50.000 C148.000,50.000 129.644,71.000 107.000,71.000 ZM113.000,38.000 C106.373,38.000 101.000,43.373 101.000,50.000 C101.000,56.627 106.373,62.000 113.000,62.000 C119.627,62.000 125.000,56.627 125.000,50.000 C125.000,43.373 119.627,38.000 113.000,38.000 ZM113.000,56.000 C109.686,56.000 107.000,53.314 107.000,50.000 C107.000,46.686 109.686,44.000 113.000,44.000 C116.314,44.000 119.000,46.686 119.000,50.000 C119.000,53.314 116.314,56.000 113.000,56.000 ZM110.500,19.000 C109.567,19.000 108.763,18.483 108.334,17.726 C100.231,9.857 89.187,5.000 77.000,5.000 C64.813,5.000 53.769,9.857 45.666,17.726 C45.237,18.483 44.433,19.000 43.500,19.000 C42.119,19.000 41.000,17.881 41.000,16.500 C41.000,15.847 41.256,15.259 41.665,14.813 L41.575,14.718 C50.629,5.628 63.156,-0.000 77.000,-0.000 C90.844,-0.000 103.371,5.628 112.425,14.718 L112.335,14.813 C112.744,15.259 113.000,15.847 113.000,16.500 C113.000,17.881 111.881,19.000 110.500,19.000 ZM53.000,49.484 C61.406,48.626 77.810,47.000 81.345,47.000 C87.353,47.000 91.000,48.243 91.000,50.000 C91.000,52.234 87.111,53.000 81.345,53.000 C77.810,53.000 61.406,51.374 53.000,50.516 L53.000,49.484 ZM53.000,47.000 L9.000,50.000 L53.000,53.000 L53.000,56.000 L-0.000,50.000 L53.000,44.000 L53.000,47.000 ZM43.500,81.000 C44.433,81.000 45.237,81.517 45.666,82.274 C53.769,90.143 64.813,95.000 77.000,95.000 C89.187,95.000 100.231,90.143 108.334,82.274 C108.763,81.517 109.567,81.000 110.500,81.000 C111.881,81.000 113.000,82.119 113.000,83.500 C113.000,84.153 112.744,84.741 112.335,85.187 L112.425,85.282 C103.371,94.372 90.844,100.000 77.000,100.000 C63.156,100.000 50.629,94.372 41.575,85.282 L41.665,85.187 C41.256,84.741 41.000,84.153 41.000,83.500 C41.000,82.119 42.119,81.000 43.500,81.000 Z'
// é¢œè‰²é…ç½®
const color = {
  0: '#ff5676',
  1: '#ffd83e',
  2: '#fbff94',
  3: '#7daeff',
}
// åŽŸå§‹æ•°æ®
const dataArr = ref([])
// æŽ’序后的数据
const dataArray = computed(() => {
  return [...dataArr.value].sort((a, b) => a.NUM - b.NUM)
})
// è®¡ç®—总数
const total = computed(() => {
  return dataArray.value.reduce((sum, v) => sum + Number(v.NUM), 0)
})
// x轴数据(名称)
const xdataName = computed(() => {
  return dataArray.value.map((v) => v.NAME)
})
// y轴数据(数值,带样式)
const dataNum = computed(() => {
  return dataArray.value.map((v, i) => {
    const index = dataArray.value.length - i - 1
    const isTop3 = index < 3
    return {
      value: Number(v.NUM),
      itemStyle: {
        color: {
          type: 'linear',
          x: 1,
          y: 0,
          x2: 0,
          y2: 0,
          colorStops: [
            {
              offset: 0,
              color: isTop3 ? '#ffdae1' : '#ecf3ff',
            },
            {
              offset: 0.07,
              color: isTop3 ? color[index] : color[3],
            },
            {
              offset: 1,
              color: isTop3
                ? 'rgba(255, 86, 118, .1)'
                : 'rgba(125,174,255, .1)',
            },
          ],
          global: false,
        },
        barBorderRadius: [0, 20, 20, 0],
      },
      symbol: isTop3 ? aircraft : 'none',
      symbolPosition: 'end',
      symbolSize: [30, 25],
      symbolOffset: [35, 0],
    }
  })
})
// èƒŒæ™¯æ•°æ®
const bgData = computed(() => {
  const maxValue = Math.max(...dataNum.value.map((v) => v.value))
  return dataNum.value.map(() => maxValue + 200)
})
// tooltip
const tooltip = computed(() => {
  return {
    trigger: 'axis',
    textStyle: { fontSize: '100%' },
    formatter: function (params) {
      let result = params[0].axisValueLabel + '<br/>'
      result += `<div style="">${params[0].marker}${params[0].value}</div>`
      return result
    },
  }
})
// grid
const grid = computed(() => {
  return { top: 0, left: '20%', right: '10%', bottom: 0 }
})
// xAxis
const xAxis = computed(() => {
  return [
    {
      splitLine: { show: false },
      axisLine: { show: false },
      axisLabel: { show: false },
      axisTick: { show: false },
    },
  ]
})
// yAxis
const yAxis = computed(() => {
  return [
    {
      type: 'category',
      inverse: false,
      data: xdataName.value,
      axisLabel: {
        formatter: (params) => {
          return `{a|${params}}`
        },
        rich: {
          a: {
            width: 160,
            fontSize: 14,
            color: '#fff',
            padding: [5, 4, 5, 0],
            align: 'right',
          },
        },
      },
      axisLine: { show: false },
      axisTick: { show: false },
      splitLine: { show: false },
    },
    {
      type: 'category',
      data: dataNum.value.map((item) => item.value),
      axisLabel: {
        formatter: (params, index) => {
          const value = typeof params === 'object' ? params.value : params
          const percent = ((value / total.value) * 100).toFixed(0)
          const rank = dataArray.value.length - index
          const isTop3 = rank < 4
          return `{a${isTop3 ? rank : ''}|${percent} }{b${isTop3 ? rank : ''}|%}`
        },
        rich: {
          a: {
            fontSize: 18,
            color: '#98bfff',
            verticalAlign: 'bottom',
          },
          a1: {
            fontSize: 18,
            color: '#ff7f97',
            verticalAlign: 'bottom',
          },
          a2: {
            fontSize: 18,
            color: '#ffce64',
            verticalAlign: 'bottom',
          },
          a3: {
            fontSize: 18,
            color: '#e8ed66',
            verticalAlign: 'bottom',
          },
          b: {
            fontSize: 12,
            color: '#98bfff',
            verticalAlign: 'bottom',
          },
          b1: {
            fontSize: 12,
            color: '#ff7f97',
            verticalAlign: 'bottom',
          },
          b2: {
            fontSize: 12,
            color: '#ffce64',
            verticalAlign: 'bottom',
          },
          b3: {
            fontSize: 12,
            color: '#e8ed66',
            verticalAlign: 'bottom',
          },
        },
      },
      axisLine: { show: false },
      axisTick: { show: false },
      splitLine: { show: false },
    },
  ]
})
// series
const series = computed(() => {
  return [
    {
      z: 6,
      type: 'pictorialBar',
      data: dataNum.value,
    },
    {
      z: 6,
      type: 'bar',
      barWidth: 25,
      itemStyle: {
        color: 'rgba(255,255,255,.1)',
        barBorderRadius: [0, 20, 20, 0],
      },
      data: bgData.value,
    },
    {
      type: 'bar',
      barWidth: 25,
      barGap: '-100%',
      itemStyle: {
        color: {
          type: 'linear',
          x: 1,
          y: 0,
          x2: 0,
          y2: 0,
          colorStops: [
            {
              offset: 0,
              color: 'rgba(255, 218, 220)',
            },
            {
              offset: 0.07,
              color: 'rgba(255, 86, 118)',
            },
            {
              offset: 1,
              color: 'rgba(255, 86, 118, 0)',
            },
          ],
          global: false,
        },
        barBorderRadius: [0, 20, 20, 0],
      },
      data: dataNum.value,
    },
  ]
})
// åº”付应收统计
const statisticsReceivable = () => {
  statisticsReceivablePayable({ type: radio1.value })
    .then((res) => {
      // å‡è®¾ API è¿”回的数据格式为数组,包含 NAME å’Œ NUM å­—段
      // å¦‚果返回格式不同,需要根据实际 API è°ƒæ•´
      if (res.data && Array.isArray(res.data)) {
        dataArr.value = res.data
      } else if (res.data && res.data.list) {
        dataArr.value = res.data.list
      } else {
        // å¦‚果没有数据,使用模拟数据
        dataArr.value = [
          { NAME: '供应商A', NUM: 102 },
          { NAME: '供应商B', NUM: 122 },
          { NAME: '供应商C', NUM: 282 },
          { NAME: '供应商D', NUM: 453 },
          { NAME: '供应商E', NUM: 753 },
        ]
      }
    })
    .catch((error) => {
      console.error('获取供应商采购排名失败:', error)
      // ä½¿ç”¨æ¨¡æ‹Ÿæ•°æ®
      dataArr.value = [
        { NAME: '供应商A', NUM: 102 },
        { NAME: '供应商B', NUM: 122 },
        { NAME: '供应商C', NUM: 282 },
        { NAME: '供应商D', NUM: 453 },
        { NAME: '供应商E', NUM: 753 },
      ]
    })
}
// å¤„理日期类型切换
const handleDateTypeChange = (value) => {
  statisticsReceivable()
}
onMounted(() => {
  statisticsReceivable()
})
</script>
<style scoped>
.panel-item-customers {
  border: 1px solid #1a58b0;
  padding: 18px;
  width: 100%;
  height: 449px;
}
.switch-container {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 16px;
}
.section-title {
  font-weight: 500;
  font-size: 16px;
  color: #d9ecff;
}
</style>
src/views/reportAnalysis/dataDashboard/index.vue
@@ -21,280 +21,26 @@
      <!-- å·¦ä¾§åŒºåŸŸ -->
      <div class="left-panel">
        <!-- å®¢æˆ·ä¿¡æ¯ç»Ÿè®¡åˆ†æž -->
                <div class="panel-header">
                    <span class="panel-title">在制品统计分析</span>
                </div>
        <div class="panel-item-customers">
                    <div class="quality-cards">
                        <div class="quality-cardSec">
                            <div class="quality-card one"></div>
                            <div class="quality-cardTitle">
                                <div>总在制数量</div>
                                <div>{{workInProcessStatistics.totalQuantity}}ä»¶</div>
                            </div>
                        </div>
                        <div class="quality-cardSec">
                            <div class="quality-card two"></div>
                            <div class="quality-cardTitle">
                                <div>平均周转天数</div>
                                <div>{{workInProcessStatistics.avgTurnoverDays}}天</div>
                            </div>
                        </div>
                        <div class="quality-cardSec">
                            <div class="quality-card three"></div>
                            <div class="quality-cardTitle">
                                <div>周转效率</div>
                                <div>{{workInProcessStatistics.turnoverEfficiency}}%</div>
                            </div>
                        </div>
                    </div>
                    <!-- å·¥åºåœ¨åˆ¶å“æ•°é‡æŸ±çж图 -->
                    <div style="height: 70%">
                        <Echarts ref="chart"
                                         :chartStyle="chartStyle"
                                         :grid="grid"
                                         :legend="workInProcessBarLegend"
                                         :series="workInProcessBarSeries"
                                         :tooltip="tooltip"
                                         :xAxis="workInProcessXAxis"
                                         :yAxis="workInProcessYAxis"
                                         :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
                                         style="height: 100%"></Echarts>
                    </div>
        </div>
      <LeftTop />
        <!-- è´¨é‡ç»Ÿè®¡ -->
                <div class="panel-header">
                    <span class="panel-title">近4月质量统计</span>
                </div>
                <div class="main-panel">
                    <div class="panel-item-customers">
                        <div class="quality-cards">
                            <div class="quality-cardSec">
                                <div class="quality-card one"></div>
                                <div class="quality-cardTitle">
                                    <div>原材料检数</div>
                                    <div>{{qualityStatisticsObject.supplierNum}}ä»¶</div>
                                </div>
                            </div>
                            <div class="quality-cardSec">
                                <div class="quality-card two"></div>
                                <div class="quality-cardTitle">
                                    <div>过程检数</div>
                                    <div>{{qualityStatisticsObject.processNum}}ä»¶</div>
                                </div>
                            </div>
                            <div class="quality-cardSec">
                                <div class="quality-card three"></div>
                                <div class="quality-cardTitle">
                                    <div>出厂检数</div>
                                    <div>{{qualityStatisticsObject.factoryNum}}ä»¶</div>
                                </div>
                            </div>
                        </div>
                        <Echarts ref="chart"
                                         :chartStyle="chartStyle"
                                         :grid="grid"
                                         :legend="barLegend"
                                         :series="barSeries1"
                                         :tooltip="tooltip"
                                         :xAxis="xAxis1"
                                         :yAxis="yAxis1"
                                         :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
                                         style="height: 260px"></Echarts>
                    </div>
                </div>
      <LeftBottom />
      </div>
      <!-- ä¸­é—´åŒºåŸŸ -->
      <div class="center-panel">
        <!-- é¡¶éƒ¨ç»Ÿè®¡å¡ç‰‡ -->
        <div class="stats-cards">
          <div class="stat-card">
            <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
            <div class="card-content">
              <span class="card-label">员工总数</span>
              <span class="card-value">{{totalStaff}}</span>
            </div>
          </div>
          <div class="stat-card">
            <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
            <div class="card-content">
              <span class="card-label">客户总数</span>
              <span class="card-value">{{totalCustomers}}</span>
            </div>
          </div>
          <div class="stat-card">
            <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
            <div class="card-content">
              <span class="card-label">供应商总数</span>
              <span class="card-value">{{totalSuppliers}}</span>
            </div>
          </div>
        </div>
      <CenterTop />
        <!-- è®¾å¤‡ç»Ÿè®¡ -->
        <div class="equipment-stats">
          <div class="equipment-header">
                        <img src="@/assets/BI/shujutongjiicon@2x.png" alt="图标" class="equipment-icon" />
            <span class="equipment-title">设备统计</span>
          </div>
          <div class="equipment-items">
            <div class="equipment-item">
              <span class="equipment-value">{{equipmentNum}}</span>
              <span class="equipment-label">设备总数</span>
            </div>
            <div class="equipment-item">
              <span class="equipment-value">{{equipmentRepair}}</span>
              <span class="equipment-label">待维修设备</span>
            </div>
            <div class="equipment-item">
              <span class="equipment-value">{{equipmentMaintain}}</span>
              <span class="equipment-label">待保养设备</span>
            </div>
            <div class="equipment-item">
              <span class="equipment-value">{{totalMeasuring}}</span>
              <span class="equipment-label">计量器具总数</span>
            </div>
          </div>
        </div>
        <!-- äº‹ä»¶åç§° -->
        <div class="event-info">
          <div class="event-header">
                        <img src="@/assets/BI/shijianmingxiicon@2x.png" alt="图标" class="event-icon" />
            <span class="event-title">事件名称</span>
          </div>
          <div class="event-content">
                        <ul class="todo-list" v-if="todoList.length > 0" ref="refTodoList">
   <li v-for="item in todoList" :key="item.id">
    <div style="display: flex;flex-direction: column;justify-content: space-between;width: 100%;gap: 20px">
     <div style="display: flex;justify-content: space-between;align-items: center;">
      <div class="todo-title">待办编号:{{item.approveId}}</div>
      <div class="todo-division">部门:{{item.approveDeptName}}</div>
      <div class="todo-time">{{item.approveTime}}</div>
     </div>
     <div class="todo-division">待办事由:{{item.approveReason}}</div>
    </div>
   </li>
 </ul>
                        <div v-else style="text-align: center">
                            æš‚无数据
                        </div>
          </div>
        </div>
                <div class="financial-header">
                    <span class="financial-title">各生产订单的完成进度统计</span>
                </div>
                <div class="main-panel">
                    <div class="panel-item-customers">
                        <div class="order-statistics-cards" style="margin-bottom: 0px;">
                            <div class="quality-cardSec">
                                <div class="quality-card four"></div>
                                <div class="quality-cardTitle">
                                    <div>总订单数</div>
                                    <div>{{orderStatisticsObject.totalOrderCount}}ä»¶</div>
                                </div>
                            </div>
                            <div class="quality-cardSec">
                                <div class="quality-card five"></div>
                                <div class="quality-cardTitle">
                                    <div>未完成订单数</div>
                                    <div>{{orderStatisticsObject.uncompletedOrderCount}}ä»¶</div>
                                </div>
                            </div>
                            <div class="quality-cardSec">
                                <div class="quality-card six"></div>
                                <div class="quality-cardTitle">
                                    <div>部分完成订单数</div>
                                    <div>{{orderStatisticsObject.partialCompletedOrderCount}}ä»¶</div>
                                </div>
                            </div>
                            <div class="quality-cardSec">
                                <div class="quality-card seven"></div>
                                <div class="quality-cardTitle">
                                    <div>已完成订单数</div>
                                    <div>{{orderStatisticsObject.completedOrderCount}}ä»¶</div>
                                </div>
                            </div>
                        </div>
                        <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>
      <CenterBottom />
      </div>
      <!-- å³ä¾§åŒºåŸŸ -->
      <div class="right-panel">
        <!-- åº”收应付统计 -->
                <div class="panel-header">
                    <span class="panel-title">应收应付统计</span>
                </div>
                <div class="panel-item-customers">
                    <div style="display: flex;justify-content: space-between;margin-bottom: 20px;">
                        <div class="section-title">应收应付统计</div>
<!--                        <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable" class="custom-radio-group">-->
<!--                            <el-radio-button label="按周" :value="1" />-->
<!--                            <el-radio-button label="按月" :value="2" />-->
<!--                            <el-radio-button label="按季度" :value="3" />-->
<!--                        </el-radio-group>-->
                    </div>
                    <Echarts ref="chart"
                                     :color="barColors2"
                                     :chartStyle="chartStyle"
                                     :grid="grid"
                   :legend="barLegend2"
                                     :series="barSeries"
                                     :tooltip="tooltip"
                                     :xAxis="xAxis"
                                     :yAxis="yAxis"
                                     :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
                                     style="height: 260px"></Echarts>
                </div>
      <RightTop />
        <!-- å›žæ¬¾ä¸Žå¼€ç¥¨åˆ†æž -->
         <div class="panel-header">
                    <span class="panel-title">近一月回款与开票分析</span>
                </div>
        <div class="panel-item-customers" style="padding-top: 60px;">
                    <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries"
                                 :tooltip="tooltipLine" :xAxis="xAxis2" :yAxis="yAxis2" :options="{backgroundColor: 'transparent', textStyle: {color: '#FFFFFF'}}" style="height: 270px;"></Echarts>
                </div>
       <RightBottom />
      </div>
      </div>
      </div>
@@ -302,28 +48,15 @@
</template>
<script setup>
import * as echarts from 'echarts'
import { ref, reactive, onMounted, onBeforeUnmount, nextTick } from 'vue'
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
import autofit from 'autofit.js'
import Echarts from "@/components/Echarts/echarts.vue";
import LeftTop from './components/basic/left-top.vue'
import LeftBottom from './components/basic/left-bottom.vue'
import CenterTop from './components/basic/center-top.vue'
import CenterBottom from './components/basic/center-bottom.vue'
import RightTop from './components/basic/right-top.vue'
import RightBottom from './components/basic/right-bottom.vue'
import useUserStore from '@/store/modules/user'
import {
    analysisCustomerContractAmounts, getAmountHalfYear,
    homeTodos,
    qualityStatistics,
    statisticsReceivablePayable,
    getProgressStatistics,
      getWorkInProcessTurnover
} from "@/api/viewIndex.js";
import {staffOnJobListPage} from "@/api/personnelManagement/employeeRecord.js";
import {listCustomer} from "@/api/basicData/customerFile.js";
import {listSupplier} from "@/api/basicData/supplierManageFile.js";
import {getLedgerPage} from "@/api/equipmentManagement/ledger.js";
import {getRepairPage} from "@/api/equipmentManagement/repair.js";
import {getUpkeepPage} from "@/api/equipmentManagement/upkeep.js";
import {measuringInstrumentListPage} from "@/api/equipmentManagement/measurementEquipment.js";
import {listPageAnalysis} from "@/api/financialManagement/expenseManagement.js";
import {productOrderListPage} from "@/api/productionManagement/productionOrder.js";
// å…¨å±ç›¸å…³çŠ¶æ€
const isFullscreen = ref(false);
@@ -336,410 +69,6 @@
// ç”¨æˆ·store
const userStore = useUserStore()
// å“åº”式数据
const currentTime = ref('')
const currentDate = ref('')
const timer = ref(null)
const charts = ref([])
// å›¾è¡¨å¼•用
const customerPieChartRef = ref(null)
const salesBarChartRef = ref(null)
const dataBarChartRef = ref(null)
const financialAreaChartRef = ref(null)
const realtimeLineChartRef = ref(null)
const refContractList = ref(null)
const refTodoList = ref(null)
const progressTableRef = ref(null)
const timerScroll = ref(null)
const progressTableScrollTimer = ref(null)
const isTableScrolling = ref(false)
const tableScrollTimeout = ref(null)
const tableRowRefs = ref([])
const rowsUnderHeader = ref(new Set())
const chartStylePie = {
    width: '100%',
    height: '100%' // è®¾ç½®å›¾è¡¨å®¹å™¨çš„高度
}
const materialPieSeries = ref([
    {
        type: 'pie',
        radius: ['0%', '90%'],
        avoidLabelOverlap: false,
        itemStyle: {
            borderColor: '#fff',
            borderWidth: 0
        },
        label: {
            show: false
        },
        data: []
    }
])
const pieLegend = reactive({
    show: false,
})
const sum = ref(0)
const totalStaff = ref(0)
const totalCustomers = ref(0)
const totalSuppliers = ref(0)
const yny = ref(0)
const chain = ref(0)
const equipmentNum = ref(0)
const equipmentRepair = ref(0)
const equipmentMaintain = ref(0)
const totalMeasuring = ref(0)
const pieTooltip = reactive({
    trigger: 'item',
    formatter: function (params) {
        // åŠ¨æ€ç”Ÿæˆæç¤ºä¿¡æ¯ï¼ŒåŸºäºŽæ•°æ®é¡¹çš„ name å±žæ€§
        const description = params.name === '本月回款金额' ? '本月回款金额' : '应收款金额';
        return `<div style="color: #B8C8E0">${description} ${params.value}元 ${params.percent}%</div>`;
    },
    position: 'right'
})
const qualityStatisticsObject = ref({
    supplierNum: 0,
    processNum: 0,
    factoryNum: 0,
})
// è®¢å•统计对象
const orderStatisticsObject = ref({
    totalOrderCount: 0,
    uncompletedOrderCount: 0,
    partialCompletedOrderCount: 0,
    completedOrderCount: 0,
})
// åœ¨åˆ¶å“å‘¨è½¬ç»Ÿè®¡å¯¹è±¡
const workInProcessStatistics = ref({
    totalQuantity: 0,
    avgTurnoverDays: 0,
    turnoverEfficiency: 0,
})
const chartStyle = {
    width: '100%',
    height: '150%' // è®¾ç½®å›¾è¡¨å®¹å™¨çš„高度
}
const barSeries = ref([
    {
        name: '应付金额',
        type: 'bar',
        data: [],
        label: {
            show: true,
        },
        itemStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: '#00A4ED' },
                { offset: 1, color: '#4EE4FF' }
            ])
        }
    },
    {
        name: '应收金额',
        type: 'bar',
        data: [],
        label: {
            show: true,
        },
        itemStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: '#537EF5' },
                { offset: 1, color: '#9061F8' }
            ])
        }
    }
])
const radio1 = ref(1)
const barColors2 = ['#5181DB', '#D369E0', '#F2CA6D', '#60CCA8']
const grid = {
    left: '3%',
    right: '4%',
    bottom: '3%',
    containLabel: true
}
const lineLegend = {
    show: true,
  textStyle: { color: '#B8C8E0' },
    data: ['开票', '回款']
}
const lineSeries = ref([
    {
        type: 'line',
        data: [],
        label: {
            show: true
        },
        showSymbol: true, // æ˜¾ç¤ºåœ†ç‚¹
    },
])
const tooltipLine = {
    trigger: 'axis',
}
const yAxis2 = ref([
    {
        type: 'value',
    }
])
const xAxis2 = ref([
    {
        type: 'category',
        data: [],
        axisLabel: {
            interval: 0,
            formatter: function(value) {
                return value.replace(/~/g, '\n');
            },
        }
    }
])
const barLegend2 = {
    show: true,
    textStyle: { color: '#B8C8E0' },
    data: ['应付金额', '应收金额']
}
const barLegend = {
    show: true,
    textStyle: { color: '#B8C8E0' },
    data: ['原材料合格数', '过程合格数', '出不合格数']
}
const barLegend1 = {
    show: false,
    textStyle: { color: '#B8C8E0' },
    data: []
}
const barSeries11 = ref([
    {
        name: '生产订单统计',
        type: 'bar',
        barGap: 0,
        emphasis: {
            focus: 'series'
        },
        itemStyle: {
            // ä½¿ç”¨å‡½æ•°æ ¹æ®æ•°æ®ç´¢å¼•返回不同颜色
            color: function(params) {
                const colorStops = [
                    [
                        { offset: 1, color: '#00A4ED' },
                        { offset: 0, color: '#4EE4FF' }
                    ],
                    [
                        { offset: 1, color: '#3378FF' },
                        { offset: 0, color: '#4E8AFF' }
                    ],
                    [
                        { offset: 1, color: '#FF6B6B' },
                        { offset: 0, color: '#FF8E8E' }
                    ],
                    [
                        { offset: 1, color: '#537EF5' },
                        { offset: 0, color: '#9061F8' }
                    ]
                ]
                const stops = colorStops[params.dataIndex] || colorStops[0]
                return {
                    type: 'linear',
                    x: 0,
                    y: 0,
                    x2: 0,
                    y2: 1,
                    colorStops: stops
                }
            }
        },
        data: []
    }
])
const barSeries1 = ref([
    {
        name: '原材料合格数',
        type: 'bar',
        barGap: 0,
        emphasis: {
            focus: 'series'
        },
        itemStyle: {
            color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [
                    { offset: 1, color: '#00A4ED' },
                    { offset: 0, color: '#4EE4FF' }
                ]
            }
        },
        data: []
    },
    {
        name: '过程合格数',
        type: 'bar',
        emphasis: {
            focus: 'series'
        },
        itemStyle: {
            color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [
                    { offset: 1, color: '#3378FF' },
                    { offset: 0, color: '#4E8AFF' }
                ]
            }
        },
        data: []
    },
    {
        name: '出厂合格数',
        type: 'bar',
        emphasis: {
            focus: 'series'
        },
        itemStyle: {
            color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [
                    { offset: 1, color: '#537EF5' },
                    { offset: 0, color: '#9061F8' }
                ]
            }
        },
        data: []
    },
])
const tooltip = {
    trigger: 'axis',
    axisPointer: {
        type: 'shadow'
    },
    formatter: function (params) {
        let result = params[0].axisValueLabel + '<br/>';
        params.forEach(item => {
            result += `<div style="color: #B8C8E0">${item.marker} ${item.seriesName}: ${item.value}</div>`;
        });
        return result;
    }
}
const xAxis = [{
    type: 'value',
}]
const yAxis = [{
    type: 'category',
    data: ['应收应付统计']
}]
const xAxis1 = ref([{
    type: 'category',
    axisTick: { show: false },
    axisLabel: { color: '#B8C8E0' },
    data: []
}])
const yAxis1 = [{
    type: 'value',
    axisLabel: { color: '#B8C8E0' }
}]
const xAxis3 = ref([{
    type: 'category',
    axisTick: { show: false },
    axisLabel: { color: '#B8C8E0' },
    data: []
}])
const yAxis3 = [{
    type: 'value',
    axisLabel: { color: '#B8C8E0' }
}]
// åœ¨åˆ¶å“å·¥åºæŸ±çŠ¶å›¾é…ç½®
const workInProcessXAxis = ref([{
    type: 'category',
    axisTick: { show: false },
    axisLabel: { color: '#B8C8E0' },
    data: []
}])
const workInProcessYAxis = [{
    type: 'value',
    axisLabel: { color: '#B8C8E0' },
    name: ''
}]
const workInProcessBarLegend = {
    show: false,
    textStyle: { color: '#B8C8E0' },
    data: []
}
const workInProcessBarSeries = ref([
    {
        name: '在制品数量',
        type: 'bar',
        barWidth: 25, // å›ºå®šæŸ±çŠ¶å›¾å®½åº¦ä¸º40px
        barGap: 0,
        emphasis: {
            focus: 'series'
        },
        itemStyle: {
            color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [
                    { offset: 0, color: '#4EE4FF' },
                    { offset: 1, color: '#00A4ED' }
                ]
            }
        },
        label: {
            show: true,
            position: 'top',
            color: '#B8C8E0'
        },
        data: []
    }
])
// å¾…办事项
const todoList = ref([])
// ç”Ÿäº§è®¢å•完成进度表格数据
const progressTableData = ref([])
// è®¡ç®—完成进度百分比
const calculateProgress = (item) => {
    if (!item) return 0
    // ä¼˜å…ˆä½¿ç”¨completionStatus字段
    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)
    }
    // å¦‚果没有completionStatus,则根据完成数量和需求数量计算
    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 calculateScale = () => {
@@ -755,13 +84,6 @@
  const scaleX = containerWidth / designWidth
  const scaleY = containerHeight / designHeight
  scaleRatio.value = Math.min(scaleX, scaleY)
  // è§¦å‘图表resize
  charts.value.forEach(chart => {
    if (chart && chart.resize) {
      chart.resize()
    }
  })
}
// çª—口大小变化处理
@@ -772,506 +94,6 @@
  }, 100)
}
// é”€æ¯å›¾è¡¨å®žä¾‹
const disposeCharts = () => {
  charts.value.forEach(chart => {
    if (chart && chart.dispose) {
      chart.dispose()
    }
  })
  charts.value = []
}
// åˆåŒé‡‘额
const analysisCustomer = () => {
    analysisCustomerContractAmounts().then((res) => {
        sum.value = res.data.sum
        yny.value = res.data.yny
        chain.value = res.data.chain
        // ä¸ºæ¯ä¸ªæ•°æ®é¡¹åˆ†é…éšæœºé¢œè‰²
        materialPieSeries.value[0].data = res.data.item.map(item => ({
            ...item,
            itemStyle: { color: getRandomColor() }
        }))
    })
}
// åœ¨åˆ¶å“å‘¨è½¬ç»Ÿè®¡
const workInProcessTurnoverInfo = () => {
    getWorkInProcessTurnover().then((res) => {
        console.log("在制品周转统计数据:", res)
        if (!res || !res.data) {
            console.warn('在制品周转统计数据为空')
            return
        }
        // ä»ŽæŽ¥å£èŽ·å–ç»Ÿè®¡æ•°æ®
        workInProcessStatistics.value = {
            totalQuantity: res.data.totalOrderCount || 0,
            avgTurnoverDays: res.data.averageTurnoverDays || 0,
            turnoverEfficiency: res.data.turnoverEfficiency || 0,
        }
        // è®¾ç½®å·¥åºæŸ±çŠ¶å›¾æ•°æ®
        // X轴:processDetails (工序详情数组)
        // Y轴:processQuantityDetails (工序数量详情数组)
        if (res.data.processDetails && Array.isArray(res.data.processDetails)) {
            // è®¾ç½®X轴数据(工序名称)
            workInProcessXAxis.value[0].data = res.data.processDetails
        } else {
            workInProcessXAxis.value[0].data = []
        }
        if (res.data.processQuantityDetails && Array.isArray(res.data.processQuantityDetails)) {
            // è®¾ç½®Y轴数据(在制品数量)
            workInProcessBarSeries.value[0].data = res.data.processQuantityDetails
        } else {
            workInProcessBarSeries.value[0].data = []
        }
    }).catch((error) => {
        console.error('获取在制品周转统计失败:', error)
    })
}
// è´¨æ£€ç»Ÿè®¡
const qualityStatisticsInfo = () => {
    qualityStatistics().then((res) => {
        res.data.item.forEach(item => {
            xAxis1.value[0].data.push(item.date)
            barSeries1.value[0].data.push(item.supplierNum)
            barSeries1.value[1].data.push(item.processNum)
            barSeries1.value[2].data.push(item.factoryNum)
        })
        qualityStatisticsObject.value.supplierNum = res.data.supplierNum
        qualityStatisticsObject.value.processNum = res.data.processNum
        qualityStatisticsObject.value.factoryNum = res.data.factoryNum
    })
}
// å„生产订单的完成进度统计
const progressStatisticsInfo = () => {
    // ä»Žç»Ÿè®¡æŽ¥å£èŽ·å–ç»Ÿè®¡æ•°æ®
    getProgressStatistics().then((res) => {
        console.log("生产订单完成进度统计数据:", res)
        if (!res || !res.data) {
            console.warn('生产订单完成进度统计数据为空')
            return
        }
        // ä»ŽæŽ¥å£èŽ·å–ç»Ÿè®¡æ•°æ®
        orderStatisticsObject.value = {
            totalOrderCount: res.data.totalOrderCount || 0,
            uncompletedOrderCount: res.data.uncompletedOrderCount || 0,
            partialCompletedOrderCount: res.data.partialCompletedOrderCount || 0,
            completedOrderCount: res.data.completedOrderCount || 0
        }
        progressTableData.value = res.data.completedOrderDetails || []
        // é‡ç½®è¡Œå¼•用
        tableRowRefs.value = []
        rowsUnderHeader.value.clear()
        // åœ¨èŽ·å–åˆ°æ•°æ®åŽï¼Œåˆå§‹åŒ–æ»šåŠ¨åŠŸèƒ½
        nextTick(() => {
            initProgressTableScroll()
        })
    }).catch((error) => {
        console.error('获取生产订单完成进度统计失败:', error)
    })
}
// è´¢åŠ¡ç»Ÿè®¡
// const accountStatisticsInfo = () => {
//     listPageAnalysis().then((res) => {
//         xAxis3.value[0].data = res.data.days
//         barSeries11.value[0].data = res.data.totalIncome
//     })
// }
const getNum = () => {
    const params = {
        pageNum: -1,
        pageSize: -1,
    }
    staffOnJobListPage({...params, staffState: 1}).then(res => {
        totalStaff.value = res.data.total
    })
    listCustomer(params).then((res) => {
        totalCustomers.value = res.total;
    });
    listSupplier(params).then((res) => {
        totalSuppliers.value = res.data.total
    });
}
const getLedgerNum = () => {
    const params = {
        pageNum: -1,
        pageSize: -1,
    }
    getLedgerPage(params).then((res) => {
        equipmentNum.value = res.data.total
    });
    getRepairPage({...params, status:0}).then((res) => {
        equipmentRepair.value = res.data.total
    });
    getUpkeepPage({...params, status:0}).then((res) => {
        equipmentMaintain.value = res.data.total
    });
    measuringInstrumentListPage(params).then((res) => {
        totalMeasuring.value = res.data.total
    });
}
// å¾…办事项
const todoInfoS = () => {
    homeTodos().then((res) => {
        todoList.value = res.data
        // åœ¨èŽ·å–åˆ°å¾…åŠžäº‹é¡¹æ•°æ®åŽï¼Œåˆå§‹åŒ–æ»šåŠ¨åŠŸèƒ½
        nextTick(() => {
            initTodoListScroll()
        })
    })
}
// åº”付应收统计
const statisticsReceivable = (type) => {
    statisticsReceivablePayable({type: radio1.value}).then((res) => {
        // è®¾ç½®åº”付金额数据
        barSeries.value[0].data = [
            { value: res.data.payableMoney }
        ]
        // è®¾ç½®åº”收金额数据
        barSeries.value[1].data = [
            { value: res.data.receivableMoney }
        ]
    })
}
const getAmountHalfYearNum = async () => {
    const res = await getAmountHalfYear()
    console.log(res)
    const monthName = []
    const receiptAmount = []
    const invoiceAmount = []
    res.data.forEach(item => {
        monthName.push(item.month)
        receiptAmount.push(item.receiptAmount)
        invoiceAmount.push(item.invoiceAmount)
    })
    // æ­£ç¡®å“åº”式赋值:创建新的 xAxis å’Œ series å¯¹è±¡
    xAxis2.value[0].data = monthName
    xAxis2.value[0].data = monthName.map(item => item.replace(/~/g, '\n~'));
    lineSeries.value = [
        {
            name: '开票',
            type: 'line',
            data: receiptAmount,
            stack: 'Total',
            areaStyle: {
                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                    {
                        offset: 0,
                        color: 'rgba(131, 207, 255, 1)'
                    },
                    {
                        offset: 1,
                        color: 'rgba(186, 228, 255, 1)'
                    }
                ])
            },
            itemStyle: {
                color: '#2D99FF',
                borderColor: '#2D99FF'
            },
            emphasis: {
                focus: 'series'
            },
            lineStyle: {
                width: 0
            },
            showSymbol: true,
        },
        {
            name: '回款',
            type: 'line',
            data: invoiceAmount,
            stack: 'Total',
            lineStyle: {
                width: 0
            },
            itemStyle: {
                color: '#83CFFF',
                borderColor: '#83CFFF'
            },
            showSymbol: true,
            areaStyle: {
                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                    {
                        offset: 0,
                        color: 'rgba(54, 153, 255, 1)'
                    },
                    {
                        offset: 1,
                        color: 'rgba(89, 169, 254, 1)'
                    }
                ])
            },
            emphasis: {
                focus: 'series'
            },
        }
    ]
}
// è‡ªåŠ¨è½®æ¢å‘¨ã€æœˆã€å­£åº¦çš„å®šæ—¶å™¨
const autoSwitchTimer = ref(null)
// è®¾ç½®è¡Œå¼•用
const setRowRef = (el, index) => {
    if (el) {
        tableRowRefs.value[index] = el
    }
}
// åˆ¤æ–­è¡Œæ˜¯å¦åœ¨è¡¨å¤´ä¸‹æ–¹
const isRowUnderHeader = (index) => {
    return 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
    // æ¸…理之前可能存在的克隆行(保留原始数据行)
    // åŽŸå§‹æ•°æ®è¡Œçš„æ•°é‡åº”è¯¥ç­‰äºŽ progressTableData.value.length
    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 initTodoListScroll = () => {
    const todoList = refTodoList.value
    // å¼ºåˆ¶å¯ç”¨æ»šåŠ¨ï¼Œä¸æ£€æŸ¥ä»»ä½•æ¡ä»¶
    if (todoList) {
        // åˆ›å»ºä¸€ä¸ªå…‹éš†é¡¹ï¼Œç”¨äºŽå®žçŽ°æ— ç¼æ»šåŠ¨
        const scrollItems = Array.from(todoList.querySelectorAll('li'))
        if (scrollItems.length > 0) {
            // ç¡®ä¿æœ‰è¶³å¤Ÿçš„项目用于滚动
            // å¦‚果项目太少,多复制几次以确保滚动效果
            if (scrollItems.length < 4) {
                const originalItems = [...scrollItems]
                for (let i = 0; i < 4; i++) {
                    originalItems.forEach(item => {
                        const clone = item.cloneNode(true)
                        todoList.appendChild(clone)
                    })
                }
                // é‡æ–°èŽ·å–æ‰€æœ‰é¡¹ç›®
                scrollItems.push(...Array.from(todoList.querySelectorAll('li')).slice(scrollItems.length));
            }
            const itemHeight = scrollItems[0]?.offsetHeight || 0
            const containerHeight = todoList.clientHeight
            const cloneCount = Math.ceil(containerHeight / itemHeight) + 2
            // å…‹éš†å‰å‡ ä¸ªé¡¹ç›®å¹¶æ·»åŠ åˆ°åˆ—è¡¨æœ«å°¾ï¼Œå®žçŽ°æ— ç¼æ»šåŠ¨
            for (let i = 0; i < cloneCount; i++) {
                const clone = scrollItems[i % scrollItems.length].cloneNode(true)
                todoList.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) // æ ‡å‡†åŒ–为60fps的速度
                    // å½“滚动超过原始内容长度时,重置位置实现无缝滚动
                    const maxScroll = Math.max(todoList.scrollHeight - containerHeight - cloneCount * itemHeight, itemHeight * scrollItems.length)
                    if (scrollPosition >= maxScroll) {
                        scrollPosition = 0
                        todoList.scrollTop = 0
                    } else {
                        todoList.scrollTop = scrollPosition
                    }
                }
                todoList._animationFrame = requestAnimationFrame(scrollAnimation)
            }
            // å¯åŠ¨æ»šåŠ¨åŠ¨ç”»
            todoList._animationFrame = requestAnimationFrame(scrollAnimation)
            // è®¾ç½®æ»šåЍ-暂停-滚动的循环效果
            const pauseTimer = setInterval(() => {
                isPaused = !isPaused
            }, pauseTime)
            // æ¸…理定时器
            todoList._pauseTimer = pauseTimer
        }
    }
}
const getRandomColor = () => {
    // ç”Ÿæˆæµ…色:R、G、B åˆ†é‡éƒ½åœ¨ 150-255 ä¹‹é—´
    const r = Math.floor(Math.random() * 106) + 150; // 150-255
    const g = Math.floor(Math.random() * 106) + 150; // 150-255
    const b = Math.floor(Math.random() * 106) + 150; // 150-255
    // å°† RGB è½¬æ¢ä¸ºåå…­è¿›åˆ¶é¢œè‰²
    return '#' + r.toString(16).padStart(2, '0') + g.toString(16).padStart(2, '0') + b.toString(16).padStart(2, '0');
}
// æ›´æ–°æ—¶é—´
const updateTime = () => {
  const now = new Date()
  currentTime.value = now.toLocaleTimeString('zh-CN', { hour12: false })
  currentDate.value = now.toLocaleDateString('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    weekday: 'long'
  })
}
// åˆå§‹åŒ–æ—¶é—´
const initTime = () => {
  updateTime()
  timer.value = setInterval(updateTime, 1000)
}
// å…¨å±åŠŸèƒ½å®žçŽ° - é’ˆå¯¹scale-container元素
const toggleFullscreen = () => {
    const element = document.querySelector('.scale-container')
@@ -1312,146 +134,19 @@
// ç”Ÿå‘½å‘¨æœŸé’©å­
onMounted(() => {
  initTime()
  // ä½¿ç”¨nextTick确保DOM完全渲染后再初始化图表
  // ä½¿ç”¨nextTick确保DOM完全渲染后再初始化
  nextTick(() => {
    // è®¡ç®—初始缩放比例
    calculateScale()
    // åˆå§‹åŒ–autofit自适应(如果需要保留autofit,可以保留,但主要缩放由scale-container控制)
    // autofit.init({ dh: 800, dw: 1280, el: '.data-dashboard', resize: true }, false)
    // æ·»åŠ è‡ªåŠ¨æ»šåŠ¨åŠ¨ç”»æ•ˆæžœ - å®¢æˆ·ä¿¡æ¯åˆ—表
    const contractList = refContractList.value
    if (contractList && contractList.scrollHeight > contractList.clientHeight) {
      // åˆ›å»ºä¸€ä¸ªå…‹éš†é¡¹ï¼Œç”¨äºŽå®žçŽ°æ— ç¼æ»šåŠ¨
      const scrollItems = Array.from(contractList.querySelectorAll('li'))
      const itemHeight = scrollItems[0]?.offsetHeight || 0
      const containerHeight = contractList.clientHeight
      const cloneCount = Math.ceil(containerHeight / itemHeight) + 2
      // å…‹éš†å‰å‡ ä¸ªé¡¹ç›®å¹¶æ·»åŠ åˆ°åˆ—è¡¨æœ«å°¾ï¼Œå®žçŽ°æ— ç¼æ»šåŠ¨
      for (let i = 0; i < cloneCount; i++) {
        const clone = scrollItems[i % scrollItems.length].cloneNode(true)
        contractList.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) // æ ‡å‡†åŒ–为60fps的速度
          // å½“滚动超过原始内容长度时,重置位置实现无缝滚动
          if (scrollPosition >= contractList.scrollHeight - containerHeight - cloneCount * itemHeight) {
            scrollPosition = 0
            contractList.scrollTop = 0
          } else {
            contractList.scrollTop = scrollPosition
          }
        }
        timerScroll.value = requestAnimationFrame(scrollAnimation)
      }
      // å¯åŠ¨æ»šåŠ¨åŠ¨ç”»
      timerScroll.value = requestAnimationFrame(scrollAnimation)
      // è®¾ç½®æ»šåЍ-暂停-滚动的循环效果
      const pauseTimer = setInterval(() => {
        isPaused = !isPaused
      }, pauseTime)
      // æ¸…理定时器
      contractList._pauseTimer = pauseTimer
    }
    // å¾…办事项列表滚动功能已移至todoInfoS函数中,在获取数据后初始化
  })
  
  window.addEventListener('resize', handleResize)
  window.addEventListener('fullscreenchange', handleFullscreenChange)
  window.addEventListener('webkitfullscreenchange', handleFullscreenChange)
  window.addEventListener('MSFullscreenChange', handleFullscreenChange)
  analysisCustomer()
  workInProcessTurnoverInfo()
  qualityStatisticsInfo()
    // accountStatisticsInfo()
    progressStatisticsInfo()
  getNum()
  getLedgerNum()
  todoInfoS()
    statisticsReceivable()
    getAmountHalfYearNum()
  // è®¾ç½®è‡ªåŠ¨è½®æ¢å‘¨ã€æœˆã€å­£åº¦çš„å®šæ—¶å™¨ï¼Œæ¯10秒切换一次
  autoSwitchTimer.value = setInterval(() => {
    // å¾ªçŽ¯åˆ‡æ¢ï¼š1(周) -> 2(月) -> 3(季度) -> 1(周)
    radio1.value = radio1.value === 3 ? 1 : radio1.value + 1
    statisticsReceivable()
  }, 10000) // 10秒切换一次
})
onBeforeUnmount(() => {
  if (timer.value) {
    clearInterval(timer.value)
  }
  if (timerScroll.value) {
    cancelAnimationFrame(timerScroll.value)
  }
  // æ¸…理滚动列表的暂停定时器
  const contractList = refContractList.value
  if (contractList && contractList._pauseTimer) {
    clearInterval(contractList._pauseTimer)
  }
  // æ¸…理待办事项列表的动画和定时器
  const todoList = refTodoList.value
  if (todoList) {
    if (todoList._animationFrame) {
      cancelAnimationFrame(todoList._animationFrame)
      todoList._animationFrame = null
    }
    if (todoList._pauseTimer) {
      clearInterval(todoList._pauseTimer)
      todoList._pauseTimer = null
    }
  }
  // æ¸…理生产订单进度表格的动画和定时器
  const progressTable = progressTableRef.value
  if (progressTable) {
    if (progressTableScrollTimer.value) {
      cancelAnimationFrame(progressTableScrollTimer.value)
      progressTableScrollTimer.value = null
    }
    if (progressTable._pauseTimer) {
      clearInterval(progressTable._pauseTimer)
      progressTable._pauseTimer = null
    }
  }
  // æ¸…理表格滚动定时器
  if (tableScrollTimeout.value) {
    clearTimeout(tableScrollTimeout.value)
    tableScrollTimeout.value = null
  }
  // æ¸…理自动轮换周、月、季度的定时器
  if (autoSwitchTimer.value) {
    clearInterval(autoSwitchTimer.value)
    autoSwitchTimer.value = null
  }
  window.removeEventListener('resize', handleResize)
  window.removeEventListener('fullscreenchange', handleFullscreenChange)
  window.removeEventListener('webkitfullscreenchange', handleFullscreenChange)
@@ -1461,7 +156,6 @@
    window.removeEventListener('resize', window._autofitUpdateHandler)
    delete window._autofitUpdateHandler
  }
  disposeCharts()
  // å…³é—­autofit
  autofit.off()
})
@@ -1597,440 +291,5 @@
  flex-direction: column;
  gap: 20px;
}
.panel-item-customers {
    border: 1px solid #1A58B0;
    padding: 18px;
    width: 100%;
    height: 540px;
}
.panel-title-second {
    height: 60px;
    display: flex;
    gap: 12px;
    margin-bottom: 20px;
    align-items: center;
}
.quality-cards {
    display: flex;
    gap: 12px;
    width: 100%;
    height: 54px;
    justify-content: space-between;
    align-items: center;
}
.quality-cardSec {
    display: flex;
}
.quality-cardTitle {
    font-weight: 400;
    font-size: 14px;
    color: #FFFFFF;
    display: flex;
    align-items: flex-start;
    flex-direction: column;
}
.quality-card {
    width: 80px;
    height: 60px;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
.quality-card.one {
    background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png");
}
.quality-card.two {
    background-image: url("@/assets/BI/guochengyijianicon@2x.png");
}
.quality-card.three {
    background-image: url("@/assets/BI/chuchangyijianicon@2x.png");
}
/* è®¢å•统计卡片样式 */
.order-statistics-cards {
    display: flex;
    gap: 12px;
    width: 100%;
    height: 94px;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
}
.quality-card.four {
    background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png");
}
.quality-card.five {
    background-image: url("@/assets/BI/guochengyijianicon@2x.png");
}
.quality-card.six {
    background-image: url("@/assets/BI/chuchangyijianicon@2x.png");
}
.quality-card.seven {
    background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png");
}
.panel-title-icon {
    width: 60px;
    height: 60px;
    background-image: url("@/assets/BI/hetongicon.png");
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
.panel-header {
    background-image: url("@/assets/BI/kehuhetongback@2x.png");
    background-size: 100% 100%;
    background-position: center;
    background-repeat: no-repeat;
}
.panel-title {
    width: 100%;
    font-weight: 500;
    font-size: 16px;
    color: #D9ECFF;
    padding-left: 46px;
    line-height: 36px;
}
.total-customers {
    background-image: url("@/assets/BI/hetongjineback@2x.png");
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    width: 90%;
    height: 60px;
    display: flex;
    align-items: center;
    padding: 0 20px;
    gap: 20px;
}
.total-customers .label {
    font-weight: 500;
    font-size: 16px;
    color: #FFFFFF;
}
.total-customers .value {
    font-weight: 500;
    font-size: 40px;
    background: linear-gradient(360deg, #008BFD 0%, #FFFFFF 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
}
.contract-list {
    margin-top: 16px;
    font-size: 14px;
    color: #666;
    list-style: none;
    padding: 0;
    height: 82%;
    overflow-y: auto;
    width: 460px;
    /* éšè—æ»šåŠ¨æ¡ä½†ä¿ç•™æ»šåŠ¨åŠŸèƒ½ */
    scrollbar-width: none; /* Firefox */
    -ms-overflow-style: none; /* IE和Edge */
}
/* Chrome、Safari和Opera */
.contract-list::-webkit-scrollbar {
    display: none;
}
.line {
    position: relative;
    width: 230px;
}
.line::after {
    content: '';
    position: absolute;
    right: 2px;
    top: 0;
    bottom: 0;
    width: 1px;
    background-color: #C9C5C5;
    border-radius: 2px;
}
.contract-list li {
    margin-top: 10px;
}
.stats-cards {
  display: flex;
  gap: 30px;
}
.stat-card {
  flex: 1;
  display: flex;
  align-items: center;
    background-image: url("@/assets/BI/border@2x.png");
    background-size: 100% 100%;
    background-position: center;
    background-repeat: no-repeat;
  height: 142px;
}
.card-icon {
  width: 100px;
  height: 100px;
  margin: 20px 20px 0 10px;
}
.card-content {
  display: flex;
  flex-direction: column;
    gap: 10px;
}
.card-value {
    font-weight: 500;
    font-size: 40px;
  background: linear-gradient(360deg, #008BFD 0%, #FFFFFF 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}
.card-label {
    font-weight: 400;
    font-size: 19px;
    color: rgba(208,231,255,0.7);
}
.equipment-stats {
    border: 1px solid #1A58B0;
  padding: 18px;
  height: 240px;
}
.equipment-header {
    font-weight: 500;
    font-size: 21px;
    display: flex;
    border-bottom: 1px solid;
    border-image: linear-gradient( 270deg, rgba(0,126,255,0) 0%, rgba(0,126,255,0.4549) 35%, #007EFF 78%, #007EFF 100%) 1;
    padding-bottom: 2px;
}
.equipment-title {
    font-weight: 500;
    font-size: 21px;
    background: linear-gradient(360deg, #056DFF 0%, #43E8FC 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
    line-height: 50px;
}
.equipment-icon {
    width: 50px;
    height: 50px;
}
.equipment-items {
  display: flex;
  justify-content: space-around;
  gap: 30px;
}
.equipment-item {
  text-align: center;
}
.equipment-value {
  display: block;
    font-weight: 500;
    font-size: 40px;
    color: #FFFFFF;
    width: 120px;
    height: 110px;
    line-height: 110px;
    background-image: url("@/assets/BI/shujutongji@2x.png");
    background-size: 100% 100%;
    background-position: center;
    background-repeat: no-repeat;
  margin-bottom: 8px;
}
.equipment-label {
    font-weight: 500;
    font-size: 21px;
    color: #FFFFFE;
}
.event-info {
    background-image: url("@/assets/BI/shijianmingchengbeijing@2x.png");
    background-size: 100% 100%;
    background-position: center;
    background-repeat: no-repeat;
  padding: 20px;
  height: 186px;
}
.event-header {
    display: flex;
    align-items: center;
}
.event-icon {
    width: 40px;
    height: 40px;
}
.event-title {
    font-weight: 500;
    font-size: 24px;
    color: #FFFFFE;
    line-height: 30px;
}
.todo-list {
  list-style: none;
  padding: 0;
  margin: 0;
  height: 120px; /* æŒ‰ç”¨æˆ·è¦æ±‚调整高度 */
  overflow: hidden;
  font-size: 15px;
}
.todo-list li {
    border-radius: 8px;
    margin-bottom: 12px;
    padding: 12px 40px;
    height: 74px;
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.todo-title {
    font-weight: 400;
    font-size: 20px;
    color: #FFFFFE;
    position: relative;
}
.todo-title::before {
    content: ''; /* å¿…需,表示这里有一个内容 */
    position: absolute;
    left: -10px; /* å®šä½åˆ°å·¦ä¾§ */
    top: 50%; /* åž‚直居中 */
    transform: translateY(-50%); /* å¾®è°ƒåž‚直居中 */
    width: 6px; /* åœ†çš„直径 */
    height: 6px; /* åœ†çš„直径 */
    background: #498CEB;
    border-radius: 50%; /* è®©å…¶å˜æˆåœ†å½¢ */
}
.todo-division {
    font-weight: 400;
    font-size: 20px;
    color: #FFFFFE;
}
.todo-time {
    font-weight: 400;
    font-size: 20px;
    color: #FFFFFE;
}
.financial-header {
    background-image: url("@/assets/BI/caiwufenxiback@2x.png");
    background-size: 100% 100%;
    background-position: center;
    background-repeat: no-repeat;
}
.financial-title {
    width: 100%;
    font-weight: 500;
    font-size: 16px;
    color: #D9ECFF;
    padding-left: 46px;
    line-height: 36px;
}
/* è‡ªå®šä¹‰å•选按钮组样式 */
.custom-radio-group :deep(.el-radio-button__inner) {
  background-color: transparent;
  color: white;
  border-color: rgba(255, 255, 255, 0.3);
}
.custom-radio-group :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
  background-color: rgba(255, 255, 255, 0.2);
  color: white;
  border-color: rgba(255, 255, 255, 0.5);
  box-shadow: -1px 0 0 0 rgba(255, 255, 255, 0.5);
}
/* ç”Ÿäº§è®¢å•进度表格样式 */
.progress-table-container {
  height: 200px;
  overflow-y: auto;
  overflow-x: hidden;
  margin-top: 10px;
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE和Edge */
}
.progress-table-container::-webkit-scrollbar {
  display: none; /* Chrome、Safari和Opera */
}
.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;
}
/* el-progress ç»„件样式调整 */
.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>
src/views/reportAnalysis/dataDashboard/index0.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,2036 @@
<template>
        <div class="scale-container">
            <div class="data-dashboard" :style="{ transform: `scale(${scaleRatio})` }">
      <!-- å…¨å±æŒ‰é’® - ç§»åŠ¨åˆ°å·¦ä¸Šè§’ -->
      <button class="fullscreen-btn" @click="toggleFullscreen" :title="isFullscreen ? '退出全屏' : '全屏显示'">
        <svg v-if="!isFullscreen" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/>
        </svg>
        <svg v-else width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>
        </svg>
      </button>
      <!-- é¡¶éƒ¨æ ‡é¢˜æ  -->
      <div class="dashboard-header">
        <div class="factory-name">{{ userStore.currentFactoryName }}</div>
      </div>
      <!-- ä¸»è¦å†…容区域 -->
      <div class="dashboard-content">
      <!-- å·¦ä¾§åŒºåŸŸ -->
      <div class="left-panel">
        <!-- å®¢æˆ·ä¿¡æ¯ç»Ÿè®¡åˆ†æž -->
                <div class="panel-header">
                    <span class="panel-title">在制品统计分析</span>
                </div>
        <div class="panel-item-customers">
                    <div class="quality-cards">
                        <div class="quality-cardSec">
                            <div class="quality-card one"></div>
                            <div class="quality-cardTitle">
                                <div>总在制数量</div>
                                <div>{{workInProcessStatistics.totalQuantity}}ä»¶</div>
                            </div>
                        </div>
                        <div class="quality-cardSec">
                            <div class="quality-card two"></div>
                            <div class="quality-cardTitle">
                                <div>平均周转天数</div>
                                <div>{{workInProcessStatistics.avgTurnoverDays}}天</div>
                            </div>
                        </div>
                        <div class="quality-cardSec">
                            <div class="quality-card three"></div>
                            <div class="quality-cardTitle">
                                <div>周转效率</div>
                                <div>{{workInProcessStatistics.turnoverEfficiency}}%</div>
                            </div>
                        </div>
                    </div>
                    <!-- å·¥åºåœ¨åˆ¶å“æ•°é‡æŸ±çж图 -->
                    <div style="height: 70%">
                        <Echarts ref="chart"
                                         :chartStyle="chartStyle"
                                         :grid="grid"
                                         :legend="workInProcessBarLegend"
                                         :series="workInProcessBarSeries"
                                         :tooltip="tooltip"
                                         :xAxis="workInProcessXAxis"
                                         :yAxis="workInProcessYAxis"
                                         :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
                                         style="height: 100%"></Echarts>
                    </div>
        </div>
        <!-- è´¨é‡ç»Ÿè®¡ -->
                <div class="panel-header">
                    <span class="panel-title">近4月质量统计</span>
                </div>
                <div class="main-panel">
                    <div class="panel-item-customers">
                        <div class="quality-cards">
                            <div class="quality-cardSec">
                                <div class="quality-card one"></div>
                                <div class="quality-cardTitle">
                                    <div>原材料检数</div>
                                    <div>{{qualityStatisticsObject.supplierNum}}ä»¶</div>
                                </div>
                            </div>
                            <div class="quality-cardSec">
                                <div class="quality-card two"></div>
                                <div class="quality-cardTitle">
                                    <div>过程检数</div>
                                    <div>{{qualityStatisticsObject.processNum}}ä»¶</div>
                                </div>
                            </div>
                            <div class="quality-cardSec">
                                <div class="quality-card three"></div>
                                <div class="quality-cardTitle">
                                    <div>出厂检数</div>
                                    <div>{{qualityStatisticsObject.factoryNum}}ä»¶</div>
                                </div>
                            </div>
                        </div>
                        <Echarts ref="chart"
                                         :chartStyle="chartStyle"
                                         :grid="grid"
                                         :legend="barLegend"
                                         :series="barSeries1"
                                         :tooltip="tooltip"
                                         :xAxis="xAxis1"
                                         :yAxis="yAxis1"
                                         :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
                                         style="height: 260px"></Echarts>
                    </div>
                </div>
      </div>
      <!-- ä¸­é—´åŒºåŸŸ -->
      <div class="center-panel">
        <!-- é¡¶éƒ¨ç»Ÿè®¡å¡ç‰‡ -->
        <div class="stats-cards">
          <div class="stat-card">
            <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
            <div class="card-content">
              <span class="card-label">员工总数</span>
              <span class="card-value">{{totalStaff}}</span>
            </div>
          </div>
          <div class="stat-card">
            <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
            <div class="card-content">
              <span class="card-label">客户总数</span>
              <span class="card-value">{{totalCustomers}}</span>
            </div>
          </div>
          <div class="stat-card">
            <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
            <div class="card-content">
              <span class="card-label">供应商总数</span>
              <span class="card-value">{{totalSuppliers}}</span>
            </div>
          </div>
        </div>
        <!-- è®¾å¤‡ç»Ÿè®¡ -->
        <div class="equipment-stats">
          <div class="equipment-header">
                        <img src="@/assets/BI/shujutongjiicon@2x.png" alt="图标" class="equipment-icon" />
            <span class="equipment-title">设备统计</span>
          </div>
          <div class="equipment-items">
            <div class="equipment-item">
              <span class="equipment-value">{{equipmentNum}}</span>
              <span class="equipment-label">设备总数</span>
            </div>
            <div class="equipment-item">
              <span class="equipment-value">{{equipmentRepair}}</span>
              <span class="equipment-label">待维修设备</span>
            </div>
            <div class="equipment-item">
              <span class="equipment-value">{{equipmentMaintain}}</span>
              <span class="equipment-label">待保养设备</span>
            </div>
            <div class="equipment-item">
              <span class="equipment-value">{{totalMeasuring}}</span>
              <span class="equipment-label">计量器具总数</span>
            </div>
          </div>
        </div>
        <!-- äº‹ä»¶åç§° -->
        <div class="event-info">
          <div class="event-header">
                        <img src="@/assets/BI/shijianmingxiicon@2x.png" alt="图标" class="event-icon" />
            <span class="event-title">事件名称</span>
          </div>
          <div class="event-content">
                        <ul class="todo-list" v-if="todoList.length > 0" ref="refTodoList">
   <li v-for="item in todoList" :key="item.id">
    <div style="display: flex;flex-direction: column;justify-content: space-between;width: 100%;gap: 20px">
     <div style="display: flex;justify-content: space-between;align-items: center;">
      <div class="todo-title">待办编号:{{item.approveId}}</div>
      <div class="todo-division">部门:{{item.approveDeptName}}</div>
      <div class="todo-time">{{item.approveTime}}</div>
     </div>
     <div class="todo-division">待办事由:{{item.approveReason}}</div>
    </div>
   </li>
 </ul>
                        <div v-else style="text-align: center">
                            æš‚无数据
                        </div>
          </div>
        </div>
                <div class="financial-header">
                    <span class="financial-title">各生产订单的完成进度统计</span>
                </div>
                <div class="main-panel">
                    <div class="panel-item-customers">
                        <div class="order-statistics-cards" style="margin-bottom: 0px;">
                            <div class="quality-cardSec">
                                <div class="quality-card four"></div>
                                <div class="quality-cardTitle">
                                    <div>总订单数</div>
                                    <div>{{orderStatisticsObject.totalOrderCount}}ä»¶</div>
                                </div>
                            </div>
                            <div class="quality-cardSec">
                                <div class="quality-card five"></div>
                                <div class="quality-cardTitle">
                                    <div>未完成订单数</div>
                                    <div>{{orderStatisticsObject.uncompletedOrderCount}}ä»¶</div>
                                </div>
                            </div>
                            <div class="quality-cardSec">
                                <div class="quality-card six"></div>
                                <div class="quality-cardTitle">
                                    <div>部分完成订单数</div>
                                    <div>{{orderStatisticsObject.partialCompletedOrderCount}}ä»¶</div>
                                </div>
                            </div>
                            <div class="quality-cardSec">
                                <div class="quality-card seven"></div>
                                <div class="quality-cardTitle">
                                    <div>已完成订单数</div>
                                    <div>{{orderStatisticsObject.completedOrderCount}}ä»¶</div>
                                </div>
                            </div>
                        </div>
                        <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>
      <!-- å³ä¾§åŒºåŸŸ -->
      <div class="right-panel">
        <!-- åº”收应付统计 -->
                <div class="panel-header">
                    <span class="panel-title">应收应付统计</span>
                </div>
                <div class="panel-item-customers">
                    <div style="display: flex;justify-content: space-between;margin-bottom: 20px;">
                        <div class="section-title">应收应付统计</div>
<!--                        <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable" class="custom-radio-group">-->
<!--                            <el-radio-button label="按周" :value="1" />-->
<!--                            <el-radio-button label="按月" :value="2" />-->
<!--                            <el-radio-button label="按季度" :value="3" />-->
<!--                        </el-radio-group>-->
                    </div>
                    <Echarts ref="chart"
                                     :color="barColors2"
                                     :chartStyle="chartStyle"
                                     :grid="grid"
                   :legend="barLegend2"
                                     :series="barSeries"
                                     :tooltip="tooltip"
                                     :xAxis="xAxis"
                                     :yAxis="yAxis"
                                     :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
                                     style="height: 260px"></Echarts>
                </div>
        <!-- å›žæ¬¾ä¸Žå¼€ç¥¨åˆ†æž -->
         <div class="panel-header">
                    <span class="panel-title">近一月回款与开票分析</span>
                </div>
        <div class="panel-item-customers" style="padding-top: 60px;">
                    <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries"
                                 :tooltip="tooltipLine" :xAxis="xAxis2" :yAxis="yAxis2" :options="{backgroundColor: 'transparent', textStyle: {color: '#FFFFFF'}}" style="height: 270px;"></Echarts>
                </div>
      </div>
      </div>
      </div>
    </div>
</template>
<script setup>
import * as echarts from 'echarts'
import { ref, reactive, onMounted, onBeforeUnmount, nextTick } from 'vue'
import autofit from 'autofit.js'
import Echarts from "@/components/Echarts/echarts.vue";
import useUserStore from '@/store/modules/user'
import {
    analysisCustomerContractAmounts, getAmountHalfYear,
    homeTodos,
    qualityStatistics,
    statisticsReceivablePayable,
    getProgressStatistics,
      getWorkInProcessTurnover
} from "@/api/viewIndex.js";
import {staffOnJobListPage} from "@/api/personnelManagement/employeeRecord.js";
import {listCustomer} from "@/api/basicData/customerFile.js";
import {listSupplier} from "@/api/basicData/supplierManageFile.js";
import {getLedgerPage} from "@/api/equipmentManagement/ledger.js";
import {getRepairPage} from "@/api/equipmentManagement/repair.js";
import {getUpkeepPage} from "@/api/equipmentManagement/upkeep.js";
import {measuringInstrumentListPage} from "@/api/equipmentManagement/measurementEquipment.js";
import {listPageAnalysis} from "@/api/financialManagement/expenseManagement.js";
import {productOrderListPage} from "@/api/productionManagement/productionOrder.js";
// å…¨å±ç›¸å…³çŠ¶æ€
const isFullscreen = ref(false);
// ç¼©æ”¾æ¯”例
const scaleRatio = ref(1)
// è®¾è®¡å°ºå¯¸ï¼ˆåŸºå‡†å°ºå¯¸ï¼‰- æ ¹æ®å®žé™…设计稿调整
const designWidth = 1920
const designHeight = 1080
// ç”¨æˆ·store
const userStore = useUserStore()
// å“åº”式数据
const currentTime = ref('')
const currentDate = ref('')
const timer = ref(null)
const charts = ref([])
// å›¾è¡¨å¼•用
const customerPieChartRef = ref(null)
const salesBarChartRef = ref(null)
const dataBarChartRef = ref(null)
const financialAreaChartRef = ref(null)
const realtimeLineChartRef = ref(null)
const refContractList = ref(null)
const refTodoList = ref(null)
const progressTableRef = ref(null)
const timerScroll = ref(null)
const progressTableScrollTimer = ref(null)
const isTableScrolling = ref(false)
const tableScrollTimeout = ref(null)
const tableRowRefs = ref([])
const rowsUnderHeader = ref(new Set())
const chartStylePie = {
    width: '100%',
    height: '100%' // è®¾ç½®å›¾è¡¨å®¹å™¨çš„高度
}
const materialPieSeries = ref([
    {
        type: 'pie',
        radius: ['0%', '90%'],
        avoidLabelOverlap: false,
        itemStyle: {
            borderColor: '#fff',
            borderWidth: 0
        },
        label: {
            show: false
        },
        data: []
    }
])
const pieLegend = reactive({
    show: false,
})
const sum = ref(0)
const totalStaff = ref(0)
const totalCustomers = ref(0)
const totalSuppliers = ref(0)
const yny = ref(0)
const chain = ref(0)
const equipmentNum = ref(0)
const equipmentRepair = ref(0)
const equipmentMaintain = ref(0)
const totalMeasuring = ref(0)
const pieTooltip = reactive({
    trigger: 'item',
    formatter: function (params) {
        // åŠ¨æ€ç”Ÿæˆæç¤ºä¿¡æ¯ï¼ŒåŸºäºŽæ•°æ®é¡¹çš„ name å±žæ€§
        const description = params.name === '本月回款金额' ? '本月回款金额' : '应收款金额';
        return `<div style="color: #B8C8E0">${description} ${params.value}元 ${params.percent}%</div>`;
    },
    position: 'right'
})
const qualityStatisticsObject = ref({
    supplierNum: 0,
    processNum: 0,
    factoryNum: 0,
})
// è®¢å•统计对象
const orderStatisticsObject = ref({
    totalOrderCount: 0,
    uncompletedOrderCount: 0,
    partialCompletedOrderCount: 0,
    completedOrderCount: 0,
})
// åœ¨åˆ¶å“å‘¨è½¬ç»Ÿè®¡å¯¹è±¡
const workInProcessStatistics = ref({
    totalQuantity: 0,
    avgTurnoverDays: 0,
    turnoverEfficiency: 0,
})
const chartStyle = {
    width: '100%',
    height: '150%' // è®¾ç½®å›¾è¡¨å®¹å™¨çš„高度
}
const barSeries = ref([
    {
        name: '应付金额',
        type: 'bar',
        data: [],
        label: {
            show: true,
        },
        itemStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: '#00A4ED' },
                { offset: 1, color: '#4EE4FF' }
            ])
        }
    },
    {
        name: '应收金额',
        type: 'bar',
        data: [],
        label: {
            show: true,
        },
        itemStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: '#537EF5' },
                { offset: 1, color: '#9061F8' }
            ])
        }
    }
])
const radio1 = ref(1)
const barColors2 = ['#5181DB', '#D369E0', '#F2CA6D', '#60CCA8']
const grid = {
    left: '3%',
    right: '4%',
    bottom: '3%',
    containLabel: true
}
const lineLegend = {
    show: true,
  textStyle: { color: '#B8C8E0' },
    data: ['开票', '回款']
}
const lineSeries = ref([
    {
        type: 'line',
        data: [],
        label: {
            show: true
        },
        showSymbol: true, // æ˜¾ç¤ºåœ†ç‚¹
    },
])
const tooltipLine = {
    trigger: 'axis',
}
const yAxis2 = ref([
    {
        type: 'value',
    }
])
const xAxis2 = ref([
    {
        type: 'category',
        data: [],
        axisLabel: {
            interval: 0,
            formatter: function(value) {
                return value.replace(/~/g, '\n');
            },
        }
    }
])
const barLegend2 = {
    show: true,
    textStyle: { color: '#B8C8E0' },
    data: ['应付金额', '应收金额']
}
const barLegend = {
    show: true,
    textStyle: { color: '#B8C8E0' },
    data: ['原材料合格数', '过程合格数', '出不合格数']
}
const barLegend1 = {
    show: false,
    textStyle: { color: '#B8C8E0' },
    data: []
}
const barSeries11 = ref([
    {
        name: '生产订单统计',
        type: 'bar',
        barGap: 0,
        emphasis: {
            focus: 'series'
        },
        itemStyle: {
            // ä½¿ç”¨å‡½æ•°æ ¹æ®æ•°æ®ç´¢å¼•返回不同颜色
            color: function(params) {
                const colorStops = [
                    [
                        { offset: 1, color: '#00A4ED' },
                        { offset: 0, color: '#4EE4FF' }
                    ],
                    [
                        { offset: 1, color: '#3378FF' },
                        { offset: 0, color: '#4E8AFF' }
                    ],
                    [
                        { offset: 1, color: '#FF6B6B' },
                        { offset: 0, color: '#FF8E8E' }
                    ],
                    [
                        { offset: 1, color: '#537EF5' },
                        { offset: 0, color: '#9061F8' }
                    ]
                ]
                const stops = colorStops[params.dataIndex] || colorStops[0]
                return {
                    type: 'linear',
                    x: 0,
                    y: 0,
                    x2: 0,
                    y2: 1,
                    colorStops: stops
                }
            }
        },
        data: []
    }
])
const barSeries1 = ref([
    {
        name: '原材料合格数',
        type: 'bar',
        barGap: 0,
        emphasis: {
            focus: 'series'
        },
        itemStyle: {
            color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [
                    { offset: 1, color: '#00A4ED' },
                    { offset: 0, color: '#4EE4FF' }
                ]
            }
        },
        data: []
    },
    {
        name: '过程合格数',
        type: 'bar',
        emphasis: {
            focus: 'series'
        },
        itemStyle: {
            color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [
                    { offset: 1, color: '#3378FF' },
                    { offset: 0, color: '#4E8AFF' }
                ]
            }
        },
        data: []
    },
    {
        name: '出厂合格数',
        type: 'bar',
        emphasis: {
            focus: 'series'
        },
        itemStyle: {
            color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [
                    { offset: 1, color: '#537EF5' },
                    { offset: 0, color: '#9061F8' }
                ]
            }
        },
        data: []
    },
])
const tooltip = {
    trigger: 'axis',
    axisPointer: {
        type: 'shadow'
    },
    formatter: function (params) {
        let result = params[0].axisValueLabel + '<br/>';
        params.forEach(item => {
            result += `<div style="color: #B8C8E0">${item.marker} ${item.seriesName}: ${item.value}</div>`;
        });
        return result;
    }
}
const xAxis = [{
    type: 'value',
}]
const yAxis = [{
    type: 'category',
    data: ['应收应付统计']
}]
const xAxis1 = ref([{
    type: 'category',
    axisTick: { show: false },
    axisLabel: { color: '#B8C8E0' },
    data: []
}])
const yAxis1 = [{
    type: 'value',
    axisLabel: { color: '#B8C8E0' }
}]
const xAxis3 = ref([{
    type: 'category',
    axisTick: { show: false },
    axisLabel: { color: '#B8C8E0' },
    data: []
}])
const yAxis3 = [{
    type: 'value',
    axisLabel: { color: '#B8C8E0' }
}]
// åœ¨åˆ¶å“å·¥åºæŸ±çŠ¶å›¾é…ç½®
const workInProcessXAxis = ref([{
    type: 'category',
    axisTick: { show: false },
    axisLabel: { color: '#B8C8E0' },
    data: []
}])
const workInProcessYAxis = [{
    type: 'value',
    axisLabel: { color: '#B8C8E0' },
    name: ''
}]
const workInProcessBarLegend = {
    show: false,
    textStyle: { color: '#B8C8E0' },
    data: []
}
const workInProcessBarSeries = ref([
    {
        name: '在制品数量',
        type: 'bar',
        barWidth: 25, // å›ºå®šæŸ±çŠ¶å›¾å®½åº¦ä¸º40px
        barGap: 0,
        emphasis: {
            focus: 'series'
        },
        itemStyle: {
            color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [
                    { offset: 0, color: '#4EE4FF' },
                    { offset: 1, color: '#00A4ED' }
                ]
            }
        },
        label: {
            show: true,
            position: 'top',
            color: '#B8C8E0'
        },
        data: []
    }
])
// å¾…办事项
const todoList = ref([])
// ç”Ÿäº§è®¢å•完成进度表格数据
const progressTableData = ref([])
// è®¡ç®—完成进度百分比
const calculateProgress = (item) => {
    if (!item) return 0
    // ä¼˜å…ˆä½¿ç”¨completionStatus字段
    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)
    }
    // å¦‚果没有completionStatus,则根据完成数量和需求数量计算
    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 calculateScale = () => {
  const container = document.querySelector('.scale-container')
  if (!container) return
  // èŽ·å–å®¹å™¨çš„å®žé™…å°ºå¯¸
    const rect = container.getBoundingClientRect?.()
    const containerWidth = container.clientWidth || rect?.width || window.innerWidth
    const containerHeight = container.clientHeight || rect?.height || window.innerHeight
  // è®¡ç®—宽高缩放比例,取较小值以保证内容完整显示(等比缩放)
  const scaleX = containerWidth / designWidth
  const scaleY = containerHeight / designHeight
  scaleRatio.value = Math.min(scaleX, scaleY)
  // è§¦å‘图表resize
  charts.value.forEach(chart => {
    if (chart && chart.resize) {
      chart.resize()
    }
  })
}
// çª—口大小变化处理
const handleResize = () => {
  // å»¶è¿Ÿæ‰§è¡Œï¼Œç¡®ä¿DOM更新完成
  setTimeout(() => {
    calculateScale()
  }, 100)
}
// é”€æ¯å›¾è¡¨å®žä¾‹
const disposeCharts = () => {
  charts.value.forEach(chart => {
    if (chart && chart.dispose) {
      chart.dispose()
    }
  })
  charts.value = []
}
// åˆåŒé‡‘额
const analysisCustomer = () => {
    analysisCustomerContractAmounts().then((res) => {
        sum.value = res.data.sum
        yny.value = res.data.yny
        chain.value = res.data.chain
        // ä¸ºæ¯ä¸ªæ•°æ®é¡¹åˆ†é…éšæœºé¢œè‰²
        materialPieSeries.value[0].data = res.data.item.map(item => ({
            ...item,
            itemStyle: { color: getRandomColor() }
        }))
    })
}
// åœ¨åˆ¶å“å‘¨è½¬ç»Ÿè®¡
const workInProcessTurnoverInfo = () => {
    getWorkInProcessTurnover().then((res) => {
        console.log("在制品周转统计数据:", res)
        if (!res || !res.data) {
            console.warn('在制品周转统计数据为空')
            return
        }
        // ä»ŽæŽ¥å£èŽ·å–ç»Ÿè®¡æ•°æ®
        workInProcessStatistics.value = {
            totalQuantity: res.data.totalOrderCount || 0,
            avgTurnoverDays: res.data.averageTurnoverDays || 0,
            turnoverEfficiency: res.data.turnoverEfficiency || 0,
        }
        // è®¾ç½®å·¥åºæŸ±çŠ¶å›¾æ•°æ®
        // X轴:processDetails (工序详情数组)
        // Y轴:processQuantityDetails (工序数量详情数组)
        if (res.data.processDetails && Array.isArray(res.data.processDetails)) {
            // è®¾ç½®X轴数据(工序名称)
            workInProcessXAxis.value[0].data = res.data.processDetails
        } else {
            workInProcessXAxis.value[0].data = []
        }
        if (res.data.processQuantityDetails && Array.isArray(res.data.processQuantityDetails)) {
            // è®¾ç½®Y轴数据(在制品数量)
            workInProcessBarSeries.value[0].data = res.data.processQuantityDetails
        } else {
            workInProcessBarSeries.value[0].data = []
        }
    }).catch((error) => {
        console.error('获取在制品周转统计失败:', error)
    })
}
// è´¨æ£€ç»Ÿè®¡
const qualityStatisticsInfo = () => {
    qualityStatistics().then((res) => {
        res.data.item.forEach(item => {
            xAxis1.value[0].data.push(item.date)
            barSeries1.value[0].data.push(item.supplierNum)
            barSeries1.value[1].data.push(item.processNum)
            barSeries1.value[2].data.push(item.factoryNum)
        })
        qualityStatisticsObject.value.supplierNum = res.data.supplierNum
        qualityStatisticsObject.value.processNum = res.data.processNum
        qualityStatisticsObject.value.factoryNum = res.data.factoryNum
    })
}
// å„生产订单的完成进度统计
const progressStatisticsInfo = () => {
    // ä»Žç»Ÿè®¡æŽ¥å£èŽ·å–ç»Ÿè®¡æ•°æ®
    getProgressStatistics().then((res) => {
        console.log("生产订单完成进度统计数据:", res)
        if (!res || !res.data) {
            console.warn('生产订单完成进度统计数据为空')
            return
        }
        // ä»ŽæŽ¥å£èŽ·å–ç»Ÿè®¡æ•°æ®
        orderStatisticsObject.value = {
            totalOrderCount: res.data.totalOrderCount || 0,
            uncompletedOrderCount: res.data.uncompletedOrderCount || 0,
            partialCompletedOrderCount: res.data.partialCompletedOrderCount || 0,
            completedOrderCount: res.data.completedOrderCount || 0
        }
        progressTableData.value = res.data.completedOrderDetails || []
        // é‡ç½®è¡Œå¼•用
        tableRowRefs.value = []
        rowsUnderHeader.value.clear()
        // åœ¨èŽ·å–åˆ°æ•°æ®åŽï¼Œåˆå§‹åŒ–æ»šåŠ¨åŠŸèƒ½
        nextTick(() => {
            initProgressTableScroll()
        })
    }).catch((error) => {
        console.error('获取生产订单完成进度统计失败:', error)
    })
}
// è´¢åŠ¡ç»Ÿè®¡
// const accountStatisticsInfo = () => {
//     listPageAnalysis().then((res) => {
//         xAxis3.value[0].data = res.data.days
//         barSeries11.value[0].data = res.data.totalIncome
//     })
// }
const getNum = () => {
    const params = {
        pageNum: -1,
        pageSize: -1,
    }
    staffOnJobListPage({...params, staffState: 1}).then(res => {
        totalStaff.value = res.data.total
    })
    listCustomer(params).then((res) => {
        totalCustomers.value = res.total;
    });
    listSupplier(params).then((res) => {
        totalSuppliers.value = res.data.total
    });
}
const getLedgerNum = () => {
    const params = {
        pageNum: -1,
        pageSize: -1,
    }
    getLedgerPage(params).then((res) => {
        equipmentNum.value = res.data.total
    });
    getRepairPage({...params, status:0}).then((res) => {
        equipmentRepair.value = res.data.total
    });
    getUpkeepPage({...params, status:0}).then((res) => {
        equipmentMaintain.value = res.data.total
    });
    measuringInstrumentListPage(params).then((res) => {
        totalMeasuring.value = res.data.total
    });
}
// å¾…办事项
const todoInfoS = () => {
    homeTodos().then((res) => {
        todoList.value = res.data
        // åœ¨èŽ·å–åˆ°å¾…åŠžäº‹é¡¹æ•°æ®åŽï¼Œåˆå§‹åŒ–æ»šåŠ¨åŠŸèƒ½
        nextTick(() => {
            initTodoListScroll()
        })
    })
}
// åº”付应收统计
const statisticsReceivable = (type) => {
    statisticsReceivablePayable({type: radio1.value}).then((res) => {
        // è®¾ç½®åº”付金额数据
        barSeries.value[0].data = [
            { value: res.data.payableMoney }
        ]
        // è®¾ç½®åº”收金额数据
        barSeries.value[1].data = [
            { value: res.data.receivableMoney }
        ]
    })
}
const getAmountHalfYearNum = async () => {
    const res = await getAmountHalfYear()
    console.log(res)
    const monthName = []
    const receiptAmount = []
    const invoiceAmount = []
    res.data.forEach(item => {
        monthName.push(item.month)
        receiptAmount.push(item.receiptAmount)
        invoiceAmount.push(item.invoiceAmount)
    })
    // æ­£ç¡®å“åº”式赋值:创建新的 xAxis å’Œ series å¯¹è±¡
    xAxis2.value[0].data = monthName
    xAxis2.value[0].data = monthName.map(item => item.replace(/~/g, '\n~'));
    lineSeries.value = [
        {
            name: '开票',
            type: 'line',
            data: receiptAmount,
            stack: 'Total',
            areaStyle: {
                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                    {
                        offset: 0,
                        color: 'rgba(131, 207, 255, 1)'
                    },
                    {
                        offset: 1,
                        color: 'rgba(186, 228, 255, 1)'
                    }
                ])
            },
            itemStyle: {
                color: '#2D99FF',
                borderColor: '#2D99FF'
            },
            emphasis: {
                focus: 'series'
            },
            lineStyle: {
                width: 0
            },
            showSymbol: true,
        },
        {
            name: '回款',
            type: 'line',
            data: invoiceAmount,
            stack: 'Total',
            lineStyle: {
                width: 0
            },
            itemStyle: {
                color: '#83CFFF',
                borderColor: '#83CFFF'
            },
            showSymbol: true,
            areaStyle: {
                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                    {
                        offset: 0,
                        color: 'rgba(54, 153, 255, 1)'
                    },
                    {
                        offset: 1,
                        color: 'rgba(89, 169, 254, 1)'
                    }
                ])
            },
            emphasis: {
                focus: 'series'
            },
        }
    ]
}
// è‡ªåŠ¨è½®æ¢å‘¨ã€æœˆã€å­£åº¦çš„å®šæ—¶å™¨
const autoSwitchTimer = ref(null)
// è®¾ç½®è¡Œå¼•用
const setRowRef = (el, index) => {
    if (el) {
        tableRowRefs.value[index] = el
    }
}
// åˆ¤æ–­è¡Œæ˜¯å¦åœ¨è¡¨å¤´ä¸‹æ–¹
const isRowUnderHeader = (index) => {
    return 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
    // æ¸…理之前可能存在的克隆行(保留原始数据行)
    // åŽŸå§‹æ•°æ®è¡Œçš„æ•°é‡åº”è¯¥ç­‰äºŽ progressTableData.value.length
    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 initTodoListScroll = () => {
    const todoList = refTodoList.value
    // å¼ºåˆ¶å¯ç”¨æ»šåŠ¨ï¼Œä¸æ£€æŸ¥ä»»ä½•æ¡ä»¶
    if (todoList) {
        // åˆ›å»ºä¸€ä¸ªå…‹éš†é¡¹ï¼Œç”¨äºŽå®žçŽ°æ— ç¼æ»šåŠ¨
        const scrollItems = Array.from(todoList.querySelectorAll('li'))
        if (scrollItems.length > 0) {
            // ç¡®ä¿æœ‰è¶³å¤Ÿçš„项目用于滚动
            // å¦‚果项目太少,多复制几次以确保滚动效果
            if (scrollItems.length < 4) {
                const originalItems = [...scrollItems]
                for (let i = 0; i < 4; i++) {
                    originalItems.forEach(item => {
                        const clone = item.cloneNode(true)
                        todoList.appendChild(clone)
                    })
                }
                // é‡æ–°èŽ·å–æ‰€æœ‰é¡¹ç›®
                scrollItems.push(...Array.from(todoList.querySelectorAll('li')).slice(scrollItems.length));
            }
            const itemHeight = scrollItems[0]?.offsetHeight || 0
            const containerHeight = todoList.clientHeight
            const cloneCount = Math.ceil(containerHeight / itemHeight) + 2
            // å…‹éš†å‰å‡ ä¸ªé¡¹ç›®å¹¶æ·»åŠ åˆ°åˆ—è¡¨æœ«å°¾ï¼Œå®žçŽ°æ— ç¼æ»šåŠ¨
            for (let i = 0; i < cloneCount; i++) {
                const clone = scrollItems[i % scrollItems.length].cloneNode(true)
                todoList.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) // æ ‡å‡†åŒ–为60fps的速度
                    // å½“滚动超过原始内容长度时,重置位置实现无缝滚动
                    const maxScroll = Math.max(todoList.scrollHeight - containerHeight - cloneCount * itemHeight, itemHeight * scrollItems.length)
                    if (scrollPosition >= maxScroll) {
                        scrollPosition = 0
                        todoList.scrollTop = 0
                    } else {
                        todoList.scrollTop = scrollPosition
                    }
                }
                todoList._animationFrame = requestAnimationFrame(scrollAnimation)
            }
            // å¯åŠ¨æ»šåŠ¨åŠ¨ç”»
            todoList._animationFrame = requestAnimationFrame(scrollAnimation)
            // è®¾ç½®æ»šåЍ-暂停-滚动的循环效果
            const pauseTimer = setInterval(() => {
                isPaused = !isPaused
            }, pauseTime)
            // æ¸…理定时器
            todoList._pauseTimer = pauseTimer
        }
    }
}
const getRandomColor = () => {
    // ç”Ÿæˆæµ…色:R、G、B åˆ†é‡éƒ½åœ¨ 150-255 ä¹‹é—´
    const r = Math.floor(Math.random() * 106) + 150; // 150-255
    const g = Math.floor(Math.random() * 106) + 150; // 150-255
    const b = Math.floor(Math.random() * 106) + 150; // 150-255
    // å°† RGB è½¬æ¢ä¸ºåå…­è¿›åˆ¶é¢œè‰²
    return '#' + r.toString(16).padStart(2, '0') + g.toString(16).padStart(2, '0') + b.toString(16).padStart(2, '0');
}
// æ›´æ–°æ—¶é—´
const updateTime = () => {
  const now = new Date()
  currentTime.value = now.toLocaleTimeString('zh-CN', { hour12: false })
  currentDate.value = now.toLocaleDateString('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    weekday: 'long'
  })
}
// åˆå§‹åŒ–æ—¶é—´
const initTime = () => {
  updateTime()
  timer.value = setInterval(updateTime, 1000)
}
// å…¨å±åŠŸèƒ½å®žçŽ° - é’ˆå¯¹scale-container元素
const toggleFullscreen = () => {
    const element = document.querySelector('.scale-container')
    if (!element) return
    if (!isFullscreen.value) {
        if (element.requestFullscreen) {
            element.requestFullscreen()
        } else if (element.webkitRequestFullscreen) {
            element.webkitRequestFullscreen()
        } else if (element.msRequestFullscreen) {
            element.msRequestFullscreen()
        }
    } else {
        if (document.exitFullscreen) {
            document.exitFullscreen()
        } else if (document.webkitExitFullscreen) {
            document.webkitExitFullscreen()
        } else if (document.msExitFullscreen) {
            document.msExitFullscreen()
        }
    }
}
// ç›‘听全屏变化事件
const handleFullscreenChange = () => {
  const fullscreenElement = document.fullscreenElement ||
                           document.webkitFullscreenElement ||
                           document.msFullscreenElement
  isFullscreen.value = fullscreenElement && fullscreenElement.classList.contains('scale-container')
  // å…¨å±çŠ¶æ€å˜åŒ–æ—¶ï¼Œå»¶è¿Ÿé‡æ–°è®¡ç®—ç¼©æ”¾æ¯”ä¾‹ï¼ˆç¡®ä¿DOM更新完成)
  setTimeout(() => {
    calculateScale()
  }, 200)
}
// ç”Ÿå‘½å‘¨æœŸé’©å­
onMounted(() => {
  initTime()
  // ä½¿ç”¨nextTick确保DOM完全渲染后再初始化图表
  nextTick(() => {
    // è®¡ç®—初始缩放比例
    calculateScale()
    // åˆå§‹åŒ–autofit自适应(如果需要保留autofit,可以保留,但主要缩放由scale-container控制)
    // autofit.init({ dh: 800, dw: 1280, el: '.data-dashboard', resize: true }, false)
    // æ·»åŠ è‡ªåŠ¨æ»šåŠ¨åŠ¨ç”»æ•ˆæžœ - å®¢æˆ·ä¿¡æ¯åˆ—表
    const contractList = refContractList.value
    if (contractList && contractList.scrollHeight > contractList.clientHeight) {
      // åˆ›å»ºä¸€ä¸ªå…‹éš†é¡¹ï¼Œç”¨äºŽå®žçŽ°æ— ç¼æ»šåŠ¨
      const scrollItems = Array.from(contractList.querySelectorAll('li'))
      const itemHeight = scrollItems[0]?.offsetHeight || 0
      const containerHeight = contractList.clientHeight
      const cloneCount = Math.ceil(containerHeight / itemHeight) + 2
      // å…‹éš†å‰å‡ ä¸ªé¡¹ç›®å¹¶æ·»åŠ åˆ°åˆ—è¡¨æœ«å°¾ï¼Œå®žçŽ°æ— ç¼æ»šåŠ¨
      for (let i = 0; i < cloneCount; i++) {
        const clone = scrollItems[i % scrollItems.length].cloneNode(true)
        contractList.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) // æ ‡å‡†åŒ–为60fps的速度
          // å½“滚动超过原始内容长度时,重置位置实现无缝滚动
          if (scrollPosition >= contractList.scrollHeight - containerHeight - cloneCount * itemHeight) {
            scrollPosition = 0
            contractList.scrollTop = 0
          } else {
            contractList.scrollTop = scrollPosition
          }
        }
        timerScroll.value = requestAnimationFrame(scrollAnimation)
      }
      // å¯åŠ¨æ»šåŠ¨åŠ¨ç”»
      timerScroll.value = requestAnimationFrame(scrollAnimation)
      // è®¾ç½®æ»šåЍ-暂停-滚动的循环效果
      const pauseTimer = setInterval(() => {
        isPaused = !isPaused
      }, pauseTime)
      // æ¸…理定时器
      contractList._pauseTimer = pauseTimer
    }
    // å¾…办事项列表滚动功能已移至todoInfoS函数中,在获取数据后初始化
  })
  window.addEventListener('resize', handleResize)
  window.addEventListener('fullscreenchange', handleFullscreenChange)
  window.addEventListener('webkitfullscreenchange', handleFullscreenChange)
  window.addEventListener('MSFullscreenChange', handleFullscreenChange)
  analysisCustomer()
  workInProcessTurnoverInfo()
  qualityStatisticsInfo()
    // accountStatisticsInfo()
    progressStatisticsInfo()
  getNum()
  getLedgerNum()
  todoInfoS()
    statisticsReceivable()
    getAmountHalfYearNum()
  // è®¾ç½®è‡ªåŠ¨è½®æ¢å‘¨ã€æœˆã€å­£åº¦çš„å®šæ—¶å™¨ï¼Œæ¯10秒切换一次
  autoSwitchTimer.value = setInterval(() => {
    // å¾ªçŽ¯åˆ‡æ¢ï¼š1(周) -> 2(月) -> 3(季度) -> 1(周)
    radio1.value = radio1.value === 3 ? 1 : radio1.value + 1
    statisticsReceivable()
  }, 10000) // 10秒切换一次
})
onBeforeUnmount(() => {
  if (timer.value) {
    clearInterval(timer.value)
  }
  if (timerScroll.value) {
    cancelAnimationFrame(timerScroll.value)
  }
  // æ¸…理滚动列表的暂停定时器
  const contractList = refContractList.value
  if (contractList && contractList._pauseTimer) {
    clearInterval(contractList._pauseTimer)
  }
  // æ¸…理待办事项列表的动画和定时器
  const todoList = refTodoList.value
  if (todoList) {
    if (todoList._animationFrame) {
      cancelAnimationFrame(todoList._animationFrame)
      todoList._animationFrame = null
    }
    if (todoList._pauseTimer) {
      clearInterval(todoList._pauseTimer)
      todoList._pauseTimer = null
    }
  }
  // æ¸…理生产订单进度表格的动画和定时器
  const progressTable = progressTableRef.value
  if (progressTable) {
    if (progressTableScrollTimer.value) {
      cancelAnimationFrame(progressTableScrollTimer.value)
      progressTableScrollTimer.value = null
    }
    if (progressTable._pauseTimer) {
      clearInterval(progressTable._pauseTimer)
      progressTable._pauseTimer = null
    }
  }
  // æ¸…理表格滚动定时器
  if (tableScrollTimeout.value) {
    clearTimeout(tableScrollTimeout.value)
    tableScrollTimeout.value = null
  }
  // æ¸…理自动轮换周、月、季度的定时器
  if (autoSwitchTimer.value) {
    clearInterval(autoSwitchTimer.value)
    autoSwitchTimer.value = null
  }
  window.removeEventListener('resize', handleResize)
  window.removeEventListener('fullscreenchange', handleFullscreenChange)
  window.removeEventListener('webkitfullscreenchange', handleFullscreenChange)
  window.removeEventListener('MSFullscreenChange', handleFullscreenChange)
  // ç§»é™¤æˆ‘们添加的autofit动态调整监听器
  if (window._autofitUpdateHandler) {
    window.removeEventListener('resize', window._autofitUpdateHandler)
    delete window._autofitUpdateHandler
  }
  disposeCharts()
  // å…³é—­autofit
  autofit.off()
})
</script>
<style scoped>
/* å¤–部缩放容器 - å æ®æ•´ä¸ªè§†å£ */
.scale-container {
  position: relative;
    width: 100%;
    /* é¡µé¢åœ¨å¸¸è§„布局下(有顶栏)默认减去 84px,避免内容被裁切 */
    height: calc(100vh - 84px);
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #000;
    overflow: hidden;
}
/* å†…部内容区域 - å›ºå®šè®¾è®¡å°ºå¯¸ */
.data-dashboard {
  position: relative;
  width: 1920px;
  height: 1080px;
    background-image: url("@/assets/BI/backImage@2x.png");
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    transform-origin: center center;
}
/* å…¨å±çŠ¶æ€çš„æ ·å¼ - ä½œç”¨äºŽscale-container */
.scale-container:fullscreen {
    width: 100vw;
    height: 100vh;
  margin: 0;
  padding: 0;
  background-color: #000;
  z-index: 9999;
}
/* Webkit浏览器前缀 */
.scale-container:-webkit-full-screen {
  width: 100vw;
  height: 100vh;
  margin: 0;
  padding: 0;
  background-color: #000;
  z-index: 9999;
}
/* MS浏览器前缀 */
.scale-container:-ms-fullscreen {
  width: 100vw;
  height: 100vh;
  margin: 0;
  padding: 0;
  background-color: #000;
  z-index: 9999;
}
.dashboard-header {
  position: relative;
  z-index: 1;
    height: 86px;
    background-image: url("@/assets/BI/biaoti.png");
    background-size: cover;
    background-repeat: no-repeat;
  display: flex;
  align-items: center;
  justify-content: center;
}
.factory-name {
  font-weight: 600;
font-size: 52px;
color: #FFFFFF;
top: 16px;
position: absolute;
}
.fullscreen-btn {
  position: absolute;
  top: 10px;
  left: 20px;
  width: 40px;
  height: 40px;
  background: rgba(0, 20, 60, 0.8);
  border: 1px solid rgba(0, 212, 255, 0.3);
  border-radius: 6px;
  color: #00d4ff;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s;
  z-index: 10000;
}
.fullscreen-btn:hover {
  background: rgba(0, 30, 90, 0.9);
  border-color: rgba(0, 212, 255, 0.5);
}
.dashboard-content {
  position: relative;
  z-index: 1;
  display: flex;
  gap: 30px;
  padding: 0 30px;
    height: calc(100% - 86px);
  overflow: hidden;
}
/* ç¡®ä¿å„面板能够正确显示 */
.left-panel, .center-panel, .right-panel {
  overflow: hidden;
}
.left-panel,
.right-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 24px;
    width: 520px;
}
.center-panel {
  flex: 1.5;
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.panel-item-customers {
    border: 1px solid #1A58B0;
    padding: 18px;
    width: 100%;
    height: 540px;
}
.panel-title-second {
    height: 60px;
    display: flex;
    gap: 12px;
    margin-bottom: 20px;
    align-items: center;
}
.quality-cards {
    display: flex;
    gap: 12px;
    width: 100%;
    height: 54px;
    justify-content: space-between;
    align-items: center;
}
.quality-cardSec {
    display: flex;
}
.quality-cardTitle {
    font-weight: 400;
    font-size: 14px;
    color: #FFFFFF;
    display: flex;
    align-items: flex-start;
    flex-direction: column;
}
.quality-card {
    width: 80px;
    height: 60px;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
.quality-card.one {
    background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png");
}
.quality-card.two {
    background-image: url("@/assets/BI/guochengyijianicon@2x.png");
}
.quality-card.three {
    background-image: url("@/assets/BI/chuchangyijianicon@2x.png");
}
/* è®¢å•统计卡片样式 */
.order-statistics-cards {
    display: flex;
    gap: 12px;
    width: 100%;
    height: 94px;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
}
.quality-card.four {
    background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png");
}
.quality-card.five {
    background-image: url("@/assets/BI/guochengyijianicon@2x.png");
}
.quality-card.six {
    background-image: url("@/assets/BI/chuchangyijianicon@2x.png");
}
.quality-card.seven {
    background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png");
}
.panel-title-icon {
    width: 60px;
    height: 60px;
    background-image: url("@/assets/BI/hetongicon.png");
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
.panel-header {
    background-image: url("@/assets/BI/kehuhetongback@2x.png");
    background-size: 100% 100%;
    background-position: center;
    background-repeat: no-repeat;
}
.panel-title {
    width: 100%;
    font-weight: 500;
    font-size: 16px;
    color: #D9ECFF;
    padding-left: 46px;
    line-height: 36px;
}
.total-customers {
    background-image: url("@/assets/BI/hetongjineback@2x.png");
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    width: 90%;
    height: 60px;
    display: flex;
    align-items: center;
    padding: 0 20px;
    gap: 20px;
}
.total-customers .label {
    font-weight: 500;
    font-size: 16px;
    color: #FFFFFF;
}
.total-customers .value {
    font-weight: 500;
    font-size: 40px;
    background: linear-gradient(360deg, #008BFD 0%, #FFFFFF 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
}
.contract-list {
    margin-top: 16px;
    font-size: 14px;
    color: #666;
    list-style: none;
    padding: 0;
    height: 82%;
    overflow-y: auto;
    width: 460px;
    /* éšè—æ»šåŠ¨æ¡ä½†ä¿ç•™æ»šåŠ¨åŠŸèƒ½ */
    scrollbar-width: none; /* Firefox */
    -ms-overflow-style: none; /* IE和Edge */
}
/* Chrome、Safari和Opera */
.contract-list::-webkit-scrollbar {
    display: none;
}
.line {
    position: relative;
    width: 230px;
}
.line::after {
    content: '';
    position: absolute;
    right: 2px;
    top: 0;
    bottom: 0;
    width: 1px;
    background-color: #C9C5C5;
    border-radius: 2px;
}
.contract-list li {
    margin-top: 10px;
}
.stats-cards {
  display: flex;
  gap: 30px;
}
.stat-card {
  flex: 1;
  display: flex;
  align-items: center;
    background-image: url("@/assets/BI/border@2x.png");
    background-size: 100% 100%;
    background-position: center;
    background-repeat: no-repeat;
  height: 142px;
}
.card-icon {
  width: 100px;
  height: 100px;
  margin: 20px 20px 0 10px;
}
.card-content {
  display: flex;
  flex-direction: column;
    gap: 10px;
}
.card-value {
    font-weight: 500;
    font-size: 40px;
  background: linear-gradient(360deg, #008BFD 0%, #FFFFFF 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}
.card-label {
    font-weight: 400;
    font-size: 19px;
    color: rgba(208,231,255,0.7);
}
.equipment-stats {
    border: 1px solid #1A58B0;
  padding: 18px;
  height: 240px;
}
.equipment-header {
    font-weight: 500;
    font-size: 21px;
    display: flex;
    border-bottom: 1px solid;
    border-image: linear-gradient( 270deg, rgba(0,126,255,0) 0%, rgba(0,126,255,0.4549) 35%, #007EFF 78%, #007EFF 100%) 1;
    padding-bottom: 2px;
}
.equipment-title {
    font-weight: 500;
    font-size: 21px;
    background: linear-gradient(360deg, #056DFF 0%, #43E8FC 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
    line-height: 50px;
}
.equipment-icon {
    width: 50px;
    height: 50px;
}
.equipment-items {
  display: flex;
  justify-content: space-around;
  gap: 30px;
}
.equipment-item {
  text-align: center;
}
.equipment-value {
  display: block;
    font-weight: 500;
    font-size: 40px;
    color: #FFFFFF;
    width: 120px;
    height: 110px;
    line-height: 110px;
    background-image: url("@/assets/BI/shujutongji@2x.png");
    background-size: 100% 100%;
    background-position: center;
    background-repeat: no-repeat;
  margin-bottom: 8px;
}
.equipment-label {
    font-weight: 500;
    font-size: 21px;
    color: #FFFFFE;
}
.event-info {
    background-image: url("@/assets/BI/shijianmingchengbeijing@2x.png");
    background-size: 100% 100%;
    background-position: center;
    background-repeat: no-repeat;
  padding: 20px;
  height: 186px;
}
.event-header {
    display: flex;
    align-items: center;
}
.event-icon {
    width: 40px;
    height: 40px;
}
.event-title {
    font-weight: 500;
    font-size: 24px;
    color: #FFFFFE;
    line-height: 30px;
}
.todo-list {
  list-style: none;
  padding: 0;
  margin: 0;
  height: 120px; /* æŒ‰ç”¨æˆ·è¦æ±‚调整高度 */
  overflow: hidden;
  font-size: 15px;
}
.todo-list li {
    border-radius: 8px;
    margin-bottom: 12px;
    padding: 12px 40px;
    height: 74px;
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.todo-title {
    font-weight: 400;
    font-size: 20px;
    color: #FFFFFE;
    position: relative;
}
.todo-title::before {
    content: ''; /* å¿…需,表示这里有一个内容 */
    position: absolute;
    left: -10px; /* å®šä½åˆ°å·¦ä¾§ */
    top: 50%; /* åž‚直居中 */
    transform: translateY(-50%); /* å¾®è°ƒåž‚直居中 */
    width: 6px; /* åœ†çš„直径 */
    height: 6px; /* åœ†çš„直径 */
    background: #498CEB;
    border-radius: 50%; /* è®©å…¶å˜æˆåœ†å½¢ */
}
.todo-division {
    font-weight: 400;
    font-size: 20px;
    color: #FFFFFE;
}
.todo-time {
    font-weight: 400;
    font-size: 20px;
    color: #FFFFFE;
}
.financial-header {
    background-image: url("@/assets/BI/caiwufenxiback@2x.png");
    background-size: 100% 100%;
    background-position: center;
    background-repeat: no-repeat;
}
.financial-title {
    width: 100%;
    font-weight: 500;
    font-size: 16px;
    color: #D9ECFF;
    padding-left: 46px;
    line-height: 36px;
}
/* è‡ªå®šä¹‰å•选按钮组样式 */
.custom-radio-group :deep(.el-radio-button__inner) {
  background-color: transparent;
  color: white;
  border-color: rgba(255, 255, 255, 0.3);
}
.custom-radio-group :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
  background-color: rgba(255, 255, 255, 0.2);
  color: white;
  border-color: rgba(255, 255, 255, 0.5);
  box-shadow: -1px 0 0 0 rgba(255, 255, 255, 0.5);
}
/* ç”Ÿäº§è®¢å•进度表格样式 */
.progress-table-container {
  height: 200px;
  overflow-y: auto;
  overflow-x: hidden;
  margin-top: 10px;
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE和Edge */
}
.progress-table-container::-webkit-scrollbar {
  display: none; /* Chrome、Safari和Opera */
}
.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;
}
/* el-progress ç»„件样式调整 */
.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>