| | |
| | | <div> |
| | | <!-- 顶部统计卡片 --> |
| | | <div class="stats-cards"> |
| | | <div class="stat-card"> |
| | | <div |
| | | v-for="item in statItems" |
| | | :key="item.name" |
| | | 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 class="card-compare" :class="compareClass(staffYoY)"> |
| | | <span class="card-label">{{ item.name }}</span> |
| | | <span class="card-value">{{ item.value }}</span> |
| | | <div class="card-compare" :class="compareClass(Number(item.rate))"> |
| | | <span>同比</span> |
| | | <span class="compare-value">{{ formatPercent(staffYoY) }}</span> |
| | | <span 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-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> |
| | | </div> |
| | | </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 class="card-compare" :class="compareClass(suppliersYoY)"> |
| | | <span>同比</span> |
| | | <span class="compare-value">{{ formatPercent(suppliersYoY) }}</span> |
| | | <span class="compare-icon">{{ suppliersYoY >= 0 ? '↑' : '↓' }}</span> |
| | | <span class="compare-value">{{ formatPercent(item.rate) }}</span> |
| | | <span class="compare-icon">{{ Number(item.rate) >= 0 ? '↑' : '↓' }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { summaryStatistics } from '@/api/viewIndex.js' |
| | | import { ref, onMounted, inject, watch } from 'vue' |
| | | import { salesPurchaseStorageProductCount } from '@/api/viewIndex.js' |
| | | |
| | | // 统计数据 |
| | | const totalStaff = ref(0) |
| | | const totalCustomers = ref(0) |
| | | const totalSuppliers = ref(0) |
| | | // 同比 |
| | | const staffYoY = ref(0) |
| | | const customersYoY = ref(0) |
| | | const suppliersYoY = ref(0) |
| | | const statItems = ref([]) |
| | | |
| | | const formatPercent = (val) => { |
| | | const num = Number(val) || 0 |
| | | return `${Math.abs(num).toFixed(2)}%` |
| | | return `${num.toFixed(2)}%` |
| | | } |
| | | |
| | | const compareClass = (val) => (val >= 0 ? 'compare-up' : 'compare-down') |
| | | |
| | | // 获取员工、客户、供应商数量 |
| | | 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 |
| | | }).catch(err => { |
| | | console.error('获取基础统计数据失败:', err) |
| | | const fetchData = () => { |
| | | salesPurchaseStorageProductCount() |
| | | .then((res) => { |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | statItems.value = res.data.map((item) => ({ |
| | | name: item.name, |
| | | value: item.value, |
| | | rate: item.rate, |
| | | })) |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.error('获取销售/采购/储存产品数失败:', err) |
| | | }) |
| | | } |
| | | |
| | | const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null) |
| | | if (dataDashboardRefreshTick) { |
| | | watch(dataDashboardRefreshTick, () => { |
| | | fetchData() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getNum() |
| | | fetchData() |
| | | }) |
| | | </script> |
| | | |
| | |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | height: 142px; |
| | | transition: all 0.3s ease; |
| | | position: relative; |
| | | overflow: hidden; |
| | | animation: cardFadeIn 0.6s ease-out both; |
| | | } |
| | | |
| | | .stat-card:nth-child(1) { animation-delay: 0.1s; } |
| | | .stat-card:nth-child(2) { animation-delay: 0.2s; } |
| | | .stat-card:nth-child(3) { animation-delay: 0.3s; } |
| | | |
| | | @keyframes cardFadeIn { |
| | | from { opacity: 0; transform: translateY(20px); } |
| | | to { opacity: 1; transform: translateY(0); } |
| | | } |
| | | |
| | | .stat-card:hover { |
| | | transform: translateY(-3px); |
| | | box-shadow: 0 10px 30px rgba(0, 212, 255, 0.2); |
| | | } |
| | | |
| | | /* 卡片底部光线 */ |
| | | .stat-card::after { |
| | | content: ''; |
| | | position: absolute; |
| | | bottom: 0; |
| | | left: 10%; |
| | | right: 10%; |
| | | height: 2px; |
| | | background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.5), transparent); |
| | | opacity: 0; |
| | | transition: opacity 0.3s; |
| | | } |
| | | |
| | | .stat-card:hover::after { |
| | | opacity: 1; |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 100px; |
| | | height: 100px; |
| | | width: 70px; |
| | | height: 70px; |
| | | margin: 20px 20px 0 10px; |
| | | animation: iconFloat 4s ease-in-out infinite; |
| | | } |
| | | |
| | | @keyframes iconFloat { |
| | | 0%, 100% { transform: translateY(0); } |
| | | 50% { transform: translateY(-5px); } |
| | | } |
| | | |
| | | .card-content { |
| | |
| | | |
| | | .card-value { |
| | | font-weight: 500; |
| | | font-size: 40px; |
| | | font-size: 32px; |
| | | background: linear-gradient(360deg, #008bfd 0%, #ffffff 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .stat-card:hover .card-value { |
| | | text-shadow: 0 0 15px rgba(0, 212, 255, 0.4); |
| | | transform: scale(1.05); |
| | | } |
| | | |
| | | .card-label { |
| | | font-weight: 400; |
| | | font-size: 19px; |
| | | font-size: 16px; |
| | | color: rgba(208, 231, 255, 0.7); |
| | | } |
| | | |