From a3c508233dd94b50c8005ec3a5d40b91341d6434 Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期四, 29 一月 2026 17:58:30 +0800
Subject: [PATCH] Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-management into dev_New
---
src/views/reportAnalysis/financialAnalysis/components/center-top.vue | 308 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 308 insertions(+), 0 deletions(-)
diff --git a/src/views/reportAnalysis/financialAnalysis/components/center-top.vue b/src/views/reportAnalysis/financialAnalysis/components/center-top.vue
new file mode 100644
index 0000000..85f4928
--- /dev/null
+++ b/src/views/reportAnalysis/financialAnalysis/components/center-top.vue
@@ -0,0 +1,308 @@
+<template>
+ <div>
+ <!-- 椤堕儴鏀舵敮鍗$墖 -->
+ <div class="finance-cards">
+ <!-- 鏈堝害鏀跺叆 -->
+ <div class="finance-card income-card">
+ <div class="icon-box">
+ <img src="@/assets/BI/icon@2x.png" alt="鍥炬爣" class="card-icon" />
+ </div>
+ <div class="card-body">
+ <div class="card-left">
+ <div class="card-title">鏈堝害鏀跺叆</div>
+ <div class="card-amount">
+ <span>{{ formatAmountWanNumber(income.amount) }}</span>
+ <span v-if="isWanAmount(income.amount)" class="card-amount-unit">涓�</span>
+ </div>
+ </div>
+ <div class="card-right">
+ <div class="metric-row">
+ <span class="metric-label">鍥炴鐜�</span>
+ <span class="metric-value" :class="metricClass(income.repayRate)">
+ {{ formatPercent(income.repayRate.value) }}
+ <span class="arrow">{{ metricArrow(income.repayRate) }}</span>
+ </span>
+ </div>
+ <div class="metric-row">
+ <span class="metric-label">閫炬湡鏁�</span>
+ <span class="metric-value metric-up">
+ {{ formatAmountWanNumber(income.overdueCount) }}
+ <span
+ v-if="isWanAmount(income.overdueCount)"
+ class="metric-unit"
+ >
+ 涓�
+ </span>
+ </span>
+ </div>
+ <div class="metric-row">
+ <span class="metric-label">閫炬湡鐜�</span>
+ <span class="metric-value" :class="metricClass(income.overdueRate)">
+ {{ formatPercent(income.overdueRate.value) }}
+ <span class="arrow">{{ metricArrow(income.overdueRate) }}</span>
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- 鏈堝害鏀嚭 -->
+ <div class="finance-card expense-card">
+ <div class="icon-box">
+ <img src="@/assets/BI/icon@2x.png" alt="鍥炬爣" class="card-icon" />
+ </div>
+ <div class="card-body">
+ <div class="card-left">
+ <div class="card-title">鏈堝害鏀嚭</div>
+ <div class="card-amount">
+ <span>{{ formatAmountWanNumber(expense.amount) }}</span>
+ <span v-if="isWanAmount(expense.amount)" class="card-amount-unit">涓�</span>
+ </div>
+ </div>
+ <div class="card-right">
+ <div class="metric-row">
+ <span class="metric-label">浠樻鐜�</span>
+ <span class="metric-value" :class="metricClass(expense.netProfit)">
+ {{ formatPercent(expense.netProfit.value) }}
+ <span class="arrow">{{ metricArrow(expense.netProfit) }}</span>
+ </span>
+ </div>
+ <div class="metric-row">
+ <span class="metric-label">姣涘埄娑�</span>
+ <span class="metric-value metric-down">
+ {{ formatAmountWanNumber(expense.grossProfit) }}
+ <span
+ v-if="isWanAmount(expense.grossProfit)"
+ class="metric-unit"
+ >
+ 涓�
+ </span>
+ </span>
+ </div>
+ <div class="metric-row">
+ <span class="metric-label">鍒╂鼎鐜�</span>
+ <span class="metric-value" :class="metricClass(expense.profitRate)">
+ {{ formatPercent(expense.profitRate.value) }}
+ <span class="arrow">{{ metricArrow(expense.profitRate) }}</span>
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ </div>
+</template>
+
+<script setup>
+import { onMounted, ref } from 'vue'
+import { getMonthlyIncome, getMonthlyExpenditure } from '@/api/viewIndex'
+
+const income = ref({
+ amount: 0,
+ repayRate: { value: 0, trend: 0 },
+ overdueCount: 0,
+ overdueRate: { value: 0, trend: 0 },
+})
+
+const expense = ref({
+ amount: 0,
+ netProfit: { value: 0, trend: 0 },
+ grossProfit: 0,
+ profitRate: { value: 0, trend: 0 },
+})
+
+const fetchMonthlyIncome = async () => {
+ const res = await getMonthlyIncome()
+ const data = res?.data || {}
+
+ income.value.amount = data.monthlyIncome ?? 0
+ const collectionRate = Number(data.collectionRate ?? 0)
+ const overdueRate = Number(data.overdueRate ?? 0)
+ income.value.repayRate = {
+ value: collectionRate,
+ trend: collectionRate >= 0 ? 1 : -1,
+ }
+ income.value.overdueCount = data.overdueNum ?? 0
+ income.value.overdueRate = {
+ value: overdueRate,
+ trend: overdueRate >= 0 ? 1 : -1,
+ }
+}
+
+const fetchMonthlyExpenditure = async () => {
+ const res = await getMonthlyExpenditure()
+ const data = res?.data || {}
+
+ expense.value.amount = data.monthlyExpenditure ?? 0
+ const paymentRate = Number(data.paymentRate ?? 0)
+ expense.value.netProfit = {
+ value: paymentRate,
+ trend: paymentRate >= 0 ? 1 : -1,
+ }
+ expense.value.grossProfit = data.grossProfit ?? 0
+
+ const profitMarginRate = Number(data.profitMarginRate ?? 0)
+ expense.value.profitRate = {
+ value: profitMarginRate,
+ trend: profitMarginRate >= 0 ? 1 : -1,
+ }
+}
+
+const isWanAmount = (val) => {
+ const num = Number(val) || 0
+ return Math.abs(num) >= 10000
+}
+
+const formatAmountWanNumber = (val) => {
+ const num = Number(val) || 0
+ if (Math.abs(num) >= 10000) {
+ return (num / 10000).toFixed(2)
+ }
+ return num.toFixed(2)
+}
+
+const formatPercent = (val) => {
+ const num = Number(val) || 0
+ // 鐧惧垎姣斿睍绀哄缁堢敤缁濆鍊硷紝灏忔暟淇濈暀涓や綅
+ return `${Math.abs(num).toFixed(2)}%`
+}
+
+const metricClass = (metric) => {
+ if (metric?.trend === undefined || metric?.trend === null) return 'metric-up'
+ return Number(metric.trend) >= 0 ? 'metric-up' : 'metric-down'
+}
+
+const metricArrow = (metric) => {
+ if (metric?.trend === undefined || metric?.trend === null) return ''
+ return Number(metric.trend) >= 0 ? '鈫�' : '鈫�'
+}
+
+onMounted(() => {
+ fetchMonthlyIncome()
+ fetchMonthlyExpenditure()
+})
+</script>
+
+<style scoped>
+.finance-cards {
+ display: flex;
+ justify-content: space-between;
+ gap: 16px;
+}
+
+.finance-card {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ padding: 18px 10px;
+ background-image: url('@/assets/BI/border@2x.png');
+ background-size: 100% 100%;
+ background-position: center;
+ background-repeat: no-repeat;
+ min-height: 138px;
+}
+
+.icon-box {
+ width: 92px;
+ height: 92px;
+ /* border: 1px dashed rgba(208, 231, 255, 0.55); */
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 18px;
+}
+
+.card-icon {
+ width: 78px;
+ height: 78px;
+}
+
+.card-body {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 18px;
+}
+
+.card-left {
+ min-width: 90px;
+}
+
+.card-title {
+ font-weight: 400;
+ font-size: 18px;
+ color: rgba(208, 231, 255, 0.7);
+}
+
+.card-amount {
+ font-weight: 500;
+ font-size: 36px;
+ line-height: 1.1;
+ margin-top: 8px;
+ display: inline-flex;
+ align-items: baseline;
+ white-space: nowrap;
+ background: linear-gradient(360deg, #008bfd 0%, #ffffff 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.card-amount-unit {
+ font-size: 20px;
+ margin-left: 4px;
+}
+
+.card-right {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ padding-right: 6px;
+}
+
+.metric-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 14px;
+ color: #d0e7ff;
+ white-space: nowrap;
+}
+
+.metric-label {
+ margin-right: 12px;
+}
+
+.metric-label {
+ opacity: 0.8;
+}
+
+.metric-value {
+ font-weight: 600;
+ display: inline-flex;
+ align-items: center;
+}
+
+.metric-unit {
+ font-size: 12px;
+ margin-left: 2px;
+}
+
+.metric-value .arrow {
+ font-size: 13px;
+ margin-left: 4px;
+}
+
+.metric-up {
+ color: #00c853;
+}
+
+.metric-down {
+ color: #ff5252;
+}
+
+
+</style>
--
Gitblit v1.9.3