feat(bid): 新增投标报表统计分析模块
本次提交新增了完整的投标报表统计分析功能,包括: 添加用于数据检查与菜单初始化的 SQL 脚本 实现采购概览仪表板、采购成本分析及供应商绩效报告的后端服务、Mapper、Controller 及 VO 类 添加前端 API、路由配置以及使用 ECharts 可视化图表的页面组件 为仪表板添加通用的 KPI 卡片组件
This commit is contained in:
104
ruoyi-ui/src/views/bid/report/components/KpiCard.vue
Normal file
104
ruoyi-ui/src/views/bid/report/components/KpiCard.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<el-card shadow="hover" class="kpi-card" :style="{ '--kpi-accent': accentColor }">
|
||||
<div class="kpi-label">{{ label }}</div>
|
||||
<div class="kpi-value">
|
||||
<span class="kpi-number">{{ displayValue }}</span>
|
||||
<span class="kpi-unit" v-if="unit">{{ unit }}</span>
|
||||
</div>
|
||||
<div class="kpi-trend" :class="trendClass" v-if="changeRate > 0">
|
||||
<i :class="trendIcon"></i>
|
||||
{{ changeRateText }}
|
||||
<span class="trend-label">环比上月</span>
|
||||
</div>
|
||||
<div class="kpi-trend no-change" v-else>
|
||||
<i class="el-icon-minus"></i> 持平
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const COLORS = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399']
|
||||
const CARD_NAMES = ['采购总额', 'RFQ总数', '采购单数', '活跃供应商']
|
||||
|
||||
export default {
|
||||
name: 'KpiCard',
|
||||
props: {
|
||||
label: { type: String, required: true },
|
||||
value: { type: [Number, String], default: 0 },
|
||||
unit: { type: String, default: '' },
|
||||
changeRate: { type: Number, default: 0 },
|
||||
trend: { type: String, default: 'up' }
|
||||
},
|
||||
computed: {
|
||||
safeValue() {
|
||||
const v = Number(this.value)
|
||||
return isNaN(v) ? 0 : v
|
||||
},
|
||||
displayValue() {
|
||||
const v = this.safeValue
|
||||
if (this.label === '采购总额') {
|
||||
if (v >= 100000000) return '¥' + (v / 100000000).toFixed(2) + '亿'
|
||||
if (v >= 10000) return '¥' + (v / 10000).toFixed(2) + '万'
|
||||
return '¥' + v.toLocaleString()
|
||||
}
|
||||
return v.toLocaleString()
|
||||
},
|
||||
trendClass() { return this.trend === 'up' ? 'trend-up' : 'trend-down' },
|
||||
trendIcon() { return this.trend === 'up' ? 'el-icon-top' : 'el-icon-bottom' },
|
||||
changeRateText() {
|
||||
const rate = Number(this.changeRate) || 0
|
||||
return rate.toFixed(1) + '%'
|
||||
},
|
||||
accentColor() {
|
||||
const idx = CARD_NAMES.indexOf(this.label)
|
||||
return COLORS[idx >= 0 ? idx : 4]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.kpi-card {
|
||||
border-left: 4px solid var(--kpi-accent, #409EFF);
|
||||
border-radius: 6px;
|
||||
transition: transform .2s;
|
||||
}
|
||||
.kpi-card:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.kpi-label {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.kpi-value {
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
.kpi-number {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.kpi-unit {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.kpi-trend {
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.trend-up { color: #67C23A; }
|
||||
.trend-down { color: #F56C6C; }
|
||||
.no-change { color: #C0C4CC; }
|
||||
.trend-label {
|
||||
color: #C0C4CC;
|
||||
margin-left: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user