From 617ad108a3c7f4e676229b1495185e66fac644b7 Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期三, 25 三月 2026 17:11:02 +0800
Subject: [PATCH] 销售统计看板自适应,以及中心背景替换

---
 src/views/reportAnalysis/qualityAnalysis/components/CarouselCards.vue |  524 ++++++++++++++++++++++++++++++----------------------------
 1 files changed, 271 insertions(+), 253 deletions(-)

diff --git a/src/views/reportAnalysis/qualityAnalysis/components/CarouselCards.vue b/src/views/reportAnalysis/qualityAnalysis/components/CarouselCards.vue
index 0498824..922d7d4 100644
--- a/src/views/reportAnalysis/qualityAnalysis/components/CarouselCards.vue
+++ b/src/views/reportAnalysis/qualityAnalysis/components/CarouselCards.vue
@@ -1,306 +1,324 @@
 <template>
   <div class="carousel-cards">
-    <button 
-      v-if="canScrollLeft"
-      class="nav-button nav-button-left"
-      @click="scrollLeftFn"
-    >
-      <img src="@/assets/BI/jiantou.png" alt="宸︾澶�" />
+    <button v-if="canScrollLeft"
+            class="nav-button nav-button-left"
+            @click="scrollLeftFn">
+      <img src="@/assets/BI/jiantou.png"
+           alt="宸︾澶�" />
     </button>
-    <div 
-      class="cards-container" 
-      :style="{ '--visible-count': visibleCount }"
-      ref="cardsContainerRef"
-    >
-      <div
-        v-for="(item, index) in items"
-        :key="index"
-        class="card-item"
-      >
-        <div v-if="item.icon" class="card-icon" :style="{ backgroundImage: `url(${item.icon})` }"></div>
+    <div class="cards-container"
+         :style="{ '--visible-count': visibleCount }"
+         ref="cardsContainerRef">
+      <div v-for="(item, index) in items"
+           :key="index"
+           class="card-item">
+        <div v-if="item.icon"
+             class="card-icon"
+             :style="{ backgroundImage: `url(${item.icon})` }"></div>
         <div class="card-title">
           <div class="card-label">{{ item.label }}</div>
           <div class="card-value">
             <span class="value-number">{{ item.value }}</span>
             <span class="value-unit">{{ item.unit }}</span>
           </div>
-          <div v-if="item.rate ?? item.ratio ?? item.percent" class="card-rate">
+          <div v-if="item.rate ?? item.ratio ?? item.percent"
+               class="card-rate">
             <span class="rate-value">{{ item.rate ?? item.ratio ?? item.percent }}%</span>
           </div>
         </div>
       </div>
     </div>
-    <button 
-      v-if="canScrollRight"
-      class="nav-button nav-button-right"
-      @click="scrollRightFn"
-    >
-      <img src="@/assets/BI/jiantou.png" alt="鍙崇澶�" />
+    <button v-if="canScrollRight"
+            class="nav-button nav-button-right"
+            @click="scrollRightFn">
+      <img src="@/assets/BI/jiantou.png"
+           alt="鍙崇澶�" />
     </button>
   </div>
 </template>
 
 <script setup>
-import { ref, onMounted, onBeforeUnmount, nextTick, watch, computed } from 'vue'
+  import {
+    ref,
+    onMounted,
+    onBeforeUnmount,
+    nextTick,
+    watch,
+    computed,
+  } from "vue";
 
-const props = defineProps({
-  items: {
-    type: Array,
-    default: () => [],
-    validator: (value) => {
-      return value.every(item => 
-        item && typeof item.label !== 'undefined' && 
-        typeof item.value !== 'undefined' && 
-        typeof item.unit !== 'undefined'
-      )
-    }
-  },
-  visibleCount: {
-    type: Number,
-    default: 3
-  }
-})
+  const props = defineProps({
+    items: {
+      type: Array,
+      default: () => [],
+      validator: value => {
+        return value.every(
+          item =>
+            item &&
+            typeof item.label !== "undefined" &&
+            typeof item.value !== "undefined" &&
+            typeof item.unit !== "undefined"
+        );
+      },
+    },
+    visibleCount: {
+      type: Number,
+      default: 3,
+    },
+  });
 
-const cardsContainerRef = ref(null)
-const currentScrollLeft = ref(0)
-const maxScrollLeft = ref(0)
+  const cardsContainerRef = ref(null);
+  const currentScrollLeft = ref(0);
+  const maxScrollLeft = ref(0);
 
-// 妫�鏌ユ槸鍚﹀彲浠ュ悜宸︽粴鍔�
-const canScrollLeft = computed(() => {
-  return currentScrollLeft.value > 0
-})
+  // 妫�鏌ユ槸鍚﹀彲浠ュ悜宸︽粴鍔�
+  const canScrollLeft = computed(() => {
+    return currentScrollLeft.value > 0;
+  });
 
-// 妫�鏌ユ槸鍚﹀彲浠ュ悜鍙虫粴鍔�
-const canScrollRight = computed(() => {
-  return currentScrollLeft.value < maxScrollLeft.value
-})
+  // 妫�鏌ユ槸鍚﹀彲浠ュ悜鍙虫粴鍔�
+  const canScrollRight = computed(() => {
+    return currentScrollLeft.value < maxScrollLeft.value;
+  });
 
-// 鏇存柊婊氬姩鐘舵��
-const updateScrollState = () => {
-  const container = cardsContainerRef.value
-  if (!container) return
-  
-  currentScrollLeft.value = container.scrollLeft
-  maxScrollLeft.value = container.scrollWidth - container.clientWidth
-}
+  // 鏇存柊婊氬姩鐘舵��
+  const updateScrollState = () => {
+    const container = cardsContainerRef.value;
+    if (!container) return;
 
-// 鍚戝乏婊氬姩
-const scrollLeftFn = () => {
-  const container = cardsContainerRef.value
-  if (!container) return
-  
-  const scrollItems = Array.from(container.querySelectorAll('.card-item'))
-  if (scrollItems.length === 0) return
-  
-  const itemWidth = scrollItems[0]?.offsetWidth || 0
-  const gap = 12
-  const scrollDistance = itemWidth + gap
-  
-  container.scrollBy({
-    left: -scrollDistance,
-    behavior: 'smooth'
-  })
-  
-  // 寤惰繜鏇存柊鐘舵�侊紝绛夊緟婊氬姩鍔ㄧ敾瀹屾垚
-  setTimeout(() => {
-    updateScrollState()
-  }, 300)
-}
+    currentScrollLeft.value = container.scrollLeft;
+    maxScrollLeft.value = container.scrollWidth - container.clientWidth;
+  };
 
-// 鍚戝彸婊氬姩
-const scrollRightFn = () => {
-  const container = cardsContainerRef.value
-  if (!container) return
-  
-  const scrollItems = Array.from(container.querySelectorAll('.card-item'))
-  if (scrollItems.length === 0) return
-  
-  const itemWidth = scrollItems[0]?.offsetWidth || 0
-  const gap = 12
-  const scrollDistance = itemWidth + gap
-  
-  container.scrollBy({
-    left: scrollDistance,
-    behavior: 'smooth'
-  })
-  
-  // 寤惰繜鏇存柊鐘舵�侊紝绛夊緟婊氬姩鍔ㄧ敾瀹屾垚
-  setTimeout(() => {
-    updateScrollState()
-  }, 300)
-}
+  // 鍚戝乏婊氬姩
+  const scrollLeftFn = () => {
+    const container = cardsContainerRef.value;
+    if (!container) return;
 
-// 鐩戝惉 items 鍙樺寲锛屾洿鏂版粴鍔ㄧ姸鎬�
-watch(() => props.items, () => {
-  nextTick(() => {
-    updateScrollState()
-  })
-}, { deep: true })
+    const scrollItems = Array.from(container.querySelectorAll(".card-item"));
+    if (scrollItems.length === 0) return;
 
-onMounted(() => {
-  nextTick(() => {
-    updateScrollState()
-    // 鐩戝惉婊氬姩浜嬩欢
-    const container = cardsContainerRef.value
+    const itemWidth = scrollItems[0]?.offsetWidth || 0;
+    const gap = 12;
+    const scrollDistance = itemWidth + gap;
+
+    container.scrollBy({
+      left: -scrollDistance,
+      behavior: "smooth",
+    });
+
+    // 寤惰繜鏇存柊鐘舵�侊紝绛夊緟婊氬姩鍔ㄧ敾瀹屾垚
+    setTimeout(() => {
+      updateScrollState();
+    }, 300);
+  };
+
+  // 鍚戝彸婊氬姩
+  const scrollRightFn = () => {
+    const container = cardsContainerRef.value;
+    if (!container) return;
+
+    const scrollItems = Array.from(container.querySelectorAll(".card-item"));
+    if (scrollItems.length === 0) return;
+
+    const itemWidth = scrollItems[0]?.offsetWidth || 0;
+    const gap = 12;
+    const scrollDistance = itemWidth + gap;
+
+    container.scrollBy({
+      left: scrollDistance,
+      behavior: "smooth",
+    });
+
+    // 寤惰繜鏇存柊鐘舵�侊紝绛夊緟婊氬姩鍔ㄧ敾瀹屾垚
+    setTimeout(() => {
+      updateScrollState();
+    }, 300);
+  };
+
+  // 鐩戝惉 items 鍙樺寲锛屾洿鏂版粴鍔ㄧ姸鎬�
+  watch(
+    () => props.items,
+    () => {
+      nextTick(() => {
+        updateScrollState();
+      });
+    },
+    { deep: true }
+  );
+
+  onMounted(() => {
+    nextTick(() => {
+      updateScrollState();
+      // 鐩戝惉婊氬姩浜嬩欢
+      const container = cardsContainerRef.value;
+      if (container) {
+        container.addEventListener("scroll", updateScrollState);
+      }
+    });
+  });
+
+  onBeforeUnmount(() => {
+    // 娓呯悊婊氬姩浜嬩欢鐩戝惉鍣�
+    const container = cardsContainerRef.value;
     if (container) {
-      container.addEventListener('scroll', updateScrollState)
+      container.removeEventListener("scroll", updateScrollState);
     }
-  })
-})
-
-onBeforeUnmount(() => {
-  // 娓呯悊婊氬姩浜嬩欢鐩戝惉鍣�
-  const container = cardsContainerRef.value
-  if (container) {
-    container.removeEventListener('scroll', updateScrollState)
-  }
-})
+  });
 </script>
 
 <style scoped>
-.carousel-cards {
-  width: 100%;
-  overflow: hidden;
-  position: relative;
-  display: flex;
-  align-items: center;
-}
+  .carousel-cards {
+    width: 100%;
+    overflow: hidden;
+    position: relative;
+    display: flex;
+    align-items: center;
+  }
 
-.cards-container {
-  display: flex;
-  gap: 12px;
-  width: 100%;
-  overflow-x: auto;
-  overflow-y: hidden;
-  scrollbar-width: none; /* Firefox */
-  -ms-overflow-style: none; /* IE and Edge */
-  padding-bottom: 4px;
-  scroll-behavior: smooth;
-}
+  .cards-container {
+    display: flex;
+    gap: 12px;
+    width: 100%;
+    overflow-x: auto;
+    overflow-y: hidden;
+    scrollbar-width: none; /* Firefox */
+    -ms-overflow-style: none; /* IE and Edge */
+    padding-bottom: 4px;
+    scroll-behavior: smooth;
+  }
 
-.cards-container::-webkit-scrollbar {
-  display: none; /* Chrome, Safari, Opera */
-}
+  .cards-container::-webkit-scrollbar {
+    display: none; /* Chrome, Safari, Opera */
+  }
 
-.nav-button {
-  position: absolute;
-  top: 50%;
-  transform: translateY(-50%);
-  width: 32px;
-  height: 32px;
-  background: rgba(26, 88, 176, 0.6);
-  border: 1px solid rgba(26, 88, 176, 0.8);
-  border-radius: 50%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  cursor: pointer;
-  z-index: 10;
-  transition: all 0.3s ease;
-  padding: 0;
-}
+  .nav-button {
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+    width: 32px;
+    height: 32px;
+    background: rgba(26, 88, 176, 0.6);
+    border: 1px solid rgba(26, 88, 176, 0.8);
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    z-index: 10;
+    transition: all 0.3s ease;
+    padding: 0;
+  }
 
-.nav-button:hover {
-  background: rgba(26, 88, 176, 0.8);
-  transform: translateY(-50%) scale(1.1);
-}
+  .nav-button:hover {
+    background: rgba(26, 88, 176, 0.8);
+    transform: translateY(-50%) scale(1.1);
+  }
 
-.nav-button-left {
-  left: -16px;
-}
+  .nav-button-left {
+    left: -16px;
+  }
 
-.nav-button-left img {
-  width: 16px;
-  height: 16px;
-  transform: rotate(180deg);
-}
+  .nav-button-left img {
+    width: 16px;
+    height: 16px;
+    transform: rotate(180deg);
+  }
 
-.nav-button-right {
-  right: -16px;
-}
+  .nav-button-right {
+    right: -16px;
+  }
 
-.nav-button-right img {
-  width: 16px;
-  height: 16px;
-}
+  .nav-button-right img {
+    width: 16px;
+    height: 16px;
+  }
 
-.card-item {
-  flex: 0 0 calc((100% - (var(--visible-count) - 1) * 12px) / var(--visible-count));
-  min-width: calc((100% - (var(--visible-count) - 1) * 12px) / var(--visible-count));
-  display: flex;
-  align-items: center;
-  background: linear-gradient(269deg, rgba(27,57,126,0.13) 0%, rgba(33,137,206,0.33) 98.13%, #24AFF4 100%);
-  border-radius: 8px 8px 8px 8px;
-  padding: 12px 16px;
-  transition: all 0.3s ease;
-}
+  .card-item {
+    flex: 0 0
+      calc((100% - (var(--visible-count) - 1) * 12px) / var(--visible-count));
+    min-width: calc(
+      (100% - (var(--visible-count) - 1) * 12px) / var(--visible-count)
+    );
+    display: flex;
+    align-items: center;
+    background: linear-gradient(
+      269deg,
+      rgba(27, 57, 126, 0.13) 0%,
+      rgba(33, 137, 206, 0.33) 98.13%,
+      #24aff4 100%
+    );
+    border-radius: 8px 8px 8px 8px;
+    padding: 12px 16px;
+    transition: all 0.3s ease;
+  }
 
-.card-item:hover {
-  transform: translateY(-2px);
-}
+  .card-item:hover {
+    transform: translateY(-2px);
+  }
 
-.card-icon {
-  width: 80px;
-  height: 60px;
-  background-size: cover;
-  background-position: center;
-  background-repeat: no-repeat;
-  flex-shrink: 0;
-  margin-right: 12px;
-}
+  .card-icon {
+    width: 80px;
+    height: 60px;
+    background-size: cover;
+    background-position: center;
+    background-repeat: no-repeat;
+    flex-shrink: 0;
+    margin-right: 12px;
+  }
 
-.card-title {
-  display: flex;
-  align-items: flex-start;
-  flex-direction: column;
-  flex: 1;
-}
+  .card-title {
+    display: flex;
+    align-items: flex-start;
+    flex-direction: column;
+    flex: 1;
+  }
 
-.card-label {
-  font-weight: 400;
-  font-size: 14px;
-  color: #FFFFFF;
-  margin-bottom: 4px;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  width: 100%;
-}
+  .card-label {
+    font-weight: 400;
+    font-size: 14px;
+    color: #ffffff;
+    margin-bottom: 4px;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    width: 100%;
+  }
 
-.card-value {
-  display: flex;
-  align-items: baseline;
-  gap: 4px;
-}
+  .card-value {
+    display: flex;
+    align-items: baseline;
+    gap: 4px;
+  }
 
-.card-rate {
-  margin-top: 4px;
-  display: flex;
-  align-items: center;
-  gap: 6px;
-  font-weight: 400;
-  font-size: 12px;
-  color: rgba(255, 255, 255, 0.85);
-}
+  .card-rate {
+    margin-top: 4px;
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    font-weight: 400;
+    font-size: 12px;
+    color: rgba(255, 255, 255, 0.85);
+  }
 
-.rate-label {
-  opacity: 0.85;
-}
+  .rate-label {
+    opacity: 0.85;
+  }
 
-.rate-value {
-  font-weight: 500;
-}
+  .rate-value {
+    font-weight: 500;
+  }
 
-.value-number {
-  font-weight: 400;
-  font-size: 14px;
-  color: #FFFFFF;
-  line-height: 1;
-}
+  .value-number {
+    font-weight: 400;
+    font-size: 14px;
+    color: #ffffff;
+    line-height: 1;
+  }
 
-.value-unit {
-  font-size: 14px;
-  color: #FFFFFF;
-  font-weight: 400;
-}
+  .value-unit {
+    font-size: 14px;
+    color: #ffffff;
+    font-weight: 400;
+  }
 </style>

--
Gitblit v1.9.3