This commit is contained in:
‘937886381’
2025-12-30 09:04:48 +08:00
parent 80deffbb42
commit 7b3873f9ea
232 changed files with 13127 additions and 17011 deletions

View File

@@ -1,28 +1,16 @@
<template>
<div style="flex: 1">
<Container
name="累计数据对比"
icon="cockpitItemIcon"
size="operatingLarge"
topSize="large"
>
<Container name="累计数据对比" icon="cockpitItemIcon" size="operatingLarge" topSize="large">
<!-- 1. 移除 .kpi-content 的固定高度改为自适应 -->
<div
class="kpi-content"
style="padding: 14px 16px; display: flex; width: 100%; gap: 16px"
>
<div
class="left"
style="
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
<div class="left" style="
height: 380px;
display: flex;
width: 348px;
background-color: rgba(249, 252, 255, 1);
flex-direction: column;
"
>
<div
style="
">
<div style="
font-weight: 400;
font-size: 18px;
color: #000000;
@@ -30,28 +18,25 @@
letter-spacing: 1px;
font-style: normal;
padding: 16px 0 0 16px;
"
>
">
集团情况
</div>
<operatingTopBar :chartData="chartData" />
<operatingTopBar :chartData="groupData" />
</div>
<div
class="right"
style="
<div class="right" style="
height: 380px;
display: flex;
width: 1220px;
background-color: rgba(249, 252, 255, 1);
"
>
">
<!-- <top-item /> -->
<operatingBar :chartData="chartData" />
<operatingBar :chartData="chartData" @sort-change="sortChange" />
</div>
</div>
</Container>
</div>
</template>
<script>
import Container from "../components/container.vue";
import operatingBar from "./operatingBar.vue";
@@ -61,29 +46,20 @@ export default {
name: "ProductionStatus",
components: { Container, operatingBar, operatingTopBar },
props: {
salesTrendMap: {
type: Object,
default: () => ({}),
},
grossMarginTrendMap: {
totalData: {
type: Object,
default: () => ({}),
required: true, // 标记为必填,避免空数据导致异常
},
},
data() {
return {
chartData: null, // 初始化 chartData 为 null
chartData: null, // 工厂图表数据
groupData: {}, // 集团(凯盛新能)数据
};
},
watch: {
grossMarginTrendMap: {
handler() {
this.processChartData();
},
immediate: true,
deep: true,
},
salesTrendMap: {
totalData: {
handler() {
this.processChartData();
},
@@ -92,143 +68,92 @@ export default {
},
},
methods: {
/**
* 核心处理函数:在所有数据都准备好后,才组装 chartData
*/
processChartData() {
// 关键改动:增加数据有效性检查
// 检查 salesTrendMap 是否有实际数据(不只是空对象)
const isSalesDataReady = Object.keys(this.salesTrendMap).length > 0;
// 检查 grossMarginTrendMap 是否有实际数据
const isGrossMarginDataReady =
Object.keys(this.grossMarginTrendMap).length > 0;
// 如果任一数据未准备好,则不更新 chartData或传递一个加载中的状态
// 你可以根据业务需求调整这里的逻辑,比如:
// 1. 等待两者都准备好
// 2. 只要有一个准备好了就更新,但确保另一个有合理的默认值
// --- 方案一:等待两者都准备好 ---
// if (!isSalesDataReady || !isGrossMarginDataReady) {
// console.log('数据尚未全部准备好,暂不更新图表');
// this.chartData = {
// grossMarginLocations: [],
// salesLocations: [],
// grossMargin: { rates: [], reals: [], targets: [], flags: [] },
// sales: { rates: [], reals: [], targets: [], flags: [] },
// };
// return;
// }
// --- 方案二 (推荐):有什么数据就显示什么,没有的就显示空 ---
// 这种方式更友好,用户可以先看到部分数据
const grossMarginLocations = isGrossMarginDataReady
? Object.keys(this.grossMarginTrendMap)
: [];
const salesLocations = isSalesDataReady
? Object.keys(this.salesTrendMap)
: [];
const processedGrossMarginData = isGrossMarginDataReady
? this.processSingleDataset(
grossMarginLocations,
this.grossMarginTrendMap
)
: { rates: [], reals: [], targets: [], flags: [] };
const processedSalesData = isSalesDataReady
? this.processSingleDataset(salesLocations, this.salesTrendMap)
: { rates: [], reals: [], targets: [], flags: [] };
// 3. 组装最终的 chartData 对象
this.chartData = {
grossMarginLocations: grossMarginLocations,
salesLocations: salesLocations,
grossMargin: processedGrossMarginData,
sales: processedSalesData,
};
console.log("chartData 已更新:", this.chartData);
// 透传排序变化事件给父组件
sortChange(value) {
this.$emit('sort-change', value);
},
/**
* 通用数据处理函数(纯函数
* @param {Array} locations - 某个指标的地点数组
* @param {Object} dataMap - 该指标的原始数据映射
* @returns {Object} - 格式化后的数据对象
* 判断完成率对应的flag值<100为0≥100为1
* @param {number} rate 完成率原始值如80代表80%
* @returns {0|1} flag值
*/
processSingleDataset(locations, dataMap) {
const rates = [];
const reals = [];
const targets = [];
const flags = [];
getRateFlag(rate) {
if (isNaN(rate) || rate === null || rate === undefined) return 0;
return +(rate >= 100 || rate === 0); // + 号将布尔值转为数字true→1false→0
},
locations.forEach((location) => {
const data = dataMap[location] || {};
// 优化:处理 data.rate 为 null/undefined 的情况
const rate =
data.rate !== null && data.rate !== undefined
? Math.round(data.rate * 100)
: 0;
/**
* 核心处理函数解析thisMonData组装集团和工厂数据
*/
processChartData() {
// 1. 处理集团数据(凯盛新能)
const ksxnData = this.totalData['凯盛新能'] || {
completeRate: 0,
diff: 0,
real: 0,
target: 0,
thb: 0
};
this.groupData = {
locations: ['凯盛新能'],
targets: [ksxnData.target],
diff: [ksxnData.diff],
reals: [ksxnData.real],
rate: [ksxnData.completeRate],
flags: [this.getRateFlag(ksxnData.completeRate)],
thb: [ksxnData.thb] // 新增thb字段如果子组件需要
};
rates.push(rate);
reals.push(data.real ?? 0); // 使用空值合并运算符
targets.push(data.target ?? 0);
// 2. 处理工厂数据(排除凯盛新能)
const factoryKeys = Object.keys(this.totalData).filter(key => key !== '凯盛新能');
const factoryDataList = factoryKeys.map(key => this.totalData[key]);
// 优化:更清晰的逻辑
if (data.target === 0) {
flags.push(1); // 如果目标为0默认达标
} else {
flags.push(rate >= 100 ? 1 : 0);
}
});
// 3. 组装工厂chartData格式与集团一致适配子组件
this.chartData = {
locations: factoryKeys, // 工厂名称数组
targets: factoryDataList.map(item => item.target || 0), // 目标值
diff: factoryDataList.map(item => item.diff || 0), // 差值
reals: factoryDataList.map(item => item.real || 0), // 实际值
rates: factoryDataList.map(item => item.completeRate || 0), // 完成率
flags: factoryDataList.map(item => this.getRateFlag(item.completeRate)), // 完成率标识
thb: factoryDataList.map(item => item.thb || 0) // thb字段
};
return { rates, reals, targets, flags };
console.log('组装后的集团数据:', this.groupData);
console.log('组装后的工厂数据:', this.chartData);
},
},
};
</script>
<style lang="scss" scoped>
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
/* 原有样式保持不变 */
.scroll-container {
/* 1. 固定容器高度根据页面布局调整示例300px超出则滚动 */
max-height: 210px;
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
overflow-y: auto;
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
overflow-x: hidden;
/* 4. 内边距:与标题栏和容器边缘对齐 */
padding: 10px 0;
/* 5. 隐藏滚动条(兼容主流浏览器) */
/* Chrome/Safari */
&::-webkit-scrollbar {
display: none;
}
/* Firefox */
scrollbar-width: none;
/* IE/Edge */
-ms-overflow-style: none;
}
/* 设备项样式优化:增加间距,避免拥挤 */
.proBarInfo {
display: flex;
flex-direction: column;
padding: 8px 27px;
/* 调整内边距,优化排版 */
margin-bottom: 10px;
/* 设备项之间的垂直间距 */
}
/* 原有样式保留,优化细节 */
.proBarInfoEqInfo {
display: flex;
justify-content: space-between;
align-items: center;
/* 垂直居中,避免序号/文字错位 */
}
.slot {
@@ -241,14 +166,12 @@ export default {
font-size: 16px;
color: #68b5ff;
line-height: 23px;
/* 垂直居中文字 */
text-align: center;
font-style: normal;
}
.eq-name {
margin-left: 8px;
/* 增加与序号的间距 */
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 16px;
@@ -274,7 +197,6 @@ export default {
height: 14px;
border: 1px solid #adadad;
margin: 0 8px;
/* 优化分割线间距 */
}
.yield {
@@ -291,22 +213,18 @@ export default {
.proBarInfoEqInfoLeft {
display: flex;
align-items: center;
/* 序号和设备名垂直居中 */
}
.proBarInfoEqInfoRight {
display: flex;
align-items: center;
/* 状态/分割线/百分比垂直居中 */
}
.proBarWrapper {
position: relative;
height: 10px;
margin-top: 6px;
/* 进度条与上方信息的间距 */
border-radius: 5px;
/* 进度条圆角,优化视觉 */
overflow: hidden;
}
@@ -322,19 +240,15 @@ export default {
top: 0;
left: 0;
height: 100%;
background: linear-gradient(
65deg,
rgba(53, 223, 247, 0) 0%,
rgba(54, 220, 246, 0.92) 92%,
#36f6e5 100%,
#37acf5 100%
);
background: linear-gradient(65deg,
rgba(53, 223, 247, 0) 0%,
rgba(54, 220, 246, 0.92) 92%,
#36f6e5 100%,
#37acf5 100%);
border-radius: 5px;
transition: width 0.3s ease;
/* 进度变化时添加过渡动画,更流畅 */
}
/* 图表相关样式保留 */
.chartImgBottom {
position: absolute;
bottom: 45px;
@@ -353,7 +267,7 @@ export default {
</style>
<style>
/* 全局 tooltip 样式(不使用 scoped确保生效 */
/* 全局 tooltip 样式 */
.production-status-chart-tooltip {
background: #0a2b4f77 !important;
border: none !important;