7 小时以前 1c829f9177eab675b22be4d20ef2761ed1bbb423
src/views/reportAnalysis/dataDashboard/components/basic/center-top.vue
@@ -3,38 +3,60 @@
    <!-- 顶部统计卡片 -->
    <div class="stats-cards">
      <div class="stat-card">
        <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
        <div class="card-icon" style="color: #4a6cf0">
          <!-- 员工 -->
          <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
            <circle cx="12" cy="8" r="4" />
            <path d="M4 21v-1a8 8 0 0 1 16 0v1" />
          </svg>
        </div>
        <div class="card-content">
          <span class="card-label">员工总数</span>
          <span class="card-value">{{ totalStaff }}</span>
          <div class="card-compare" :class="compareClass(staffYoY)">
            <span>同比</span>
            <span class="compare-value">{{ formatPercent(staffYoY) }}</span>
            <span class="compare-icon">{{ staffYoY >= 0 ? '↑' : '↓' }}</span>
            <span v-if="!isZero(staffYoY)" class="compare-icon">{{ staffYoY > 0 ? '↑' : '↓' }}</span>
          </div>
        </div>
      </div>
      <div class="stat-card">
        <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
        <div class="card-icon" style="color: #35c6d6">
          <!-- 客户 -->
          <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
            <circle cx="9" cy="8" r="3.5" />
            <path d="M2.5 20v-1a6.5 6.5 0 0 1 13 0v1" />
            <path d="M16 5a3.5 3.5 0 0 1 0 6.5" />
            <path d="M17.5 13.5A6.5 6.5 0 0 1 21.5 20" />
          </svg>
        </div>
        <div class="card-content">
          <span class="card-label">客户总数</span>
          <span class="card-value">{{ totalCustomers }}</span>
          <div class="card-compare" :class="compareClass(customersYoY)">
            <span>同比</span>
            <span class="compare-value">{{ formatPercent(customersYoY) }}</span>
            <span class="compare-icon">{{ customersYoY >= 0 ? '↑' : '↓' }}</span>
            <span v-if="!isZero(customersYoY)" class="compare-icon">{{ customersYoY > 0 ? '↑' : '↓' }}</span>
          </div>
        </div>
      </div>
      <div class="stat-card">
        <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
        <div class="card-icon" style="color: #8e7ce6">
          <!-- 供应商 -->
          <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
            <rect x="1.5" y="5" width="13" height="11" rx="1.5" />
            <path d="M14.5 9h4l3 3.5v3.5h-2" />
            <circle cx="6" cy="18.5" r="1.8" />
            <circle cx="17.5" cy="18.5" r="1.8" />
          </svg>
        </div>
        <div class="card-content">
          <span class="card-label">供应商总数</span>
          <span class="card-value">{{ totalSuppliers }}</span>
          <div class="card-compare" :class="compareClass(suppliersYoY)">
            <span>同比</span>
            <span class="compare-value">{{ formatPercent(suppliersYoY) }}</span>
            <span class="compare-icon">{{ suppliersYoY >= 0 ? '↑' : '↓' }}</span>
            <span v-if="!isZero(suppliersYoY)" class="compare-icon">{{ suppliersYoY > 0 ? '↑' : '↓' }}</span>
          </div>
        </div>
      </div>
@@ -42,13 +64,12 @@
    <!-- 设备统计 -->
    <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 class="section-header">
        <svg class="section-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
          <circle cx="12" cy="12" r="3" />
          <path d="M19.4 15a1.7 1.7 0 0 0 .34 1.87l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.7 1.7 0 0 0-1.87-.34 1.7 1.7 0 0 0-1.03 1.55V21a2 2 0 1 1-4 0v-.09a1.7 1.7 0 0 0-1.11-1.55 1.7 1.7 0 0 0-1.87.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.7 1.7 0 0 0 .34-1.87 1.7 1.7 0 0 0-1.55-1.03H3a2 2 0 1 1 0-4h.09a1.7 1.7 0 0 0 1.55-1.11 1.7 1.7 0 0 0-.34-1.87l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.7 1.7 0 0 0 1.87.34h.08A1.7 1.7 0 0 0 10.31 3.1V3a2 2 0 1 1 4 0v.09a1.7 1.7 0 0 0 1.03 1.55 1.7 1.7 0 0 0 1.87-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.7 1.7 0 0 0-.34 1.87v.08a1.7 1.7 0 0 0 1.55 1.03H21a2 2 0 1 1 0 4h-.09a1.7 1.7 0 0 0-1.55 1.03z" />
        </svg>
        <span class="section-title">设备统计</span>
      </div>
      <div class="equipment-items">
        <div class="equipment-item">
@@ -70,40 +91,28 @@
      </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 class="section-header">
        <svg class="section-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
          <rect x="4" y="4" width="16" height="17" rx="2" />
          <path d="M9 2.5h6v3H9z" />
          <path d="M8.5 11h7M8.5 15.5h5" />
        </svg>
        <span class="section-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.approveTypeName }}</div>
                <div class="todo-division">申请部门:{{ item.approveDeptName }}</div>
                <div class="todo-time">{{ item.approveTime }}</div>
              </div>
            <div class="todo-row">
              <span class="todo-division todo-reason">待办事由:{{ item.approveReason || '-' }}</span>
              <span class="todo-title">申请类型:{{ item.approveTypeName || '-' }}</span>
              <span class="todo-division">申请部门:{{ item.approveDeptName || '-' }}</span>
              <span class="todo-time">{{ item.approveTime || '-' }}</span>
            </div>
          </li>
        </ul>
        <div v-else style="text-align: center">暂无数据</div>
        <div v-else class="event-empty">暂无数据</div>
      </div>
    </div>
  </div>
@@ -139,17 +148,25 @@
  return `${Math.abs(num).toFixed(2)}%`
}
const compareClass = (val) => (val >= 0 ? 'compare-up' : 'compare-down')
const isZero = (val) => !(Number(val) || 0)
const compareClass = (val) => {
  const num = Number(val) || 0
  if (num > 0) return 'compare-up'
  if (num < 0) return 'compare-down'
  return 'compare-flat'
}
// 获取员工、客户、供应商数量
const getNum = () => {
  summaryStatistics().then((res) => {
    totalStaff.value = res.data.totalStaff
    staffYoY.value = res.data.staffGrowthRate
    totalCustomers.value = res.data.totalCustomer
    customersYoY.value = res.data.customerGrowthRate
    totalSuppliers.value = res.data.totalSupplier
    suppliersYoY.value = res.data.supplierGrowthRate
    const d = res.data || {}
    totalStaff.value = Number(d.totalStaff) || 0
    staffYoY.value = Number(d.staffGrowthRate) || 0
    totalCustomers.value = Number(d.totalCustomer) || 0
    customersYoY.value = Number(d.customerGrowthRate) || 0
    totalSuppliers.value = Number(d.totalSupplier) || 0
    suppliersYoY.value = Number(d.supplierGrowthRate) || 0
  }).catch(err => {
    console.error('获取基础统计数据失败:', err)
  })
@@ -162,16 +179,16 @@
    pageSize: -1,
  }
  getLedgerPage(params).then((res) => {
    equipmentNum.value = res.data.total
    equipmentNum.value = Number(res.data?.total) || 0
  })
  getRepairPage({ ...params, status: 0 }).then((res) => {
    equipmentRepair.value = res.data.total
    equipmentRepair.value = Number(res.data?.total) || 0
  })
  getUpkeepPage({ ...params, status: 0 }).then((res) => {
    equipmentMaintain.value = res.data.total
    equipmentMaintain.value = Number(res.data?.total) || 0
  })
  measuringInstrumentListPage(params).then((res) => {
    totalMeasuring.value = res.data.total
    totalMeasuring.value = Number(res.data?.total) || 0
  })
}
@@ -260,11 +277,14 @@
// 待办事项
const todoInfoS = () => {
  homeTodos().then((res) => {
    todoList.value = res.data
    todoList.value = res.data || []
    // 在获取到待办事项数据后,初始化滚动功能
    nextTick(() => {
      initTodoListScroll()
    })
  }).catch(err => {
    console.error('获取待办事项失败:', err)
    todoList.value = []
  })
}
@@ -300,38 +320,56 @@
  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;
  background: var(--bi-panel-bg);
  border: 1px solid var(--bi-panel-border);
  border-radius: 8px;
  position: relative;
  overflow: hidden;
  height: 142px;
}
/* 顶部品牌蓝亮线,替代图片描边 */
.stat-card::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(90deg, var(--bi-accent) 0%, transparent 70%);
}
.card-icon {
  width: 100px;
  height: 100px;
  margin: 20px 20px 0 10px;
  width: 52px;
  height: 52px;
  margin: 0 16px 0 20px;
  border-radius: 8px;
  background: rgba(74, 108, 240, 0.12);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.card-content {
  display: flex;
  flex-direction: column;
  gap: 10px;
  gap: 8px;
  min-width: 0;
}
.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;
  font-weight: 600;
  font-size: 36px;
  color: var(--bi-text-strong);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.card-label {
  font-weight: 400;
  font-size: 19px;
  color: rgba(208, 231, 255, 0.7);
  font-size: 16px;
  color: var(--bi-text);
}
.card-compare {
@@ -339,16 +377,17 @@
  align-items: center;
  gap: 6px;
  font-size: 15px;
  color: #d0e7ff;
}
.card-compare > span:first-child {
  font-size: 13px;
  opacity: 0.8;
  color: var(--bi-text);
}
.compare-value {
  font-weight: 600;
  color: var(--bi-text);
  font-variant-numeric: tabular-nums;
}
.compare-icon {
@@ -359,163 +398,150 @@
.compare-up .compare-value,
.compare-up .compare-icon {
  color: #00c853;
  color: var(--bi-up, #34c77b);
}
.compare-down .compare-value,
.compare-down .compare-icon {
  color: #ff5252;
  color: var(--bi-down, #ff6b6b);
}
/* 持平:不显示箭头,颜色中性 */
.compare-flat .compare-value {
  color: var(--bi-text);
}
.equipment-stats,
.event-info {
  background: var(--bi-panel-bg);
  border: 1px solid var(--bi-panel-border);
  border-radius: 8px;
  padding: 14px 18px;
}
.equipment-stats {
  border: 1px solid #1a58b0;
  padding: 18px;
  height: 240px;
  padding-top: 0px;
  margin-top: 20px;
  box-sizing: border-box;
}
.equipment-header {
  font-weight: 500;
  font-size: 21px;
.event-info {
  height: 186px;
  margin-top: 20px;
  box-sizing: border-box;
}
.section-header {
  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;
  align-items: center;
  gap: 8px;
  color: var(--bi-accent-bright);
  border-bottom: 1px solid rgba(143, 163, 200, 0.15);
  padding-bottom: 10px;
}
.equipment-title {
.section-icon {
  flex-shrink: 0;
}
.section-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;
  font-size: 16px;
  color: var(--bi-text-strong);
  letter-spacing: 1px;
}
.equipment-items {
  display: flex;
  justify-content: space-around;
  gap: 30px;
  gap: 20px;
  padding-top: 22px;
}
.equipment-item {
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.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;
  font-weight: 600;
  font-size: 36px;
  color: var(--bi-text-strong);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.equipment-label {
  font-weight: 500;
  font-size: 16px;
  color: #fffffe;
  font-weight: 400;
  font-size: 14px;
  color: var(--bi-text);
}
.event-info {
  background-image: url('@/assets/BI/shijianmingchengbeijing@2x.png');
  background-size: 100% 100%;
  background-position: center;
  background-repeat: no-repeat;
  padding: 20px;
  padding-top: 10px;
  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;
.event-content {
  padding-top: 8px;
}
.todo-list {
  list-style: none;
  padding: 0;
  margin: 0;
  height: 120px; /* 按用户要求调整高度 */
  height: 96px;
  overflow: hidden;
  font-size: 15px;
  font-size: 14px;
}
.todo-list li {
  border-radius: 8px;
  margin-bottom: 12px;
  padding: 12px 40px;
  height: 74px;
  margin-bottom: 8px;
}
.todo-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 24px;
  padding: 9px 14px;
  background: rgba(13, 24, 52, 0.6);
  border: 1px solid rgba(30, 49, 96, 0.8);
  border-radius: 6px;
}
.todo-title {
  font-weight: 400;
  font-size: 16px;
  color: #fffffe;
  position: relative;
  font-size: 14px;
  color: var(--bi-text);
  white-space: nowrap;
}
.todo-division {
  font-weight: 400;
  font-size: 16px;
  color: #fffffe;
  position: relative;
  font-size: 14px;
  color: var(--bi-text);
  white-space: nowrap;
}
.todo-division::before {
  content: '';
  position: absolute;
  left: -20px;
  top: 50%;
  transform: translateY(-50%);
  width: 6px;
  height: 6px;
  background: #498ceb;
  border-radius: 50%;
.todo-reason {
  color: var(--bi-text-strong);
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  flex: 1;
}
.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;
  font-size: 13px;
  color: var(--bi-text);
  background: rgba(74, 108, 240, 0.15);
  border-radius: 4px;
  padding: 3px 10px;
  font-variant-numeric: tabular-nums;
}
.event-empty {
  height: 96px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--bi-text);
  font-size: 14px;
  letter-spacing: 1px;
}
</style>