Files
yudao-dev/src/views/home/components/purchase-Item.vue
‘937886381’ babbe98c09 xiugai
2026-01-12 16:07:02 +08:00

369 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="coreItem">
<div class="item" @click="handleRoute(item.route)" v-for="(item, index) in itemList" :key="index">
<div class="name">{{ item.name }}</div>
<div class="item-content">
<div class="content-wrapper">
<div class="left">
<div class="number" style="color: rgba(103, 103, 103, 0.79);">{{ item.targetValue }}</div>
<div class="title" style="color: rgba(134, 134, 135, 1);">目标值</div>
</div>
<div class="line"></div>
<!-- 实际值根据 实际值目标值 动态绑定类名 -->
<div class="right">
<div class="number" :class="{
'number-exceed': item.currentValue >= item.targetValue,
'number-below': item.currentValue < item.targetValue
}">
{{ item.currentValue }}
</div>
<div class="title" style="color: rgba(134, 134, 135, 1);">实际值</div>
</div>
</div>
<div class="line"></div>
<!-- 进度条同步绑定类名 -->
<div class="progress-group">
<div class="progress-container">
<div class="progress-bar" :style="{ width: item.progressWidth + '%' }" :class="{
'bar-exceed': item.currentValue >= item.targetValue,
'bar-below': item.currentValue < item.targetValue
}"></div>
</div>
</div>
<!-- 完成率同步绑定类名 -->
<div class="yield" style="display: flex;justify-content: space-between;">
<div class="progress-percent" :class="{
'percent-exceed': item.currentValue >= item.targetValue,
'percent-below': item.currentValue < item.targetValue
}">完成率</div>
<div class="progress-percent" :class="{
'percent-exceed': item.currentValue >= item.targetValue,
'percent-below': item.currentValue < item.targetValue
}">
{{ item.progressDisplay }}
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Container",
components: {},
props: {
finance: {
type: Object,
default: () => ({}) // 明确props默认值为空对象
},
dateData: {
type: Object,
default: () => ({})
}
},
data() {
return {
// 关键修复1初始化itemList为空数组必选否则初始状态下v-for报错且数据无法响应式更新
itemList: []
};
},
watch: {
finance: {
handler(newVal) {
// 关键修复2增强数据判断避免空值/无效值触发错误
if (newVal && Object.keys(newVal).length > 0) {
// 转换数据并赋值给itemList响应式更新
this.itemList = this.transformData(newVal);
console.log('finance更新itemList已同步', this.itemList);
} else {
// 当finance为空时重置itemList为空数组
this.itemList = [];
}
},
immediate: true, // 组件挂载时立即执行,初始化数据
deep: true // 深度监听finance对象内部属性变化
}
},
methods: {
// 解析rate字符串提取百分比数值
// 改进的 parseRateString 方法:能处理数字和字符串类型
parseRateString(rateValue) {
// 如果是 undefined 或 null返回默认值
if (rateValue === undefined || rateValue === null) {
return { displayText: '0%', progressValue: 0 };
}
// 如果是数字类型,直接处理
if (typeof rateValue === 'number') {
return {
displayText: `${rateValue.toFixed(2)}%`,
progressValue: Math.min(Math.max(rateValue, 0), 100)
};
}
// 如果是字符串类型,使用正则表达式处理
if (typeof rateValue === 'string') {
// 尝试匹配百分比数字,如"减亏93%"中的93
const match = rateValue.match(/(\d+(\.\d+)?)%/);
if (match) {
const percentValue = parseFloat(match[1]);
return {
displayText: rateValue,
progressValue: Math.min(Math.max(percentValue, 0), 100)
};
}
// 如果没有匹配到百分比,尝试解析纯数字
const numMatch = rateValue.match(/\d+(\.\d+)?/);
if (numMatch) {
const numValue = parseFloat(numMatch[0]);
return {
displayText: rateValue,
progressValue: Math.min(Math.max(numValue, 0), 100)
};
}
}
// 默认返回
return {
displayText: '0%',
progressValue: 0
};
},
transformData(rawData) {
// 定义指标映射关系,包括名称、对应的数据键和路由
const Mapping = [
{
key: 'operatingRevenue',
name: '营业收入·万元',
route: '/operatingRevenue/operatingRevenueIndex',
isPercentage: true // 需要加%符号
},
{
key: 'operatingIncome',
name: '经营性利润·万元',
route: '/operatingProfit/operatingProfit',
isPercentage: false // 不需要加%符号使用原始rate字符串
},
{
key: 'totalProfit',
name: '利润总额·万元',
route: '/totalProfit/totalProfit',
isPercentage: false // 不需要加%符号使用原始rate字符串
},
{
key: 'grossMargin',
name: '毛利率·%',
route: '/grossMargin/grossMargin',
isPercentage: true // 需要加%符号
}
];
// 遍历映射关系,转换数据
return Mapping.map(mappingItem => {
// 关键修复3兜底更严谨避免rawData[mappingItem.key]不存在导致报错
const data = rawData[mappingItem.key] || { rate: '0%', real: 0, target: 0 };
// 额外兜底避免data中的属性为undefined
const target = data.target || 0;
const real = data.real || 0;
const rate = data.rate || '0%';
// 解析rate字符串
const parsedRate = this.parseRateString(rate);
// 进度条宽度限制在0-100之间
const progressWidth = Math.min(Math.max(parsedRate.progressValue, 0), 100);
// 显示文本处理
let progressDisplay;
if (mappingItem.isPercentage) {
// 对于需要加%的指标,确保有%符号
progressDisplay = parsedRate.displayText.includes('%')
? parsedRate.displayText
: `${parsedRate.displayText}%`;
} else {
// 对于经营性利润和利润总额直接使用原始rate字符串
progressDisplay = parsedRate.displayText;
}
return {
name: mappingItem.name,
targetValue: target,
currentValue: real,
progressWidth: progressWidth, // 用于进度条宽度
progressDisplay: progressDisplay, // 用于显示文本
route: mappingItem.route
};
});
},
handleRoute(route) {
if (route) {
this.$router.push({
path: route,
query: {
// 关键修复4dateData是对象需序列化后传递否则路由query无法正常接收对象
dateData: this.dateData
}
});
}
}
}
};
</script>
<style scoped lang="scss">
.coreItem {
display: flex;
gap: 8px;
// padding: 8px; // 避免边缘item hover阴影被截断
}
.item {
width: 170px;
height: 228px;
background: #f9fcff;
padding: 12px 0px 17px 12px;
box-sizing: border-box;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
box-shadow: 0px 4px 12px 2px #B5CDE5;
transform: translateY(-2px);
}
.name {
height: 18px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 18px;
color: #000000;
line-height: 18px;
letter-spacing: 1px;
text-align: left;
font-style: normal;
}
.item-content {
display: flex;
flex-direction: column;
justify-content: space-between;
height: calc(100% - 26px);
}
.content-wrapper {
display: flex;
flex-direction: column;
gap: 10px;
}
.line {
width: 149px;
height: 1px;
background: linear-gradient(to left, rgba(255, 0, 0, 0), #cbcbcb);
}
.left,
.right {
margin-top: 11px;
display: flex;
flex-direction: column;
gap: 2px;
width: 100%;
}
/* 实际值 - 基础样式(无颜色) */
.number {
height: 22px;
font-family: PingFangSC, PingFang SC;
font-weight: 600;
font-size: 24px;
line-height: 22px;
text-align: left;
font-style: normal;
}
/* 实际值 - 实际值≥目标值(绿色) */
.number-exceed {
color: rgba(54, 181, 138, 1) !important;
}
/* 实际值 - 实际值<目标值(黄色) */
.number-below {
color: rgba(249, 164, 74, 1) !important;
}
.title {
height: 14px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 12px;
color: #868687;
line-height: 14px;
text-align: left;
font-style: normal;
}
.progress-group {
display: flex;
align-items: center;
gap: 8px;
margin-top: 15px;
}
.progress-container {
width: 138px;
height: 10px;
background: #ECEFF7;
border-radius: 8px;
overflow: hidden;
}
/* 进度条 - 基础样式(无颜色) */
.progress-bar {
height: 100%;
border-radius: 8px;
transition: width 0.5s ease;
}
/* 进度条 - 实际值≥目标值(绿色) */
.bar-exceed {
background: rgba(98, 213, 180, 1) !important;
opacity: 1 !important;
}
/* 进度条 - 实际值<目标值(黄色) */
.bar-below {
background: rgba(249, 164, 74, 1) !important;
opacity: 1 !important;
}
/* 百分比 - 基础样式(无颜色) */
.progress-percent {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 12px;
line-height: 1;
}
/* 百分比 - 实际值≥目标值(绿色) */
.percent-exceed {
color: rgba(54, 181, 138, 1) !important;
}
/* 百分比 - 实际值<目标值(黄色) */
.percent-below {
color: rgba(249, 164, 74, 1) !important;
}
.yield {
width: 138px;
margin-top: 3px;
}
}
</style>