285 lines
7.7 KiB
Vue
285 lines
7.7 KiB
Vue
<template>
|
||
<div class="coreItem">
|
||
<div class="item" v-for="(item, index) in itemList" :key="index" @click="handleRouter(item)">
|
||
<div class="unit">{{ item.unit }}</div>
|
||
<div class="item-content">
|
||
<div class="content-wrapper">
|
||
<div class="left">
|
||
<div class="number">{{ item.targetValue }}</div>
|
||
<div class="title">目标值</div>
|
||
</div>
|
||
<div class="line"></div>
|
||
<!-- 实际值:根据与目标值的比较动态变色 -->
|
||
<div class="right">
|
||
<div class="number" :class="{
|
||
'exceed-target': item.currentValue > item.targetValue,
|
||
'below-target': item.currentValue < item.targetValue,
|
||
'equal-target': item.currentValue === item.targetValue
|
||
}">
|
||
{{ item.currentValue }}
|
||
</div>
|
||
<div class="title">实际值</div>
|
||
</div>
|
||
</div>
|
||
<!-- 进度条和百分比:同步变色逻辑 -->
|
||
<div class="progress-group">
|
||
<div class="progress-container">
|
||
<div class="progress-bar" :style="{ width: item.progress + '%' }" :class="{
|
||
'exceed-pro-target': item.currentValue > item.targetValue,
|
||
'below-pro-target': item.currentValue < item.targetValue,
|
||
'equal-pro-target': item.currentValue === item.targetValue
|
||
}"></div>
|
||
</div>
|
||
<div class="progress-percent" :class="{
|
||
'exceed-target': item.currentValue > item.targetValue,
|
||
'below-target': item.currentValue < item.targetValue,
|
||
'equal-target': item.currentValue === item.targetValue
|
||
}">
|
||
{{ item.progress }}%
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: "Container",
|
||
components: {},
|
||
props: ["sale", "dateData"],
|
||
data() {
|
||
return {
|
||
itemList: [] // 初始化为空数组,等待数据加载
|
||
};
|
||
},
|
||
watch: {
|
||
// 监听 sale 数据变化,实时更新 itemList
|
||
sale: {
|
||
handler(newVal) {
|
||
if (newVal) {
|
||
this.itemList = this.transformData(newVal);
|
||
}
|
||
},
|
||
immediate: true, // 组件初始化时立即执行一次
|
||
deep: true // 深度监听对象内部属性变化
|
||
}
|
||
},
|
||
methods: {
|
||
/**
|
||
* 核心转换函数:将 sale 对象转换为 itemList 数组
|
||
* @param {Object} rawData - 原始的 sale 数据对象
|
||
* @returns {Array} - 转换后的 itemList 数组
|
||
*/
|
||
transformData(rawData) {
|
||
// 定义销售指标映射关系(键名、显示名称、单位、路由路径)
|
||
const saleMapping = [
|
||
{ key: 'unitPrice', unit: '单价·元/㎡', path: '/unitPriceAnalysis/unitPriceAnalysis' },
|
||
{ key: 'netPrice', unit: '净价·元/㎡', path: '/netPriceAnalysis/netPriceAnalysis' },
|
||
{ key: 'sales', unit: '销量·万㎡', path: '/salesVolumeAnalysis/salesVolumeAnalysis' },
|
||
{ key: 'panel', unit: '双镀面板·万㎡', path: '/salesVolumeAnalysis/salesVolumeAnalysis' }
|
||
];
|
||
|
||
// 遍历映射关系,转换数据
|
||
return saleMapping.map(mappingItem => {
|
||
// 获取对应指标的数据,若不存在则使用默认值
|
||
const indicatorData = rawData[mappingItem.key] || { rate: 0, real: 0, target: 0 };
|
||
|
||
return {
|
||
unit: mappingItem.unit,
|
||
targetValue: indicatorData.target, // 目标值
|
||
currentValue: indicatorData.real, // 实际值
|
||
progress: indicatorData.rate || 0, // 完成率
|
||
path: mappingItem.path // 路由路径
|
||
};
|
||
});
|
||
},
|
||
|
||
// 处理路由跳转
|
||
handleRouter(obj) {
|
||
if (obj.path) {
|
||
this.$router.push({
|
||
path: obj.path,
|
||
query: {
|
||
dateData:this.dateData
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.coreItem {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
grid-template-rows: 1fr 1fr;
|
||
gap: 8px;
|
||
// padding: 8px; // 避免边缘item hover时阴影被截断
|
||
}
|
||
|
||
.item {
|
||
width: 253px;
|
||
height: 110px;
|
||
background: #f9fcff;
|
||
padding: 12px;
|
||
box-sizing: border-box;
|
||
cursor: pointer; // 提示可点击
|
||
transition: all 0.3s ease; // 动画过渡
|
||
|
||
&:hover {
|
||
box-shadow: 0px 4px 12px 2px #B5CDE5;
|
||
transform: translateY(-2px); // 轻微上浮增强交互感
|
||
}
|
||
|
||
.unit {
|
||
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;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.item-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
height: calc(100% - 26px);
|
||
}
|
||
|
||
.content-wrapper {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-around;
|
||
flex: 1;
|
||
}
|
||
|
||
.line {
|
||
width: 1px;
|
||
height: 46px;
|
||
background: linear-gradient(to bottom, rgba(255, 0, 0, 0), #cbcbcb);
|
||
}
|
||
|
||
.left,
|
||
.right {
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: 2px;
|
||
flex: 1;
|
||
}
|
||
|
||
.number {
|
||
height: 22px;
|
||
font-family: PingFangSC, PingFang SC;
|
||
font-weight: 600;
|
||
font-size: 24px;
|
||
color: rgba(103, 103, 103, 0.79);
|
||
/* 默认颜色(等于目标值时) */
|
||
line-height: 22px;
|
||
text-align: center;
|
||
font-style: normal;
|
||
}
|
||
|
||
.title {
|
||
height: 14px;
|
||
font-family: PingFangSC, PingFang SC;
|
||
font-weight: 400;
|
||
font-size: 12px;
|
||
color: #868687;
|
||
line-height: 14px;
|
||
text-align: center;
|
||
font-style: normal;
|
||
}
|
||
|
||
.progress-group {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.progress-container {
|
||
width: 190px;
|
||
height: 10px;
|
||
background: #ECEFF7;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.progress-bar {
|
||
height: 100%;
|
||
background: rgba(98, 213, 180, 1);
|
||
/* 默认进度条颜色(等于目标值时) */
|
||
border-radius: 8px;
|
||
opacity: 0.7;
|
||
transition: width 0.5s ease; // 进度条动画
|
||
}
|
||
|
||
.progress-percent {
|
||
font-family: PingFangSC, PingFang SC;
|
||
font-weight: 400;
|
||
font-size: 12px;
|
||
color: #868687;
|
||
/* 默认百分比颜色(等于目标值时) */
|
||
line-height: 1;
|
||
}
|
||
|
||
/* 实际值 > 目标值:绿色样式 */
|
||
.exceed-target {
|
||
color: rgba(98, 213, 180, 1) !important;
|
||
/* 文字绿色 */
|
||
// background: rgba(98, 213, 180, 1) !important;
|
||
/* 进度条绿色 */
|
||
opacity: 1 !important;
|
||
}
|
||
|
||
/* 实际值 < 目标值:黄色样式 */
|
||
.below-target {
|
||
color: rgba(249, 164, 74, 1) !important;
|
||
/* 文字黄色 */
|
||
// background: rgba(249, 164, 74, 1) !important;
|
||
/* 进度条黄色 */
|
||
opacity: 1 !important;
|
||
}
|
||
|
||
.exceed-pro-target {
|
||
// color: rgba(98, 213, 180, 1) !important;
|
||
/* 文字绿色 */
|
||
background: rgba(98, 213, 180, 1) !important;
|
||
/* 进度条绿色 */
|
||
opacity: 1 !important;
|
||
}
|
||
|
||
/* 实际值 < 目标值:黄色样式 */
|
||
.below-pro-target {
|
||
// color: rgba(249, 164, 74, 1) !important;
|
||
/* 文字黄色 */
|
||
background: rgba(249, 164, 74, 1) !important;
|
||
/* 进度条黄色 */
|
||
opacity: 1 !important;
|
||
}
|
||
/* 实际值 = 目标值:默认灰色(可自定义) */
|
||
.equal-target{
|
||
color: rgba(98, 213, 180, 1) !important;
|
||
/* 文字绿色 */
|
||
// background: rgba(98, 213, 180, 1) !important;
|
||
/* 进度条绿色 */
|
||
opacity: 1 !important;
|
||
}
|
||
.equal-pro-target {
|
||
// color: rgba(98, 213, 180, 1) !important;
|
||
/* 文字绿色 */
|
||
background: rgba(98, 213, 180, 1) !important;
|
||
/* 进度条绿色 */
|
||
opacity: 1 !important;
|
||
}
|
||
}
|
||
</style>
|