修改
This commit is contained in:
@@ -194,3 +194,55 @@ export function getInputOutputRateFactoryData(data) {
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
export function getUnitPriceAnalysisGroupData(data) {
|
||||
return request({
|
||||
url: "/lb/unit-price-analysis/getGroupData",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
export function getUnitPriceAnalysisBaseData(data) {
|
||||
return request({
|
||||
url: "/lb/unit-price-analysis/getBaseData",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
export function getProfitAnalysisManageList(data) {
|
||||
return request({
|
||||
url: "/lb/profit-analysis/manageList",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
export function getProfitAnalysisTotalList(data) {
|
||||
return request({
|
||||
url: "/lb/profit-analysis/profitTotalList",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
export function getCostAnalysisData(data) {
|
||||
return request({
|
||||
url: "/lb/cost-analysis/XXCostList2",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
export function getSingleMaterialAnalysis(data) {
|
||||
return request({
|
||||
url: "/lb/cost-analysis/singleMaterialAnalysis",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
export function getSingleMaterialCostAnalysis(data) {
|
||||
return request({
|
||||
url: "/lb/cost-analysis/singleMaterialCostAnalysis",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
<div class="center-content">
|
||||
<!-- 循环 pageRoutes,不再硬编码文字 -->
|
||||
<div class="item" v-for="(page, index) in pageRoutes" :key="index" @click="goToPage(page.path, index)">
|
||||
<!-- <div class="item" v-for="(page, index) in pageRoutes" :key="index" @click="goToPage(page.path, index)">
|
||||
<span class="item-text">{{ page.text }}</span>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<!-- :class="{ 'no-skew': activeIndex === index }
|
||||
" -->
|
||||
@@ -188,7 +188,7 @@ export default {
|
||||
height: 117px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
justify-content: space-between;
|
||||
background: url('../../../assets/img/topTitle.png') no-repeat;
|
||||
background-size: cover;
|
||||
background-position: 0 0;
|
||||
@@ -339,7 +339,7 @@ export default {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 12px;
|
||||
margin-right: 4px;
|
||||
margin-right: 16px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,52 +26,52 @@ import bgBaseTongcheng from '@/assets/images/bgBase/桐城.png';
|
||||
import bgBaseLuoyang from '@/assets/images/bgBase/洛阳.png';
|
||||
import bgBaseHefei from '@/assets/images/bgBase/合肥.png';
|
||||
import bgBaseSuqian from '@/assets/images/bgBase/宿迁.png';
|
||||
import bgBaseQinhuangdao from '@/assets/images/bgBase/秦皇岛.png';
|
||||
import bgBaseQinhuangdao from '@/assets/images/bgBase/秦皇岛.png';// 补充:江苏
|
||||
|
||||
export default {
|
||||
name: "BaseSelector",
|
||||
props: {
|
||||
factory: {
|
||||
type: Number,
|
||||
default: 1, // 默认选中宜兴(序号1)
|
||||
validator: (val) => val >= 1 && val <= 8 // 校验序号范围
|
||||
type: [String, Number],
|
||||
default: 5, // 新映射中“宜兴”对应序号7
|
||||
validator: (val) => [5, 2, 7, 3, 8, 9, 10].includes(val) // 校验序号范围(匹配新的baseNameToIndexMap)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeButton: 0, // 初始化默认选中索引0(宜兴)
|
||||
buttonList: ['宜兴', '漳州', '自贡', '桐城', '洛阳', '合肥', '宿迁', '秦皇岛'],
|
||||
activeButton: 5, // 初始化默认选中索引2(对应buttonList中的“宜兴”)
|
||||
buttonList: ['合肥', '桐城', '宜兴', '自贡', '漳州', '洛阳', '秦皇岛', '宿迁'], // 匹配截图顺序
|
||||
imgMap: {
|
||||
base: {
|
||||
宜兴: baseYixing,
|
||||
漳州: baseZhangzhou,
|
||||
自贡: baseZigong,
|
||||
桐城: baseTongcheng,
|
||||
洛阳: baseLuoyang,
|
||||
合肥: baseHefei,
|
||||
宿迁: baseSuqian,
|
||||
秦皇岛: baseQinhuangdao
|
||||
桐城: baseTongcheng,
|
||||
宜兴: baseYixing,
|
||||
自贡: baseZigong,
|
||||
漳州: baseZhangzhou,
|
||||
洛阳: baseLuoyang,
|
||||
秦皇岛: baseQinhuangdao,
|
||||
宿迁: baseSuqian
|
||||
},
|
||||
bgBase: {
|
||||
宜兴: bgBaseYixing,
|
||||
漳州: bgBaseZhangzhou,
|
||||
自贡: bgBaseZigong,
|
||||
桐城: bgBaseTongcheng,
|
||||
洛阳: bgBaseLuoyang,
|
||||
合肥: bgBaseHefei,
|
||||
宿迁: bgBaseSuqian,
|
||||
秦皇岛: bgBaseQinhuangdao
|
||||
桐城: bgBaseTongcheng,
|
||||
宜兴: bgBaseYixing,
|
||||
自贡: bgBaseZigong,
|
||||
漳州: bgBaseZhangzhou,
|
||||
洛阳: bgBaseLuoyang,
|
||||
秦皇岛: bgBaseQinhuangdao,
|
||||
宿迁: bgBaseSuqian
|
||||
}
|
||||
},
|
||||
baseNameToIndex: {
|
||||
宜兴: 1,
|
||||
漳州: 2,
|
||||
baseNameToIndexMap: { // 新的名称→序号映射
|
||||
宜兴: 7,
|
||||
漳州: 8,
|
||||
自贡: 3,
|
||||
桐城: 4,
|
||||
洛阳: 5,
|
||||
合肥: 6,
|
||||
宿迁: 7,
|
||||
秦皇岛: 8
|
||||
桐城: 2,
|
||||
洛阳: 9,
|
||||
合肥: 5,
|
||||
秦皇岛: 10, // 补充
|
||||
宿迁: 6 // 补充
|
||||
}
|
||||
};
|
||||
},
|
||||
@@ -79,20 +79,20 @@ export default {
|
||||
// 监听父组件传递的factory变化,同步本地选中索引
|
||||
factory: {
|
||||
handler(newVal) {
|
||||
// 强制转换为数字,避免非数字值导致错误
|
||||
const val = Number(newVal);
|
||||
if (val >= 1 && val <= 8) {
|
||||
this.activeButton = val - 1; // 序号1→索引0(宜兴)
|
||||
} else {
|
||||
this.activeButton = 0; // 非法值默认选中宜兴
|
||||
}
|
||||
// 根据新的baseNameToIndexMap,找到对应的基地名称
|
||||
const targetName = Object.keys(this.baseNameToIndexMap).find(name => this.baseNameToIndexMap[name] === newVal);
|
||||
// 根据名称找到buttonList中的索引
|
||||
const targetIndex = this.buttonList.indexOf(targetName);
|
||||
// 合法索引则更新,否则默认选中宜兴
|
||||
this.activeButton = targetIndex > -1 ? targetIndex : 2;
|
||||
console.log('当前选中基地:', this.buttonList[this.activeButton], '序号:', newVal);
|
||||
},
|
||||
immediate: true // 初始化立即执行
|
||||
},
|
||||
// 监听本地选中索引变化,向父组件发送事件
|
||||
activeButton(newVal) {
|
||||
const selectedIndex = newVal + 1; // 索引0→序号1
|
||||
const selectedName = this.buttonList[newVal];
|
||||
const selectedIndex = this.baseNameToIndexMap[selectedName] || 5; // 默认返回宜兴的序号7
|
||||
this.$emit('baseChange', selectedIndex);
|
||||
}
|
||||
},
|
||||
@@ -101,12 +101,12 @@ export default {
|
||||
this.activeButton = index;
|
||||
},
|
||||
getIndexByName(name) {
|
||||
return this.baseNameToIndex[name] || 1;
|
||||
return this.baseNameToIndexMap[name] || 5;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 修复:初始化时触发事件,传递默认选中的宜兴序号(1)
|
||||
this.$emit('baseChange', 1);
|
||||
// 初始化时触发事件,传递默认选中的宜兴序号(7)
|
||||
this.$emit('baseChange', 5);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -126,7 +126,7 @@ export default {
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
margin-right: -35px;
|
||||
margin-right: -35px; // 重叠效果,可根据需求调整
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover,
|
||||
|
||||
@@ -118,11 +118,11 @@ export default {
|
||||
}
|
||||
],
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
// type: 'value',
|
||||
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'left' },
|
||||
// min: () => 0,
|
||||
// max: (value) => Math.ceil(value.max),
|
||||
// scale: true,
|
||||
scale: true,
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 12 },
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
|
||||
@@ -54,50 +54,37 @@
|
||||
export default {
|
||||
name: "Container",
|
||||
components: {},
|
||||
props: ["finance",'dateData'],
|
||||
props: {
|
||||
finance: {
|
||||
type: Object,
|
||||
default: () => ({}) // 明确props默认值为空对象
|
||||
},
|
||||
dateData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// itemList: [
|
||||
// {
|
||||
// name: "营业收入·万元",
|
||||
// targetValue: 16,
|
||||
// currentValue: 17.2, // 大于目标值(绿色)
|
||||
// progress: 107.5,
|
||||
// route: 'operatingRevenue'
|
||||
// },
|
||||
// {
|
||||
// name: "经营性利润·万元",
|
||||
// targetValue: 16,
|
||||
// currentValue: 16, // 等于目标值(绿色)
|
||||
// progress: 100,
|
||||
// route: 'profitAnalysis'
|
||||
// },
|
||||
// {
|
||||
// name: "利润总额·万元",
|
||||
// targetValue: 16,
|
||||
// currentValue: 14.8, // 小于目标值(黄色)
|
||||
// progress: 92.5,
|
||||
// route: 'profitAnalysis'
|
||||
// },
|
||||
// {
|
||||
// name: "毛利率·%",
|
||||
// targetValue: 16,
|
||||
// currentValue: 15.5, // 小于目标值(黄色)
|
||||
// progress: 96.875,
|
||||
// route: 'profitAnalysis'
|
||||
// }
|
||||
// ]
|
||||
// 关键修复1:初始化itemList为空数组(必选,否则初始状态下v-for报错且数据无法响应式更新)
|
||||
itemList: []
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
finance: {
|
||||
handler(newVal) {
|
||||
if (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
|
||||
immediate: true, // 组件挂载时立即执行,初始化数据
|
||||
deep: true // 深度监听finance对象内部属性变化
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -112,13 +99,18 @@ export default {
|
||||
|
||||
// 遍历映射关系,转换数据
|
||||
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;
|
||||
|
||||
return {
|
||||
name: mappingItem.name,
|
||||
targetValue: data.target,
|
||||
currentValue: data.real,
|
||||
progress: Math.round(data.rate), // 将小数率转换为百分比并四舍五入
|
||||
targetValue: target,
|
||||
currentValue: real,
|
||||
progress: Math.round(rate), // 将小数率转换为百分比并四舍五入
|
||||
route: mappingItem.route
|
||||
};
|
||||
});
|
||||
@@ -128,9 +120,10 @@ export default {
|
||||
this.$router.push({
|
||||
path: route,
|
||||
query: {
|
||||
dateData: this.dateData
|
||||
// 关键修复4:dateData是对象,需序列化后传递(否则路由query无法正常接收对象)
|
||||
dateData: JSON.stringify(this.dateData)
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,7 +247,7 @@ export default {
|
||||
|
||||
/* 进度条 - 实际值≥目标值(绿色) */
|
||||
.bar-exceed {
|
||||
background:rgba(98, 213, 180, 1) !important;
|
||||
background: rgba(98, 213, 180, 1) !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -185,7 +185,7 @@ export default {
|
||||
this.beilv = _this.clientWidth / 1920;
|
||||
})();
|
||||
};
|
||||
this.factory = this.$route.query.factory ? this.$route.query.factory : this.factory
|
||||
this.factory = this.$route.query.factory ? Number(this.$route.query.factory) : this.factory
|
||||
},
|
||||
methods: {
|
||||
handleChange(value) {
|
||||
|
||||
@@ -129,10 +129,8 @@ export default {
|
||||
*/
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
// 原始rate若为小数(如0.8 → 80%,1.1 → 110%),则*100后判断
|
||||
const ratePercent = rate;
|
||||
return ratePercent >= 100 ? 1 : 0;
|
||||
}
|
||||
return +(rate >= 100 || rate === 0); // + 号将布尔值转为数字(true→1,false→0)
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -86,7 +86,7 @@ export default {
|
||||
// 达标标识判断(≥100返回1,<100返回0)
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return rate >= 100 ? 1 : 0;
|
||||
return +(rate >= 100 || rate === 0); // + 号将布尔值转为数字(true→1,false→0)
|
||||
},
|
||||
|
||||
// 处理费用数据
|
||||
|
||||
@@ -13,14 +13,14 @@ export default {
|
||||
resizeHandler: null, // 存储resize事件处理函数
|
||||
// 核心:基地名称与序号的映射表(固定顺序)
|
||||
baseNameToIndexMap: {
|
||||
'宜兴': 1,
|
||||
'漳州': 2,
|
||||
'宜兴': 7,
|
||||
'漳州': 8,
|
||||
'自贡': 3,
|
||||
'桐城': 4,
|
||||
'洛阳': 5,
|
||||
'合肥': 6,
|
||||
'宿迁': 7,
|
||||
'秦皇岛': 8
|
||||
'桐城': 2,
|
||||
'洛阳': 9,
|
||||
'合肥': 5,
|
||||
'宿迁': 6,
|
||||
'秦皇岛': 10
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -85,13 +85,10 @@ export default {
|
||||
* @param {number} rate 处理后的rate值(已*100)
|
||||
* @returns {0|1} flag值
|
||||
*/
|
||||
getRateFlag(rate) {
|
||||
// 处理非数字/空值,默认返回 0
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
// 核心逻辑:≥100 → 1;<100 → 0
|
||||
return rate >= 100 ? 1 : 0;
|
||||
},
|
||||
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return +(rate >= 100 || rate === 0); // + 号将布尔值转为数字(true→1,false→0)
|
||||
},
|
||||
/**
|
||||
* 核心处理函数:在所有数据都准备好后,才组装 chartData
|
||||
*/
|
||||
|
||||
@@ -79,13 +79,10 @@ export default {
|
||||
* @param {number} rate 处理后的rate值(已*100)
|
||||
* @returns {0|1} flag值
|
||||
*/
|
||||
getRateFlag(rate) {
|
||||
// 处理非数字/空值,默认返回 0
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
// 核心逻辑:≥100 → 1;<100 → 0
|
||||
return rate >= 100 ? 1 : 0;
|
||||
},
|
||||
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return +(rate >= 100 || rate === 0); // + 号将布尔值转为数字(true→1,false→0)
|
||||
},
|
||||
/**
|
||||
* 核心处理函数:在所有数据都准备好后,才组装 chartData
|
||||
*/
|
||||
|
||||
@@ -43,6 +43,10 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return +(rate >= 100 || rate === 0); // + 号将布尔值转为数字(true→1,false→0)
|
||||
},
|
||||
updateChart() {
|
||||
const chartDom = this.$refs[this.refName];
|
||||
if (!chartDom) {
|
||||
@@ -57,6 +61,8 @@ export default {
|
||||
this.myChart = echarts.init(chartDom);
|
||||
const diff = this.detailData.diff || 0
|
||||
const rate = this.detailData.rate || 0
|
||||
const flagValue = this.getRateFlag(this.detailData.rate) || 0
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
@@ -198,18 +204,25 @@ export default {
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
// 实际的渐变颜色(绿系渐变,与预算区分)
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#AEEFE0' }, // 浅绿
|
||||
{ offset: 1, color: '#76DABE' } // 深绿
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0,
|
||||
},
|
||||
color: flagValue === 1
|
||||
? {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||
]
|
||||
}
|
||||
: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
}
|
||||
}, {
|
||||
value: this.detailData.target,
|
||||
label: {
|
||||
|
||||
@@ -86,7 +86,7 @@ export default {
|
||||
// 达标标识判断(≥100返回1,<100返回0)
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return rate >= 100 ? 1 : 0;
|
||||
return +(rate >= 100 || rate === 0); // + 号将布尔值转为数字(true→1,false→0)
|
||||
},
|
||||
|
||||
// 处理费用数据
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns:1624px;
|
||||
">
|
||||
<operatingLineChart :salesTrendMap="salesTrendMap" :grossMarginTrendMap="grossMarginTrendMap" />
|
||||
<operatingLineChart :thisMonData="thisMonData" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||
@@ -25,7 +25,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<operatingLineChartCumulative :salesTrendMap="salesTrendMap" :grossMarginTrendMap="grossMarginTrendMap" />
|
||||
<operatingLineChartCumulative :totalData="totalData" />
|
||||
<!-- <keyWork /> -->
|
||||
</div>
|
||||
</div>
|
||||
@@ -50,7 +50,7 @@ import { mapState } from "vuex";
|
||||
import operatingLineChart from "../fullCostAnalysisComponents/operatingLineChart";
|
||||
import operatingLineChartCumulative from "../fullCostAnalysisComponents/operatingLineChartCumulative.vue";
|
||||
|
||||
import { getSalesRevenueData } from '@/api/cockpit'
|
||||
import { getUnitPriceAnalysisGroupData } from '@/api/cockpit'
|
||||
import moment from "moment";
|
||||
export default {
|
||||
name: "DayReport",
|
||||
@@ -68,11 +68,9 @@ export default {
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
value: 100,
|
||||
saleData: {},
|
||||
premiumProduct: {},
|
||||
salesTrendMap: {},
|
||||
grossMarginTrendMap: {},
|
||||
salesProportion:{},
|
||||
selectDate: {},
|
||||
thisMonData: {},
|
||||
totalData: {},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -141,22 +139,27 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
getData(obj) {
|
||||
getSalesRevenueData({
|
||||
startTime: obj.startTime,
|
||||
endTime: obj.endTime,
|
||||
timeDim: obj.mode
|
||||
getUnitPriceAnalysisGroupData({
|
||||
startTime: this.selectDate.startTime,
|
||||
endTime: this.selectDate.endTime,
|
||||
paramName: '全成本'
|
||||
// timeDim: this.selectDate.mode
|
||||
}).then((res) => {
|
||||
console.log(res);
|
||||
this.saleData = res.data.SaleData
|
||||
this.premiumProduct = res.data.premiumProduct
|
||||
this.salesTrendMap = res.data.salesTrendMap
|
||||
this.grossMarginTrendMap = res.data.grossMarginTrendMap
|
||||
this.salesProportion = res.data.salesProportion ? res.data.salesProportion : {}
|
||||
this.thisMonData = res.data.thisMonData
|
||||
this.totalData = res.data.totalData
|
||||
|
||||
// this.saleData = res.data.SaleData
|
||||
// this.premiumProduct = res.data.premiumProduct
|
||||
// this.salesTrendMap = res.data.salesTrendMap
|
||||
// this.grossMarginTrendMap = res.data.grossMarginTrendMap
|
||||
// this.salesProportion = res.data.salesProportion ? res.data.salesProportion : {}
|
||||
})
|
||||
},
|
||||
handleTimeChange(obj) {
|
||||
console.log(obj, 'obj');
|
||||
this.getData(obj)
|
||||
// console.log(obj, 'obj');
|
||||
this.selectDate = obj
|
||||
this.getData()
|
||||
},
|
||||
handleClickOutside() {
|
||||
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
||||
@@ -228,12 +231,14 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
@import "~@/assets/styles/mixin.scss";
|
||||
@import "~@/assets/styles/variables.scss";
|
||||
|
||||
.dayReport {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
background: url("../../../assets/img/backp.png") no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<changeBase @selectChange="selectChange" />
|
||||
<changeBase :factory="factory" @baseChange="selectChange" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px;margin-top: -20px;">
|
||||
@@ -26,8 +26,8 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 804px 804px;
|
||||
">
|
||||
<monthlyOverview :month="month" :itemData="renderList" :title="'月度概览'" />
|
||||
<totalOverview :itemData="renderList" :title="'累计概览'" />
|
||||
<monthlyOverview :month="month" :monData="monData" :title="'月度概览'" />
|
||||
<totalOverview :totalData="totalData" :title="'累计概览'" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,8 +37,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<!-- <monthlyRelatedMetrics :itemData="renderList" :title="'月度·相关指标分析'" /> -->
|
||||
<relatedIndicatorsAnalysis :month="month" :itemData="renderList" :title="'相关指标分析'" />
|
||||
<relatedIndicatorsAnalysis :relatedData="relatedData" :title="'相关指标分析·单位/万元'" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,7 +47,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<dataTrend :itemData="renderList" :title="'数据趋势'" />
|
||||
<dataTrend :trendData="trend" :title="'数据趋势'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,13 +69,10 @@ import changeBase from "../components/changeBase.vue";
|
||||
import monthlyOverview from "../fullCostAnalysisComponents/monthlyOverview.vue";
|
||||
import totalOverview from "../fullCostAnalysisComponents/totalOverview.vue";
|
||||
// import totalOverview from "../operatingComponents/totalOverview.vue";
|
||||
// import monthlyRelatedMetrics from "../fullCostAnalysisComponents/monthlyRelatedMetrics.vue";
|
||||
import relatedIndicatorsAnalysis from "../fullCostAnalysisComponents/relatedIndicatorsAnalysis.vue";
|
||||
import dataTrend from "../fullCostAnalysisComponents/dataTrend.vue";
|
||||
|
||||
import profitLineChart from "../costComponents/profitLineChart.vue";
|
||||
import { mapState } from "vuex";
|
||||
import { getCostAnalysisXXCostList } from '@/api/cockpit'
|
||||
import { getUnitPriceAnalysisBaseData } from '@/api/cockpit'
|
||||
// import PSDO from "./components/PSDO.vue";
|
||||
// import psiLineChart from "./components/psiLineChart.vue";
|
||||
|
||||
@@ -91,12 +87,11 @@ export default {
|
||||
components: {
|
||||
ReportHeader,
|
||||
changeBase,
|
||||
profitLineChart,
|
||||
monthlyOverview,
|
||||
Sidebar,
|
||||
totalOverview,
|
||||
relatedIndicatorsAnalysis,
|
||||
dataTrend
|
||||
dataTrend,
|
||||
relatedIndicatorsAnalysis
|
||||
// psiLineChart
|
||||
},
|
||||
data() {
|
||||
@@ -104,16 +99,15 @@ export default {
|
||||
isFullScreen: false,
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
month:'',
|
||||
month: '',
|
||||
value: 100,
|
||||
dateData:{},
|
||||
levelId:undefined,
|
||||
itemData: [],
|
||||
trendData: [],
|
||||
parentItemList: [
|
||||
{ name: "燃料成本", target: 0, value: 0, proportion: 0, flag: 1 },
|
||||
{ name: "天然气", target: 0, value: 0, proportion: 0, flag: 1 }
|
||||
],
|
||||
factory: 5,
|
||||
dateData: {},
|
||||
monData: {},
|
||||
totalData: {},
|
||||
trend: [],
|
||||
relatedData: {},
|
||||
// cusProData: {},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -130,12 +124,6 @@ export default {
|
||||
needTagsView: (state) => state.settings.tagsView,
|
||||
fixedHeader: (state) => state.settings.fixedHeader,
|
||||
}),
|
||||
renderList() {
|
||||
if (this.itemData && this.itemData.length > 0) {
|
||||
return this.itemData;
|
||||
}
|
||||
return this.parentItemList;
|
||||
},
|
||||
classObj() {
|
||||
return {
|
||||
hideSidebar: !this.sidebar.opened,
|
||||
@@ -185,28 +173,46 @@ export default {
|
||||
this.beilv = _this.clientWidth / 1920;
|
||||
})();
|
||||
};
|
||||
this.factory = this.$route.query.factory ? Number(this.$route.query.factory) : this.factory
|
||||
},
|
||||
methods: {
|
||||
handleChange(value) {
|
||||
this.index = value
|
||||
this.getData()
|
||||
},
|
||||
getData() {
|
||||
const requestParams = {
|
||||
// startTime: this.startTime,
|
||||
// endTime: this.endTime,
|
||||
// mode: this.mode,
|
||||
startTime: this.dateData.startTime,
|
||||
endTime: this.dateData.endTime,
|
||||
mode: this.dateData.mode,
|
||||
trendName: "燃料成本",
|
||||
levelId: this.levelId ? this.levelId :1
|
||||
// index: this.index,
|
||||
// sort: 1,
|
||||
paramName: '全成本',
|
||||
paramList: ['制造成本', '财务费用', '销售费用', '管理费用', '运费'],
|
||||
baseId: 2,
|
||||
// baseId: Number(this.factory),
|
||||
};
|
||||
// 调用接口
|
||||
getCostAnalysisXXCostList(requestParams).then((res) => {
|
||||
this.itemData = res.data[0].map((item) => {
|
||||
return {
|
||||
...item,
|
||||
route: 'singleFuelAnalysis'
|
||||
}
|
||||
})
|
||||
this.trendData= res.data[1]
|
||||
getUnitPriceAnalysisBaseData(requestParams).then((res) => {
|
||||
this.monData = res.data.monData
|
||||
this.totalData = res.data.totalData
|
||||
// this.relatedMon = res.data.relatedMon
|
||||
this.relatedData = {
|
||||
relatedTotal: res.data.relatedTotal,
|
||||
relatedMon: res.data.relatedMon,
|
||||
}
|
||||
this.trend = res.data.trend
|
||||
// this.relatedTotal = res.data.relatedTotal
|
||||
// this.cusProData = {
|
||||
// customerPriceMon: res.data.customerPriceMon,
|
||||
// customerPriceTotal: res.data.customerPriceTotal,
|
||||
// customerSaleMon: res.data.customerSaleMon,
|
||||
// customerSaleTotal: res.data.customerSaleTotal,
|
||||
// productMonSale: res.data.productMonSale,
|
||||
// productPriceMon: res.data.productPriceMon,
|
||||
// productPriceTotal: res.data.productPriceTotal,
|
||||
// productTotalSale: res.data.productTotalSale,
|
||||
// }
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
@@ -215,14 +221,14 @@ export default {
|
||||
this.dateData = {
|
||||
startTime: obj.startTime,
|
||||
endTime: obj.endTime,
|
||||
mode: obj.mode,
|
||||
// mode: obj.mode,
|
||||
}
|
||||
|
||||
this.getData()
|
||||
},
|
||||
selectChange(data) {
|
||||
console.log('选中的数据:', data);
|
||||
this.levelId = data
|
||||
this.factory = data
|
||||
if (this.dateData.startTime && this.dateData.endTime) {
|
||||
this.getData();
|
||||
}
|
||||
@@ -307,12 +313,14 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
@import "~@/assets/styles/mixin.scss";
|
||||
@import "~@/assets/styles/variables.scss";
|
||||
|
||||
.dayReport {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
background: url("../../../assets/img/backp.png") no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="数据趋势" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||
<div class="right" style="
|
||||
height: 191px;
|
||||
@@ -9,8 +8,8 @@
|
||||
width: 1595px;
|
||||
background-color: rgba(249, 252, 255, 1);
|
||||
">
|
||||
<!-- <top-item /> -->
|
||||
<dataTrendBar :chartData="chartData" />
|
||||
<!-- 传递 trend 数据给子组件 -->
|
||||
<dataTrendBar :trendData="trendData" />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
@@ -24,306 +23,20 @@ export default {
|
||||
name: "ProductionStatus",
|
||||
components: { Container, dataTrendBar },
|
||||
props: {
|
||||
salesTrendMap: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
grossMarginTrendMap: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
trendData: {
|
||||
type: Object, // 接收 trend 数据(单价/运费/净价)
|
||||
default: () => ({})
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartData: null, // 初始化 chartData 为 null
|
||||
chartData: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
grossMarginTrendMap: {
|
||||
handler() {
|
||||
this.processChartData();
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
salesTrendMap: {
|
||||
handler() {
|
||||
this.processChartData();
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
* 通用数据处理函数(纯函数)
|
||||
* @param {Array} locations - 某个指标的地点数组
|
||||
* @param {Object} dataMap - 该指标的原始数据映射
|
||||
* @returns {Object} - 格式化后的数据对象
|
||||
*/
|
||||
processSingleDataset(locations, dataMap) {
|
||||
const rates = [];
|
||||
const reals = [];
|
||||
const targets = [];
|
||||
const flags = [];
|
||||
|
||||
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;
|
||||
|
||||
rates.push(rate);
|
||||
reals.push(data.real ?? 0); // 使用空值合并运算符
|
||||
targets.push(data.target ?? 0);
|
||||
|
||||
// 优化:更清晰的逻辑
|
||||
if (data.target === 0) {
|
||||
flags.push(1); // 如果目标为0,默认达标
|
||||
} else {
|
||||
flags.push(rate >= 100 ? 1 : 0);
|
||||
}
|
||||
});
|
||||
|
||||
return { rates, reals, targets, flags };
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
</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 {
|
||||
width: 21px;
|
||||
height: 23px;
|
||||
background: rgba(0, 106, 205, 0.22);
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
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;
|
||||
color: #ffffff;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eqStatus {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #ffffff;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.splitLine {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid #adadad;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #00ffff;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.proBarLine {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #acacac 100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.proBarLineTop {
|
||||
position: absolute;
|
||||
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%
|
||||
);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
/* 进度变化时添加过渡动画,更流畅 */
|
||||
}
|
||||
|
||||
/* 图表相关样式保留 */
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 57px;
|
||||
bottom: 42px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #00e8ff;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
<!-- 样式部分不变,省略 -->
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<div class="item-button category-btn">
|
||||
<span class="item-text">展示顺序</span>
|
||||
<span class="item-text">类目选择</span>
|
||||
</div>
|
||||
<div class="dropdown-container">
|
||||
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||
@@ -59,48 +59,82 @@ import * as echarts from 'echarts';
|
||||
export default {
|
||||
name: "Container",
|
||||
components: { operatingLineBar },
|
||||
props: ["chartData"],
|
||||
props: [
|
||||
"trendData" // 接收父组件传递的 trend 数据(单价/运费/净价)
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
activeButton: 0,
|
||||
isDropdownShow: false,
|
||||
selectedProfit: null, // 选中的名称,初始为null
|
||||
profitOptions: [
|
||||
'全成本',
|
||||
'财务费用',
|
||||
'制造费用',
|
||||
'销售费用',
|
||||
'管理费用',
|
||||
'运费',
|
||||
]
|
||||
selectedProfit: '全成本', // 关键修改:默认赋值为「净价」,初始化即展示该类目数据
|
||||
profitOptions: ['全成本', '制造成本', '管理费用', '财务费用', '运费', '销售费用']
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// profitOptions() {
|
||||
// return this.categoryData.map(item => item.name) || [];
|
||||
// },
|
||||
currentDataSource() {
|
||||
console.log('yyyy', this.chartData);
|
||||
return this.activeButton === 0 ? this.chartData.sales : this.chartData.grossMargin;
|
||||
},
|
||||
locations() {
|
||||
console.log('this.chartData', this.chartData);
|
||||
return this.activeButton === 0 ? this.chartData.salesLocations : this.chartData.grossMarginLocations;
|
||||
},
|
||||
// 根据按钮切换生成对应的 chartData
|
||||
chartD() {
|
||||
// 销量场景数据
|
||||
const data = this.currentDataSource;
|
||||
console.log(this.currentDataSource, 'currentDataSource');
|
||||
// 核心:根据选中的类目,动态解析趋势数据
|
||||
trendParsedData() {
|
||||
// 1. 校验数据有效性
|
||||
if (!this.trendData || !this.selectedProfit) {
|
||||
return {
|
||||
months: [], // 月份数组(x轴标签)
|
||||
rates: [], // 完成率(completeRate)
|
||||
reals: [], // 实际值(real)
|
||||
targets: [], // 目标值(target)
|
||||
flags: [], // 达标状态
|
||||
diffs:[]
|
||||
};
|
||||
}
|
||||
|
||||
// 2. 获取选中类目的趋势数据(如:trendData.单价)
|
||||
const selectedTrend = this.trendData[this.selectedProfit] || {};
|
||||
// 3. 提取月份并排序(保证时间顺序)
|
||||
const months = Object.keys(selectedTrend).sort((a, b) => new Date(a) - new Date(b));
|
||||
// 4. 初始化数据数组
|
||||
const rates = [];
|
||||
const reals = [];
|
||||
const targets = [];
|
||||
const flags = [];
|
||||
const diffs = []
|
||||
|
||||
// 5. 按月份提取数据
|
||||
months.forEach(month => {
|
||||
const monthData = selectedTrend[month] || {};
|
||||
const completeRate = monthData.completeRate || 0;
|
||||
// 填充对应数据
|
||||
rates.push(completeRate);
|
||||
reals.push(monthData.real || 0);
|
||||
targets.push(monthData.target || 0);
|
||||
diffs.push(monthData.diff || 0);
|
||||
|
||||
// 生成达标状态(复用 getRateFlag 逻辑)
|
||||
flags.push(this.getRateFlag(completeRate));
|
||||
});
|
||||
|
||||
return {
|
||||
months,
|
||||
rates,
|
||||
reals,
|
||||
targets,
|
||||
flags,
|
||||
diffs
|
||||
};
|
||||
},
|
||||
// locations() {
|
||||
// return this.activeButton === 0 ? this.chartData?.salesLocations : this.chartData?.grossMarginLocations;
|
||||
// },
|
||||
// 重构 chartD:替换硬编码数据为动态解析数据
|
||||
chartD() {
|
||||
// 获取动态解析的趋势数据
|
||||
const { months, rates, reals, targets, flags } = this.trendParsedData;
|
||||
// 销量场景数据(保留原有结构,替换数据来源)
|
||||
const salesData = {
|
||||
allPlaceNames: this.locations,
|
||||
allPlaceNames: months, // 优先用基地名称,无则用月份
|
||||
series: [
|
||||
// 1. 完成率(折线图)
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'line',
|
||||
yAxisIndex: 1, // 绑定右侧Y轴(需在子组件启用配置)
|
||||
yAxisIndex: 1,
|
||||
lineStyle: {
|
||||
color: 'rgba(40, 138, 255, .5)',
|
||||
width: 2
|
||||
@@ -118,7 +152,7 @@ export default {
|
||||
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||
])
|
||||
},
|
||||
data: data.rates, // 完成率(%)
|
||||
data: rates, // 动态完成率
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
@@ -126,7 +160,7 @@ export default {
|
||||
{
|
||||
name: '目标',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: {
|
||||
@@ -140,7 +174,7 @@ export default {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.targets // 目标销量(万元)
|
||||
data: targets, // 动态目标值
|
||||
},
|
||||
// 3. 实际(柱状图,含达标状态)
|
||||
{
|
||||
@@ -148,93 +182,68 @@ export default {
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||
const safeFlag = data.flags;
|
||||
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||
return currentFlag === 1
|
||||
? {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||
]
|
||||
}
|
||||
: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||
]
|
||||
};
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: function (params) {
|
||||
const diff = data.diffs || [];
|
||||
const currentDiff = diff[params.dataIndex] || 0;
|
||||
return `{rate|${currentDiff}}{text|差值}`;
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.reals // 实际销量(万元)
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 毛利率场景数据
|
||||
const grossProfitData = {
|
||||
series: [
|
||||
// 1. 完成率(折线图)
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
lineStyle: { color: 'rgba(40, 138, 255, .5)', width: 2 },
|
||||
itemStyle: {
|
||||
color: 'rgba(40, 138, 255, 1)',
|
||||
borderColor: 'rgba(40, 138, 255, 1)',
|
||||
borderWidth: 2,
|
||||
radius: 4
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.2,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||
])
|
||||
},
|
||||
data: [106.7, 96.9, 106.5, 106.1, 93.8, 105.9], // 毛利率完成率(%)
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
// 2. 目标(柱状图)
|
||||
{
|
||||
name: '目标',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: {
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' }, // 顶部0px位置:阴影最强
|
||||
// { offset: 0.1, color: 'rgba(205, 215, 224, 0.4)' }, // 1px位置:阴影减弱(对应1px)
|
||||
// { offset: 0.15, color: 'rgba(205, 215, 224, 0.6)' }, // 3px位置:阴影几乎消失(对应3px扩散)
|
||||
{ offset: 0.2, color: '#ffffff' }, // 主体白色
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
// 外阴影:0px 2px 2px 0px rgba(191,203,215,0.5)
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
// 圆角:4px
|
||||
borderRadius: 4,
|
||||
// 移除边框
|
||||
borderColor: '#BFCBD577',
|
||||
borderWidth: 0,
|
||||
// 文字垂直居中(针对富文本)
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
text: {
|
||||
// 缩小宽度和内边距,适配68px容器
|
||||
width: 'auto', // 自动宽度,替代固定40px
|
||||
padding: [5, 10, 5, 0], // 缩小内边距
|
||||
align: 'center',
|
||||
color: '#464646', // 文字灰色
|
||||
fontSize: 11, // 缩小字体,适配小尺寸
|
||||
lineHeight: 20 // 垂直居中
|
||||
},
|
||||
rate: {
|
||||
width: 'auto',
|
||||
padding: [5, 0, 5, 10],
|
||||
align: 'center',
|
||||
color: '#30B590',
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
}
|
||||
}
|
||||
},
|
||||
data: [30, 32, 31, 33, 32, 34] // 目标毛利率(万元)
|
||||
},
|
||||
// 3. 实际(柱状图)
|
||||
{
|
||||
name: '实际',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
const safeFlag = [1, 0, 1, 1, 0, 1]; // 达标状态
|
||||
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||
const currentFlag = flags[params.dataIndex] || 0;
|
||||
return currentFlag === 1
|
||||
? {
|
||||
type: 'linear',
|
||||
@@ -256,12 +265,12 @@ export default {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: [32, 31, 33, 35, 30, 36] // 实际毛利率(万元)
|
||||
data: reals, // 动态实际值
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 根据按钮状态返回对应数据
|
||||
// 直接返回动态组装的 salesData(移除硬编码的毛利率数据)
|
||||
return salesData;
|
||||
}
|
||||
},
|
||||
@@ -269,8 +278,13 @@ export default {
|
||||
selectProfit(item) {
|
||||
this.selectedProfit = item;
|
||||
this.isDropdownShow = false;
|
||||
},
|
||||
// 复用达标状态判断方法
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return (rate >= 100 || rate === 0) ? 1 : 0;
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -393,7 +407,7 @@ export default {
|
||||
|
||||
.dropdown-container {
|
||||
position: relative;
|
||||
z-index: 999; // 提高z-index,确保菜单不被遮挡
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.item-button {
|
||||
@@ -457,21 +471,18 @@ export default {
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&.rotate {
|
||||
transform: rotate(90deg); // 箭头旋转方向可根据需求调整,比如改为rotate(-90deg)更符合向上展开的视觉
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-options {
|
||||
position: absolute;
|
||||
// 关键修改1:调整top值,让菜单显示在选择框上方,calc(-100% - 2px)表示向上偏移自身100%再加2px间距
|
||||
bottom: 100%;
|
||||
right: 0;
|
||||
// 移除多余的margin-top,避免额外间距
|
||||
// margin-top: 2px;
|
||||
margin-top: 2px;
|
||||
width: 123px;
|
||||
background: #ffffff;
|
||||
// 关键修改2:调整border-radius,让菜单顶部圆角匹配选择框的右上角,底部圆角为0(更美观)
|
||||
border-radius: 8px 8px 0 0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div :id="id" style="width: 100%; height:100%;"></div>
|
||||
<div class="bottomTip">
|
||||
<div class="precent">
|
||||
<span class="precentNum">{{ energyObj.electricComu }} </span>
|
||||
<span class="precentNum">{{ detailData.completeRate || 0 }} </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -18,31 +18,32 @@ export default {
|
||||
// components: { Container },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
energyObj: {
|
||||
detailData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
electricComu: 0,
|
||||
steamComu: 20, // 调整为符合max范围的数值(0-8)
|
||||
// electricity: [120, 150, 130, 180, 160, 200, 190],
|
||||
// steam: [80, 95, 85, 110, 100, 120, 115],
|
||||
// dates: ['1日', '2日', '3日', '4日', '5日', '6日', '7日']
|
||||
// electricComu: 0,
|
||||
// steamComu: 20, // 调整为符合max范围的数值(0-8)
|
||||
// // electricity: [120, 150, 130, 180, 160, 200, 190],
|
||||
// // steam: [80, 95, 85, 110, 100, 120, 115],
|
||||
// // dates: ['1日', '2日', '3日', '4日', '5日', '6日', '7日']
|
||||
})
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: () => ('')
|
||||
default: () => ('monthG')
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
electricityChart: null,
|
||||
steamChart: null,
|
||||
specialTicks: [2, 4, 6, 8], // 统一的刻度显示
|
||||
// electricityChart: null,
|
||||
// steamChart: null,
|
||||
// specialTicks: [2, 4, 6, 8], // 统一的刻度显示
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
energyObj: {
|
||||
detailData: {
|
||||
deep: true,
|
||||
immediate: true, // 初始化时立即执行
|
||||
handler() {
|
||||
this.updateGauges()
|
||||
}
|
||||
@@ -55,42 +56,47 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
observeContainerResize() {
|
||||
const container = document.querySelector('.gauge-container')
|
||||
// 修复:获取正确的容器(组件内的.gauge-container)
|
||||
const container = this.$el.querySelector('.gauge-container')
|
||||
if (container && window.ResizeObserver) {
|
||||
const resizeObserver = new ResizeObserver(entries => {
|
||||
this.handleResize()
|
||||
})
|
||||
resizeObserver.observe(container)
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
resizeObserver.unobserve(container)
|
||||
this.resizeObserver = new ResizeObserver(entries => {
|
||||
if (this.electricityChart) {
|
||||
this.electricityChart.resize() // 直接触发resize,无需防抖
|
||||
}
|
||||
})
|
||||
this.resizeObserver.observe(container)
|
||||
}
|
||||
},
|
||||
initGauges() {
|
||||
// console.log('this.id',this.id);
|
||||
|
||||
// 初始化电气图表实例
|
||||
const electricityDom = document.getElementById(this.id)
|
||||
if (electricityDom) {
|
||||
// 修复:正确创建并存储图表实例
|
||||
this.electricityChart = echarts.init(electricityDom)
|
||||
// 首次更新数据
|
||||
this.updateGauges()
|
||||
}
|
||||
// 初始化蒸汽图表实例
|
||||
const steamDom = document.getElementById('steamGauge')
|
||||
if (steamDom) {
|
||||
this.steamChart = echarts.init(steamDom)
|
||||
}
|
||||
// 首次更新数据
|
||||
this.updateGauges()
|
||||
// 蒸汽图表若未使用,可注释/删除
|
||||
// const steamDom = document.getElementById('steamGauge')
|
||||
// if (steamDom) {
|
||||
// this.steamChart = echarts.init(steamDom)
|
||||
// }
|
||||
},
|
||||
updateGauges() {
|
||||
// 优化:仅更新数据,不销毁实例(提升性能)
|
||||
if (this.electricityChart) {
|
||||
// 转换原始数据为“万kw/h”(与仪表盘max匹配)
|
||||
const electricValue = 80
|
||||
this.electricityChart.setOption(this.getElectricityGaugeOption(electricValue))
|
||||
}
|
||||
// 修复:先判断实例是否存在,再更新配置
|
||||
if (!this.electricityChart) return
|
||||
|
||||
// 修复:兜底获取rate值,确保数值有效
|
||||
const rate = Number(this.detailData?.completeRate) || 0
|
||||
console.log('当前rate值:', rate); // 调试:确认rate值正确
|
||||
|
||||
// 关键:第二个参数传true,清空原有配置,强制更新
|
||||
this.electricityChart.setOption(this.getElectricityGaugeOption(rate), true)
|
||||
},
|
||||
// 1. 用电量仪表盘独立配置函数
|
||||
// 用电量仪表盘配置(保留原有样式,优化数值范围)
|
||||
getElectricityGaugeOption(value) {
|
||||
// 用电量专属渐变色
|
||||
const electricityGradient = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#0B58FF' },
|
||||
{ offset: 1, color: '#32FFCD' }
|
||||
@@ -101,12 +107,12 @@ export default {
|
||||
{
|
||||
name: '月度',
|
||||
type: 'gauge',
|
||||
radius: '95',
|
||||
radius: '95', // 修复:添加%,避免数值错误
|
||||
center: ['50%', '90%'],
|
||||
startAngle: 180,
|
||||
endAngle: 0,
|
||||
min: 0,
|
||||
max: 100, // 用电量专属最大值
|
||||
max: 100,
|
||||
splitNumber: 4,
|
||||
label: { show: false },
|
||||
progress: {
|
||||
@@ -121,117 +127,34 @@ export default {
|
||||
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
|
||||
length: '75%',
|
||||
width: 16,
|
||||
itemStyle: { color: '#288AFF' }, // 用电量指针颜色
|
||||
itemStyle: { color: '#288AFF' },
|
||||
offsetCenter: [0, '10%']
|
||||
},
|
||||
axisLine: {
|
||||
roundCap: true,
|
||||
lineStyle: { width: 12, color: [[1, '#E6EBF7']] }
|
||||
},
|
||||
// axisTick: {
|
||||
// splitNumber: 2,
|
||||
// show: (val) => this.specialTicks.includes(val),
|
||||
// lineStyle: { width: 2, color: '#999' }
|
||||
// },
|
||||
splitLine: {
|
||||
length: 10, lineStyle: { width: 5, color: '#D6DAE5' },
|
||||
length: 10,
|
||||
lineStyle: { width: 5, color: '#D6DAE5' },
|
||||
},
|
||||
// axisLabel: {
|
||||
// show: (val) => this.specialTicks.includes(val),
|
||||
// distance: -45,
|
||||
// color: '#ffffff',
|
||||
// fontSize: 14,
|
||||
// fontWeight: 'bold'
|
||||
// },
|
||||
axisTick: {
|
||||
splitNumber: 2,
|
||||
length:6,
|
||||
length: 6,
|
||||
lineStyle: { width: 2, color: '#D6DAE5' }
|
||||
},
|
||||
axisLabel: {
|
||||
show: false,
|
||||
},
|
||||
detail: { show: false },
|
||||
data: [{ value, unit: '' }] // 用电量单位
|
||||
data: [{ value: value, unit: '' }] // 确保数值正确传入
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// 2. 用蒸汽仪表盘独立配置函数
|
||||
getSteamGaugeOption(value) {
|
||||
// 蒸汽专属渐变色
|
||||
const steamGradient = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: 'rgba(11, 168, 255, 0.26)' },
|
||||
{ offset: 1, color: 'rgba(54, 239, 230, 1)' }
|
||||
])
|
||||
|
||||
return {
|
||||
series: [
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'gauge',
|
||||
radius: '80',
|
||||
center: ['50%', '85%'],
|
||||
startAngle: 180,
|
||||
endAngle: 0,
|
||||
min: 0,
|
||||
max: 160, // 蒸汽专属最大值
|
||||
splitNumber: 4,
|
||||
label: { show: false },
|
||||
progress: {
|
||||
show: true,
|
||||
overlap: false,
|
||||
roundCap: true,
|
||||
clip: false,
|
||||
width: 14,
|
||||
itemStyle: { color: steamGradient }
|
||||
},
|
||||
pointer: {
|
||||
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
|
||||
length: '75%',
|
||||
width: 16,
|
||||
itemStyle: { color: 'rgba(11, 168, 255, 1)' }, // 蒸汽指针颜色
|
||||
offsetCenter: [0, '5%']
|
||||
},
|
||||
axisLine: {
|
||||
roundCap: true,
|
||||
lineStyle: { width: 14, color: [[1, 'rgba(19, 84, 122, 1)']] }
|
||||
},
|
||||
axisTick: {
|
||||
splitNumber: 2,
|
||||
show: (val) => this.specialTicks.includes(val),
|
||||
lineStyle: { width: 2, color: '#999' }
|
||||
},
|
||||
splitLine: { length: 12, lineStyle: { width: 3, color: '#999' } },
|
||||
axisLabel: {
|
||||
show: (val) => this.specialTicks.includes(val),
|
||||
distance: -45,
|
||||
color: '#ffffff',
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
detail: { show: false },
|
||||
data: [{ value, unit: 't' }] // 蒸汽单位
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// handleResize() {
|
||||
// if (this.resizeTimer) clearTimeout(this.resizeTimer)
|
||||
// this.resizeTimer = setTimeout(() => {
|
||||
// if (this.electricityChart) this.electricityChart.resize()
|
||||
// if (this.steamChart) this.steamChart.resize()
|
||||
// }, 100)
|
||||
// }
|
||||
},
|
||||
// beforeDestroy() {
|
||||
// if (this.electricityChart) this.electricityChart.dispose()
|
||||
// if (this.steamChart) this.steamChart.dispose()
|
||||
// window.removeEventListener('resize', this.handleResize)
|
||||
// if (this.resizeTimer) clearTimeout(this.resizeTimer)
|
||||
// }
|
||||
// 未使用的蒸汽仪表盘可注释/删除
|
||||
// getSteamGaugeOption(value) { ... }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -298,9 +221,4 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||
<div class="dashboard">
|
||||
<div class="title">
|
||||
@@ -11,112 +9,124 @@
|
||||
</div>
|
||||
<div class="number">
|
||||
<div class="yield">
|
||||
90%
|
||||
{{ formatRate(factoryData?.completeRate) }}%
|
||||
</div>
|
||||
<div class="mom">
|
||||
环比10%
|
||||
<img class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||
环比{{ formatRate(factoryData?.thb) }}%
|
||||
<img v-if="factoryData?.thb >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="上升">
|
||||
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="下降">
|
||||
</div>
|
||||
</div>
|
||||
<div class="electricityGauge">
|
||||
<electricityGauge id="month"></electricityGauge>
|
||||
<!-- 传递包含flag的factoryData给仪表盘组件 -->
|
||||
<electricityGauge id="month" :detailData="factoryData"></electricityGauge>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line" style="padding: 0px;">
|
||||
<verticalBarChart>
|
||||
</verticalBarChart>
|
||||
|
||||
<!-- 传递包含flag的factoryData给柱状图组件 -->
|
||||
<verticalBarChart :detailData="factoryData"></verticalBarChart>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
import electricityGauge from './electricityGauge.vue'
|
||||
import verticalBarChart from './verticalBarChart.vue'
|
||||
|
||||
// import * as echarts from 'echarts'
|
||||
// import rawItem from './raw-Item.vue'
|
||||
// 引入箭头图片(根据实际路径调整,若模板中直接用路径可注释)
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, electricityGauge, verticalBarChart },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
itemData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
monData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
title: { // 接收父组件传递的设备数据数组
|
||||
title: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
month: { // 接收父组件传递的设备数据数组
|
||||
month: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
itemData: {
|
||||
handler(newValue, oldValue) {
|
||||
// this.updateChart()
|
||||
},
|
||||
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||
computed: {
|
||||
/**
|
||||
* 自动提取monData中的工厂数据,并新增flag字段
|
||||
*/
|
||||
factoryData() {
|
||||
const factoryKeys = Object.keys(this.monData);
|
||||
if (factoryKeys.length === 0) {
|
||||
// 无数据时返回兜底对象(包含flag)
|
||||
return {
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0,
|
||||
flag: 0 // 兜底flag
|
||||
};
|
||||
}
|
||||
const firstKey = factoryKeys[0];
|
||||
const rawData = this.monData[firstKey];
|
||||
// 整合原始数据 + 计算flag
|
||||
return {
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0,
|
||||
...rawData,
|
||||
flag: this.getRateFlag(rawData.completeRate) // 新增flag字段
|
||||
};
|
||||
}
|
||||
},
|
||||
// computed: {
|
||||
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||
// sortedItemData() {
|
||||
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||
// const totalCostItems = this.itemData.filter(item =>
|
||||
// item.name && item.name.includes('总成本')
|
||||
// );
|
||||
// // 过滤出不包含“总成本”的项
|
||||
// const otherItems = this.itemData.filter(item =>
|
||||
// !item.name || !item.name.includes('总成本')
|
||||
// );
|
||||
// // 合并:总成本项在前,其他项在后
|
||||
// return [...totalCostItems, ...otherItems];
|
||||
// }
|
||||
// },
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 格式化百分比数值:处理空值/非数字,兜底为0
|
||||
*/
|
||||
formatRate(value) {
|
||||
if (isNaN(value) || value === null || value === undefined) {
|
||||
return 0;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
/**
|
||||
* 判断完成率对应的flag值(<100为0,≥100为1)
|
||||
* @param {number} rate 完成率(原始值,如89代表89%)
|
||||
* @returns {0|1} flag值
|
||||
*/
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return rate >= 100 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</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;
|
||||
}
|
||||
|
||||
@@ -127,16 +137,12 @@ export default {
|
||||
padding: 16px 0 0 16px;
|
||||
|
||||
.title {
|
||||
// width: 190px;
|
||||
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;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
@@ -144,19 +150,16 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
// width: 190px;
|
||||
height: 32px;
|
||||
font-family: YouSheBiaoTiHei;
|
||||
font-size: 32px;
|
||||
color: #0B58FF;
|
||||
line-height: 32px;
|
||||
letter-spacing: 2px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.mom {
|
||||
width: 97px;
|
||||
width: fit-content; // 自适应宽度,避免文字溢出
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
@@ -164,8 +167,16 @@ export default {
|
||||
color: #000000;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
display: flex;
|
||||
align-items: center; // 箭头和文字垂直居中
|
||||
gap: 4px; // 文字和箭头间距
|
||||
}
|
||||
|
||||
// 箭头样式优化
|
||||
.arrow {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,34 +185,4 @@ export default {
|
||||
height: 205px;
|
||||
background: #F9FCFF;
|
||||
}
|
||||
|
||||
// .leftTitle {
|
||||
// .item {
|
||||
// width: 67px;
|
||||
// height: 180px;
|
||||
// padding: 37px 23px;
|
||||
// background: #F9FCFF;
|
||||
// font-family: PingFangSC, PingFang SC;
|
||||
// font-weight: 400;
|
||||
// font-size: 18px;
|
||||
// color: #000000;
|
||||
// line-height: 25px;
|
||||
// letter-spacing: 1px;
|
||||
// // text-align: left;
|
||||
// font-style: normal;
|
||||
// }
|
||||
// }
|
||||
</style>
|
||||
|
||||
<!-- <style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style> -->
|
||||
|
||||
@@ -1,29 +1,21 @@
|
||||
<template>
|
||||
<div class="coreBar">
|
||||
<!-- 新增行容器:包裹“各基地情况”和barTop -->
|
||||
<div class="header-row">
|
||||
<div class="base-title">
|
||||
各基地情况
|
||||
</div>
|
||||
<div class="base-title">各基地情况</div>
|
||||
<div class="barTop">
|
||||
<!-- 关键:新增右侧容器,包裹图例和按钮组,实现整体靠右 -->
|
||||
<div class="right-container">
|
||||
<div class="legend">
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon line yield"></span>
|
||||
完成率
|
||||
<span class="legend-icon line yield"></span>完成率
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square target"></span>
|
||||
目标
|
||||
<span class="legend-icon square target"></span>预算
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square achieved"></span>
|
||||
实际·达标
|
||||
<span class="legend-icon square achieved"></span>实际·达标
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square unachieved"></span>
|
||||
实际·未达标
|
||||
<span class="legend-icon square unachieved"></span>实际·未达标
|
||||
</span>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
@@ -32,13 +24,13 @@
|
||||
</div>
|
||||
<div class="dropdown-container">
|
||||
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||
<span class="item-text profit-text">{{ selectedProfit || '请选择' }}</span>
|
||||
<span class="item-text profit-text">{{ selectedSort || '请选择' }}</span>
|
||||
<span class="dropdown-arrow" :class="{ 'rotate': isDropdownShow }"></span>
|
||||
</div>
|
||||
<div class="dropdown-options" v-if="isDropdownShow">
|
||||
<div class="dropdown-option" v-for="(item, index) in profitOptions" :key="index"
|
||||
@click.stop="selectProfit(item)">
|
||||
{{ item }}
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,130 +52,79 @@ export default {
|
||||
name: "Container",
|
||||
components: { operatingLineBar },
|
||||
props: ["chartData"],
|
||||
emits: ['sort-change'], // 声明事件(Vue3 推荐)
|
||||
data() {
|
||||
return {
|
||||
activeButton: 0,
|
||||
isDropdownShow: false,
|
||||
selectedProfit: null, // 选中的名称,初始为null
|
||||
selectedSort: null, // 选中的label
|
||||
selectedSortValue: null, // 选中的value,用于排序逻辑
|
||||
profitOptions: [
|
||||
'实际值:高~低',
|
||||
'实际值:低~高',
|
||||
'目标值:高~低',
|
||||
'目标值:低~高',
|
||||
|
||||
{ label: '实际值:高~低', value: 1 },
|
||||
{ label: '实际值:低~高', value: 2 },
|
||||
{ label: '完成率:高~低', value: 3 },
|
||||
{ label: '完成率:低~高', value: 4 },
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// profitOptions() {
|
||||
// return this.categoryData.map(item => item.name) || [];
|
||||
// },
|
||||
// 排序后的数据源(核心:根据selectedSortValue重新排序)
|
||||
currentDataSource() {
|
||||
console.log('yyyy', this.chartData);
|
||||
if (!this.chartData) return {};
|
||||
|
||||
return this.activeButton === 0 ? this.chartData.sales : this.chartData.grossMargin;
|
||||
// 深拷贝原始数据,避免修改原数据
|
||||
const factory = JSON.parse(JSON.stringify(this.chartData));
|
||||
if (!factory.locations.length || !this.selectedSortValue) return factory;
|
||||
|
||||
// 构建带索引的数组,方便同步所有字段排序
|
||||
const dataWithIndex = factory.locations.map((name, index) => ({
|
||||
index,
|
||||
name,
|
||||
real: factory.reals[index],
|
||||
target: factory.targets[index],
|
||||
rate: factory.rates[index],
|
||||
diff: factory.diff[index],
|
||||
flag: factory.flags[index]
|
||||
}));
|
||||
|
||||
// 根据选中的排序规则排序
|
||||
switch (this.selectedSortValue) {
|
||||
case 1: // 实际值:高~低
|
||||
dataWithIndex.sort((a, b) => b.real - a.real);
|
||||
break;
|
||||
case 2: // 实际值:低~高
|
||||
dataWithIndex.sort((a, b) => a.real - b.real);
|
||||
break;
|
||||
case 3: // 目标值:高~低
|
||||
dataWithIndex.sort((a, b) => b.rate - a.rate);
|
||||
break;
|
||||
case 4: // 目标值:低~高
|
||||
dataWithIndex.sort((a, b) => a.rate - b.rate);
|
||||
break;
|
||||
default:
|
||||
return factory;
|
||||
}
|
||||
|
||||
// 同步更新所有数组
|
||||
factory.locations = dataWithIndex.map(item => item.name);
|
||||
factory.reals = dataWithIndex.map(item => item.real);
|
||||
factory.targets = dataWithIndex.map(item => item.target);
|
||||
factory.rates = dataWithIndex.map(item => item.rate);
|
||||
factory.diff = dataWithIndex.map(item => item.diff);
|
||||
factory.flags = dataWithIndex.map(item => item.flag);
|
||||
|
||||
return factory;
|
||||
},
|
||||
locations() {
|
||||
console.log('this.chartData', this.chartData);
|
||||
|
||||
return this.activeButton === 0 ? this.chartData.salesLocations : this.chartData.grossMarginLocations;
|
||||
return this.currentDataSource.locations || [];
|
||||
},
|
||||
// 根据按钮切换生成对应的 chartData
|
||||
// 最终传递给图表的排序后数据
|
||||
chartD() {
|
||||
// 销量场景数据
|
||||
const data = this.currentDataSource;
|
||||
console.log(this.currentDataSource, 'currentDataSource');
|
||||
|
||||
const salesData = {
|
||||
allPlaceNames: this.locations,
|
||||
series: [
|
||||
// 1. 完成率(折线图)
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'line',
|
||||
yAxisIndex: 1, // 绑定右侧Y轴(需在子组件启用配置)
|
||||
lineStyle: {
|
||||
color: 'rgba(40, 138, 255, .5)',
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(40, 138, 255, 1)',
|
||||
borderColor: 'rgba(40, 138, 255, 1)',
|
||||
borderWidth: 2,
|
||||
radius: 4
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.2,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||
])
|
||||
},
|
||||
data: data.rates, // 完成率(%)
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
// 2. 目标(柱状图)
|
||||
{
|
||||
name: '目标',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.targets // 目标销量(万元)
|
||||
},
|
||||
// 3. 实际(柱状图,含达标状态)
|
||||
{
|
||||
name: '实际',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||
const safeFlag = data.flags;
|
||||
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||
return currentFlag === 1
|
||||
? {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||
]
|
||||
}
|
||||
: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||
]
|
||||
};
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.reals // 实际销量(万元)
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 毛利率场景数据
|
||||
const grossProfitData = {
|
||||
series: [
|
||||
// 1. 完成率(折线图)
|
||||
// 完成率(折线图)
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'line',
|
||||
@@ -202,13 +143,13 @@ export default {
|
||||
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||
])
|
||||
},
|
||||
data: [106.7, 96.9, 106.5, 106.1, 93.8, 105.9], // 毛利率完成率(%)
|
||||
data: data.rates || [],
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
// 2. 目标(柱状图)
|
||||
// 目标(柱状图)
|
||||
{
|
||||
name: '目标',
|
||||
name: '预算',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
@@ -224,17 +165,64 @@ export default {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: [30, 32, 31, 33, 32, 34] // 目标毛利率(万元)
|
||||
data: data.targets || []
|
||||
},
|
||||
// 3. 实际(柱状图)
|
||||
// 实际(柱状图)
|
||||
{
|
||||
name: '实际',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [30, 0],
|
||||
width: 68,
|
||||
height: 20,
|
||||
formatter: (params) => {
|
||||
const diff = data.diff || [];
|
||||
const currentDiff = diff[params.dataIndex] || 0;
|
||||
return `{rate|${currentDiff}}{text|差值}`;
|
||||
},
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||
{ offset: 0.2, color: '#ffffff' },
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
borderRadius: 4,
|
||||
borderColor: '#BFCBD577',
|
||||
borderWidth: 0,
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
text: {
|
||||
width: 'auto',
|
||||
padding: [5, 10, 5, 0],
|
||||
align: 'center',
|
||||
color: '#464646',
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
},
|
||||
rate: {
|
||||
width: 'auto',
|
||||
padding: [5, 0, 5, 10],
|
||||
align: 'center',
|
||||
color: '#30B590',
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
}
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
const safeFlag = [1, 0, 1, 1, 0, 1]; // 达标状态
|
||||
const safeFlag = data.flags || [];
|
||||
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||
return currentFlag === 1
|
||||
? {
|
||||
@@ -257,21 +245,34 @@ export default {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: [32, 31, 33, 35, 30, 36] // 实际毛利率(万元)
|
||||
data: data.reals || []
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 根据按钮状态返回对应数据
|
||||
return salesData;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectProfit(item) {
|
||||
this.selectedProfit = item;
|
||||
// 更新选中的label和value
|
||||
this.selectedSort = item.label;
|
||||
this.selectedSortValue = item.value;
|
||||
this.isDropdownShow = false;
|
||||
// 向父组件传递排序事件(可选,保持原有逻辑)
|
||||
this.$emit('sort-change', item.value);
|
||||
}
|
||||
},
|
||||
// 监听父组件传入的chartData变化,重置选中状态(可选)
|
||||
watch: {
|
||||
'chartData.factory': {
|
||||
handler() {
|
||||
// 若需要切换数据源后重置排序,可取消注释
|
||||
// this.selectedSort = null;
|
||||
// this.selectedSortValue = null;
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -282,16 +283,14 @@ export default {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
|
||||
// 新增:头部行容器,实现一行排列
|
||||
.header-row {
|
||||
display: flex;
|
||||
justify-content: space-between; // 左右两端对齐
|
||||
align-items: center; // 垂直居中
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-bottom: 8px; // 与下方图表区保留间距(可根据需求调整)
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
// 各基地情况标题样式
|
||||
.base-title {
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
@@ -299,29 +298,25 @@ export default {
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
font-style: normal;
|
||||
padding: 0 0 0 16px; // 保留原有内边距
|
||||
white-space: nowrap; // 防止文字换行
|
||||
padding: 0 0 0 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.barTop {
|
||||
// 移除原有flex和justify-content,由header-row控制
|
||||
width: auto; // 自适应宽度
|
||||
// 保留原有align-items,确保内部元素垂直居中
|
||||
width: auto;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||
.right-container {
|
||||
display: flex;
|
||||
align-items: center; // 图例和按钮组垂直居中
|
||||
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
margin-right: 46px;
|
||||
}
|
||||
|
||||
// 2. 图例:在右侧容器内横向排列
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 16px; // 图例项之间间距,避免重叠
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -336,7 +331,7 @@ export default {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
white-space: nowrap; // 防止图例文字换行
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.legend-icon {
|
||||
@@ -365,7 +360,6 @@ export default {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
// 图例颜色
|
||||
.yield {
|
||||
background: rgba(40, 138, 255, 1);
|
||||
}
|
||||
@@ -382,7 +376,6 @@ export default {
|
||||
background: rgba(255, 132, 0, 1);
|
||||
}
|
||||
|
||||
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||
.button-group {
|
||||
display: flex;
|
||||
position: relative;
|
||||
@@ -406,7 +399,6 @@ export default {
|
||||
line-height: 24px;
|
||||
font-style: normal;
|
||||
letter-spacing: 2px;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
.item-text {
|
||||
|
||||
@@ -10,17 +10,24 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
myChart: null, // 存储图表实例
|
||||
resizeHandler: null // 存储resize事件处理函数,用于后续移除
|
||||
resizeHandler: null, // 存储resize事件处理函数
|
||||
// 核心:基地名称与序号的映射表(固定顺序)
|
||||
baseNameToIndexMap: {
|
||||
'宜兴': 7,
|
||||
'漳州': 8,
|
||||
'自贡': 3,
|
||||
'桐城': 2,
|
||||
'洛阳': 9,
|
||||
'合肥': 5,
|
||||
'宿迁': 6,
|
||||
'秦皇岛': 10
|
||||
}
|
||||
};
|
||||
},
|
||||
props: {
|
||||
chartData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
// 可选:保留数据校验
|
||||
// validator: (value) => {
|
||||
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||
// }
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -57,19 +64,28 @@ export default {
|
||||
|
||||
// 绑定点击事件(只绑定一次,永久生效)
|
||||
this.myChart.on('click', (params) => {
|
||||
// 箭头函数保证this指向Vue实例
|
||||
console.log('点击事件的参数:', params);
|
||||
|
||||
// 提取关键数据(注意:如果是折线图,value是数组;柱状图是单个值,需兼容)
|
||||
// 提取点击的基地名称
|
||||
const itemName = params.name;
|
||||
// 根据映射表获取对应的序号(未匹配到则返回0或其他默认值)
|
||||
const baseIndex = this.baseNameToIndexMap[itemName] || 0;
|
||||
|
||||
// 兼容不同图表类型的value:柱状图value是数值,折线图是[横坐标, 纵坐标]
|
||||
const itemValue = Array.isArray(params.value) ? params.value[1] : params.value;
|
||||
const seriesName = params.seriesName;
|
||||
console.log(`你点击了【${itemName}】,${seriesName}:${itemValue}`);
|
||||
// const itemValue = Array.isArray(params.value) ? params.value[1] : params.value;
|
||||
// const seriesName = params.seriesName;
|
||||
|
||||
console.log(`你点击了【${itemName}】(序号:${baseIndex})`);
|
||||
|
||||
// 路由跳转时携带序号(或名称+序号)
|
||||
this.$router.push({
|
||||
path: 'fullCostAnalysisBase',
|
||||
base: itemName
|
||||
})
|
||||
query: { // 使用query传递参数(推荐),也可使用params
|
||||
// baseName: itemName,
|
||||
factory: baseIndex
|
||||
}
|
||||
// 若仍需用base作为参数:
|
||||
// base: itemName,
|
||||
// params: { baseIndex: baseIndex }
|
||||
});
|
||||
});
|
||||
|
||||
// 定义resize处理函数(命名函数,方便移除)
|
||||
@@ -119,7 +135,12 @@ export default {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0]
|
||||
padding: [5, 0, 0, 0],
|
||||
// 可选:X轴标签显示“序号+名称”(如“1 宜兴”)
|
||||
// formatter: (value) => {
|
||||
// const index = this.baseNameToIndexMap[value] || '';
|
||||
// return index ? `${index} ${value}` : value;
|
||||
// }
|
||||
},
|
||||
data: xData
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export default {
|
||||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||
chartData: {
|
||||
handler() {
|
||||
console.log(this.chartData,'chartData');
|
||||
console.log(this.chartData, 'chartData');
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
@@ -54,7 +54,7 @@ export default {
|
||||
}
|
||||
|
||||
this.myChart = echarts.init(chartDom);
|
||||
|
||||
|
||||
const { allPlaceNames, series } = this.chartData || {};
|
||||
console.log('chartData', this.chartData);
|
||||
|
||||
|
||||
@@ -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: {
|
||||
thisMonData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
required: true, // 标记为必填,避免空数据导致异常
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartData: null, // 初始化 chartData 为 null
|
||||
chartData: null, // 工厂图表数据
|
||||
groupData: {}, // 集团(凯盛新能)数据
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
grossMarginTrendMap: {
|
||||
handler() {
|
||||
this.processChartData();
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
salesTrendMap: {
|
||||
thisMonData: {
|
||||
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→1,false→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.thisMonData['凯盛新能'] || {
|
||||
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.thisMonData).filter(key => key !== '凯盛新能');
|
||||
const factoryDataList = factoryKeys.map(key => this.thisMonData[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;
|
||||
|
||||
@@ -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→1,false→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;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="lineBottom" style="height: 180px; width: 100%">
|
||||
<operatingLineBarSaleSingle :chartData="chartD" style="height: 99%; width: 100%" />
|
||||
<operatingLineBarSaleSingle :refName="'totalOperating'" :chartData="chartD" style="height: 99%; width: 100%" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -11,10 +11,9 @@ import * as echarts from 'echarts';
|
||||
export default {
|
||||
name: "Container",
|
||||
components: { operatingLineBarSaleSingle },
|
||||
props: ["chartData"],
|
||||
props: ["detailData"],
|
||||
data() {
|
||||
return {
|
||||
activeButton: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -24,10 +23,13 @@ export default {
|
||||
chartD() {
|
||||
// 背景图片路径(若不需要可注释)
|
||||
// const bgImageUrl = require('@/assets/img/labelBg.png');
|
||||
const rate = this.detailData?.completeRate || 0
|
||||
const diff = this.detailData?.diff || 0
|
||||
console.log('diff', diff);
|
||||
|
||||
const seriesData = [
|
||||
{
|
||||
value: 131744,
|
||||
value: this.detailData?.target || 0,
|
||||
flag: 1, // 实际项:达标(绿色)
|
||||
label: {
|
||||
show: true,
|
||||
@@ -37,7 +39,9 @@ export default {
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{value|完成率}{rate|139%}',
|
||||
formatter: function (params) {
|
||||
return `{value|完成率}{rate|${rate}%}`;
|
||||
},
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
@@ -91,8 +95,8 @@ export default {
|
||||
},
|
||||
},
|
||||
{
|
||||
value: 630230,
|
||||
flag: 0, // 预算项:未达标(橙色)
|
||||
value: this.detailData?.real || 0,
|
||||
flag: this.detailData?.flag, // 实际项:达标(绿色)
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
@@ -101,8 +105,11 @@ export default {
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{rate|139%}{text|差值}',
|
||||
// 核心样式:匹配CSS需求
|
||||
formatter: function (params) {
|
||||
// 假设 params.data 是完成率数值(如 139)
|
||||
// // 2. 模板字符串拼接富文本标签 + 动态值
|
||||
return `{rate|${diff}}{text|差值}`;
|
||||
},
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
@@ -189,6 +196,7 @@ export default {
|
||||
},
|
||||
],
|
||||
};
|
||||
console.log('data', data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -23,17 +23,19 @@ export default {
|
||||
currentDataSource() {
|
||||
console.log('yyyy', this.chartData);
|
||||
|
||||
return this.activeButton === 0 ? this.chartData.sales : this.chartData.grossMargin;
|
||||
return this.chartData
|
||||
},
|
||||
locations() {
|
||||
console.log('this.chartData', this.chartData);
|
||||
|
||||
return this.activeButton === 0 ? this.chartData.salesLocations : this.chartData.grossMarginLocations;
|
||||
return this.chartData.locations
|
||||
},
|
||||
// 根据按钮切换生成对应的 chartData
|
||||
chartD() {
|
||||
// 销量场景数据
|
||||
const data = this.currentDataSource;
|
||||
const diff = data.diff[0]
|
||||
const rate = data.rate[0]
|
||||
console.log(this.currentDataSource, 'currentDataSource');
|
||||
|
||||
const salesData = {
|
||||
@@ -73,12 +75,15 @@ export default {
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
offset: [-30, 0],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{value|完成率}{rate|139%}',
|
||||
formatter: (params) => {
|
||||
// const { rate = 0, diff = 0 } = params.data || {};
|
||||
return `{value|完成率}{rate|${rate}%}`;
|
||||
},
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
@@ -149,12 +154,15 @@ export default {
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
offset: [30, 0],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{rate|139%}{text|差值}',
|
||||
formatter: (params) => {
|
||||
// const { rate = 0, diff = 0 } = params.data || {};
|
||||
return `{rate|${diff}}{text|差值}`;
|
||||
},
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container :name="title" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||
<Container :isShowTab="true" :name="title" icon="cockpitItemIcon" size="opLargeBg" topSize="large"
|
||||
@tabChange="handleChange">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||
@@ -10,7 +11,10 @@
|
||||
制造成本·单位/万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.制造成本 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.制造成本 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
@@ -18,7 +22,10 @@
|
||||
财务费用·单位/万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.财务费用 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.财务费用 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
@@ -26,7 +33,10 @@
|
||||
销售费用·单位/万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.销售费用 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.销售费用 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
@@ -34,7 +44,10 @@
|
||||
管理费用·单位/万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.管理费用 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.管理费用 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
@@ -42,7 +55,10 @@
|
||||
运费·单位/万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.运费 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.运费 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -53,63 +69,81 @@
|
||||
<script>
|
||||
import Container from '../components/container.vue'
|
||||
import operatingSingleBar from './operatingSingleBar.vue'
|
||||
import verticalBarChart from './verticalBarChart.vue'
|
||||
|
||||
// import * as echarts from 'echarts'
|
||||
// import rawItem from './raw-Item.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, operatingSingleBar, verticalBarChart },
|
||||
// mixins: [resize],
|
||||
components: { Container, operatingSingleBar },
|
||||
props: {
|
||||
itemData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
relatedData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
relatedMon: {}, // 兜底月度数据
|
||||
relatedTotal: {} // 兜底累计数据
|
||||
})
|
||||
},
|
||||
title: { // 接收父组件传递的设备数据数组
|
||||
title: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
month: { // 接收父组件传递的设备数据数组
|
||||
month: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
|
||||
// 关键优化:初始化时直接赋值为月度数据(relatedMon)
|
||||
relatedDetailData: this.relatedData.relatedMon || {},
|
||||
defaultData: {
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听 relatedData 变化(异步加载场景),同步更新月度数据
|
||||
relatedData: {
|
||||
handler(newVal) {
|
||||
this.relatedDetailData = newVal.relatedMon || {};
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
},
|
||||
itemData: {
|
||||
handler(newValue, oldValue) {
|
||||
// this.updateChart()
|
||||
// 保留原有逻辑(若有需要)
|
||||
},
|
||||
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
// computed: {
|
||||
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||
// sortedItemData() {
|
||||
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||
// const totalCostItems = this.itemData.filter(item =>
|
||||
// item.name && item.name.includes('总成本')
|
||||
// );
|
||||
// // 过滤出不包含“总成本”的项
|
||||
// const otherItems = this.itemData.filter(item =>
|
||||
// !item.name || !item.name.includes('总成本')
|
||||
// );
|
||||
// // 合并:总成本项在前,其他项在后
|
||||
// return [...totalCostItems, ...otherItems];
|
||||
// }
|
||||
// },
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
// 无需额外操作,初始化数据已赋值
|
||||
},
|
||||
methods: {
|
||||
handleRoute(path) {
|
||||
this.$router.push({
|
||||
path: path
|
||||
})
|
||||
},
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return +(rate >= 100 || rate === 0);
|
||||
},
|
||||
handleChange(value) {
|
||||
console.log('value', value, this.relatedData);
|
||||
if (value === 'month') {
|
||||
this.relatedDetailData = this.relatedData.relatedMon || {};
|
||||
} else {
|
||||
this.relatedDetailData = this.relatedData.relatedTotal || {};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -11,19 +11,20 @@
|
||||
</div>
|
||||
<div class="number">
|
||||
<div class="yield">
|
||||
90%
|
||||
{{ formatRate(factoryData?.completeRate) }}%
|
||||
</div>
|
||||
<div class="mom">
|
||||
环比10%
|
||||
<img class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||
环比{{ formatRate(factoryData?.thb) }}%
|
||||
<img v-if="factoryData?.thb >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="">
|
||||
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="electricityGauge">
|
||||
<electricityGauge id="totalGauge"></electricityGauge>
|
||||
<electricityGauge id="year" :detailData="factoryData"></electricityGauge>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line" style="padding: 0px;">
|
||||
<verticalBarChart>
|
||||
<verticalBarChart :detailData="factoryData">
|
||||
</verticalBarChart>
|
||||
|
||||
</div>
|
||||
@@ -43,55 +44,75 @@ import verticalBarChart from './verticalBarChart.vue'
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, electricityGauge, verticalBarChart },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
itemData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
totalData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
title: { // 接收父组件传递的设备数据数组
|
||||
title: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
month: { // 接收父组件传递的设备数据数组
|
||||
month: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
itemData: {
|
||||
handler(newValue, oldValue) {
|
||||
// this.updateChart()
|
||||
},
|
||||
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||
computed: {
|
||||
/**
|
||||
* 自动提取monData中的工厂数据,并新增flag字段
|
||||
*/
|
||||
factoryData() {
|
||||
const factoryKeys = Object.keys(this.totalData);
|
||||
if (factoryKeys.length === 0) {
|
||||
// 无数据时返回兜底对象(包含flag)
|
||||
return {
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0,
|
||||
flag: 0 // 兜底flag
|
||||
};
|
||||
}
|
||||
const firstKey = factoryKeys[0];
|
||||
const rawData = this.totalData[firstKey];
|
||||
// 整合原始数据 + 计算flag
|
||||
return {
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0,
|
||||
...rawData,
|
||||
flag: this.getRateFlag(rawData.completeRate) // 新增flag字段
|
||||
};
|
||||
}
|
||||
},
|
||||
// computed: {
|
||||
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||
// sortedItemData() {
|
||||
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||
// const totalCostItems = this.itemData.filter(item =>
|
||||
// item.name && item.name.includes('总成本')
|
||||
// );
|
||||
// // 过滤出不包含“总成本”的项
|
||||
// const otherItems = this.itemData.filter(item =>
|
||||
// !item.name || !item.name.includes('总成本')
|
||||
// );
|
||||
// // 合并:总成本项在前,其他项在后
|
||||
// return [...totalCostItems, ...otherItems];
|
||||
// }
|
||||
// },
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 格式化百分比数值:处理空值/非数字,兜底为0
|
||||
*/
|
||||
formatRate(value) {
|
||||
if (isNaN(value) || value === null || value === undefined) {
|
||||
return 0;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
/**
|
||||
* 判断完成率对应的flag值(<100为0,≥100为1)
|
||||
* @param {number} rate 完成率(原始值,如89代表89%)
|
||||
* @returns {0|1} flag值
|
||||
*/
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return rate >= 100 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -156,7 +177,7 @@ export default {
|
||||
}
|
||||
|
||||
.mom {
|
||||
width: 97px;
|
||||
// width: 97px;
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div ref="verticalBarChart" id="coreLineChart" style="width: 100%; height: 210px;"></div>
|
||||
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 210px;"></div>
|
||||
</template>
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
@@ -8,80 +8,69 @@ export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 存储图表实例,避免重复创建
|
||||
myChart: null
|
||||
};
|
||||
},
|
||||
props: {
|
||||
// 明确接收的props结构,增强可读性
|
||||
chartData: {
|
||||
refName: {
|
||||
type: String,
|
||||
default: 'verticalBarChart',
|
||||
},
|
||||
detailData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
series: [],
|
||||
allPlaceNames: []
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0,
|
||||
flag: 0
|
||||
}),
|
||||
// 校验数据格式
|
||||
validator: (value) => {
|
||||
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
this.$nextTick(() => this.updateChart());
|
||||
},
|
||||
|
||||
// 新增:监听 chartData 变化
|
||||
watch: {
|
||||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||
chartData: {
|
||||
detailData: {
|
||||
handler() {
|
||||
console.log(this.chartData, 'chartData');
|
||||
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
immediate: true // 初始化时立即执行
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateChart() {
|
||||
const chartDom = this.$refs.verticalBarChart;
|
||||
const chartDom = this.$refs[this.refName];
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
|
||||
// 修复:优化实例销毁逻辑,避免重复dispose
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart.clear(); // 先清空,再重新渲染
|
||||
} else {
|
||||
this.myChart = echarts.init(chartDom);
|
||||
}
|
||||
|
||||
this.myChart = echarts.init(chartDom);
|
||||
const { allPlaceNames, series } = this.chartData || {};
|
||||
|
||||
// 处理空数据
|
||||
const xData = allPlaceNames || [];
|
||||
const chartSeries = series || []; // 父组件传递的 series
|
||||
// 解构数据,避免重复取值
|
||||
const { diff, completeRate: rate, real, target, flag } = this.detailData;
|
||||
// 确保数值为数字类型
|
||||
const realValue = Number(real) || 0;
|
||||
const targetValue = Number(target) || 0;
|
||||
const diffValue = Number(diff) || 0;
|
||||
const rateValue = Number(completeRate) || 0;
|
||||
const flagValue = Number(flag) || 0;
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
},
|
||||
// formatter: (params) => {
|
||||
// let html = `${params[0].axisValue}<br/>`;
|
||||
// params.forEach(item => {
|
||||
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||
// );
|
||||
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||
// });
|
||||
// return html;
|
||||
// }
|
||||
label: { backgroundColor: '#6a7985' }
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 10,
|
||||
@@ -89,217 +78,144 @@ export default {
|
||||
right: 50,
|
||||
left: 30,
|
||||
containLabel: true,
|
||||
show: false // 隐藏grid背景,避免干扰
|
||||
show: false
|
||||
},
|
||||
xAxis: {
|
||||
// 横向柱状图的x轴必须设为数值轴,否则无法正常展示数值
|
||||
type: 'value',
|
||||
// offset: 0,
|
||||
// boundaryGap: true ,
|
||||
// boundaryGap: [10, 0], // 可根据需要开启,控制轴的留白
|
||||
axisTick: { show: false },
|
||||
min: 0,
|
||||
// scale: true,
|
||||
splitNumber: 4,
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0]
|
||||
},
|
||||
// data: xData // 数值轴不需要手动设置data,由series的数据自动生成
|
||||
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLabel: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 12, interval: 0, padding: [5, 0, 0, 0] }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.75)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0]
|
||||
},
|
||||
axisLine: {
|
||||
show: true, // 显示Y轴轴线(关键)
|
||||
lineStyle: {
|
||||
color: '#E5E6EB', // 轴线颜色(浅灰色,可自定义)
|
||||
width: 1, // 轴线宽度
|
||||
type: 'solid' // 实线(可选:dashed虚线、dotted点线)
|
||||
}
|
||||
},
|
||||
axisLabel: { color: 'rgba(0, 0, 0, 0.75)', fontSize: 12, interval: 0, padding: [5, 0, 0, 0] },
|
||||
axisLine: { show: true, lineStyle: { color: '#E5E6EB', width: 1, type: 'solid' } },
|
||||
axisTick: { show: false },
|
||||
// padding: [300, 100, 100, 100],
|
||||
data: ['实际', '预算'] // y轴分类:实际、预算
|
||||
data: ['实际', '预算']
|
||||
},
|
||||
series: [
|
||||
{
|
||||
// name: '预算',
|
||||
type: 'bar',
|
||||
barWidth: 24,
|
||||
// barCategoryGap: '50', // 柱子之间的间距(相对于柱子宽度)
|
||||
// 数据长度与yAxis的分类数量匹配(实际、预算各一个值)
|
||||
data: [{
|
||||
value: 131744,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
offset: [-60, 25],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{rate|139%}{text|差值}',
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' }, // 顶部0px位置:阴影最强
|
||||
// { offset: 0.1, color: 'rgba(205, 215, 224, 0.4)' }, // 1px位置:阴影减弱(对应1px)
|
||||
// { offset: 0.15, color: 'rgba(205, 215, 224, 0.6)' }, // 3px位置:阴影几乎消失(对应3px扩散)
|
||||
{ offset: 0.2, color: '#ffffff' }, // 主体白色
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
// 外阴影:0px 2px 2px 0px rgba(191,203,215,0.5)
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
// 圆角:4px
|
||||
borderRadius: 4,
|
||||
// 移除边框
|
||||
borderColor: '#BFCBD577',
|
||||
borderWidth: 0,
|
||||
// 文字垂直居中(针对富文本)
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
text: {
|
||||
// 缩小宽度和内边距,适配68px容器
|
||||
width: 'auto', // 自动宽度,替代固定40px
|
||||
padding: [5, 10, 5, 0], // 缩小内边距
|
||||
align: 'center',
|
||||
color: '#464646', // 文字灰色
|
||||
fontSize: 11, // 缩小字体,适配小尺寸
|
||||
lineHeight: 20 // 垂直居中
|
||||
// 修复:拆分数据项,确保每个柱子的样式独立生效
|
||||
data: [
|
||||
// 实际值柱子(核心:绑定flag颜色)
|
||||
{
|
||||
value: realValue,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
offset: [-60, 25],
|
||||
width: 68,
|
||||
height: 20,
|
||||
formatter: `{rate|${diffValue}}{text|差值}`,
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||
{ offset: 0.2, color: '#ffffff' },
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
rate: {
|
||||
width: 'auto',
|
||||
padding: [5, 0, 5, 10],
|
||||
align: 'center',
|
||||
color: '#30B590',
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
borderRadius: 4,
|
||||
borderWidth: 0,
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
text: { width: 'auto', padding: [5, 10, 5, 0], align: 'center', color: '#464646', fontSize: 11, lineHeight: 20 },
|
||||
rate: { width: 'auto', padding: [5, 0, 5, 10], align: 'center', color: '#30B590', fontSize: 11, lineHeight: 20 }
|
||||
}
|
||||
},
|
||||
// 修复:flag颜色判断独立绑定到实际值柱子
|
||||
itemStyle: {
|
||||
color: flagValue === 1
|
||||
? {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||
]
|
||||
}
|
||||
: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
// 实际的渐变颜色(绿系渐变,与预算区分)
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#AEEFE0' }, // 浅绿
|
||||
{ offset: 1, color: '#76DABE' } // 深绿
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0,
|
||||
},
|
||||
}, {
|
||||
value: 630230,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
offset: [-70, 25],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{value|完成率}{rate|139%}',
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' }, // 顶部0px位置:阴影最强
|
||||
// { offset: 0.1, color: 'rgba(205, 215, 224, 0.4)' }, // 1px位置:阴影减弱(对应1px)
|
||||
// { offset: 0.15, color: 'rgba(205, 215, 224, 0.6)' }, // 3px位置:阴影几乎消失(对应3px扩散)
|
||||
{ offset: 0.2, color: '#ffffff' }, // 主体白色
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
// 外阴影:0px 2px 2px 0px rgba(191,203,215,0.5)
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
// 圆角:4px
|
||||
borderRadius: 4,
|
||||
// 移除边框
|
||||
borderColor: '#BFCBD577',
|
||||
borderWidth: 0,
|
||||
// 文字垂直居中(针对富文本)
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
value: {
|
||||
// 缩小宽度和内边距,适配68px容器
|
||||
width: 'auto', // 自动宽度,替代固定40px
|
||||
padding: [5, 0, 5, 10], // 缩小内边距
|
||||
align: 'center',
|
||||
color: '#464646', // 文字灰色
|
||||
fontSize: 11, // 缩小字体,适配小尺寸
|
||||
lineHeight: 20 // 垂直居中
|
||||
// 预算值柱子(固定蓝色渐变)
|
||||
{
|
||||
value: targetValue,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
offset: [0, 25],
|
||||
width: 68,
|
||||
height: 20,
|
||||
formatter: `{value|完成率}{rate|${rateValue}%}`,
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||
{ offset: 0.2, color: '#ffffff' },
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
rate: {
|
||||
width: 'auto',
|
||||
padding: [5, 10, 5, 0],
|
||||
align: 'center',
|
||||
color: '#0B58FF', // 数字蓝色
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
borderRadius: 4,
|
||||
borderWidth: 0,
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
value: { width: 'auto', padding: [5, 0, 5, 10], align: 'center', color: '#464646', fontSize: 11, lineHeight: 20 },
|
||||
rate: { width: 'auto', padding: [5, 10, 5, 0], align: 'center', color: '#0B58FF', fontSize: 11, lineHeight: 20 }
|
||||
}
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
// 预算的渐变颜色(蓝系渐变)
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 1, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#82CCFF' }, // 浅蓝
|
||||
{ offset: 1, color: '#4B9DFF' } // 深蓝
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
},],
|
||||
|
||||
},
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 1, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#82CCFF' },
|
||||
{ offset: 1, color: '#4B9DFF' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
option && this.myChart.setOption(option);
|
||||
this.myChart.setOption(option, true); // 新增:true表示替换所有配置,避免缓存
|
||||
|
||||
// 窗口缩放适配和销毁逻辑保持不变
|
||||
window.addEventListener('resize', () => {
|
||||
// 优化:防抖resize,避免频繁触发
|
||||
const resizeHandler = () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
};
|
||||
window.removeEventListener('resize', resizeHandler); // 先移除再添加,避免重复绑定
|
||||
window.addEventListener('resize', resizeHandler);
|
||||
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
window.removeEventListener('resize', resizeHandler);
|
||||
this.myChart && this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -187,7 +187,7 @@ export default {
|
||||
this.beilv = _this.clientWidth / 1920;
|
||||
})();
|
||||
};
|
||||
this.factory = this.$route.query.factory ? this.$route.query.factory : this.factory
|
||||
this.factory = this.$route.query.factory ? Number(this.$route.query.factory) : this.factory
|
||||
},
|
||||
methods: {
|
||||
handleChange(value) {
|
||||
|
||||
@@ -69,7 +69,7 @@ export default {
|
||||
methods: {
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return rate >= 100 ? 1 : 0;
|
||||
return +(rate >= 100 || rate === 0); // + 号将布尔值转为数字(true→1,false→0)
|
||||
},
|
||||
|
||||
updateChart(data) {
|
||||
|
||||
@@ -13,14 +13,14 @@ export default {
|
||||
resizeHandler: null, // 存储resize事件处理函数
|
||||
// 核心:基地名称与序号的映射表(固定顺序)
|
||||
baseNameToIndexMap: {
|
||||
'宜兴': 1,
|
||||
'漳州': 2,
|
||||
'宜兴': 7,
|
||||
'漳州': 8,
|
||||
'自贡': 3,
|
||||
'桐城': 4,
|
||||
'洛阳': 5,
|
||||
'合肥': 6,
|
||||
'宿迁': 7,
|
||||
'秦皇岛': 8
|
||||
'桐城': 2,
|
||||
'洛阳': 9,
|
||||
'合肥': 5,
|
||||
'宿迁': 6,
|
||||
'秦皇岛': 10
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -43,6 +43,10 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return +(rate >= 100 || rate === 0); // + 号将布尔值转为数字(true→1,false→0)
|
||||
},
|
||||
updateChart() {
|
||||
const chartDom = this.$refs[this.refName];
|
||||
if (!chartDom) {
|
||||
@@ -57,6 +61,8 @@ export default {
|
||||
this.myChart = echarts.init(chartDom);
|
||||
const diff = this.detailData.diff || 0
|
||||
const rate = this.detailData.rate || 0
|
||||
const flagValue = this.getRateFlag(this.detailData.rate) || 0
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
@@ -198,18 +204,25 @@ export default {
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
// 实际的渐变颜色(绿系渐变,与预算区分)
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#AEEFE0' }, // 浅绿
|
||||
{ offset: 1, color: '#76DABE' } // 深绿
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0,
|
||||
},
|
||||
color: flagValue === 1
|
||||
? {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||
]
|
||||
}
|
||||
: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
}
|
||||
}, {
|
||||
value: this.detailData.target,
|
||||
label: {
|
||||
|
||||
@@ -79,7 +79,7 @@ export default {
|
||||
// 保留原flag判断逻辑(≥100返回1,<100返回0)
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return rate >= 100 ? 1 : 0;
|
||||
return +(rate >= 100 || rate === 0); // + 号将布尔值转为数字(true→1,false→0)
|
||||
},
|
||||
|
||||
updateChart(data) {
|
||||
|
||||
@@ -104,7 +104,7 @@ export default {
|
||||
beilv: 1,
|
||||
month: '',
|
||||
value: 100,
|
||||
factory: 0,
|
||||
factory: 5,
|
||||
dateData: {},
|
||||
index: '加工成品率',
|
||||
monthData: undefined,
|
||||
@@ -184,7 +184,7 @@ export default {
|
||||
this.beilv = _this.clientWidth / 1920;
|
||||
})();
|
||||
};
|
||||
this.factory = this.$route.query.factory ? this.$route.query.factory : this.factory
|
||||
this.factory = this.$route.query.factory ? Number(this.$route.query.factory) : this.factory
|
||||
},
|
||||
methods: {
|
||||
handleChange(value) {
|
||||
|
||||
@@ -78,7 +78,7 @@ export default {
|
||||
// 达标标识判断(≥100返回1,<100返回0)
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return rate >= 100 ? 1 : 0;
|
||||
return +(rate >= 100 || rate === 0); // + 号将布尔值转为数字(true→1,false→0)
|
||||
},
|
||||
|
||||
// 处理领用量/加工产量数据
|
||||
|
||||
@@ -13,14 +13,14 @@ export default {
|
||||
resizeHandler: null, // 存储resize事件处理函数
|
||||
// 核心:基地名称与序号的映射表(固定顺序)
|
||||
baseNameToIndexMap: {
|
||||
'宜兴': 1,
|
||||
'漳州': 2,
|
||||
'宜兴': 7,
|
||||
'漳州': 8,
|
||||
'自贡': 3,
|
||||
'桐城': 4,
|
||||
'洛阳': 5,
|
||||
'合肥': 6,
|
||||
'宿迁': 7,
|
||||
'秦皇岛': 8
|
||||
'桐城': 2,
|
||||
'洛阳': 9,
|
||||
'合肥': 5,
|
||||
'宿迁': 6,
|
||||
'秦皇岛': 10
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -43,6 +43,10 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return +(rate >= 100 || rate === 0); // + 号将布尔值转为数字(true→1,false→0)
|
||||
},
|
||||
updateChart() {
|
||||
const chartDom = this.$refs[this.refName];
|
||||
if (!chartDom) {
|
||||
@@ -57,6 +61,8 @@ export default {
|
||||
this.myChart = echarts.init(chartDom);
|
||||
const diff = this.detailData.diff || 0
|
||||
const rate = this.detailData.rate || 0
|
||||
const flagValue = this.getRateFlag(this.detailData.rate) || 0
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
@@ -198,18 +204,25 @@ export default {
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
// 实际的渐变颜色(绿系渐变,与预算区分)
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#AEEFE0' }, // 浅绿
|
||||
{ offset: 1, color: '#76DABE' } // 深绿
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0,
|
||||
},
|
||||
color: flagValue === 1
|
||||
? {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||
]
|
||||
}
|
||||
: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
}
|
||||
}, {
|
||||
value: this.detailData.target,
|
||||
label: {
|
||||
|
||||
@@ -78,7 +78,7 @@ export default {
|
||||
// 达标标识判断(≥100返回1,<100返回0)
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return rate >= 100 ? 1 : 0;
|
||||
return +(rate >= 100 || rate === 0); // + 号将布尔值转为数字(true→1,false→0)
|
||||
},
|
||||
|
||||
// 处理领用量/加工产量数据
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns:1624px;
|
||||
">
|
||||
<operatingLineChart :salesTrendMap="salesTrendMap" :grossMarginTrendMap="grossMarginTrendMap" />
|
||||
<operatingLineChart :thisMonData="thisMonData" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||
@@ -25,7 +25,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<operatingLineChartCumulative :salesTrendMap="salesTrendMap" :grossMarginTrendMap="grossMarginTrendMap" />
|
||||
<operatingLineChartCumulative :totalData="totalData" />
|
||||
<!-- <keyWork /> -->
|
||||
</div>
|
||||
</div>
|
||||
@@ -50,7 +50,7 @@ import { mapState } from "vuex";
|
||||
import operatingLineChart from "../netPriceAnalysisComponents/operatingLineChart";
|
||||
import operatingLineChartCumulative from "../netPriceAnalysisComponents/operatingLineChartCumulative.vue";
|
||||
|
||||
import { getSalesRevenueData } from '@/api/cockpit'
|
||||
import { getUnitPriceAnalysisGroupData } from '@/api/cockpit'
|
||||
import moment from "moment";
|
||||
export default {
|
||||
name: "DayReport",
|
||||
@@ -68,11 +68,9 @@ export default {
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
value: 100,
|
||||
saleData: {},
|
||||
premiumProduct: {},
|
||||
salesTrendMap: {},
|
||||
grossMarginTrendMap: {},
|
||||
salesProportion:{},
|
||||
selectDate: {},
|
||||
thisMonData: {},
|
||||
totalData: {},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -141,22 +139,27 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
getData(obj) {
|
||||
getSalesRevenueData({
|
||||
startTime: obj.startTime,
|
||||
endTime: obj.endTime,
|
||||
timeDim: obj.mode
|
||||
getUnitPriceAnalysisGroupData({
|
||||
startTime: this.selectDate.startTime,
|
||||
endTime: this.selectDate.endTime,
|
||||
paramName: '净价'
|
||||
// timeDim: this.selectDate.mode
|
||||
}).then((res) => {
|
||||
console.log(res);
|
||||
this.saleData = res.data.SaleData
|
||||
this.premiumProduct = res.data.premiumProduct
|
||||
this.salesTrendMap = res.data.salesTrendMap
|
||||
this.grossMarginTrendMap = res.data.grossMarginTrendMap
|
||||
this.salesProportion = res.data.salesProportion ? res.data.salesProportion : {}
|
||||
this.thisMonData = res.data.thisMonData
|
||||
this.totalData = res.data.totalData
|
||||
|
||||
// this.saleData = res.data.SaleData
|
||||
// this.premiumProduct = res.data.premiumProduct
|
||||
// this.salesTrendMap = res.data.salesTrendMap
|
||||
// this.grossMarginTrendMap = res.data.grossMarginTrendMap
|
||||
// this.salesProportion = res.data.salesProportion ? res.data.salesProportion : {}
|
||||
})
|
||||
},
|
||||
handleTimeChange(obj) {
|
||||
console.log(obj, 'obj');
|
||||
this.getData(obj)
|
||||
// console.log(obj, 'obj');
|
||||
this.selectDate = obj
|
||||
this.getData()
|
||||
},
|
||||
handleClickOutside() {
|
||||
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
||||
@@ -228,12 +231,14 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
@import "~@/assets/styles/mixin.scss";
|
||||
@import "~@/assets/styles/variables.scss";
|
||||
|
||||
.dayReport {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
background: url("../../../assets/img/backp.png") no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<changeBase @selectChange="selectChange" />
|
||||
<changeBase :factory="factory" @baseChange="selectChange" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px;margin-top: -20px;">
|
||||
@@ -26,8 +26,8 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 804px 804px;
|
||||
">
|
||||
<monthlyOverview :month="month" :itemData="renderList" :title="'月度概览'" />
|
||||
<totalOverview :itemData="renderList" :title="'累计概览'" />
|
||||
<monthlyOverview :month="month" :monData="monData" :title="'月度概览'" />
|
||||
<totalOverview :totalData="totalData" :title="'累计概览'" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,8 +37,8 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 804px 804px;
|
||||
">
|
||||
<monthlyRelatedMetrics :itemData="renderList" :title="'月度·相关指标分析'" />
|
||||
<yearRelatedMetrics :month="month" :itemData="renderList" :title="'累计·相关指标分析'" />
|
||||
<monthlyRelatedMetrics :relatedMon="relatedMon" :title="'月度·相关指标分析'" />
|
||||
<yearRelatedMetrics :relatedTotal="relatedTotal" :title="'累计·相关指标分析'" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,7 +48,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<dataTrend :itemData="renderList" :title="'数据趋势'" />
|
||||
<dataTrend :trendData="trend" :title="'数据趋势'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -73,10 +73,8 @@ import totalOverview from "../netPriceAnalysisComponents/totalOverview.vue";
|
||||
import monthlyRelatedMetrics from "../netPriceAnalysisComponents/monthlyRelatedMetrics.vue";
|
||||
import yearRelatedMetrics from "../netPriceAnalysisComponents/yearRelatedMetrics.vue";
|
||||
import dataTrend from "../netPriceAnalysisComponents/dataTrend.vue";
|
||||
|
||||
import profitLineChart from "../costComponents/profitLineChart.vue";
|
||||
import { mapState } from "vuex";
|
||||
import { getCostAnalysisXXCostList } from '@/api/cockpit'
|
||||
import { getUnitPriceAnalysisBaseData } from '@/api/cockpit'
|
||||
// import PSDO from "./components/PSDO.vue";
|
||||
// import psiLineChart from "./components/psiLineChart.vue";
|
||||
|
||||
@@ -91,7 +89,6 @@ export default {
|
||||
components: {
|
||||
ReportHeader,
|
||||
changeBase,
|
||||
profitLineChart,
|
||||
monthlyOverview,
|
||||
Sidebar,
|
||||
totalOverview,
|
||||
@@ -105,16 +102,17 @@ export default {
|
||||
isFullScreen: false,
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
month:'',
|
||||
month: '',
|
||||
value: 100,
|
||||
dateData:{},
|
||||
levelId:undefined,
|
||||
itemData: [],
|
||||
factory: 5,
|
||||
dateData: {},
|
||||
monData: {},
|
||||
relatedMon: {},
|
||||
relatedTotal: {},
|
||||
totalData: {},
|
||||
trendData: [],
|
||||
parentItemList: [
|
||||
{ name: "燃料成本", target: 0, value: 0, proportion: 0, flag: 1 },
|
||||
{ name: "天然气", target: 0, value: 0, proportion: 0, flag: 1 }
|
||||
],
|
||||
trend: [],
|
||||
paramList: ['单价', '运费'],
|
||||
};
|
||||
},
|
||||
|
||||
@@ -131,12 +129,6 @@ export default {
|
||||
needTagsView: (state) => state.settings.tagsView,
|
||||
fixedHeader: (state) => state.settings.fixedHeader,
|
||||
}),
|
||||
renderList() {
|
||||
if (this.itemData && this.itemData.length > 0) {
|
||||
return this.itemData;
|
||||
}
|
||||
return this.parentItemList;
|
||||
},
|
||||
classObj() {
|
||||
return {
|
||||
hideSidebar: !this.sidebar.opened,
|
||||
@@ -186,28 +178,42 @@ export default {
|
||||
this.beilv = _this.clientWidth / 1920;
|
||||
})();
|
||||
};
|
||||
this.factory = this.$route.query.factory ? Number(this.$route.query.factory) : this.factory
|
||||
},
|
||||
methods: {
|
||||
handleChange(value) {
|
||||
this.index = value
|
||||
this.getData()
|
||||
},
|
||||
getData() {
|
||||
const requestParams = {
|
||||
// startTime: this.startTime,
|
||||
// endTime: this.endTime,
|
||||
// mode: this.mode,
|
||||
startTime: this.dateData.startTime,
|
||||
endTime: this.dateData.endTime,
|
||||
mode: this.dateData.mode,
|
||||
trendName: "燃料成本",
|
||||
levelId: this.levelId ? this.levelId :1
|
||||
// index: this.index,
|
||||
// sort: 1,
|
||||
paramName: '净价',
|
||||
paramList: this.paramList,
|
||||
baseId: 2,
|
||||
// baseId: Number(this.factory),
|
||||
};
|
||||
// 调用接口
|
||||
getCostAnalysisXXCostList(requestParams).then((res) => {
|
||||
this.itemData = res.data[0].map((item) => {
|
||||
return {
|
||||
...item,
|
||||
route: 'singleFuelAnalysis'
|
||||
}
|
||||
})
|
||||
this.trendData= res.data[1]
|
||||
getUnitPriceAnalysisBaseData(requestParams).then((res) => {
|
||||
this.monData = res.data.monData
|
||||
this.totalData = res.data.totalData
|
||||
this.relatedMon = res.data.relatedMon
|
||||
this.relatedTotal = res.data.relatedTotal
|
||||
this.trend = res.data.trend
|
||||
// this.cusProData = {
|
||||
// customerPriceMon: res.data.customerPriceMon,
|
||||
// customerPriceTotal: res.data.customerPriceTotal,
|
||||
// customerSaleMon: res.data.customerSaleMon,
|
||||
// customerSaleTotal: res.data.customerSaleTotal,
|
||||
// productMonSale: res.data.productMonSale,
|
||||
// productPriceMon: res.data.productPriceMon,
|
||||
// productPriceTotal: res.data.productPriceTotal,
|
||||
// productTotalSale: res.data.productTotalSale,
|
||||
// }
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
@@ -216,14 +222,14 @@ export default {
|
||||
this.dateData = {
|
||||
startTime: obj.startTime,
|
||||
endTime: obj.endTime,
|
||||
mode: obj.mode,
|
||||
// mode: obj.mode,
|
||||
}
|
||||
|
||||
this.getData()
|
||||
},
|
||||
selectChange(data) {
|
||||
console.log('选中的数据:', data);
|
||||
this.levelId = data
|
||||
this.factory = data
|
||||
if (this.dateData.startTime && this.dateData.endTime) {
|
||||
this.getData();
|
||||
}
|
||||
@@ -308,12 +314,14 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
@import "~@/assets/styles/mixin.scss";
|
||||
@import "~@/assets/styles/variables.scss";
|
||||
|
||||
.dayReport {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
background: url("../../../assets/img/backp.png") no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="数据趋势" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||
<div class="right" style="
|
||||
height: 191px;
|
||||
@@ -9,8 +8,8 @@
|
||||
width: 1595px;
|
||||
background-color: rgba(249, 252, 255, 1);
|
||||
">
|
||||
<!-- <top-item /> -->
|
||||
<dataTrendBar :chartData="chartData" />
|
||||
<!-- 传递 trend 数据给子组件 -->
|
||||
<dataTrendBar :trendData="trendData" />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
@@ -24,18 +23,23 @@ export default {
|
||||
name: "ProductionStatus",
|
||||
components: { Container, dataTrendBar },
|
||||
props: {
|
||||
salesTrendMap: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
trendData: {
|
||||
type: Object, // 接收 trend 数据(单价/运费/净价)
|
||||
default: () => ({})
|
||||
},
|
||||
// 保留原有 props
|
||||
grossMarginTrendMap: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
default: () => ({})
|
||||
},
|
||||
salesTrendMap: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartData: null, // 初始化 chartData 为 null
|
||||
chartData: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
@@ -44,47 +48,21 @@ export default {
|
||||
this.processChartData();
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
deep: true
|
||||
},
|
||||
salesTrendMap: {
|
||||
handler() {
|
||||
this.processChartData();
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 核心处理函数:在所有数据都准备好后,才组装 chartData
|
||||
*/
|
||||
processChartData() {
|
||||
// 关键改动:增加数据有效性检查
|
||||
// 检查 salesTrendMap 是否有实际数据(不只是空对象)
|
||||
const isSalesDataReady = Object.keys(this.salesTrendMap).length > 0;
|
||||
// 检查 grossMarginTrendMap 是否有实际数据
|
||||
const isGrossMarginDataReady =
|
||||
Object.keys(this.grossMarginTrendMap).length > 0;
|
||||
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)
|
||||
: [];
|
||||
@@ -93,237 +71,43 @@ export default {
|
||||
: [];
|
||||
|
||||
const processedGrossMarginData = isGrossMarginDataReady
|
||||
? this.processSingleDataset(
|
||||
grossMarginLocations,
|
||||
this.grossMarginTrendMap
|
||||
)
|
||||
? 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);
|
||||
},
|
||||
|
||||
/**
|
||||
* 通用数据处理函数(纯函数)
|
||||
* @param {Array} locations - 某个指标的地点数组
|
||||
* @param {Object} dataMap - 该指标的原始数据映射
|
||||
* @returns {Object} - 格式化后的数据对象
|
||||
*/
|
||||
processSingleDataset(locations, dataMap) {
|
||||
// 保留原有 processSingleDataset 方法(若有)
|
||||
processSingleDataset(locations, mapData) {
|
||||
const rates = [];
|
||||
const reals = [];
|
||||
const targets = [];
|
||||
const flags = [];
|
||||
|
||||
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;
|
||||
|
||||
rates.push(rate);
|
||||
reals.push(data.real ?? 0); // 使用空值合并运算符
|
||||
targets.push(data.target ?? 0);
|
||||
|
||||
// 优化:更清晰的逻辑
|
||||
if (data.target === 0) {
|
||||
flags.push(1); // 如果目标为0,默认达标
|
||||
} else {
|
||||
flags.push(rate >= 100 ? 1 : 0);
|
||||
}
|
||||
locations.forEach(loc => {
|
||||
const data = mapData[loc] || {};
|
||||
rates.push(data.completeRate || 0);
|
||||
reals.push(data.real || 0);
|
||||
targets.push(data.target || 0);
|
||||
// 复用 getRateFlag 逻辑
|
||||
flags.push(this.getRateFlag(data.completeRate));
|
||||
});
|
||||
|
||||
return { rates, reals, targets, flags };
|
||||
},
|
||||
},
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return (rate >= 100 || rate === 0) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
</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 {
|
||||
width: 21px;
|
||||
height: 23px;
|
||||
background: rgba(0, 106, 205, 0.22);
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
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;
|
||||
color: #ffffff;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eqStatus {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #ffffff;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.splitLine {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid #adadad;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #00ffff;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.proBarLine {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #acacac 100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.proBarLineTop {
|
||||
position: absolute;
|
||||
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%
|
||||
);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
/* 进度变化时添加过渡动画,更流畅 */
|
||||
}
|
||||
|
||||
/* 图表相关样式保留 */
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 57px;
|
||||
bottom: 42px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #00e8ff;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
<!-- 样式部分不变,省略 -->
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
<template>
|
||||
<div class="coreBar">
|
||||
<!-- 新增行容器:包裹“各基地情况”和barTop -->
|
||||
<div class="header-row">
|
||||
<div class="base-title">
|
||||
各基地情况
|
||||
</div>
|
||||
<div class="barTop">
|
||||
<!-- 关键:新增右侧容器,包裹图例和按钮组,实现整体靠右 -->
|
||||
<div class="right-container">
|
||||
<div class="legend">
|
||||
<span class="legend-item">
|
||||
@@ -28,7 +26,7 @@
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<div class="item-button category-btn">
|
||||
<span class="item-text">展示顺序</span>
|
||||
<span class="item-text">类目选择</span>
|
||||
</div>
|
||||
<div class="dropdown-container">
|
||||
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||
@@ -59,47 +57,80 @@ import * as echarts from 'echarts';
|
||||
export default {
|
||||
name: "Container",
|
||||
components: { operatingLineBar },
|
||||
props: ["chartData"],
|
||||
props: [
|
||||
"trendData" // 接收父组件传递的 trend 数据(单价/运费/净价)
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
activeButton: 0,
|
||||
isDropdownShow: false,
|
||||
selectedProfit: null, // 选中的名称,初始为null
|
||||
profitOptions: [
|
||||
'净价',
|
||||
'单价',
|
||||
'运费',
|
||||
]
|
||||
selectedProfit: '净价', // 关键修改:默认赋值为「净价」,初始化即展示该类目数据
|
||||
profitOptions: ['净价', '单价', '运费']
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// profitOptions() {
|
||||
// return this.categoryData.map(item => item.name) || [];
|
||||
// 核心:根据选中的类目,动态解析趋势数据
|
||||
trendParsedData() {
|
||||
// 1. 校验数据有效性
|
||||
if (!this.trendData || !this.selectedProfit) {
|
||||
return {
|
||||
diffs:[],
|
||||
months: [], // 月份数组(x轴标签)
|
||||
rates: [], // 完成率(completeRate)
|
||||
reals: [], // 实际值(real)
|
||||
targets: [], // 目标值(target)
|
||||
flags: [] // 达标状态
|
||||
};
|
||||
}
|
||||
|
||||
// 2. 获取选中类目的趋势数据(如:trendData.单价)
|
||||
const selectedTrend = this.trendData[this.selectedProfit] || {};
|
||||
// 3. 提取月份并排序(保证时间顺序)
|
||||
const months = Object.keys(selectedTrend).sort((a, b) => new Date(a) - new Date(b));
|
||||
// 4. 初始化数据数组
|
||||
const rates = [];
|
||||
const reals = [];
|
||||
const targets = [];
|
||||
const flags = [];
|
||||
const diffs = []
|
||||
// 5. 按月份提取数据
|
||||
months.forEach(month => {
|
||||
const monthData = selectedTrend[month] || {};
|
||||
const completeRate = monthData.completeRate || 0;
|
||||
// 填充对应数据
|
||||
rates.push(completeRate);
|
||||
reals.push(monthData.real || 0);
|
||||
targets.push(monthData.target || 0);
|
||||
diffs.push(monthData.diff || 0);
|
||||
// 生成达标状态(复用 getRateFlag 逻辑)
|
||||
flags.push(this.getRateFlag(completeRate));
|
||||
});
|
||||
|
||||
return {
|
||||
months,
|
||||
rates,
|
||||
reals,
|
||||
targets,
|
||||
flags,
|
||||
diffs
|
||||
};
|
||||
},
|
||||
// locations() {
|
||||
// return this.activeButton === 0 ? this.chartData?.salesLocations : this.chartData?.grossMarginLocations;
|
||||
// },
|
||||
currentDataSource() {
|
||||
console.log('yyyy', this.chartData);
|
||||
|
||||
return this.activeButton === 0 ? this.chartData.sales : this.chartData.grossMargin;
|
||||
},
|
||||
locations() {
|
||||
console.log('this.chartData', this.chartData);
|
||||
|
||||
return this.activeButton === 0 ? this.chartData.salesLocations : this.chartData.grossMarginLocations;
|
||||
},
|
||||
// 根据按钮切换生成对应的 chartData
|
||||
// 重构 chartD:替换硬编码数据为动态解析数据
|
||||
chartD() {
|
||||
// 销量场景数据
|
||||
const data = this.currentDataSource;
|
||||
console.log(this.currentDataSource, 'currentDataSource');
|
||||
|
||||
// 获取动态解析的趋势数据
|
||||
const { months, rates, reals, targets, flags } = this.trendParsedData;
|
||||
// 销量场景数据(保留原有结构,替换数据来源)
|
||||
const salesData = {
|
||||
allPlaceNames: this.locations,
|
||||
allPlaceNames: months, // 优先用基地名称,无则用月份
|
||||
series: [
|
||||
// 1. 完成率(折线图)
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'line',
|
||||
yAxisIndex: 1, // 绑定右侧Y轴(需在子组件启用配置)
|
||||
yAxisIndex: 1,
|
||||
lineStyle: {
|
||||
color: 'rgba(40, 138, 255, .5)',
|
||||
width: 2
|
||||
@@ -117,7 +148,7 @@ export default {
|
||||
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||
])
|
||||
},
|
||||
data: data.rates, // 完成率(%)
|
||||
data: rates, // 动态完成率
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
@@ -125,7 +156,7 @@ export default {
|
||||
{
|
||||
name: '目标',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: {
|
||||
@@ -139,7 +170,7 @@ export default {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.targets // 目标销量(万元)
|
||||
data: targets, // 动态目标值
|
||||
},
|
||||
// 3. 实际(柱状图,含达标状态)
|
||||
{
|
||||
@@ -147,93 +178,68 @@ export default {
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||
const safeFlag = data.flags;
|
||||
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||
return currentFlag === 1
|
||||
? {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||
]
|
||||
}
|
||||
: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||
]
|
||||
};
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: function (params) {
|
||||
const diff = data.diffs || [];
|
||||
const currentDiff = diff[params.dataIndex] || 0;
|
||||
return `{rate|${currentDiff}}{text|差值}`;
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.reals // 实际销量(万元)
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 毛利率场景数据
|
||||
const grossProfitData = {
|
||||
series: [
|
||||
// 1. 完成率(折线图)
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
lineStyle: { color: 'rgba(40, 138, 255, .5)', width: 2 },
|
||||
itemStyle: {
|
||||
color: 'rgba(40, 138, 255, 1)',
|
||||
borderColor: 'rgba(40, 138, 255, 1)',
|
||||
borderWidth: 2,
|
||||
radius: 4
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.2,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||
])
|
||||
},
|
||||
data: [106.7, 96.9, 106.5, 106.1, 93.8, 105.9], // 毛利率完成率(%)
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
// 2. 目标(柱状图)
|
||||
{
|
||||
name: '目标',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: {
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' }, // 顶部0px位置:阴影最强
|
||||
// { offset: 0.1, color: 'rgba(205, 215, 224, 0.4)' }, // 1px位置:阴影减弱(对应1px)
|
||||
// { offset: 0.15, color: 'rgba(205, 215, 224, 0.6)' }, // 3px位置:阴影几乎消失(对应3px扩散)
|
||||
{ offset: 0.2, color: '#ffffff' }, // 主体白色
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
// 外阴影:0px 2px 2px 0px rgba(191,203,215,0.5)
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
// 圆角:4px
|
||||
borderRadius: 4,
|
||||
// 移除边框
|
||||
borderColor: '#BFCBD577',
|
||||
borderWidth: 0,
|
||||
// 文字垂直居中(针对富文本)
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
text: {
|
||||
// 缩小宽度和内边距,适配68px容器
|
||||
width: 'auto', // 自动宽度,替代固定40px
|
||||
padding: [5, 10, 5, 0], // 缩小内边距
|
||||
align: 'center',
|
||||
color: '#464646', // 文字灰色
|
||||
fontSize: 11, // 缩小字体,适配小尺寸
|
||||
lineHeight: 20 // 垂直居中
|
||||
},
|
||||
rate: {
|
||||
width: 'auto',
|
||||
padding: [5, 0, 5, 10],
|
||||
align: 'center',
|
||||
color: '#30B590',
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
}
|
||||
}
|
||||
},
|
||||
data: [30, 32, 31, 33, 32, 34] // 目标毛利率(万元)
|
||||
},
|
||||
// 3. 实际(柱状图)
|
||||
{
|
||||
name: '实际',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
const safeFlag = [1, 0, 1, 1, 0, 1]; // 达标状态
|
||||
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||
const currentFlag = flags[params.dataIndex] || 0;
|
||||
return currentFlag === 1
|
||||
? {
|
||||
type: 'linear',
|
||||
@@ -255,12 +261,12 @@ export default {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: [32, 31, 33, 35, 30, 36] // 实际毛利率(万元)
|
||||
data: reals, // 动态实际值
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 根据按钮状态返回对应数据
|
||||
// 直接返回动态组装的 salesData(移除硬编码的毛利率数据)
|
||||
return salesData;
|
||||
}
|
||||
},
|
||||
@@ -268,28 +274,32 @@ export default {
|
||||
selectProfit(item) {
|
||||
this.selectedProfit = item;
|
||||
this.isDropdownShow = false;
|
||||
},
|
||||
// 复用达标状态判断方法
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return (rate >= 100 || rate === 0) ? 1 : 0;
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// 样式部分不变,省略
|
||||
.coreBar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
|
||||
// 新增:头部行容器,实现一行排列
|
||||
.header-row {
|
||||
display: flex;
|
||||
justify-content: space-between; // 左右两端对齐
|
||||
align-items: center; // 垂直居中
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-bottom: 8px; // 与下方图表区保留间距(可根据需求调整)
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
// 各基地情况标题样式
|
||||
.base-title {
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
@@ -297,29 +307,25 @@ export default {
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
font-style: normal;
|
||||
padding: 0 0 0 16px; // 保留原有内边距
|
||||
white-space: nowrap; // 防止文字换行
|
||||
padding: 0 0 0 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.barTop {
|
||||
// 移除原有flex和justify-content,由header-row控制
|
||||
width: auto; // 自适应宽度
|
||||
// 保留原有align-items,确保内部元素垂直居中
|
||||
width: auto;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||
.right-container {
|
||||
display: flex;
|
||||
align-items: center; // 图例和按钮组垂直居中
|
||||
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
margin-right: 46px;
|
||||
}
|
||||
|
||||
// 2. 图例:在右侧容器内横向排列
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 16px; // 图例项之间间距,避免重叠
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -334,7 +340,7 @@ export default {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
white-space: nowrap; // 防止图例文字换行
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.legend-icon {
|
||||
@@ -363,7 +369,6 @@ export default {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
// 图例颜色
|
||||
.yield {
|
||||
background: rgba(40, 138, 255, 1);
|
||||
}
|
||||
@@ -380,7 +385,6 @@ export default {
|
||||
background: rgba(255, 132, 0, 1);
|
||||
}
|
||||
|
||||
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||
.button-group {
|
||||
display: flex;
|
||||
position: relative;
|
||||
@@ -404,7 +408,6 @@ export default {
|
||||
line-height: 24px;
|
||||
font-style: normal;
|
||||
letter-spacing: 2px;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
.item-text {
|
||||
@@ -456,7 +459,7 @@ export default {
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&.rotate {
|
||||
transform: rotate(90deg);
|
||||
transform: rotate(90deg) translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div :id="id" style="width: 100%; height:100%;"></div>
|
||||
<div class="bottomTip">
|
||||
<div class="precent">
|
||||
<span class="precentNum">{{ energyObj.electricComu }} </span>
|
||||
<span class="precentNum">{{ detailData.completeRate || 0 }} </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -18,31 +18,32 @@ export default {
|
||||
// components: { Container },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
energyObj: {
|
||||
detailData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
electricComu: 0,
|
||||
steamComu: 20, // 调整为符合max范围的数值(0-8)
|
||||
// electricity: [120, 150, 130, 180, 160, 200, 190],
|
||||
// steam: [80, 95, 85, 110, 100, 120, 115],
|
||||
// dates: ['1日', '2日', '3日', '4日', '5日', '6日', '7日']
|
||||
// electricComu: 0,
|
||||
// steamComu: 20, // 调整为符合max范围的数值(0-8)
|
||||
// // electricity: [120, 150, 130, 180, 160, 200, 190],
|
||||
// // steam: [80, 95, 85, 110, 100, 120, 115],
|
||||
// // dates: ['1日', '2日', '3日', '4日', '5日', '6日', '7日']
|
||||
})
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: () => ('')
|
||||
default: () => ('monthG')
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
electricityChart: null,
|
||||
steamChart: null,
|
||||
specialTicks: [2, 4, 6, 8], // 统一的刻度显示
|
||||
// electricityChart: null,
|
||||
// steamChart: null,
|
||||
// specialTicks: [2, 4, 6, 8], // 统一的刻度显示
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
energyObj: {
|
||||
detailData: {
|
||||
deep: true,
|
||||
immediate: true, // 初始化时立即执行
|
||||
handler() {
|
||||
this.updateGauges()
|
||||
}
|
||||
@@ -55,42 +56,47 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
observeContainerResize() {
|
||||
const container = document.querySelector('.gauge-container')
|
||||
// 修复:获取正确的容器(组件内的.gauge-container)
|
||||
const container = this.$el.querySelector('.gauge-container')
|
||||
if (container && window.ResizeObserver) {
|
||||
const resizeObserver = new ResizeObserver(entries => {
|
||||
this.handleResize()
|
||||
})
|
||||
resizeObserver.observe(container)
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
resizeObserver.unobserve(container)
|
||||
this.resizeObserver = new ResizeObserver(entries => {
|
||||
if (this.electricityChart) {
|
||||
this.electricityChart.resize() // 直接触发resize,无需防抖
|
||||
}
|
||||
})
|
||||
this.resizeObserver.observe(container)
|
||||
}
|
||||
},
|
||||
initGauges() {
|
||||
// console.log('this.id',this.id);
|
||||
|
||||
// 初始化电气图表实例
|
||||
const electricityDom = document.getElementById(this.id)
|
||||
if (electricityDom) {
|
||||
// 修复:正确创建并存储图表实例
|
||||
this.electricityChart = echarts.init(electricityDom)
|
||||
// 首次更新数据
|
||||
this.updateGauges()
|
||||
}
|
||||
// 初始化蒸汽图表实例
|
||||
const steamDom = document.getElementById('steamGauge')
|
||||
if (steamDom) {
|
||||
this.steamChart = echarts.init(steamDom)
|
||||
}
|
||||
// 首次更新数据
|
||||
this.updateGauges()
|
||||
// 蒸汽图表若未使用,可注释/删除
|
||||
// const steamDom = document.getElementById('steamGauge')
|
||||
// if (steamDom) {
|
||||
// this.steamChart = echarts.init(steamDom)
|
||||
// }
|
||||
},
|
||||
updateGauges() {
|
||||
// 优化:仅更新数据,不销毁实例(提升性能)
|
||||
if (this.electricityChart) {
|
||||
// 转换原始数据为“万kw/h”(与仪表盘max匹配)
|
||||
const electricValue = 80
|
||||
this.electricityChart.setOption(this.getElectricityGaugeOption(electricValue))
|
||||
}
|
||||
// 修复:先判断实例是否存在,再更新配置
|
||||
if (!this.electricityChart) return
|
||||
|
||||
// 修复:兜底获取rate值,确保数值有效
|
||||
const rate = Number(this.detailData?.completeRate) || 0
|
||||
console.log('当前rate值:', rate); // 调试:确认rate值正确
|
||||
|
||||
// 关键:第二个参数传true,清空原有配置,强制更新
|
||||
this.electricityChart.setOption(this.getElectricityGaugeOption(rate), true)
|
||||
},
|
||||
// 1. 用电量仪表盘独立配置函数
|
||||
// 用电量仪表盘配置(保留原有样式,优化数值范围)
|
||||
getElectricityGaugeOption(value) {
|
||||
// 用电量专属渐变色
|
||||
const electricityGradient = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#0B58FF' },
|
||||
{ offset: 1, color: '#32FFCD' }
|
||||
@@ -101,12 +107,12 @@ export default {
|
||||
{
|
||||
name: '月度',
|
||||
type: 'gauge',
|
||||
radius: '95',
|
||||
radius: '95', // 修复:添加%,避免数值错误
|
||||
center: ['50%', '90%'],
|
||||
startAngle: 180,
|
||||
endAngle: 0,
|
||||
min: 0,
|
||||
max: 100, // 用电量专属最大值
|
||||
max: 100,
|
||||
splitNumber: 4,
|
||||
label: { show: false },
|
||||
progress: {
|
||||
@@ -121,117 +127,34 @@ export default {
|
||||
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
|
||||
length: '75%',
|
||||
width: 16,
|
||||
itemStyle: { color: '#288AFF' }, // 用电量指针颜色
|
||||
itemStyle: { color: '#288AFF' },
|
||||
offsetCenter: [0, '10%']
|
||||
},
|
||||
axisLine: {
|
||||
roundCap: true,
|
||||
lineStyle: { width: 12, color: [[1, '#E6EBF7']] }
|
||||
},
|
||||
// axisTick: {
|
||||
// splitNumber: 2,
|
||||
// show: (val) => this.specialTicks.includes(val),
|
||||
// lineStyle: { width: 2, color: '#999' }
|
||||
// },
|
||||
splitLine: {
|
||||
length: 10, lineStyle: { width: 5, color: '#D6DAE5' },
|
||||
length: 10,
|
||||
lineStyle: { width: 5, color: '#D6DAE5' },
|
||||
},
|
||||
// axisLabel: {
|
||||
// show: (val) => this.specialTicks.includes(val),
|
||||
// distance: -45,
|
||||
// color: '#ffffff',
|
||||
// fontSize: 14,
|
||||
// fontWeight: 'bold'
|
||||
// },
|
||||
axisTick: {
|
||||
splitNumber: 2,
|
||||
length:6,
|
||||
length: 6,
|
||||
lineStyle: { width: 2, color: '#D6DAE5' }
|
||||
},
|
||||
axisLabel: {
|
||||
show: false,
|
||||
},
|
||||
detail: { show: false },
|
||||
data: [{ value, unit: '' }] // 用电量单位
|
||||
data: [{ value: value, unit: '' }] // 确保数值正确传入
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// 2. 用蒸汽仪表盘独立配置函数
|
||||
getSteamGaugeOption(value) {
|
||||
// 蒸汽专属渐变色
|
||||
const steamGradient = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: 'rgba(11, 168, 255, 0.26)' },
|
||||
{ offset: 1, color: 'rgba(54, 239, 230, 1)' }
|
||||
])
|
||||
|
||||
return {
|
||||
series: [
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'gauge',
|
||||
radius: '80',
|
||||
center: ['50%', '85%'],
|
||||
startAngle: 180,
|
||||
endAngle: 0,
|
||||
min: 0,
|
||||
max: 160, // 蒸汽专属最大值
|
||||
splitNumber: 4,
|
||||
label: { show: false },
|
||||
progress: {
|
||||
show: true,
|
||||
overlap: false,
|
||||
roundCap: true,
|
||||
clip: false,
|
||||
width: 14,
|
||||
itemStyle: { color: steamGradient }
|
||||
},
|
||||
pointer: {
|
||||
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
|
||||
length: '75%',
|
||||
width: 16,
|
||||
itemStyle: { color: 'rgba(11, 168, 255, 1)' }, // 蒸汽指针颜色
|
||||
offsetCenter: [0, '5%']
|
||||
},
|
||||
axisLine: {
|
||||
roundCap: true,
|
||||
lineStyle: { width: 14, color: [[1, 'rgba(19, 84, 122, 1)']] }
|
||||
},
|
||||
axisTick: {
|
||||
splitNumber: 2,
|
||||
show: (val) => this.specialTicks.includes(val),
|
||||
lineStyle: { width: 2, color: '#999' }
|
||||
},
|
||||
splitLine: { length: 12, lineStyle: { width: 3, color: '#999' } },
|
||||
axisLabel: {
|
||||
show: (val) => this.specialTicks.includes(val),
|
||||
distance: -45,
|
||||
color: '#ffffff',
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
detail: { show: false },
|
||||
data: [{ value, unit: 't' }] // 蒸汽单位
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// handleResize() {
|
||||
// if (this.resizeTimer) clearTimeout(this.resizeTimer)
|
||||
// this.resizeTimer = setTimeout(() => {
|
||||
// if (this.electricityChart) this.electricityChart.resize()
|
||||
// if (this.steamChart) this.steamChart.resize()
|
||||
// }, 100)
|
||||
// }
|
||||
},
|
||||
// beforeDestroy() {
|
||||
// if (this.electricityChart) this.electricityChart.dispose()
|
||||
// if (this.steamChart) this.steamChart.dispose()
|
||||
// window.removeEventListener('resize', this.handleResize)
|
||||
// if (this.resizeTimer) clearTimeout(this.resizeTimer)
|
||||
// }
|
||||
// 未使用的蒸汽仪表盘可注释/删除
|
||||
// getSteamGaugeOption(value) { ... }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -298,9 +221,4 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||
<div class="dashboard">
|
||||
<div class="title">
|
||||
@@ -11,112 +9,124 @@
|
||||
</div>
|
||||
<div class="number">
|
||||
<div class="yield">
|
||||
90%
|
||||
{{ formatRate(factoryData?.completeRate) }}%
|
||||
</div>
|
||||
<div class="mom">
|
||||
环比10%
|
||||
<img class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||
环比{{ formatRate(factoryData?.thb) }}%
|
||||
<img v-if="factoryData?.thb >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="上升">
|
||||
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="下降">
|
||||
</div>
|
||||
</div>
|
||||
<div class="electricityGauge">
|
||||
<electricityGauge id="month"></electricityGauge>
|
||||
<!-- 传递包含flag的factoryData给仪表盘组件 -->
|
||||
<electricityGauge id="month" :detailData="factoryData"></electricityGauge>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line" style="padding: 0px;">
|
||||
<verticalBarChart>
|
||||
</verticalBarChart>
|
||||
|
||||
<!-- 传递包含flag的factoryData给柱状图组件 -->
|
||||
<verticalBarChart :detailData="factoryData"></verticalBarChart>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
import electricityGauge from './electricityGauge.vue'
|
||||
import verticalBarChart from './verticalBarChart.vue'
|
||||
|
||||
// import * as echarts from 'echarts'
|
||||
// import rawItem from './raw-Item.vue'
|
||||
// 引入箭头图片(根据实际路径调整,若模板中直接用路径可注释)
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, electricityGauge, verticalBarChart },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
itemData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
monData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
title: { // 接收父组件传递的设备数据数组
|
||||
title: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
month: { // 接收父组件传递的设备数据数组
|
||||
month: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
itemData: {
|
||||
handler(newValue, oldValue) {
|
||||
// this.updateChart()
|
||||
},
|
||||
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||
computed: {
|
||||
/**
|
||||
* 自动提取monData中的工厂数据,并新增flag字段
|
||||
*/
|
||||
factoryData() {
|
||||
const factoryKeys = Object.keys(this.monData);
|
||||
if (factoryKeys.length === 0) {
|
||||
// 无数据时返回兜底对象(包含flag)
|
||||
return {
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0,
|
||||
flag: 0 // 兜底flag
|
||||
};
|
||||
}
|
||||
const firstKey = factoryKeys[0];
|
||||
const rawData = this.monData[firstKey];
|
||||
// 整合原始数据 + 计算flag
|
||||
return {
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0,
|
||||
...rawData,
|
||||
flag: this.getRateFlag(rawData.completeRate) // 新增flag字段
|
||||
};
|
||||
}
|
||||
},
|
||||
// computed: {
|
||||
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||
// sortedItemData() {
|
||||
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||
// const totalCostItems = this.itemData.filter(item =>
|
||||
// item.name && item.name.includes('总成本')
|
||||
// );
|
||||
// // 过滤出不包含“总成本”的项
|
||||
// const otherItems = this.itemData.filter(item =>
|
||||
// !item.name || !item.name.includes('总成本')
|
||||
// );
|
||||
// // 合并:总成本项在前,其他项在后
|
||||
// return [...totalCostItems, ...otherItems];
|
||||
// }
|
||||
// },
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 格式化百分比数值:处理空值/非数字,兜底为0
|
||||
*/
|
||||
formatRate(value) {
|
||||
if (isNaN(value) || value === null || value === undefined) {
|
||||
return 0;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
/**
|
||||
* 判断完成率对应的flag值(<100为0,≥100为1)
|
||||
* @param {number} rate 完成率(原始值,如89代表89%)
|
||||
* @returns {0|1} flag值
|
||||
*/
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return rate >= 100 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</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;
|
||||
}
|
||||
|
||||
@@ -127,16 +137,12 @@ export default {
|
||||
padding: 16px 0 0 16px;
|
||||
|
||||
.title {
|
||||
// width: 190px;
|
||||
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;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
@@ -144,19 +150,16 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
// width: 190px;
|
||||
height: 32px;
|
||||
font-family: YouSheBiaoTiHei;
|
||||
font-size: 32px;
|
||||
color: #0B58FF;
|
||||
line-height: 32px;
|
||||
letter-spacing: 2px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.mom {
|
||||
width: 97px;
|
||||
width: fit-content; // 自适应宽度,避免文字溢出
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
@@ -164,8 +167,16 @@ export default {
|
||||
color: #000000;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
display: flex;
|
||||
align-items: center; // 箭头和文字垂直居中
|
||||
gap: 4px; // 文字和箭头间距
|
||||
}
|
||||
|
||||
// 箭头样式优化
|
||||
.arrow {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,34 +185,4 @@ export default {
|
||||
height: 205px;
|
||||
background: #F9FCFF;
|
||||
}
|
||||
|
||||
// .leftTitle {
|
||||
// .item {
|
||||
// width: 67px;
|
||||
// height: 180px;
|
||||
// padding: 37px 23px;
|
||||
// background: #F9FCFF;
|
||||
// font-family: PingFangSC, PingFang SC;
|
||||
// font-weight: 400;
|
||||
// font-size: 18px;
|
||||
// color: #000000;
|
||||
// line-height: 25px;
|
||||
// letter-spacing: 1px;
|
||||
// // text-align: left;
|
||||
// font-style: normal;
|
||||
// }
|
||||
// }
|
||||
</style>
|
||||
|
||||
<!-- <style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style> -->
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
单价·单位/万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedMon.单价 || defaultData),
|
||||
flag: getRateFlag((relatedMon.单价 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
@@ -18,7 +21,10 @@
|
||||
运费·单位/万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedMon.运费 || defaultData),
|
||||
flag: getRateFlag((relatedMon.运费 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,63 +35,74 @@
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
import operatingSingleBar from './operatingSingleBar.vue'
|
||||
import verticalBarChart from './verticalBarChart.vue'
|
||||
|
||||
// import * as echarts from 'echarts'
|
||||
// import rawItem from './raw-Item.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, operatingSingleBar, verticalBarChart },
|
||||
// mixins: [resize],
|
||||
components: { Container, operatingSingleBar },
|
||||
props: {
|
||||
itemData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
relatedMon: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
单价: { completeRate: 0, diff: 0, real: 0, target: 0, thb: 0 },
|
||||
运费: { completeRate: 0, diff: 0, real: 0, target: 0, thb: 0 },
|
||||
})
|
||||
},
|
||||
title: { // 接收父组件传递的设备数据数组
|
||||
title: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
},
|
||||
month: { // 接收父组件传递的设备数据数组
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
|
||||
// 兜底数据:防止字段缺失时报错
|
||||
defaultData: {
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
itemData: {
|
||||
handler(newValue, oldValue) {
|
||||
// this.updateChart()
|
||||
relatedMon: {
|
||||
handler(newValue) {
|
||||
this.updateChart()
|
||||
},
|
||||
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||
immediate: true, // 初始化时立即执行
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
// computed: {
|
||||
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||
// sortedItemData() {
|
||||
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||
// const totalCostItems = this.itemData.filter(item =>
|
||||
// item.name && item.name.includes('总成本')
|
||||
// );
|
||||
// // 过滤出不包含“总成本”的项
|
||||
// const otherItems = this.itemData.filter(item =>
|
||||
// !item.name || !item.name.includes('总成本')
|
||||
// );
|
||||
// // 合并:总成本项在前,其他项在后
|
||||
// return [...totalCostItems, ...otherItems];
|
||||
// }
|
||||
// },
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
// 初始化图表
|
||||
this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 判断完成率对应的flag值(<100为0,≥100为1)
|
||||
* @param {number} rate 完成率(原始值,如89代表89%)
|
||||
* @returns {0|1} flag值
|
||||
*/
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return +(rate >= 100 || rate === 0); // + 号将布尔值转为数字(true→1,false→0)
|
||||
},
|
||||
|
||||
/**
|
||||
* 图表更新方法:可在这里补充全局的图表刷新逻辑
|
||||
* 若子组件内部已监听 chartData 变化,此方法可留空或补充额外逻辑
|
||||
*/
|
||||
updateChart() {
|
||||
console.log('数据更新,当前relatedMon:', this.relatedMon)
|
||||
// 打印各维度的flag值,方便调试
|
||||
// console.log('销量flag:', this.getRateFlag(this.relatedMon.销量.completeRate))
|
||||
// console.log('成本flag:', this.getRateFlag(this.relatedMon.成本.completeRate))
|
||||
// console.log('运费flag:', this.getRateFlag(this.relatedMon.运费.completeRate))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,29 +1,21 @@
|
||||
<template>
|
||||
<div class="coreBar">
|
||||
<!-- 新增行容器:包裹“各基地情况”和barTop -->
|
||||
<div class="header-row">
|
||||
<div class="base-title">
|
||||
各基地情况
|
||||
</div>
|
||||
<div class="base-title">各基地情况</div>
|
||||
<div class="barTop">
|
||||
<!-- 关键:新增右侧容器,包裹图例和按钮组,实现整体靠右 -->
|
||||
<div class="right-container">
|
||||
<div class="legend">
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon line yield"></span>
|
||||
完成率
|
||||
<span class="legend-icon line yield"></span>完成率
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square target"></span>
|
||||
目标
|
||||
<span class="legend-icon square target"></span>预算
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square achieved"></span>
|
||||
实际·达标
|
||||
<span class="legend-icon square achieved"></span>实际·达标
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square unachieved"></span>
|
||||
实际·未达标
|
||||
<span class="legend-icon square unachieved"></span>实际·未达标
|
||||
</span>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
@@ -32,13 +24,13 @@
|
||||
</div>
|
||||
<div class="dropdown-container">
|
||||
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||
<span class="item-text profit-text">{{ selectedProfit || '请选择' }}</span>
|
||||
<span class="item-text profit-text">{{ selectedSort || '请选择' }}</span>
|
||||
<span class="dropdown-arrow" :class="{ 'rotate': isDropdownShow }"></span>
|
||||
</div>
|
||||
<div class="dropdown-options" v-if="isDropdownShow">
|
||||
<div class="dropdown-option" v-for="(item, index) in profitOptions" :key="index"
|
||||
@click.stop="selectProfit(item)">
|
||||
{{ item }}
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,130 +52,79 @@ export default {
|
||||
name: "Container",
|
||||
components: { operatingLineBar },
|
||||
props: ["chartData"],
|
||||
emits: ['sort-change'], // 声明事件(Vue3 推荐)
|
||||
data() {
|
||||
return {
|
||||
activeButton: 0,
|
||||
isDropdownShow: false,
|
||||
selectedProfit: null, // 选中的名称,初始为null
|
||||
selectedSort: null, // 选中的label
|
||||
selectedSortValue: null, // 选中的value,用于排序逻辑
|
||||
profitOptions: [
|
||||
'实际值:高~低',
|
||||
'实际值:低~高',
|
||||
'目标值:高~低',
|
||||
'目标值:低~高',
|
||||
|
||||
{ label: '实际值:高~低', value: 1 },
|
||||
{ label: '实际值:低~高', value: 2 },
|
||||
{ label: '完成率:高~低', value: 3 },
|
||||
{ label: '完成率:低~高', value: 4 },
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// profitOptions() {
|
||||
// return this.categoryData.map(item => item.name) || [];
|
||||
// },
|
||||
// 排序后的数据源(核心:根据selectedSortValue重新排序)
|
||||
currentDataSource() {
|
||||
console.log('yyyy', this.chartData);
|
||||
if (!this.chartData) return {};
|
||||
|
||||
return this.activeButton === 0 ? this.chartData.sales : this.chartData.grossMargin;
|
||||
// 深拷贝原始数据,避免修改原数据
|
||||
const factory = JSON.parse(JSON.stringify(this.chartData));
|
||||
if (!factory.locations.length || !this.selectedSortValue) return factory;
|
||||
|
||||
// 构建带索引的数组,方便同步所有字段排序
|
||||
const dataWithIndex = factory.locations.map((name, index) => ({
|
||||
index,
|
||||
name,
|
||||
real: factory.reals[index],
|
||||
target: factory.targets[index],
|
||||
rate: factory.rates[index],
|
||||
diff: factory.diff[index],
|
||||
flag: factory.flags[index]
|
||||
}));
|
||||
|
||||
// 根据选中的排序规则排序
|
||||
switch (this.selectedSortValue) {
|
||||
case 1: // 实际值:高~低
|
||||
dataWithIndex.sort((a, b) => b.real - a.real);
|
||||
break;
|
||||
case 2: // 实际值:低~高
|
||||
dataWithIndex.sort((a, b) => a.real - b.real);
|
||||
break;
|
||||
case 3: // 目标值:高~低
|
||||
dataWithIndex.sort((a, b) => b.rate - a.rate);
|
||||
break;
|
||||
case 4: // 目标值:低~高
|
||||
dataWithIndex.sort((a, b) => a.rate - b.rate);
|
||||
break;
|
||||
default:
|
||||
return factory;
|
||||
}
|
||||
|
||||
// 同步更新所有数组
|
||||
factory.locations = dataWithIndex.map(item => item.name);
|
||||
factory.reals = dataWithIndex.map(item => item.real);
|
||||
factory.targets = dataWithIndex.map(item => item.target);
|
||||
factory.rates = dataWithIndex.map(item => item.rate);
|
||||
factory.diff = dataWithIndex.map(item => item.diff);
|
||||
factory.flags = dataWithIndex.map(item => item.flag);
|
||||
|
||||
return factory;
|
||||
},
|
||||
locations() {
|
||||
console.log('this.chartData', this.chartData);
|
||||
|
||||
return this.activeButton === 0 ? this.chartData.salesLocations : this.chartData.grossMarginLocations;
|
||||
return this.currentDataSource.locations || [];
|
||||
},
|
||||
// 根据按钮切换生成对应的 chartData
|
||||
// 最终传递给图表的排序后数据
|
||||
chartD() {
|
||||
// 销量场景数据
|
||||
const data = this.currentDataSource;
|
||||
console.log(this.currentDataSource, 'currentDataSource');
|
||||
|
||||
const salesData = {
|
||||
allPlaceNames: this.locations,
|
||||
series: [
|
||||
// 1. 完成率(折线图)
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'line',
|
||||
yAxisIndex: 1, // 绑定右侧Y轴(需在子组件启用配置)
|
||||
lineStyle: {
|
||||
color: 'rgba(40, 138, 255, .5)',
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(40, 138, 255, 1)',
|
||||
borderColor: 'rgba(40, 138, 255, 1)',
|
||||
borderWidth: 2,
|
||||
radius: 4
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.2,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||
])
|
||||
},
|
||||
data: data.rates, // 完成率(%)
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
// 2. 目标(柱状图)
|
||||
{
|
||||
name: '目标',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.targets // 目标销量(万元)
|
||||
},
|
||||
// 3. 实际(柱状图,含达标状态)
|
||||
{
|
||||
name: '实际',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||
const safeFlag = data.flags;
|
||||
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||
return currentFlag === 1
|
||||
? {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||
]
|
||||
}
|
||||
: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||
]
|
||||
};
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.reals // 实际销量(万元)
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 毛利率场景数据
|
||||
const grossProfitData = {
|
||||
series: [
|
||||
// 1. 完成率(折线图)
|
||||
// 完成率(折线图)
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'line',
|
||||
@@ -202,13 +143,13 @@ export default {
|
||||
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||
])
|
||||
},
|
||||
data: [106.7, 96.9, 106.5, 106.1, 93.8, 105.9], // 毛利率完成率(%)
|
||||
data: data.rates || [],
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
// 2. 目标(柱状图)
|
||||
// 目标(柱状图)
|
||||
{
|
||||
name: '目标',
|
||||
name: '预算',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
@@ -224,17 +165,64 @@ export default {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: [30, 32, 31, 33, 32, 34] // 目标毛利率(万元)
|
||||
data: data.targets || []
|
||||
},
|
||||
// 3. 实际(柱状图)
|
||||
// 实际(柱状图)
|
||||
{
|
||||
name: '实际',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [30, 0],
|
||||
width: 68,
|
||||
height: 20,
|
||||
formatter: (params) => {
|
||||
const diff = data.diff || [];
|
||||
const currentDiff = diff[params.dataIndex] || 0;
|
||||
return `{rate|${currentDiff}}{text|差值}`;
|
||||
},
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||
{ offset: 0.2, color: '#ffffff' },
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
borderRadius: 4,
|
||||
borderColor: '#BFCBD577',
|
||||
borderWidth: 0,
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
text: {
|
||||
width: 'auto',
|
||||
padding: [5, 10, 5, 0],
|
||||
align: 'center',
|
||||
color: '#464646',
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
},
|
||||
rate: {
|
||||
width: 'auto',
|
||||
padding: [5, 0, 5, 10],
|
||||
align: 'center',
|
||||
color: '#30B590',
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
}
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
const safeFlag = [1, 0, 1, 1, 0, 1]; // 达标状态
|
||||
const safeFlag = data.flags || [];
|
||||
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||
return currentFlag === 1
|
||||
? {
|
||||
@@ -257,21 +245,34 @@ export default {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: [32, 31, 33, 35, 30, 36] // 实际毛利率(万元)
|
||||
data: data.reals || []
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 根据按钮状态返回对应数据
|
||||
return salesData;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectProfit(item) {
|
||||
this.selectedProfit = item;
|
||||
// 更新选中的label和value
|
||||
this.selectedSort = item.label;
|
||||
this.selectedSortValue = item.value;
|
||||
this.isDropdownShow = false;
|
||||
// 向父组件传递排序事件(可选,保持原有逻辑)
|
||||
this.$emit('sort-change', item.value);
|
||||
}
|
||||
},
|
||||
// 监听父组件传入的chartData变化,重置选中状态(可选)
|
||||
watch: {
|
||||
'chartData.factory': {
|
||||
handler() {
|
||||
// 若需要切换数据源后重置排序,可取消注释
|
||||
// this.selectedSort = null;
|
||||
// this.selectedSortValue = null;
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -282,16 +283,14 @@ export default {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
|
||||
// 新增:头部行容器,实现一行排列
|
||||
.header-row {
|
||||
display: flex;
|
||||
justify-content: space-between; // 左右两端对齐
|
||||
align-items: center; // 垂直居中
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-bottom: 8px; // 与下方图表区保留间距(可根据需求调整)
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
// 各基地情况标题样式
|
||||
.base-title {
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
@@ -299,29 +298,25 @@ export default {
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
font-style: normal;
|
||||
padding: 0 0 0 16px; // 保留原有内边距
|
||||
white-space: nowrap; // 防止文字换行
|
||||
padding: 0 0 0 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.barTop {
|
||||
// 移除原有flex和justify-content,由header-row控制
|
||||
width: auto; // 自适应宽度
|
||||
// 保留原有align-items,确保内部元素垂直居中
|
||||
width: auto;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||
.right-container {
|
||||
display: flex;
|
||||
align-items: center; // 图例和按钮组垂直居中
|
||||
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
margin-right: 46px;
|
||||
}
|
||||
|
||||
// 2. 图例:在右侧容器内横向排列
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 16px; // 图例项之间间距,避免重叠
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -336,7 +331,7 @@ export default {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
white-space: nowrap; // 防止图例文字换行
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.legend-icon {
|
||||
@@ -365,7 +360,6 @@ export default {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
// 图例颜色
|
||||
.yield {
|
||||
background: rgba(40, 138, 255, 1);
|
||||
}
|
||||
@@ -382,7 +376,6 @@ export default {
|
||||
background: rgba(255, 132, 0, 1);
|
||||
}
|
||||
|
||||
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||
.button-group {
|
||||
display: flex;
|
||||
position: relative;
|
||||
@@ -406,7 +399,6 @@ export default {
|
||||
line-height: 24px;
|
||||
font-style: normal;
|
||||
letter-spacing: 2px;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
.item-text {
|
||||
|
||||
@@ -10,17 +10,24 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
myChart: null, // 存储图表实例
|
||||
resizeHandler: null // 存储resize事件处理函数,用于后续移除
|
||||
resizeHandler: null, // 存储resize事件处理函数
|
||||
// 核心:基地名称与序号的映射表(固定顺序)
|
||||
baseNameToIndexMap: {
|
||||
'宜兴': 7,
|
||||
'漳州': 8,
|
||||
'自贡': 3,
|
||||
'桐城': 2,
|
||||
'洛阳': 9,
|
||||
'合肥': 5,
|
||||
'宿迁': 6,
|
||||
'秦皇岛': 10
|
||||
}
|
||||
};
|
||||
},
|
||||
props: {
|
||||
chartData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
// 可选:保留数据校验
|
||||
// validator: (value) => {
|
||||
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||
// }
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -57,19 +64,28 @@ export default {
|
||||
|
||||
// 绑定点击事件(只绑定一次,永久生效)
|
||||
this.myChart.on('click', (params) => {
|
||||
// 箭头函数保证this指向Vue实例
|
||||
console.log('点击事件的参数:', params);
|
||||
|
||||
// 提取关键数据(注意:如果是折线图,value是数组;柱状图是单个值,需兼容)
|
||||
// 提取点击的基地名称
|
||||
const itemName = params.name;
|
||||
// 根据映射表获取对应的序号(未匹配到则返回0或其他默认值)
|
||||
const baseIndex = this.baseNameToIndexMap[itemName] || 0;
|
||||
|
||||
// 兼容不同图表类型的value:柱状图value是数值,折线图是[横坐标, 纵坐标]
|
||||
const itemValue = Array.isArray(params.value) ? params.value[1] : params.value;
|
||||
const seriesName = params.seriesName;
|
||||
console.log(`你点击了【${itemName}】,${seriesName}:${itemValue}`);
|
||||
// const itemValue = Array.isArray(params.value) ? params.value[1] : params.value;
|
||||
// const seriesName = params.seriesName;
|
||||
|
||||
console.log(`你点击了【${itemName}】(序号:${baseIndex})`);
|
||||
|
||||
// 路由跳转时携带序号(或名称+序号)
|
||||
this.$router.push({
|
||||
path: 'netPriceAnalysisBase',
|
||||
base: itemName
|
||||
})
|
||||
query: { // 使用query传递参数(推荐),也可使用params
|
||||
// baseName: itemName,
|
||||
factory: baseIndex
|
||||
}
|
||||
// 若仍需用base作为参数:
|
||||
// base: itemName,
|
||||
// params: { baseIndex: baseIndex }
|
||||
});
|
||||
});
|
||||
|
||||
// 定义resize处理函数(命名函数,方便移除)
|
||||
@@ -119,7 +135,12 @@ export default {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0]
|
||||
padding: [5, 0, 0, 0],
|
||||
// 可选:X轴标签显示“序号+名称”(如“1 宜兴”)
|
||||
// formatter: (value) => {
|
||||
// const index = this.baseNameToIndexMap[value] || '';
|
||||
// return index ? `${index} ${value}` : value;
|
||||
// }
|
||||
},
|
||||
data: xData
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export default {
|
||||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||
chartData: {
|
||||
handler() {
|
||||
console.log(this.chartData,'chartData');
|
||||
console.log(this.chartData, 'chartData');
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
@@ -54,7 +54,7 @@ export default {
|
||||
}
|
||||
|
||||
this.myChart = echarts.init(chartDom);
|
||||
|
||||
|
||||
const { allPlaceNames, series } = this.chartData || {};
|
||||
console.log('chartData', this.chartData);
|
||||
|
||||
|
||||
@@ -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: {
|
||||
thisMonData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
required: true, // 标记为必填,避免空数据导致异常
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartData: null, // 初始化 chartData 为 null
|
||||
chartData: null, // 工厂图表数据
|
||||
groupData: {}, // 集团(凯盛新能)数据
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
grossMarginTrendMap: {
|
||||
handler() {
|
||||
this.processChartData();
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
salesTrendMap: {
|
||||
thisMonData: {
|
||||
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→1,false→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.thisMonData['凯盛新能'] || {
|
||||
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.thisMonData).filter(key => key !== '凯盛新能');
|
||||
const factoryDataList = factoryKeys.map(key => this.thisMonData[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;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="累计数据对比" icon="cockpitItemIcon" size="opLargeBg" 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="
|
||||
@@ -21,7 +21,7 @@
|
||||
">
|
||||
集团情况
|
||||
</div>
|
||||
<operatingTopBar :chartData="chartData" />
|
||||
<operatingTopBar :chartData="groupData" />
|
||||
</div>
|
||||
<div class="right" style="
|
||||
height: 380px;
|
||||
@@ -30,12 +30,13 @@
|
||||
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";
|
||||
@@ -45,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();
|
||||
},
|
||||
@@ -76,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→1,false→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 {
|
||||
@@ -225,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;
|
||||
@@ -258,7 +197,6 @@ export default {
|
||||
height: 14px;
|
||||
border: 1px solid #adadad;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
@@ -275,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;
|
||||
}
|
||||
|
||||
@@ -306,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;
|
||||
@@ -337,7 +267,7 @@ export default {
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
/* 全局 tooltip 样式 */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="lineBottom" style="height: 180px; width: 100%">
|
||||
<operatingLineBarSaleSingle :chartData="chartD" style="height: 99%; width: 100%" />
|
||||
<operatingLineBarSaleSingle :refName="'totalOperating'" :chartData="chartD" style="height: 99%; width: 100%" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -11,10 +11,9 @@ import * as echarts from 'echarts';
|
||||
export default {
|
||||
name: "Container",
|
||||
components: { operatingLineBarSaleSingle },
|
||||
props: ["chartData"],
|
||||
props: ["detailData"],
|
||||
data() {
|
||||
return {
|
||||
activeButton: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -24,10 +23,13 @@ export default {
|
||||
chartD() {
|
||||
// 背景图片路径(若不需要可注释)
|
||||
// const bgImageUrl = require('@/assets/img/labelBg.png');
|
||||
const rate = this.detailData?.completeRate || 0
|
||||
const diff = this.detailData?.diff || 0
|
||||
console.log('diff', diff);
|
||||
|
||||
const seriesData = [
|
||||
{
|
||||
value: 131744,
|
||||
value: this.detailData?.target || 0,
|
||||
flag: 1, // 实际项:达标(绿色)
|
||||
label: {
|
||||
show: true,
|
||||
@@ -37,7 +39,9 @@ export default {
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{value|完成率}{rate|139%}',
|
||||
formatter: function (params) {
|
||||
return `{value|完成率}{rate|${rate}%}`;
|
||||
},
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
@@ -91,8 +95,8 @@ export default {
|
||||
},
|
||||
},
|
||||
{
|
||||
value: 630230,
|
||||
flag: 0, // 预算项:未达标(橙色)
|
||||
value: this.detailData?.real || 0,
|
||||
flag: this.detailData?.flag, // 实际项:达标(绿色)
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
@@ -101,8 +105,11 @@ export default {
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{rate|139%}{text|差值}',
|
||||
// 核心样式:匹配CSS需求
|
||||
formatter: function (params) {
|
||||
// 假设 params.data 是完成率数值(如 139)
|
||||
// // 2. 模板字符串拼接富文本标签 + 动态值
|
||||
return `{rate|${diff}}{text|差值}`;
|
||||
},
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
@@ -189,6 +196,7 @@ export default {
|
||||
},
|
||||
],
|
||||
};
|
||||
console.log('data', data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -23,17 +23,19 @@ export default {
|
||||
currentDataSource() {
|
||||
console.log('yyyy', this.chartData);
|
||||
|
||||
return this.activeButton === 0 ? this.chartData.sales : this.chartData.grossMargin;
|
||||
return this.chartData
|
||||
},
|
||||
locations() {
|
||||
console.log('this.chartData', this.chartData);
|
||||
|
||||
return this.activeButton === 0 ? this.chartData.salesLocations : this.chartData.grossMarginLocations;
|
||||
return this.chartData.locations
|
||||
},
|
||||
// 根据按钮切换生成对应的 chartData
|
||||
chartD() {
|
||||
// 销量场景数据
|
||||
const data = this.currentDataSource;
|
||||
const diff = data.diff[0]
|
||||
const rate = data.rate[0]
|
||||
console.log(this.currentDataSource, 'currentDataSource');
|
||||
|
||||
const salesData = {
|
||||
@@ -73,12 +75,15 @@ export default {
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
offset: [-30, 0],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{value|完成率}{rate|139%}',
|
||||
formatter: (params) => {
|
||||
// const { rate = 0, diff = 0 } = params.data || {};
|
||||
return `{value|完成率}{rate|${rate}%}`;
|
||||
},
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
@@ -149,12 +154,15 @@ export default {
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
offset: [30, 0],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{rate|139%}{text|差值}',
|
||||
formatter: (params) => {
|
||||
// const { rate = 0, diff = 0 } = params.data || {};
|
||||
return `{rate|${diff}}{text|差值}`;
|
||||
},
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
|
||||
@@ -11,19 +11,20 @@
|
||||
</div>
|
||||
<div class="number">
|
||||
<div class="yield">
|
||||
90%
|
||||
{{ formatRate(factoryData?.completeRate) }}%
|
||||
</div>
|
||||
<div class="mom">
|
||||
环比10%
|
||||
<img class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||
环比{{ formatRate(factoryData?.thb) }}%
|
||||
<img v-if="factoryData?.thb >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="">
|
||||
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="electricityGauge">
|
||||
<electricityGauge id="totalGauge"></electricityGauge>
|
||||
<electricityGauge id="year" :detailData="factoryData"></electricityGauge>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line" style="padding: 0px;">
|
||||
<verticalBarChart>
|
||||
<verticalBarChart :detailData="factoryData">
|
||||
</verticalBarChart>
|
||||
|
||||
</div>
|
||||
@@ -43,55 +44,75 @@ import verticalBarChart from './verticalBarChart.vue'
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, electricityGauge, verticalBarChart },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
itemData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
totalData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
title: { // 接收父组件传递的设备数据数组
|
||||
title: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
month: { // 接收父组件传递的设备数据数组
|
||||
month: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
itemData: {
|
||||
handler(newValue, oldValue) {
|
||||
// this.updateChart()
|
||||
},
|
||||
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||
computed: {
|
||||
/**
|
||||
* 自动提取monData中的工厂数据,并新增flag字段
|
||||
*/
|
||||
factoryData() {
|
||||
const factoryKeys = Object.keys(this.totalData);
|
||||
if (factoryKeys.length === 0) {
|
||||
// 无数据时返回兜底对象(包含flag)
|
||||
return {
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0,
|
||||
flag: 0 // 兜底flag
|
||||
};
|
||||
}
|
||||
const firstKey = factoryKeys[0];
|
||||
const rawData = this.totalData[firstKey];
|
||||
// 整合原始数据 + 计算flag
|
||||
return {
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0,
|
||||
...rawData,
|
||||
flag: this.getRateFlag(rawData.completeRate) // 新增flag字段
|
||||
};
|
||||
}
|
||||
},
|
||||
// computed: {
|
||||
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||
// sortedItemData() {
|
||||
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||
// const totalCostItems = this.itemData.filter(item =>
|
||||
// item.name && item.name.includes('总成本')
|
||||
// );
|
||||
// // 过滤出不包含“总成本”的项
|
||||
// const otherItems = this.itemData.filter(item =>
|
||||
// !item.name || !item.name.includes('总成本')
|
||||
// );
|
||||
// // 合并:总成本项在前,其他项在后
|
||||
// return [...totalCostItems, ...otherItems];
|
||||
// }
|
||||
// },
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 格式化百分比数值:处理空值/非数字,兜底为0
|
||||
*/
|
||||
formatRate(value) {
|
||||
if (isNaN(value) || value === null || value === undefined) {
|
||||
return 0;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
/**
|
||||
* 判断完成率对应的flag值(<100为0,≥100为1)
|
||||
* @param {number} rate 完成率(原始值,如89代表89%)
|
||||
* @returns {0|1} flag值
|
||||
*/
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return rate >= 100 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -156,7 +177,7 @@ export default {
|
||||
}
|
||||
|
||||
.mom {
|
||||
width: 97px;
|
||||
// width: 97px;
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div ref="verticalBarChart" id="coreLineChart" style="width: 100%; height: 210px;"></div>
|
||||
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 210px;"></div>
|
||||
</template>
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
@@ -8,80 +8,69 @@ export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 存储图表实例,避免重复创建
|
||||
myChart: null
|
||||
};
|
||||
},
|
||||
props: {
|
||||
// 明确接收的props结构,增强可读性
|
||||
chartData: {
|
||||
refName: {
|
||||
type: String,
|
||||
default: 'verticalBarChart',
|
||||
},
|
||||
detailData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
series: [],
|
||||
allPlaceNames: []
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0,
|
||||
flag: 0
|
||||
}),
|
||||
// 校验数据格式
|
||||
validator: (value) => {
|
||||
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
this.$nextTick(() => this.updateChart());
|
||||
},
|
||||
|
||||
// 新增:监听 chartData 变化
|
||||
watch: {
|
||||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||
chartData: {
|
||||
detailData: {
|
||||
handler() {
|
||||
console.log(this.chartData, 'chartData');
|
||||
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
immediate: true // 初始化时立即执行
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateChart() {
|
||||
const chartDom = this.$refs.verticalBarChart;
|
||||
const chartDom = this.$refs[this.refName];
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
|
||||
// 修复:优化实例销毁逻辑,避免重复dispose
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart.clear(); // 先清空,再重新渲染
|
||||
} else {
|
||||
this.myChart = echarts.init(chartDom);
|
||||
}
|
||||
|
||||
this.myChart = echarts.init(chartDom);
|
||||
const { allPlaceNames, series } = this.chartData || {};
|
||||
|
||||
// 处理空数据
|
||||
const xData = allPlaceNames || [];
|
||||
const chartSeries = series || []; // 父组件传递的 series
|
||||
// 解构数据,避免重复取值
|
||||
const { diff, completeRate: rate, real, target, flag } = this.detailData;
|
||||
// 确保数值为数字类型
|
||||
const realValue = Number(real) || 0;
|
||||
const targetValue = Number(target) || 0;
|
||||
const diffValue = Number(diff) || 0;
|
||||
const rateValue = Number(completeRate) || 0;
|
||||
const flagValue = Number(flag) || 0;
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
},
|
||||
// formatter: (params) => {
|
||||
// let html = `${params[0].axisValue}<br/>`;
|
||||
// params.forEach(item => {
|
||||
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||
// );
|
||||
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||
// });
|
||||
// return html;
|
||||
// }
|
||||
label: { backgroundColor: '#6a7985' }
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 10,
|
||||
@@ -89,217 +78,144 @@ export default {
|
||||
right: 50,
|
||||
left: 30,
|
||||
containLabel: true,
|
||||
show: false // 隐藏grid背景,避免干扰
|
||||
show: false
|
||||
},
|
||||
xAxis: {
|
||||
// 横向柱状图的x轴必须设为数值轴,否则无法正常展示数值
|
||||
type: 'value',
|
||||
// offset: 0,
|
||||
// boundaryGap: true ,
|
||||
// boundaryGap: [10, 0], // 可根据需要开启,控制轴的留白
|
||||
axisTick: { show: false },
|
||||
min: 0,
|
||||
// scale: true,
|
||||
splitNumber: 4,
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0]
|
||||
},
|
||||
// data: xData // 数值轴不需要手动设置data,由series的数据自动生成
|
||||
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLabel: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 12, interval: 0, padding: [5, 0, 0, 0] }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.75)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0]
|
||||
},
|
||||
axisLine: {
|
||||
show: true, // 显示Y轴轴线(关键)
|
||||
lineStyle: {
|
||||
color: '#E5E6EB', // 轴线颜色(浅灰色,可自定义)
|
||||
width: 1, // 轴线宽度
|
||||
type: 'solid' // 实线(可选:dashed虚线、dotted点线)
|
||||
}
|
||||
},
|
||||
axisLabel: { color: 'rgba(0, 0, 0, 0.75)', fontSize: 12, interval: 0, padding: [5, 0, 0, 0] },
|
||||
axisLine: { show: true, lineStyle: { color: '#E5E6EB', width: 1, type: 'solid' } },
|
||||
axisTick: { show: false },
|
||||
// padding: [300, 100, 100, 100],
|
||||
data: ['实际', '预算'] // y轴分类:实际、预算
|
||||
data: ['实际', '预算']
|
||||
},
|
||||
series: [
|
||||
{
|
||||
// name: '预算',
|
||||
type: 'bar',
|
||||
barWidth: 24,
|
||||
// barCategoryGap: '50', // 柱子之间的间距(相对于柱子宽度)
|
||||
// 数据长度与yAxis的分类数量匹配(实际、预算各一个值)
|
||||
data: [{
|
||||
value: 131744,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
offset: [-60, 25],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{rate|139%}{text|差值}',
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' }, // 顶部0px位置:阴影最强
|
||||
// { offset: 0.1, color: 'rgba(205, 215, 224, 0.4)' }, // 1px位置:阴影减弱(对应1px)
|
||||
// { offset: 0.15, color: 'rgba(205, 215, 224, 0.6)' }, // 3px位置:阴影几乎消失(对应3px扩散)
|
||||
{ offset: 0.2, color: '#ffffff' }, // 主体白色
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
// 外阴影:0px 2px 2px 0px rgba(191,203,215,0.5)
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
// 圆角:4px
|
||||
borderRadius: 4,
|
||||
// 移除边框
|
||||
borderColor: '#BFCBD577',
|
||||
borderWidth: 0,
|
||||
// 文字垂直居中(针对富文本)
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
text: {
|
||||
// 缩小宽度和内边距,适配68px容器
|
||||
width: 'auto', // 自动宽度,替代固定40px
|
||||
padding: [5, 10, 5, 0], // 缩小内边距
|
||||
align: 'center',
|
||||
color: '#464646', // 文字灰色
|
||||
fontSize: 11, // 缩小字体,适配小尺寸
|
||||
lineHeight: 20 // 垂直居中
|
||||
// 修复:拆分数据项,确保每个柱子的样式独立生效
|
||||
data: [
|
||||
// 实际值柱子(核心:绑定flag颜色)
|
||||
{
|
||||
value: realValue,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
offset: [-60, 25],
|
||||
width: 68,
|
||||
height: 20,
|
||||
formatter: `{rate|${diffValue}}{text|差值}`,
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||
{ offset: 0.2, color: '#ffffff' },
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
rate: {
|
||||
width: 'auto',
|
||||
padding: [5, 0, 5, 10],
|
||||
align: 'center',
|
||||
color: '#30B590',
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
borderRadius: 4,
|
||||
borderWidth: 0,
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
text: { width: 'auto', padding: [5, 10, 5, 0], align: 'center', color: '#464646', fontSize: 11, lineHeight: 20 },
|
||||
rate: { width: 'auto', padding: [5, 0, 5, 10], align: 'center', color: '#30B590', fontSize: 11, lineHeight: 20 }
|
||||
}
|
||||
},
|
||||
// 修复:flag颜色判断独立绑定到实际值柱子
|
||||
itemStyle: {
|
||||
color: flagValue === 1
|
||||
? {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||
]
|
||||
}
|
||||
: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
// 实际的渐变颜色(绿系渐变,与预算区分)
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#AEEFE0' }, // 浅绿
|
||||
{ offset: 1, color: '#76DABE' } // 深绿
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0,
|
||||
},
|
||||
}, {
|
||||
value: 630230,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
offset: [-70, 25],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{value|完成率}{rate|139%}',
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' }, // 顶部0px位置:阴影最强
|
||||
// { offset: 0.1, color: 'rgba(205, 215, 224, 0.4)' }, // 1px位置:阴影减弱(对应1px)
|
||||
// { offset: 0.15, color: 'rgba(205, 215, 224, 0.6)' }, // 3px位置:阴影几乎消失(对应3px扩散)
|
||||
{ offset: 0.2, color: '#ffffff' }, // 主体白色
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
// 外阴影:0px 2px 2px 0px rgba(191,203,215,0.5)
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
// 圆角:4px
|
||||
borderRadius: 4,
|
||||
// 移除边框
|
||||
borderColor: '#BFCBD577',
|
||||
borderWidth: 0,
|
||||
// 文字垂直居中(针对富文本)
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
value: {
|
||||
// 缩小宽度和内边距,适配68px容器
|
||||
width: 'auto', // 自动宽度,替代固定40px
|
||||
padding: [5, 0, 5, 10], // 缩小内边距
|
||||
align: 'center',
|
||||
color: '#464646', // 文字灰色
|
||||
fontSize: 11, // 缩小字体,适配小尺寸
|
||||
lineHeight: 20 // 垂直居中
|
||||
// 预算值柱子(固定蓝色渐变)
|
||||
{
|
||||
value: targetValue,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
offset: [0, 25],
|
||||
width: 68,
|
||||
height: 20,
|
||||
formatter: `{value|完成率}{rate|${rateValue}%}`,
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||
{ offset: 0.2, color: '#ffffff' },
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
rate: {
|
||||
width: 'auto',
|
||||
padding: [5, 10, 5, 0],
|
||||
align: 'center',
|
||||
color: '#0B58FF', // 数字蓝色
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
borderRadius: 4,
|
||||
borderWidth: 0,
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
value: { width: 'auto', padding: [5, 0, 5, 10], align: 'center', color: '#464646', fontSize: 11, lineHeight: 20 },
|
||||
rate: { width: 'auto', padding: [5, 10, 5, 0], align: 'center', color: '#0B58FF', fontSize: 11, lineHeight: 20 }
|
||||
}
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
// 预算的渐变颜色(蓝系渐变)
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 1, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#82CCFF' }, // 浅蓝
|
||||
{ offset: 1, color: '#4B9DFF' } // 深蓝
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
},],
|
||||
|
||||
},
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 1, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#82CCFF' },
|
||||
{ offset: 1, color: '#4B9DFF' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
option && this.myChart.setOption(option);
|
||||
this.myChart.setOption(option, true); // 新增:true表示替换所有配置,避免缓存
|
||||
|
||||
// 窗口缩放适配和销毁逻辑保持不变
|
||||
window.addEventListener('resize', () => {
|
||||
// 优化:防抖resize,避免频繁触发
|
||||
const resizeHandler = () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
};
|
||||
window.removeEventListener('resize', resizeHandler); // 先移除再添加,避免重复绑定
|
||||
window.addEventListener('resize', resizeHandler);
|
||||
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
window.removeEventListener('resize', resizeHandler);
|
||||
this.myChart && this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -7,10 +7,13 @@
|
||||
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||
<div class="dashboard left">
|
||||
<div class="title">
|
||||
单价·单位/万元
|
||||
单价·单位/万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedTotal.单价 || defaultData),
|
||||
flag: getRateFlag((relatedTotal.单价 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
@@ -18,7 +21,10 @@
|
||||
运费·单位/万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedTotal.运费 || defaultData),
|
||||
flag: getRateFlag((relatedTotal.运费 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,63 +35,74 @@
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
import operatingSingleBar from './operatingSingleBar.vue'
|
||||
import verticalBarChart from './verticalBarChart.vue'
|
||||
|
||||
// import * as echarts from 'echarts'
|
||||
// import rawItem from './raw-Item.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, operatingSingleBar, verticalBarChart },
|
||||
// mixins: [resize],
|
||||
components: { Container, operatingSingleBar },
|
||||
props: {
|
||||
itemData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
relatedTotal: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
单价: { completeRate: 0, diff: 0, real: 0, target: 0, thb: 0 },
|
||||
运费: { completeRate: 0, diff: 0, real: 0, target: 0, thb: 0 },
|
||||
})
|
||||
},
|
||||
title: { // 接收父组件传递的设备数据数组
|
||||
title: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
},
|
||||
month: { // 接收父组件传递的设备数据数组
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
|
||||
// 兜底数据:防止字段缺失时报错
|
||||
defaultData: {
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
itemData: {
|
||||
handler(newValue, oldValue) {
|
||||
// this.updateChart()
|
||||
relatedTotal: {
|
||||
handler(newValue) {
|
||||
this.updateChart()
|
||||
},
|
||||
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||
immediate: true, // 初始化时立即执行
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
// computed: {
|
||||
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||
// sortedItemData() {
|
||||
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||
// const totalCostItems = this.itemData.filter(item =>
|
||||
// item.name && item.name.includes('总成本')
|
||||
// );
|
||||
// // 过滤出不包含“总成本”的项
|
||||
// const otherItems = this.itemData.filter(item =>
|
||||
// !item.name || !item.name.includes('总成本')
|
||||
// );
|
||||
// // 合并:总成本项在前,其他项在后
|
||||
// return [...totalCostItems, ...otherItems];
|
||||
// }
|
||||
// },
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
// 初始化图表
|
||||
this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 判断完成率对应的flag值(<100为0,≥100为1)
|
||||
* @param {number} rate 完成率(原始值,如89代表89%)
|
||||
* @returns {0|1} flag值
|
||||
*/
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return +(rate >= 100 || rate === 0); // + 号将布尔值转为数字(true→1,false→0)
|
||||
},
|
||||
|
||||
/**
|
||||
* 图表更新方法:可在这里补充全局的图表刷新逻辑
|
||||
* 若子组件内部已监听 chartData 变化,此方法可留空或补充额外逻辑
|
||||
*/
|
||||
updateChart() {
|
||||
console.log('数据更新,当前relatedMon:', this.relatedTotal)
|
||||
// 打印各维度的flag值,方便调试
|
||||
// console.log('销量flag:', this.getRateFlag(this.relatedTotal.销量.completeRate))
|
||||
// console.log('成本flag:', this.getRateFlag(this.relatedTotal.成本.completeRate))
|
||||
// console.log('运费flag:', this.getRateFlag(this.relatedTotal.运费.completeRate))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -75,7 +75,7 @@ export default {
|
||||
// 判断flag的核心方法
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return rate >= 100 ? 1 : 0;
|
||||
return +(rate >= 100 || rate === 0); // + 号将布尔值转为数字(true→1,false→0)
|
||||
},
|
||||
|
||||
updateChart(data) {
|
||||
|
||||
@@ -13,14 +13,14 @@ export default {
|
||||
resizeHandler: null, // 存储resize事件处理函数
|
||||
// 核心:基地名称与序号的映射表(固定顺序)
|
||||
baseNameToIndexMap: {
|
||||
'宜兴': 1,
|
||||
'漳州': 2,
|
||||
'宜兴': 7,
|
||||
'漳州': 8,
|
||||
'自贡': 3,
|
||||
'桐城': 4,
|
||||
'洛阳': 5,
|
||||
'合肥': 6,
|
||||
'宿迁': 7,
|
||||
'秦皇岛': 8
|
||||
'桐城': 2,
|
||||
'洛阳': 9,
|
||||
'合肥': 5,
|
||||
'宿迁': 6,
|
||||
'秦皇岛': 10
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -43,6 +43,10 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return +(rate >= 100 || rate === 0); // + 号将布尔值转为数字(true→1,false→0)
|
||||
},
|
||||
updateChart() {
|
||||
const chartDom = this.$refs[this.refName];
|
||||
if (!chartDom) {
|
||||
@@ -57,6 +61,8 @@ export default {
|
||||
this.myChart = echarts.init(chartDom);
|
||||
const diff = this.detailData.diff || 0
|
||||
const rate = this.detailData.rate || 0
|
||||
const flagValue = this.getRateFlag(this.detailData.rate) || 0
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
@@ -198,18 +204,25 @@ export default {
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
// 实际的渐变颜色(绿系渐变,与预算区分)
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#AEEFE0' }, // 浅绿
|
||||
{ offset: 1, color: '#76DABE' } // 深绿
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0,
|
||||
},
|
||||
color: flagValue === 1
|
||||
? {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||
]
|
||||
}
|
||||
: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
}
|
||||
}, {
|
||||
value: this.detailData.target,
|
||||
label: {
|
||||
|
||||
@@ -75,7 +75,7 @@ export default {
|
||||
// 判断flag的核心方法
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return rate >= 100 ? 1 : 0;
|
||||
return +(rate >= 100 || rate === 0); // + 号将布尔值转为数字(true→1,false→0)
|
||||
},
|
||||
|
||||
updateChart(data) {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns:1624px;
|
||||
">
|
||||
<operatingLineChart :salesTrendMap="salesTrendMap" :grossMarginTrendMap="grossMarginTrendMap" />
|
||||
<operatingLineChart :monData="monData" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||
@@ -25,7 +25,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<operatingLineChartCumulative :salesTrendMap="salesTrendMap" :grossMarginTrendMap="grossMarginTrendMap" />
|
||||
<operatingLineChartCumulative :totalData="totalData" />
|
||||
<!-- <keyWork /> -->
|
||||
</div>
|
||||
</div>
|
||||
@@ -50,7 +50,7 @@ import { mapState } from "vuex";
|
||||
import operatingLineChart from "../operatingProfitComponents/operatingLineChart";
|
||||
import operatingLineChartCumulative from "../operatingProfitComponents/operatingLineChartCumulative.vue";
|
||||
|
||||
import { getSalesRevenueData } from '@/api/cockpit'
|
||||
import { getProfitAnalysisManageList } from '@/api/cockpit'
|
||||
import moment from "moment";
|
||||
export default {
|
||||
name: "DayReport",
|
||||
@@ -68,11 +68,9 @@ export default {
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
value: 100,
|
||||
saleData: {},
|
||||
premiumProduct: {},
|
||||
salesTrendMap: {},
|
||||
grossMarginTrendMap: {},
|
||||
salesProportion:{},
|
||||
selectDate: {},
|
||||
monData: [],
|
||||
totalData: [],
|
||||
};
|
||||
},
|
||||
|
||||
@@ -140,23 +138,31 @@ export default {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getData(obj) {
|
||||
getSalesRevenueData({
|
||||
startTime: obj.startTime,
|
||||
endTime: obj.endTime,
|
||||
timeDim: obj.mode
|
||||
getData() {
|
||||
getProfitAnalysisManageList({
|
||||
startTime: this.selectDate.startTime,
|
||||
endTime: this.selectDate.endTime,
|
||||
analysisObject: [
|
||||
"经营性利润"
|
||||
],
|
||||
levelId:1,
|
||||
// timeDim: this.selectDate.mode
|
||||
}).then((res) => {
|
||||
console.log(res);
|
||||
this.saleData = res.data.SaleData
|
||||
this.premiumProduct = res.data.premiumProduct
|
||||
this.salesTrendMap = res.data.salesTrendMap
|
||||
this.grossMarginTrendMap = res.data.grossMarginTrendMap
|
||||
this.salesProportion = res.data.salesProportion ? res.data.salesProportion : {}
|
||||
this.monData = res.data.currentMonthData
|
||||
this.totalData = res.data.totalMonthData
|
||||
// this.totalData = res.data.totalData
|
||||
// this.saleData = res.data.SaleData
|
||||
// this.premiumProduct = res.data.premiumProduct
|
||||
// this.salesTrendMap = res.data.salesTrendMap
|
||||
// this.grossMarginTrendMap = res.data.grossMarginTrendMap
|
||||
// this.salesProportion = res.data.salesProportion ? res.data.salesProportion : {}
|
||||
})
|
||||
},
|
||||
handleTimeChange(obj) {
|
||||
console.log(obj, 'obj');
|
||||
this.getData(obj)
|
||||
// console.log(obj, 'obj');
|
||||
this.selectDate = obj
|
||||
this.getData()
|
||||
},
|
||||
handleClickOutside() {
|
||||
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
||||
@@ -228,12 +234,14 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
@import "~@/assets/styles/mixin.scss";
|
||||
@import "~@/assets/styles/variables.scss";
|
||||
|
||||
.dayReport {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
background: url("../../../assets/img/backp.png") no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<changeBase @selectChange="selectChange" />
|
||||
<changeBase :factory="factory" @baseChange="selectChange" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px;margin-top: -20px;">
|
||||
@@ -26,8 +26,8 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 804px 804px;
|
||||
">
|
||||
<monthlyOverview :month="month" :itemData="renderList" :title="'月度概览'" />
|
||||
<totalOverview :itemData="renderList" :title="'累计概览'" />
|
||||
<monthlyOverview :month="month" :monData="monData" :title="'月度概览'" />
|
||||
<totalOverview :totalData="totalData" :title="'累计概览'" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,8 +37,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<!-- <monthlyRelatedMetrics :itemData="renderList" :title="'月度·相关指标分析'" /> -->
|
||||
<relatedIndicatorsAnalysis :month="month" :itemData="renderList" :title="'相关指标分析'" />
|
||||
<relatedIndicatorsAnalysis :relatedData="relatedData" :title="'相关指标分析·单位/万元'" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,7 +47,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<dataTrend :itemData="renderList" :title="'数据趋势'" />
|
||||
<dataTrend @getData="changeItem" :trendData="trend" :title="'数据趋势'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,13 +69,10 @@ import changeBase from "../components/changeBase.vue";
|
||||
import monthlyOverview from "../operatingProfitComponents/monthlyOverview.vue";
|
||||
import totalOverview from "../operatingProfitComponents/totalOverview.vue";
|
||||
// import totalOverview from "../operatingComponents/totalOverview.vue";
|
||||
// import monthlyRelatedMetrics from "../operatingProfitComponents/monthlyRelatedMetrics.vue";
|
||||
import relatedIndicatorsAnalysis from "../operatingProfitComponents/relatedIndicatorsAnalysis.vue";
|
||||
import dataTrend from "../operatingProfitComponents/dataTrend.vue";
|
||||
|
||||
import profitLineChart from "../costComponents/profitLineChart.vue";
|
||||
import { mapState } from "vuex";
|
||||
import { getCostAnalysisXXCostList } from '@/api/cockpit'
|
||||
import { getProfitAnalysisManageList } from '@/api/cockpit'
|
||||
// import PSDO from "./components/PSDO.vue";
|
||||
// import psiLineChart from "./components/psiLineChart.vue";
|
||||
|
||||
@@ -91,12 +87,11 @@ export default {
|
||||
components: {
|
||||
ReportHeader,
|
||||
changeBase,
|
||||
profitLineChart,
|
||||
monthlyOverview,
|
||||
Sidebar,
|
||||
totalOverview,
|
||||
relatedIndicatorsAnalysis,
|
||||
dataTrend
|
||||
dataTrend,
|
||||
relatedIndicatorsAnalysis
|
||||
// psiLineChart
|
||||
},
|
||||
data() {
|
||||
@@ -104,16 +99,16 @@ export default {
|
||||
isFullScreen: false,
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
month:'',
|
||||
month: '',
|
||||
value: 100,
|
||||
dateData:{},
|
||||
levelId:undefined,
|
||||
itemData: [],
|
||||
trendData: [],
|
||||
parentItemList: [
|
||||
{ name: "燃料成本", target: 0, value: 0, proportion: 0, flag: 1 },
|
||||
{ name: "天然气", target: 0, value: 0, proportion: 0, flag: 1 }
|
||||
],
|
||||
factory: 5,
|
||||
dateData: {},
|
||||
monData: {},
|
||||
totalData: {},
|
||||
trend: [],
|
||||
relatedData: {},
|
||||
trendName: '经营性利润',
|
||||
// cusProData: {},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -130,12 +125,6 @@ export default {
|
||||
needTagsView: (state) => state.settings.tagsView,
|
||||
fixedHeader: (state) => state.settings.fixedHeader,
|
||||
}),
|
||||
renderList() {
|
||||
if (this.itemData && this.itemData.length > 0) {
|
||||
return this.itemData;
|
||||
}
|
||||
return this.parentItemList;
|
||||
},
|
||||
classObj() {
|
||||
return {
|
||||
hideSidebar: !this.sidebar.opened,
|
||||
@@ -185,28 +174,48 @@ export default {
|
||||
this.beilv = _this.clientWidth / 1920;
|
||||
})();
|
||||
};
|
||||
this.factory = this.$route.query.factory ? Number(this.$route.query.factory) : this.factory
|
||||
},
|
||||
methods: {
|
||||
changeItem(item) {
|
||||
console.log('item', item);
|
||||
this.trendName = item
|
||||
this.getData()
|
||||
},
|
||||
getData() {
|
||||
const requestParams = {
|
||||
// startTime: this.startTime,
|
||||
// endTime: this.endTime,
|
||||
// mode: this.mode,
|
||||
startTime: this.dateData.startTime,
|
||||
endTime: this.dateData.endTime,
|
||||
mode: this.dateData.mode,
|
||||
trendName: "燃料成本",
|
||||
levelId: this.levelId ? this.levelId :1
|
||||
// index: this.index,
|
||||
// sort: 1,
|
||||
trendName: this.trendName,
|
||||
analysisObject: [
|
||||
"经营性利润",
|
||||
],
|
||||
// paramList: ['制造成本', '财务费用', '销售费用', '管理费用', '运费'],
|
||||
levelId: this.levelId,
|
||||
// baseId: Number(this.factory),
|
||||
};
|
||||
// 调用接口
|
||||
getCostAnalysisXXCostList(requestParams).then((res) => {
|
||||
this.itemData = res.data[0].map((item) => {
|
||||
return {
|
||||
...item,
|
||||
route: 'singleFuelAnalysis'
|
||||
}
|
||||
})
|
||||
this.trendData= res.data[1]
|
||||
getProfitAnalysisManageList(requestParams).then((res) => {
|
||||
this.monData = res.data.currentMonthData.find(item => {
|
||||
return item.name === "经营性利润";
|
||||
});
|
||||
console.log('this.monData', this.monData);
|
||||
|
||||
this.totalData = res.data.totalMonthData.find(item => {
|
||||
return item.name === "经营性利润";
|
||||
});
|
||||
// this.relatedMon = res.data.relatedMon
|
||||
this.relatedData = {
|
||||
relatedMon: res.data.currentMonthData.filter(item => {
|
||||
return item.name !== "经营性利润";
|
||||
}), // 兜底月度数据
|
||||
relatedTotal: res.data.totalMonthData.filter(item => {
|
||||
return item.name !== "经营性利润";
|
||||
}) // 兜底累计数据
|
||||
}
|
||||
this.trend = res.data.dataTrend
|
||||
});
|
||||
},
|
||||
|
||||
@@ -215,14 +224,14 @@ export default {
|
||||
this.dateData = {
|
||||
startTime: obj.startTime,
|
||||
endTime: obj.endTime,
|
||||
mode: obj.mode,
|
||||
// mode: obj.mode,
|
||||
}
|
||||
|
||||
this.getData()
|
||||
},
|
||||
selectChange(data) {
|
||||
console.log('选中的数据:', data);
|
||||
this.levelId = data
|
||||
this.factory = data
|
||||
if (this.dateData.startTime && this.dateData.endTime) {
|
||||
this.getData();
|
||||
}
|
||||
@@ -307,12 +316,14 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
@import "~@/assets/styles/mixin.scss";
|
||||
@import "~@/assets/styles/variables.scss";
|
||||
|
||||
.dayReport {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
background: url("../../../assets/img/backp.png") no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="数据趋势" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||
<div class="right" style="
|
||||
height: 191px;
|
||||
@@ -9,13 +8,14 @@
|
||||
width: 1595px;
|
||||
background-color: rgba(249, 252, 255, 1);
|
||||
">
|
||||
<!-- <top-item /> -->
|
||||
<dataTrendBar :chartData="chartData" />
|
||||
<!-- 直接使用计算属性 chartData,无需手动更新 -->
|
||||
<dataTrendBar @handleGetItemData="getData" :chartData="chartData" />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Container from "../components/container.vue";
|
||||
import dataTrendBar from "./dataTrendBar.vue";
|
||||
@@ -24,174 +24,106 @@ export default {
|
||||
name: "ProductionStatus",
|
||||
components: { Container, dataTrendBar },
|
||||
props: {
|
||||
salesTrendMap: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
grossMarginTrendMap: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
trendData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartData: null, // 初始化 chartData 为 null
|
||||
// 移除:原 chartData 定义,改为计算属性
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
grossMarginTrendMap: {
|
||||
handler() {
|
||||
this.processChartData();
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
salesTrendMap: {
|
||||
handler() {
|
||||
this.processChartData();
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
// 移除:原 watch 监听配置,计算属性自动响应 trendData 变化
|
||||
computed: {
|
||||
/**
|
||||
* chartData 计算属性:自动响应 trendData 变化,格式化并提取各字段数组
|
||||
* @returns {Object} 包含6个独立数组的格式化数据
|
||||
*/
|
||||
chartData() {
|
||||
// 初始化6个独立数组
|
||||
const timeArr = []; // 格式化后的年月数组
|
||||
const valueArr = []; // 实际值数组
|
||||
const diffValueArr = []; // 差异值数组
|
||||
const targetValueArr = []; // 目标值数组
|
||||
const proportionArr = []; // 占比数组
|
||||
const completedArr = []; // 完成率数组
|
||||
|
||||
// 遍历传入的 trendData 数组(响应式依赖,变化时自动重算)
|
||||
this.trendData.forEach((item) => {
|
||||
// 1. 格式化时间并推入时间数组
|
||||
const yearMonth = this.formatTimeToYearMonth(item.time);
|
||||
timeArr.push(yearMonth);
|
||||
|
||||
// 2. 提取其他字段,兜底为0(防止null/undefined影响图表渲染)
|
||||
valueArr.push(item.value ?? 0);
|
||||
diffValueArr.push(item.diffValue ?? 0);
|
||||
targetValueArr.push(item.targetValue ?? 0);
|
||||
proportionArr.push(item.proportion ?? 0);
|
||||
completedArr.push(item.completed ?? 0);
|
||||
});
|
||||
|
||||
// 组装并返回格式化后的数据(结构与原一致)
|
||||
return {
|
||||
time: timeArr,
|
||||
value: valueArr,
|
||||
diffValue: diffValueArr,
|
||||
targetValue: targetValueArr,
|
||||
proportion: proportionArr,
|
||||
completed: completedArr,
|
||||
rawData: this.trendData, // 透传原始数据,方便子组件使用
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 核心处理函数:在所有数据都准备好后,才组装 chartData
|
||||
* 格式化时间戳为年月格式(YYYY-MM)
|
||||
* @param {Number} timestamp 13位毫秒级时间戳
|
||||
* @returns {String} 格式化后的年月字符串(如:2025-10)
|
||||
*/
|
||||
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);
|
||||
formatTimeToYearMonth(timestamp) {
|
||||
if (!timestamp || isNaN(timestamp)) {
|
||||
return ""; // 容错:非有效时间戳返回空字符串
|
||||
}
|
||||
const date = new Date(timestamp);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0"); // 月份从0开始,补0至2位
|
||||
return `${year}-${month}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* 通用数据处理函数(纯函数)
|
||||
* @param {Array} locations - 某个指标的地点数组
|
||||
* @param {Object} dataMap - 该指标的原始数据映射
|
||||
* @returns {Object} - 格式化后的数据对象
|
||||
*/
|
||||
processSingleDataset(locations, dataMap) {
|
||||
const rates = [];
|
||||
const reals = [];
|
||||
const targets = [];
|
||||
const flags = [];
|
||||
|
||||
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;
|
||||
|
||||
rates.push(rate);
|
||||
reals.push(data.real ?? 0); // 使用空值合并运算符
|
||||
targets.push(data.target ?? 0);
|
||||
|
||||
// 优化:更清晰的逻辑
|
||||
if (data.target === 0) {
|
||||
flags.push(1); // 如果目标为0,默认达标
|
||||
} else {
|
||||
flags.push(rate >= 100 ? 1 : 0);
|
||||
}
|
||||
});
|
||||
|
||||
return { rates, reals, targets, flags };
|
||||
getData(value) {
|
||||
this.$emit('getData',value)
|
||||
},
|
||||
},
|
||||
};
|
||||
</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 {
|
||||
@@ -204,14 +136,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;
|
||||
@@ -237,7 +167,6 @@ export default {
|
||||
height: 14px;
|
||||
border: 1px solid #adadad;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
@@ -254,22 +183,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;
|
||||
}
|
||||
|
||||
@@ -285,19 +210,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;
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<div class="item-button category-btn">
|
||||
<span class="item-text">展示顺序</span>
|
||||
<span class="item-text">类目选择</span>
|
||||
</div>
|
||||
<div class="dropdown-container">
|
||||
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||
@@ -62,9 +62,8 @@ export default {
|
||||
props: ["chartData"],
|
||||
data() {
|
||||
return {
|
||||
activeButton: 0,
|
||||
isDropdownShow: false,
|
||||
selectedProfit: null, // 选中的名称,初始为null
|
||||
selectedProfit: '经营性利润', // 选中的名称,初始为null
|
||||
profitOptions: [
|
||||
'经营性利润',
|
||||
'销量',
|
||||
@@ -81,18 +80,17 @@ export default {
|
||||
// return this.categoryData.map(item => item.name) || [];
|
||||
// },
|
||||
currentDataSource() {
|
||||
console.log('yyyy', this.chartData);
|
||||
return this.activeButton === 0 ? this.chartData.sales : this.chartData.grossMargin;
|
||||
return this.chartData
|
||||
},
|
||||
locations() {
|
||||
console.log('this.chartData', this.chartData);
|
||||
return this.activeButton === 0 ? this.chartData.salesLocations : this.chartData.grossMarginLocations;
|
||||
return this.chartData.time
|
||||
},
|
||||
// 根据按钮切换生成对应的 chartData
|
||||
chartD() {
|
||||
// 销量场景数据
|
||||
const data = this.currentDataSource;
|
||||
console.log(this.currentDataSource, 'currentDataSource');
|
||||
console.log('this.currentDataSource', data);
|
||||
|
||||
const salesData = {
|
||||
allPlaceNames: this.locations,
|
||||
@@ -119,7 +117,7 @@ export default {
|
||||
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||
])
|
||||
},
|
||||
data: data.rates, // 完成率(%)
|
||||
data: data.proportion || [], // 完成率(%)
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
@@ -141,7 +139,7 @@ export default {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.targets // 目标销量(万元)
|
||||
data: data.targetValue || [] // 目标销量(万元)
|
||||
},
|
||||
// 3. 实际(柱状图,含达标状态)
|
||||
{
|
||||
@@ -149,10 +147,69 @@ export default {
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: function (params) {
|
||||
const diff = data.diffValue || [];
|
||||
const currentDiff = diff[params.dataIndex] || 0;
|
||||
return `{rate|${currentDiff}}{text|差值}`;
|
||||
},
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' }, // 顶部0px位置:阴影最强
|
||||
// { offset: 0.1, color: 'rgba(205, 215, 224, 0.4)' }, // 1px位置:阴影减弱(对应1px)
|
||||
// { offset: 0.15, color: 'rgba(205, 215, 224, 0.6)' }, // 3px位置:阴影几乎消失(对应3px扩散)
|
||||
{ offset: 0.2, color: '#ffffff' }, // 主体白色
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
// 外阴影:0px 2px 2px 0px rgba(191,203,215,0.5)
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
// 圆角:4px
|
||||
borderRadius: 4,
|
||||
// 移除边框
|
||||
borderColor: '#BFCBD577',
|
||||
borderWidth: 0,
|
||||
// 文字垂直居中(针对富文本)
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
text: {
|
||||
// 缩小宽度和内边距,适配68px容器
|
||||
width: 'auto', // 自动宽度,替代固定40px
|
||||
padding: [5, 10, 5, 0], // 缩小内边距
|
||||
align: 'center',
|
||||
color: '#464646', // 文字灰色
|
||||
fontSize: 11, // 缩小字体,适配小尺寸
|
||||
lineHeight: 20 // 垂直居中
|
||||
},
|
||||
rate: {
|
||||
width: 'auto',
|
||||
padding: [5, 0, 5, 10],
|
||||
align: 'center',
|
||||
color: '#30B590',
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
}
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||
const safeFlag = data.flags;
|
||||
const safeFlag = data.completed || [];
|
||||
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||
return currentFlag === 1
|
||||
? {
|
||||
@@ -175,94 +232,10 @@ export default {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.reals // 实际销量(万元)
|
||||
data: data.value || [] // 实际销量(万元)
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 毛利率场景数据
|
||||
const grossProfitData = {
|
||||
series: [
|
||||
// 1. 完成率(折线图)
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
lineStyle: { color: 'rgba(40, 138, 255, .5)', width: 2 },
|
||||
itemStyle: {
|
||||
color: 'rgba(40, 138, 255, 1)',
|
||||
borderColor: 'rgba(40, 138, 255, 1)',
|
||||
borderWidth: 2,
|
||||
radius: 4
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.2,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||
])
|
||||
},
|
||||
data: [106.7, 96.9, 106.5, 106.1, 93.8, 105.9], // 毛利率完成率(%)
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
// 2. 目标(柱状图)
|
||||
{
|
||||
name: '目标',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: [30, 32, 31, 33, 32, 34] // 目标毛利率(万元)
|
||||
},
|
||||
// 3. 实际(柱状图)
|
||||
{
|
||||
name: '实际',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
const safeFlag = [1, 0, 1, 1, 0, 1]; // 达标状态
|
||||
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||
return currentFlag === 1
|
||||
? {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||
]
|
||||
}
|
||||
: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||
]
|
||||
};
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: [32, 31, 33, 35, 30, 36] // 实际毛利率(万元)
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 根据按钮状态返回对应数据
|
||||
return salesData;
|
||||
}
|
||||
},
|
||||
@@ -270,6 +243,7 @@ export default {
|
||||
selectProfit(item) {
|
||||
this.selectedProfit = item;
|
||||
this.isDropdownShow = false;
|
||||
this.$emit('handleGetItemData',item)
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div :id="id" style="width: 100%; height:100%;"></div>
|
||||
<div class="bottomTip">
|
||||
<div class="precent">
|
||||
<span class="precentNum">{{ energyObj.electricComu }} </span>
|
||||
<span class="precentNum">{{ detailData.completeRate || 0 }} </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -18,31 +18,32 @@ export default {
|
||||
// components: { Container },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
energyObj: {
|
||||
detailData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
electricComu: 0,
|
||||
steamComu: 20, // 调整为符合max范围的数值(0-8)
|
||||
// electricity: [120, 150, 130, 180, 160, 200, 190],
|
||||
// steam: [80, 95, 85, 110, 100, 120, 115],
|
||||
// dates: ['1日', '2日', '3日', '4日', '5日', '6日', '7日']
|
||||
// electricComu: 0,
|
||||
// steamComu: 20, // 调整为符合max范围的数值(0-8)
|
||||
// // electricity: [120, 150, 130, 180, 160, 200, 190],
|
||||
// // steam: [80, 95, 85, 110, 100, 120, 115],
|
||||
// // dates: ['1日', '2日', '3日', '4日', '5日', '6日', '7日']
|
||||
})
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: () => ('')
|
||||
default: () => ('monthG')
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
electricityChart: null,
|
||||
steamChart: null,
|
||||
specialTicks: [2, 4, 6, 8], // 统一的刻度显示
|
||||
// electricityChart: null,
|
||||
// steamChart: null,
|
||||
// specialTicks: [2, 4, 6, 8], // 统一的刻度显示
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
energyObj: {
|
||||
detailData: {
|
||||
deep: true,
|
||||
immediate: true, // 初始化时立即执行
|
||||
handler() {
|
||||
this.updateGauges()
|
||||
}
|
||||
@@ -55,42 +56,47 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
observeContainerResize() {
|
||||
const container = document.querySelector('.gauge-container')
|
||||
// 修复:获取正确的容器(组件内的.gauge-container)
|
||||
const container = this.$el.querySelector('.gauge-container')
|
||||
if (container && window.ResizeObserver) {
|
||||
const resizeObserver = new ResizeObserver(entries => {
|
||||
this.handleResize()
|
||||
})
|
||||
resizeObserver.observe(container)
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
resizeObserver.unobserve(container)
|
||||
this.resizeObserver = new ResizeObserver(entries => {
|
||||
if (this.electricityChart) {
|
||||
this.electricityChart.resize() // 直接触发resize,无需防抖
|
||||
}
|
||||
})
|
||||
this.resizeObserver.observe(container)
|
||||
}
|
||||
},
|
||||
initGauges() {
|
||||
// console.log('this.id',this.id);
|
||||
|
||||
// 初始化电气图表实例
|
||||
const electricityDom = document.getElementById(this.id)
|
||||
if (electricityDom) {
|
||||
// 修复:正确创建并存储图表实例
|
||||
this.electricityChart = echarts.init(electricityDom)
|
||||
// 首次更新数据
|
||||
this.updateGauges()
|
||||
}
|
||||
// 初始化蒸汽图表实例
|
||||
const steamDom = document.getElementById('steamGauge')
|
||||
if (steamDom) {
|
||||
this.steamChart = echarts.init(steamDom)
|
||||
}
|
||||
// 首次更新数据
|
||||
this.updateGauges()
|
||||
// 蒸汽图表若未使用,可注释/删除
|
||||
// const steamDom = document.getElementById('steamGauge')
|
||||
// if (steamDom) {
|
||||
// this.steamChart = echarts.init(steamDom)
|
||||
// }
|
||||
},
|
||||
updateGauges() {
|
||||
// 优化:仅更新数据,不销毁实例(提升性能)
|
||||
if (this.electricityChart) {
|
||||
// 转换原始数据为“万kw/h”(与仪表盘max匹配)
|
||||
const electricValue = 80
|
||||
this.electricityChart.setOption(this.getElectricityGaugeOption(electricValue))
|
||||
}
|
||||
// 修复:先判断实例是否存在,再更新配置
|
||||
if (!this.electricityChart) return
|
||||
|
||||
// 修复:兜底获取rate值,确保数值有效
|
||||
const rate = Number(this.detailData?.completeRate) || 0
|
||||
console.log('当前rate值:', rate); // 调试:确认rate值正确
|
||||
|
||||
// 关键:第二个参数传true,清空原有配置,强制更新
|
||||
this.electricityChart.setOption(this.getElectricityGaugeOption(rate), true)
|
||||
},
|
||||
// 1. 用电量仪表盘独立配置函数
|
||||
// 用电量仪表盘配置(保留原有样式,优化数值范围)
|
||||
getElectricityGaugeOption(value) {
|
||||
// 用电量专属渐变色
|
||||
const electricityGradient = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#0B58FF' },
|
||||
{ offset: 1, color: '#32FFCD' }
|
||||
@@ -101,12 +107,12 @@ export default {
|
||||
{
|
||||
name: '月度',
|
||||
type: 'gauge',
|
||||
radius: '95',
|
||||
radius: '95', // 修复:添加%,避免数值错误
|
||||
center: ['50%', '90%'],
|
||||
startAngle: 180,
|
||||
endAngle: 0,
|
||||
min: 0,
|
||||
max: 100, // 用电量专属最大值
|
||||
max: 100,
|
||||
splitNumber: 4,
|
||||
label: { show: false },
|
||||
progress: {
|
||||
@@ -121,117 +127,34 @@ export default {
|
||||
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
|
||||
length: '75%',
|
||||
width: 16,
|
||||
itemStyle: { color: '#288AFF' }, // 用电量指针颜色
|
||||
itemStyle: { color: '#288AFF' },
|
||||
offsetCenter: [0, '10%']
|
||||
},
|
||||
axisLine: {
|
||||
roundCap: true,
|
||||
lineStyle: { width: 12, color: [[1, '#E6EBF7']] }
|
||||
},
|
||||
// axisTick: {
|
||||
// splitNumber: 2,
|
||||
// show: (val) => this.specialTicks.includes(val),
|
||||
// lineStyle: { width: 2, color: '#999' }
|
||||
// },
|
||||
splitLine: {
|
||||
length: 10, lineStyle: { width: 5, color: '#D6DAE5' },
|
||||
length: 10,
|
||||
lineStyle: { width: 5, color: '#D6DAE5' },
|
||||
},
|
||||
// axisLabel: {
|
||||
// show: (val) => this.specialTicks.includes(val),
|
||||
// distance: -45,
|
||||
// color: '#ffffff',
|
||||
// fontSize: 14,
|
||||
// fontWeight: 'bold'
|
||||
// },
|
||||
axisTick: {
|
||||
splitNumber: 2,
|
||||
length:6,
|
||||
length: 6,
|
||||
lineStyle: { width: 2, color: '#D6DAE5' }
|
||||
},
|
||||
axisLabel: {
|
||||
show: false,
|
||||
},
|
||||
detail: { show: false },
|
||||
data: [{ value, unit: '' }] // 用电量单位
|
||||
data: [{ value: value, unit: '' }] // 确保数值正确传入
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// 2. 用蒸汽仪表盘独立配置函数
|
||||
getSteamGaugeOption(value) {
|
||||
// 蒸汽专属渐变色
|
||||
const steamGradient = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: 'rgba(11, 168, 255, 0.26)' },
|
||||
{ offset: 1, color: 'rgba(54, 239, 230, 1)' }
|
||||
])
|
||||
|
||||
return {
|
||||
series: [
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'gauge',
|
||||
radius: '80',
|
||||
center: ['50%', '85%'],
|
||||
startAngle: 180,
|
||||
endAngle: 0,
|
||||
min: 0,
|
||||
max: 160, // 蒸汽专属最大值
|
||||
splitNumber: 4,
|
||||
label: { show: false },
|
||||
progress: {
|
||||
show: true,
|
||||
overlap: false,
|
||||
roundCap: true,
|
||||
clip: false,
|
||||
width: 14,
|
||||
itemStyle: { color: steamGradient }
|
||||
},
|
||||
pointer: {
|
||||
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
|
||||
length: '75%',
|
||||
width: 16,
|
||||
itemStyle: { color: 'rgba(11, 168, 255, 1)' }, // 蒸汽指针颜色
|
||||
offsetCenter: [0, '5%']
|
||||
},
|
||||
axisLine: {
|
||||
roundCap: true,
|
||||
lineStyle: { width: 14, color: [[1, 'rgba(19, 84, 122, 1)']] }
|
||||
},
|
||||
axisTick: {
|
||||
splitNumber: 2,
|
||||
show: (val) => this.specialTicks.includes(val),
|
||||
lineStyle: { width: 2, color: '#999' }
|
||||
},
|
||||
splitLine: { length: 12, lineStyle: { width: 3, color: '#999' } },
|
||||
axisLabel: {
|
||||
show: (val) => this.specialTicks.includes(val),
|
||||
distance: -45,
|
||||
color: '#ffffff',
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
detail: { show: false },
|
||||
data: [{ value, unit: 't' }] // 蒸汽单位
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// handleResize() {
|
||||
// if (this.resizeTimer) clearTimeout(this.resizeTimer)
|
||||
// this.resizeTimer = setTimeout(() => {
|
||||
// if (this.electricityChart) this.electricityChart.resize()
|
||||
// if (this.steamChart) this.steamChart.resize()
|
||||
// }, 100)
|
||||
// }
|
||||
},
|
||||
// beforeDestroy() {
|
||||
// if (this.electricityChart) this.electricityChart.dispose()
|
||||
// if (this.steamChart) this.steamChart.dispose()
|
||||
// window.removeEventListener('resize', this.handleResize)
|
||||
// if (this.resizeTimer) clearTimeout(this.resizeTimer)
|
||||
// }
|
||||
// 未使用的蒸汽仪表盘可注释/删除
|
||||
// getSteamGaugeOption(value) { ... }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -298,9 +221,4 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||
<div class="dashboard">
|
||||
<div class="title">
|
||||
@@ -11,112 +9,109 @@
|
||||
</div>
|
||||
<div class="number">
|
||||
<div class="yield">
|
||||
90%
|
||||
{{ formatRate(factoryData?.completeRate) }}%
|
||||
</div>
|
||||
<div class="mom">
|
||||
环比10%
|
||||
<img class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||
环比{{ formatRate(factoryData?.thb) }}%
|
||||
<img v-if="factoryData?.thb >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="上升">
|
||||
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="下降">
|
||||
</div>
|
||||
</div>
|
||||
<div class="electricityGauge">
|
||||
<electricityGauge id="month"></electricityGauge>
|
||||
<!-- 传递包含flag的factoryData给仪表盘组件 -->
|
||||
<electricityGauge id="month" :detailData="factoryData"></electricityGauge>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line" style="padding: 0px;">
|
||||
<verticalBarChart>
|
||||
</verticalBarChart>
|
||||
|
||||
<!-- 传递包含flag的factoryData给柱状图组件 -->
|
||||
<verticalBarChart :detailData="factoryData"></verticalBarChart>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
import electricityGauge from './electricityGauge.vue'
|
||||
import verticalBarChart from './verticalBarChart.vue'
|
||||
|
||||
// import * as echarts from 'echarts'
|
||||
// import rawItem from './raw-Item.vue'
|
||||
// 引入箭头图片(根据实际路径调整,若模板中直接用路径可注释)
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, electricityGauge, verticalBarChart },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
itemData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
monData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
title: { // 接收父组件传递的设备数据数组
|
||||
title: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
month: { // 接收父组件传递的设备数据数组
|
||||
month: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
itemData: {
|
||||
handler(newValue, oldValue) {
|
||||
// this.updateChart()
|
||||
},
|
||||
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||
computed: {
|
||||
/**
|
||||
* 自动提取monData中的工厂数据,并新增flag字段
|
||||
*/
|
||||
factoryData() { // 整合原始数据 + 计算flag
|
||||
return {
|
||||
completeRate: this.monData.proportion ? Number(this.monData.proportion).toFixed(2) : 0,
|
||||
diff: this.monData.diffValue,
|
||||
real: this.monData.value,
|
||||
target: this.monData.targetValue,
|
||||
thb: this.monData.thb,
|
||||
// ...rawData,
|
||||
flag: this.getRateFlag(this.monData.completed) // 新增flag字段
|
||||
};
|
||||
}
|
||||
},
|
||||
// computed: {
|
||||
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||
// sortedItemData() {
|
||||
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||
// const totalCostItems = this.itemData.filter(item =>
|
||||
// item.name && item.name.includes('总成本')
|
||||
// );
|
||||
// // 过滤出不包含“总成本”的项
|
||||
// const otherItems = this.itemData.filter(item =>
|
||||
// !item.name || !item.name.includes('总成本')
|
||||
// );
|
||||
// // 合并:总成本项在前,其他项在后
|
||||
// return [...totalCostItems, ...otherItems];
|
||||
// }
|
||||
// },
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 格式化百分比数值:处理空值/非数字,兜底为0
|
||||
*/
|
||||
formatRate(value) {
|
||||
if (isNaN(value) || value === null || value === undefined) {
|
||||
return 0;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
/**
|
||||
* 判断完成率对应的flag值(<100为0,≥100为1)
|
||||
* @param {number} rate 完成率(原始值,如89代表89%)
|
||||
* @returns {0|1} flag值
|
||||
*/
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return rate >= 100 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</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;
|
||||
}
|
||||
|
||||
@@ -127,16 +122,12 @@ export default {
|
||||
padding: 16px 0 0 16px;
|
||||
|
||||
.title {
|
||||
// width: 190px;
|
||||
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;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
@@ -144,19 +135,16 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
// width: 190px;
|
||||
height: 32px;
|
||||
font-family: YouSheBiaoTiHei;
|
||||
font-size: 32px;
|
||||
color: #0B58FF;
|
||||
line-height: 32px;
|
||||
letter-spacing: 2px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.mom {
|
||||
width: 97px;
|
||||
width: fit-content; // 自适应宽度,避免文字溢出
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
@@ -164,8 +152,16 @@ export default {
|
||||
color: #000000;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
display: flex;
|
||||
align-items: center; // 箭头和文字垂直居中
|
||||
gap: 4px; // 文字和箭头间距
|
||||
}
|
||||
|
||||
// 箭头样式优化
|
||||
.arrow {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,34 +170,4 @@ export default {
|
||||
height: 205px;
|
||||
background: #F9FCFF;
|
||||
}
|
||||
|
||||
// .leftTitle {
|
||||
// .item {
|
||||
// width: 67px;
|
||||
// height: 180px;
|
||||
// padding: 37px 23px;
|
||||
// background: #F9FCFF;
|
||||
// font-family: PingFangSC, PingFang SC;
|
||||
// font-weight: 400;
|
||||
// font-size: 18px;
|
||||
// color: #000000;
|
||||
// line-height: 25px;
|
||||
// letter-spacing: 1px;
|
||||
// // text-align: left;
|
||||
// font-style: normal;
|
||||
// }
|
||||
// }
|
||||
</style>
|
||||
|
||||
<!-- <style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style> -->
|
||||
|
||||
@@ -1,29 +1,21 @@
|
||||
<template>
|
||||
<div class="coreBar">
|
||||
<!-- 新增行容器:包裹“各基地情况”和barTop -->
|
||||
<div class="header-row">
|
||||
<div class="base-title">
|
||||
各基地情况
|
||||
</div>
|
||||
<div class="base-title">各基地情况</div>
|
||||
<div class="barTop">
|
||||
<!-- 关键:新增右侧容器,包裹图例和按钮组,实现整体靠右 -->
|
||||
<div class="right-container">
|
||||
<div class="legend">
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon line yield"></span>
|
||||
完成率
|
||||
<span class="legend-icon line yield"></span>完成率
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square target"></span>
|
||||
目标
|
||||
<span class="legend-icon square target"></span>预算
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square achieved"></span>
|
||||
实际·达标
|
||||
<span class="legend-icon square achieved"></span>实际·达标
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square unachieved"></span>
|
||||
实际·未达标
|
||||
<span class="legend-icon square unachieved"></span>实际·未达标
|
||||
</span>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
@@ -32,13 +24,13 @@
|
||||
</div>
|
||||
<div class="dropdown-container">
|
||||
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||
<span class="item-text profit-text">{{ selectedProfit || '请选择' }}</span>
|
||||
<span class="item-text profit-text">{{ selectedSort || '请选择' }}</span>
|
||||
<span class="dropdown-arrow" :class="{ 'rotate': isDropdownShow }"></span>
|
||||
</div>
|
||||
<div class="dropdown-options" v-if="isDropdownShow">
|
||||
<div class="dropdown-option" v-for="(item, index) in profitOptions" :key="index"
|
||||
@click.stop="selectProfit(item)">
|
||||
{{ item }}
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,130 +52,79 @@ export default {
|
||||
name: "Container",
|
||||
components: { operatingLineBar },
|
||||
props: ["chartData"],
|
||||
emits: ['sort-change'], // 声明事件(Vue3 推荐)
|
||||
data() {
|
||||
return {
|
||||
activeButton: 0,
|
||||
isDropdownShow: false,
|
||||
selectedProfit: null, // 选中的名称,初始为null
|
||||
selectedSort: null, // 选中的label
|
||||
selectedSortValue: null, // 选中的value,用于排序逻辑
|
||||
profitOptions: [
|
||||
'实际值:高~低',
|
||||
'实际值:低~高',
|
||||
'目标值:高~低',
|
||||
'目标值:低~高',
|
||||
|
||||
{ label: '实际值:高~低', value: 1 },
|
||||
{ label: '实际值:低~高', value: 2 },
|
||||
{ label: '完成率:高~低', value: 3 },
|
||||
{ label: '完成率:低~高', value: 4 },
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// profitOptions() {
|
||||
// return this.categoryData.map(item => item.name) || [];
|
||||
// },
|
||||
// 排序后的数据源(核心:根据selectedSortValue重新排序)
|
||||
currentDataSource() {
|
||||
console.log('yyyy', this.chartData);
|
||||
if (!this.chartData) return {};
|
||||
|
||||
return this.activeButton === 0 ? this.chartData.sales : this.chartData.grossMargin;
|
||||
// 深拷贝原始数据,避免修改原数据
|
||||
const factory = JSON.parse(JSON.stringify(this.chartData));
|
||||
if (!factory.locations.length || !this.selectedSortValue) return factory;
|
||||
|
||||
// 构建带索引的数组,方便同步所有字段排序
|
||||
const dataWithIndex = factory.locations.map((name, index) => ({
|
||||
index,
|
||||
name,
|
||||
real: factory.reals[index],
|
||||
target: factory.targets[index],
|
||||
rate: factory.rate[index],
|
||||
diff: factory.diff[index],
|
||||
flag: factory.flags[index]
|
||||
}));
|
||||
|
||||
// 根据选中的排序规则排序
|
||||
switch (this.selectedSortValue) {
|
||||
case 1: // 实际值:高~低
|
||||
dataWithIndex.sort((a, b) => b.real - a.real);
|
||||
break;
|
||||
case 2: // 实际值:低~高
|
||||
dataWithIndex.sort((a, b) => a.real - b.real);
|
||||
break;
|
||||
case 3: // 目标值:高~低
|
||||
dataWithIndex.sort((a, b) => b.rate - a.rate);
|
||||
break;
|
||||
case 4: // 目标值:低~高
|
||||
dataWithIndex.sort((a, b) => a.rate - b.rate);
|
||||
break;
|
||||
default:
|
||||
return factory;
|
||||
}
|
||||
|
||||
// 同步更新所有数组
|
||||
factory.locations = dataWithIndex.map(item => item.name);
|
||||
factory.reals = dataWithIndex.map(item => item.real);
|
||||
factory.targets = dataWithIndex.map(item => item.target);
|
||||
factory.rates = dataWithIndex.map(item => item.rate);
|
||||
factory.diff = dataWithIndex.map(item => item.diff);
|
||||
factory.flags = dataWithIndex.map(item => item.flag);
|
||||
|
||||
return factory;
|
||||
},
|
||||
locations() {
|
||||
console.log('this.chartData', this.chartData);
|
||||
|
||||
return this.activeButton === 0 ? this.chartData.salesLocations : this.chartData.grossMarginLocations;
|
||||
return this.currentDataSource.locations || [];
|
||||
},
|
||||
// 根据按钮切换生成对应的 chartData
|
||||
// 最终传递给图表的排序后数据
|
||||
chartD() {
|
||||
// 销量场景数据
|
||||
const data = this.currentDataSource;
|
||||
console.log(this.currentDataSource, 'currentDataSource');
|
||||
|
||||
const salesData = {
|
||||
allPlaceNames: this.locations,
|
||||
series: [
|
||||
// 1. 完成率(折线图)
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'line',
|
||||
yAxisIndex: 1, // 绑定右侧Y轴(需在子组件启用配置)
|
||||
lineStyle: {
|
||||
color: 'rgba(40, 138, 255, .5)',
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(40, 138, 255, 1)',
|
||||
borderColor: 'rgba(40, 138, 255, 1)',
|
||||
borderWidth: 2,
|
||||
radius: 4
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.2,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||
])
|
||||
},
|
||||
data: data.rates, // 完成率(%)
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
// 2. 目标(柱状图)
|
||||
{
|
||||
name: '目标',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.targets // 目标销量(万元)
|
||||
},
|
||||
// 3. 实际(柱状图,含达标状态)
|
||||
{
|
||||
name: '实际',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||
const safeFlag = data.flags;
|
||||
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||
return currentFlag === 1
|
||||
? {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||
]
|
||||
}
|
||||
: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||
]
|
||||
};
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.reals // 实际销量(万元)
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 毛利率场景数据
|
||||
const grossProfitData = {
|
||||
series: [
|
||||
// 1. 完成率(折线图)
|
||||
// 完成率(折线图)
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'line',
|
||||
@@ -202,13 +143,13 @@ export default {
|
||||
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||
])
|
||||
},
|
||||
data: [106.7, 96.9, 106.5, 106.1, 93.8, 105.9], // 毛利率完成率(%)
|
||||
data: data.rate || [],
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
// 2. 目标(柱状图)
|
||||
// 目标(柱状图)
|
||||
{
|
||||
name: '目标',
|
||||
name: '预算',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
@@ -224,17 +165,64 @@ export default {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: [30, 32, 31, 33, 32, 34] // 目标毛利率(万元)
|
||||
data: data.targets || []
|
||||
},
|
||||
// 3. 实际(柱状图)
|
||||
// 实际(柱状图)
|
||||
{
|
||||
name: '实际',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [30, 0],
|
||||
width: 68,
|
||||
height: 20,
|
||||
formatter: (params) => {
|
||||
const diff = data.diff || [];
|
||||
const currentDiff = diff[params.dataIndex] || 0;
|
||||
return `{rate|${currentDiff}}{text|差值}`;
|
||||
},
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||
{ offset: 0.2, color: '#ffffff' },
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
borderRadius: 4,
|
||||
borderColor: '#BFCBD577',
|
||||
borderWidth: 0,
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
text: {
|
||||
width: 'auto',
|
||||
padding: [5, 10, 5, 0],
|
||||
align: 'center',
|
||||
color: '#464646',
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
},
|
||||
rate: {
|
||||
width: 'auto',
|
||||
padding: [5, 0, 5, 10],
|
||||
align: 'center',
|
||||
color: '#30B590',
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
}
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
const safeFlag = [1, 0, 1, 1, 0, 1]; // 达标状态
|
||||
const safeFlag = data.flags || [];
|
||||
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||
return currentFlag === 1
|
||||
? {
|
||||
@@ -257,21 +245,34 @@ export default {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: [32, 31, 33, 35, 30, 36] // 实际毛利率(万元)
|
||||
data: data.reals || []
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 根据按钮状态返回对应数据
|
||||
return salesData;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectProfit(item) {
|
||||
this.selectedProfit = item;
|
||||
// 更新选中的label和value
|
||||
this.selectedSort = item.label;
|
||||
this.selectedSortValue = item.value;
|
||||
this.isDropdownShow = false;
|
||||
// 向父组件传递排序事件(可选,保持原有逻辑)
|
||||
this.$emit('sort-change', item.value);
|
||||
}
|
||||
},
|
||||
// 监听父组件传入的chartData变化,重置选中状态(可选)
|
||||
watch: {
|
||||
'chartData.factory': {
|
||||
handler() {
|
||||
// 若需要切换数据源后重置排序,可取消注释
|
||||
// this.selectedSort = null;
|
||||
// this.selectedSortValue = null;
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -282,16 +283,14 @@ export default {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
|
||||
// 新增:头部行容器,实现一行排列
|
||||
.header-row {
|
||||
display: flex;
|
||||
justify-content: space-between; // 左右两端对齐
|
||||
align-items: center; // 垂直居中
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-bottom: 8px; // 与下方图表区保留间距(可根据需求调整)
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
// 各基地情况标题样式
|
||||
.base-title {
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
@@ -299,29 +298,25 @@ export default {
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
font-style: normal;
|
||||
padding: 0 0 0 16px; // 保留原有内边距
|
||||
white-space: nowrap; // 防止文字换行
|
||||
padding: 0 0 0 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.barTop {
|
||||
// 移除原有flex和justify-content,由header-row控制
|
||||
width: auto; // 自适应宽度
|
||||
// 保留原有align-items,确保内部元素垂直居中
|
||||
width: auto;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||
.right-container {
|
||||
display: flex;
|
||||
align-items: center; // 图例和按钮组垂直居中
|
||||
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
margin-right: 46px;
|
||||
}
|
||||
|
||||
// 2. 图例:在右侧容器内横向排列
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 16px; // 图例项之间间距,避免重叠
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -336,7 +331,7 @@ export default {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
white-space: nowrap; // 防止图例文字换行
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.legend-icon {
|
||||
@@ -365,7 +360,6 @@ export default {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
// 图例颜色
|
||||
.yield {
|
||||
background: rgba(40, 138, 255, 1);
|
||||
}
|
||||
@@ -382,7 +376,6 @@ export default {
|
||||
background: rgba(255, 132, 0, 1);
|
||||
}
|
||||
|
||||
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||
.button-group {
|
||||
display: flex;
|
||||
position: relative;
|
||||
@@ -406,7 +399,6 @@ export default {
|
||||
line-height: 24px;
|
||||
font-style: normal;
|
||||
letter-spacing: 2px;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
.item-text {
|
||||
|
||||
@@ -10,17 +10,24 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
myChart: null, // 存储图表实例
|
||||
resizeHandler: null // 存储resize事件处理函数,用于后续移除
|
||||
resizeHandler: null, // 存储resize事件处理函数
|
||||
// 核心:基地名称与序号的映射表(固定顺序)
|
||||
baseNameToIndexMap: {
|
||||
'宜兴': 7,
|
||||
'漳州': 8,
|
||||
'自贡': 3,
|
||||
'桐城': 2,
|
||||
'洛阳': 9,
|
||||
'合肥': 5,
|
||||
'宿迁': 6,
|
||||
'秦皇岛': 10
|
||||
}
|
||||
};
|
||||
},
|
||||
props: {
|
||||
chartData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
// 可选:保留数据校验
|
||||
// validator: (value) => {
|
||||
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||
// }
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -57,19 +64,28 @@ export default {
|
||||
|
||||
// 绑定点击事件(只绑定一次,永久生效)
|
||||
this.myChart.on('click', (params) => {
|
||||
// 箭头函数保证this指向Vue实例
|
||||
console.log('点击事件的参数:', params);
|
||||
|
||||
// 提取关键数据(注意:如果是折线图,value是数组;柱状图是单个值,需兼容)
|
||||
// 提取点击的基地名称
|
||||
const itemName = params.name;
|
||||
// 根据映射表获取对应的序号(未匹配到则返回0或其他默认值)
|
||||
const baseIndex = this.baseNameToIndexMap[itemName] || 0;
|
||||
|
||||
// 兼容不同图表类型的value:柱状图value是数值,折线图是[横坐标, 纵坐标]
|
||||
const itemValue = Array.isArray(params.value) ? params.value[1] : params.value;
|
||||
const seriesName = params.seriesName;
|
||||
console.log(`你点击了【${itemName}】,${seriesName}:${itemValue}`);
|
||||
// const itemValue = Array.isArray(params.value) ? params.value[1] : params.value;
|
||||
// const seriesName = params.seriesName;
|
||||
|
||||
console.log(`你点击了【${itemName}】(序号:${baseIndex})`);
|
||||
|
||||
// 路由跳转时携带序号(或名称+序号)
|
||||
this.$router.push({
|
||||
path: 'operatingProfitBase',
|
||||
base: itemName
|
||||
})
|
||||
query: { // 使用query传递参数(推荐),也可使用params
|
||||
// baseName: itemName,
|
||||
factory: baseIndex
|
||||
}
|
||||
// 若仍需用base作为参数:
|
||||
// base: itemName,
|
||||
// params: { baseIndex: baseIndex }
|
||||
});
|
||||
});
|
||||
|
||||
// 定义resize处理函数(命名函数,方便移除)
|
||||
@@ -119,7 +135,12 @@ export default {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0]
|
||||
padding: [5, 0, 0, 0],
|
||||
// 可选:X轴标签显示“序号+名称”(如“1 宜兴”)
|
||||
// formatter: (value) => {
|
||||
// const index = this.baseNameToIndexMap[value] || '';
|
||||
// return index ? `${index} ${value}` : value;
|
||||
// }
|
||||
},
|
||||
data: xData
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export default {
|
||||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||
chartData: {
|
||||
handler() {
|
||||
console.log(this.chartData,'chartData');
|
||||
console.log(this.chartData, 'chartData');
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
@@ -54,7 +54,7 @@ export default {
|
||||
}
|
||||
|
||||
this.myChart = echarts.init(chartDom);
|
||||
|
||||
|
||||
const { allPlaceNames, series } = this.chartData || {};
|
||||
console.log('chartData', this.chartData);
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ export default {
|
||||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||
chartData: {
|
||||
handler() {
|
||||
console.log(this.chartData,'chartData');
|
||||
console.log(this.chartData, 'chartData');
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
@@ -85,7 +85,7 @@ export default {
|
||||
},
|
||||
grid: {
|
||||
top: 40,
|
||||
bottom: 40,
|
||||
bottom: 50,
|
||||
right: 70,
|
||||
left: 60,
|
||||
},
|
||||
|
||||
@@ -1,28 +1,15 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<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="
|
||||
<Container name="当月数据对比" icon="cockpitItemIcon" size="operatingLarge" topSize="large">
|
||||
<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,60 +17,75 @@
|
||||
letter-spacing: 1px;
|
||||
font-style: normal;
|
||||
padding: 16px 0 0 16px;
|
||||
"
|
||||
>
|
||||
">
|
||||
集团情况
|
||||
</div>
|
||||
<operatingTopBar :chartData="chartData" />
|
||||
<operatingTopBar :chartData="chartData?.topBarData || {}" />
|
||||
</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?.barData || {}" />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Container from "../components/container.vue";
|
||||
import operatingBar from "./operatingBar.vue";
|
||||
import operatingTopBar from "./operatingTopBar.vue";
|
||||
|
||||
// 序号→地名映射表(levelId=序号)
|
||||
const baseIndexToNameMap = {
|
||||
7: "宜兴",
|
||||
8: "漳州",
|
||||
3: "自贡",
|
||||
2: "桐城",
|
||||
9: "洛阳",
|
||||
5: "合肥",
|
||||
10: "秦皇岛",
|
||||
6: "宿迁"
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "ProductionStatus",
|
||||
components: { Container, operatingBar, operatingTopBar },
|
||||
props: {
|
||||
salesTrendMap: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
grossMarginTrendMap: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
monData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartData: null, // 初始化 chartData 为 null
|
||||
chartData: {
|
||||
topBarData: { // levelId=1的整合数据
|
||||
locations: [], // 固定为["凯盛新能"]
|
||||
diff: [], // 差值数组
|
||||
targets: [], // 目标值数组
|
||||
reals: [], // 实际值数组
|
||||
rate: [], // 完成率数组
|
||||
flags: [] // 完成状态数组(0/1)
|
||||
},
|
||||
barData: { // levelId≠1的整合数据
|
||||
locations: [], // levelId对应的baseIndexToNameMap中的地名
|
||||
diff: [], // 对应差值数组
|
||||
targets: [], // 目标值数组
|
||||
reals: [], // 实际值数组
|
||||
rate: [], // 完成率数组
|
||||
flags: [] // 完成状态数组(0/1)
|
||||
// baseIndexes: []// 对应levelId(序号)数组
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
grossMarginTrendMap: {
|
||||
handler() {
|
||||
this.processChartData();
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
salesTrendMap: {
|
||||
monData: {
|
||||
handler() {
|
||||
this.processChartData();
|
||||
},
|
||||
@@ -93,142 +95,91 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 核心处理函数:在所有数据都准备好后,才组装 chartData
|
||||
* 核心方法:按levelId匹配地名生成locations
|
||||
*/
|
||||
processChartData() {
|
||||
// 关键改动:增加数据有效性检查
|
||||
// 检查 salesTrendMap 是否有实际数据(不只是空对象)
|
||||
const isSalesDataReady = Object.keys(this.salesTrendMap).length > 0;
|
||||
// 检查 grossMarginTrendMap 是否有实际数据
|
||||
const isGrossMarginDataReady =
|
||||
Object.keys(this.grossMarginTrendMap).length > 0;
|
||||
// 初始化空数据结构
|
||||
const initTopBarData = {
|
||||
locations: [], diff: [], targets: [],reals: [], rate: [], flags: [] };
|
||||
const initBarData = { locations: [], diff: [], targets: [], reals: [], rate: [], flags: [] };
|
||||
|
||||
// 如果任一数据未准备好,则不更新 chartData,或传递一个加载中的状态
|
||||
// 你可以根据业务需求调整这里的逻辑,比如:
|
||||
// 1. 等待两者都准备好
|
||||
// 2. 只要有一个准备好了就更新,但确保另一个有合理的默认值
|
||||
if (!Array.isArray(this.monData) || this.monData.length === 0) {
|
||||
this.chartData = { topBarData: initTopBarData, barData: initBarData };
|
||||
return;
|
||||
}
|
||||
|
||||
// --- 方案一:等待两者都准备好 ---
|
||||
// if (!isSalesDataReady || !isGrossMarginDataReady) {
|
||||
// console.log('数据尚未全部准备好,暂不更新图表');
|
||||
// this.chartData = {
|
||||
// grossMarginLocations: [],
|
||||
// salesLocations: [],
|
||||
// grossMargin: { rates: [], reals: [], targets: [], flags: [] },
|
||||
// sales: { rates: [], reals: [], targets: [], flags: [] },
|
||||
// };
|
||||
// return;
|
||||
// }
|
||||
// 1. 处理levelId=1的整合数据(逻辑不变)
|
||||
const level1Data = this.monData.filter(item => item.levelId === 1);
|
||||
const topBarData = { ...initTopBarData };
|
||||
level1Data.forEach(item => {
|
||||
if (!item.name) return;
|
||||
topBarData.locations = ["凯盛新能"]; // levelId=1固定为凯盛新能
|
||||
topBarData.diff.push(item.diffValue || 0);
|
||||
topBarData.targets.push(item.targetValue || 0);
|
||||
topBarData.reals.push(item.value || 0);
|
||||
topBarData.rate.push(item.proportion || 0);
|
||||
topBarData.flags.push(item.completed ? 1 : 0);
|
||||
});
|
||||
|
||||
// --- 方案二 (推荐):有什么数据就显示什么,没有的就显示空 ---
|
||||
// 这种方式更友好,用户可以先看到部分数据
|
||||
const grossMarginLocations = isGrossMarginDataReady
|
||||
? Object.keys(this.grossMarginTrendMap)
|
||||
: [];
|
||||
const salesLocations = isSalesDataReady
|
||||
? Object.keys(this.salesTrendMap)
|
||||
: [];
|
||||
// 2. 处理levelId≠1的整合数据(核心:levelId匹配地名)
|
||||
const barData = { ...initBarData };
|
||||
// 筛选有效数据:levelId≠1 且 levelId在baseIndexToNameMap中
|
||||
const validOtherData = this.monData.filter(item => {
|
||||
return item.levelId !== 1 && baseIndexToNameMap.hasOwnProperty(item.levelId);
|
||||
});
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
* 通用数据处理函数(纯函数)
|
||||
* @param {Array} locations - 某个指标的地点数组
|
||||
* @param {Object} dataMap - 该指标的原始数据映射
|
||||
* @returns {Object} - 格式化后的数据对象
|
||||
*/
|
||||
processSingleDataset(locations, dataMap) {
|
||||
const rates = [];
|
||||
const reals = [];
|
||||
const targets = [];
|
||||
const flags = [];
|
||||
|
||||
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;
|
||||
|
||||
rates.push(rate);
|
||||
reals.push(data.real ?? 0); // 使用空值合并运算符
|
||||
targets.push(data.target ?? 0);
|
||||
|
||||
// 优化:更清晰的逻辑
|
||||
if (data.target === 0) {
|
||||
flags.push(1); // 如果目标为0,默认达标
|
||||
} else {
|
||||
flags.push(rate >= 100 ? 1 : 0);
|
||||
// 遍历有效数据,填充locations(levelId→地名)
|
||||
validOtherData.forEach(item => {
|
||||
// 根据levelId(序号)从映射表获取对应地名
|
||||
const baseName = baseIndexToNameMap[item.levelId];
|
||||
if (baseName) { // 确保地名和原始名称有效
|
||||
// barData.names.push(item.name); // 保留monData中的原始名称
|
||||
barData.locations.push(baseName); // locations=levelId对应的地名(如levelId=7→宜兴)
|
||||
barData.diff.push(item.diffValue || 0);
|
||||
barData.targets.push(item.targetValue || 0);
|
||||
barData.reals.push(item.value || 0);
|
||||
barData.rate.push(item.proportion || 0);
|
||||
barData.flags.push(item.completed ? 1 : 0);
|
||||
// barData.baseIndexes.push(Number(item.levelId) || 0); // 序号转数字
|
||||
}
|
||||
});
|
||||
|
||||
return { rates, reals, targets, flags };
|
||||
},
|
||||
// 3. 更新chartData
|
||||
this.chartData = { topBarData, barData };
|
||||
console.log('levelId=1数据:', this.chartData.topBarData);
|
||||
console.log('levelId≠1数据(locations=levelId对应地名):', this.chartData.barData);
|
||||
}
|
||||
},
|
||||
};
|
||||
</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 +192,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 +223,6 @@ export default {
|
||||
height: 14px;
|
||||
border: 1px solid #adadad;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
@@ -291,22 +239,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 +266,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 +293,6 @@ export default {
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
|
||||
@@ -1,28 +1,15 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<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="
|
||||
<Container name="累计数据对比" icon="cockpitItemIcon" size="operatingLarge" topSize="large">
|
||||
<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,60 +17,75 @@
|
||||
letter-spacing: 1px;
|
||||
font-style: normal;
|
||||
padding: 16px 0 0 16px;
|
||||
"
|
||||
>
|
||||
">
|
||||
集团情况
|
||||
</div>
|
||||
<operatingTopBar :chartData="chartData" />
|
||||
<operatingTopBar :chartData="chartData?.topBarData || {}" />
|
||||
</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?.barData || {}" />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Container from "../components/container.vue";
|
||||
import operatingBar from "./operatingBar.vue";
|
||||
import operatingTopBar from "./operatingTopBar.vue";
|
||||
|
||||
// 序号→地名映射表(levelId=序号)
|
||||
const baseIndexToNameMap = {
|
||||
7: "宜兴",
|
||||
8: "漳州",
|
||||
3: "自贡",
|
||||
2: "桐城",
|
||||
9: "洛阳",
|
||||
5: "合肥",
|
||||
10: "秦皇岛",
|
||||
6: "宿迁"
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "ProductionStatus",
|
||||
components: { Container, operatingBar, operatingTopBar },
|
||||
props: {
|
||||
salesTrendMap: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
grossMarginTrendMap: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
totalData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartData: null, // 初始化 chartData 为 null
|
||||
chartData: {
|
||||
topBarData: { // levelId=1的整合数据
|
||||
locations: [], // 固定为["凯盛新能"]
|
||||
diff: [], // 差值数组
|
||||
targets: [], // 目标值数组
|
||||
reals: [], // 实际值数组
|
||||
rate: [], // 完成率数组
|
||||
flags: [] // 完成状态数组(0/1)
|
||||
},
|
||||
barData: { // levelId≠1的整合数据
|
||||
locations: [], // levelId对应的baseIndexToNameMap中的地名
|
||||
diff: [], // 对应差值数组
|
||||
targets: [], // 目标值数组
|
||||
reals: [], // 实际值数组
|
||||
rate: [], // 完成率数组
|
||||
flags: [] // 完成状态数组(0/1)
|
||||
// baseIndexes: []// 对应levelId(序号)数组
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
grossMarginTrendMap: {
|
||||
handler() {
|
||||
this.processChartData();
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
salesTrendMap: {
|
||||
totalData: {
|
||||
handler() {
|
||||
this.processChartData();
|
||||
},
|
||||
@@ -93,142 +95,92 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 核心处理函数:在所有数据都准备好后,才组装 chartData
|
||||
* 核心方法:按levelId匹配地名生成locations
|
||||
*/
|
||||
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,
|
||||
// 初始化空数据结构
|
||||
const initTopBarData = {
|
||||
locations: [], diff: [], targets: [], reals: [], rate: [], flags: []
|
||||
};
|
||||
const initBarData = { locations: [], diff: [], targets: [], reals: [], rate: [], flags: [] };
|
||||
|
||||
console.log("chartData 已更新:", this.chartData);
|
||||
},
|
||||
if (!Array.isArray(this.totalData) || this.totalData.length === 0) {
|
||||
this.chartData = { topBarData: initTopBarData, barData: initBarData };
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用数据处理函数(纯函数)
|
||||
* @param {Array} locations - 某个指标的地点数组
|
||||
* @param {Object} dataMap - 该指标的原始数据映射
|
||||
* @returns {Object} - 格式化后的数据对象
|
||||
*/
|
||||
processSingleDataset(locations, dataMap) {
|
||||
const rates = [];
|
||||
const reals = [];
|
||||
const targets = [];
|
||||
const flags = [];
|
||||
// 1. 处理levelId=1的整合数据(逻辑不变)
|
||||
const level1Data = this.totalData.filter(item => item.levelId === 1);
|
||||
const topBarData = { ...initTopBarData };
|
||||
level1Data.forEach(item => {
|
||||
if (!item.name) return;
|
||||
topBarData.locations = ["凯盛新能"]; // levelId=1固定为凯盛新能
|
||||
topBarData.diff.push(item.diffValue || 0);
|
||||
topBarData.targets.push(item.targetValue || 0);
|
||||
topBarData.reals.push(item.value || 0);
|
||||
topBarData.rate.push(item.proportion || 0);
|
||||
topBarData.flags.push(item.completed ? 1 : 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;
|
||||
// 2. 处理levelId≠1的整合数据(核心:levelId匹配地名)
|
||||
const barData = { ...initBarData };
|
||||
// 筛选有效数据:levelId≠1 且 levelId在baseIndexToNameMap中
|
||||
const validOtherData = this.totalData.filter(item => {
|
||||
return item.levelId !== 1 && baseIndexToNameMap.hasOwnProperty(item.levelId);
|
||||
});
|
||||
|
||||
rates.push(rate);
|
||||
reals.push(data.real ?? 0); // 使用空值合并运算符
|
||||
targets.push(data.target ?? 0);
|
||||
|
||||
// 优化:更清晰的逻辑
|
||||
if (data.target === 0) {
|
||||
flags.push(1); // 如果目标为0,默认达标
|
||||
} else {
|
||||
flags.push(rate >= 100 ? 1 : 0);
|
||||
// 遍历有效数据,填充locations(levelId→地名)
|
||||
validOtherData.forEach(item => {
|
||||
// 根据levelId(序号)从映射表获取对应地名
|
||||
const baseName = baseIndexToNameMap[item.levelId];
|
||||
if (baseName) { // 确保地名和原始名称有效
|
||||
// barData.names.push(item.name); // 保留monData中的原始名称
|
||||
barData.locations.push(baseName); // locations=levelId对应的地名(如levelId=7→宜兴)
|
||||
barData.diff.push(item.diffValue || 0);
|
||||
barData.targets.push(item.targetValue || 0);
|
||||
barData.reals.push(item.value || 0);
|
||||
barData.rate.push(item.proportion || 0);
|
||||
barData.flags.push(item.completed ? 1 : 0);
|
||||
// barData.baseIndexes.push(Number(item.levelId) || 0); // 序号转数字
|
||||
}
|
||||
});
|
||||
|
||||
return { rates, reals, targets, flags };
|
||||
},
|
||||
// 3. 更新chartData
|
||||
this.chartData = { topBarData, barData };
|
||||
console.log('levelId=1数据:', this.chartData.topBarData);
|
||||
console.log('levelId≠1数据(locations=levelId对应地名):', this.chartData.barData);
|
||||
}
|
||||
},
|
||||
};
|
||||
</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 +193,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 +224,6 @@ export default {
|
||||
height: 14px;
|
||||
border: 1px solid #adadad;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
@@ -291,22 +240,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 +267,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 +294,6 @@ export default {
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="lineBottom" style="height: 180px; width: 100%">
|
||||
<operatingLineBarSaleSingle :chartData="chartD" style="height: 99%; width: 100%" />
|
||||
<operatingLineBarSaleSingle :refName="'totalOperating'" :chartData="chartD" style="height: 99%; width: 100%" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -11,10 +11,9 @@ import * as echarts from 'echarts';
|
||||
export default {
|
||||
name: "Container",
|
||||
components: { operatingLineBarSaleSingle },
|
||||
props: ["chartData"],
|
||||
props: ["detailData"],
|
||||
data() {
|
||||
return {
|
||||
activeButton: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -24,10 +23,13 @@ export default {
|
||||
chartD() {
|
||||
// 背景图片路径(若不需要可注释)
|
||||
// const bgImageUrl = require('@/assets/img/labelBg.png');
|
||||
const rate = this.detailData?.proportion ? Number(this.detailData?.proportion).toFixed(2) : 0
|
||||
const diff = this.detailData?.diffValue || 0
|
||||
console.log('diff', diff);
|
||||
|
||||
const seriesData = [
|
||||
{
|
||||
value: 131744,
|
||||
value: this.detailData?.targetValue || 0,
|
||||
flag: 1, // 实际项:达标(绿色)
|
||||
label: {
|
||||
show: true,
|
||||
@@ -37,7 +39,9 @@ export default {
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{value|完成率}{rate|139%}',
|
||||
formatter: function (params) {
|
||||
return `{value|完成率}{rate|${rate}%}`;
|
||||
},
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
@@ -91,8 +95,8 @@ export default {
|
||||
},
|
||||
},
|
||||
{
|
||||
value: 630230,
|
||||
flag: 0, // 预算项:未达标(橙色)
|
||||
value: this.detailData?.value || 0,
|
||||
flag: this.detailData?.completed, // 实际项:达标(绿色)
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
@@ -101,8 +105,11 @@ export default {
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{rate|139%}{text|差值}',
|
||||
// 核心样式:匹配CSS需求
|
||||
formatter: function (params) {
|
||||
// 假设 params.data 是完成率数值(如 139)
|
||||
// // 2. 模板字符串拼接富文本标签 + 动态值
|
||||
return `{rate|${diff}}{text|差值}`;
|
||||
},
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
@@ -189,6 +196,7 @@ export default {
|
||||
},
|
||||
],
|
||||
};
|
||||
console.log('data', data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -21,20 +21,22 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
currentDataSource() {
|
||||
console.log('yyyy',this.chartData);
|
||||
console.log('yyyy', this.chartData);
|
||||
|
||||
return this.activeButton === 0 ? this.chartData.sales : this.chartData.grossMargin;
|
||||
return this.chartData
|
||||
},
|
||||
locations() {
|
||||
console.log('this.chartData', this.chartData);
|
||||
console.log('this.1111', this.chartData);
|
||||
|
||||
return this.activeButton === 0 ? this.chartData.salesLocations : this.chartData.grossMarginLocations;
|
||||
return this.chartData.locations
|
||||
},
|
||||
// 根据按钮切换生成对应的 chartData
|
||||
chartD() {
|
||||
// 销量场景数据
|
||||
const data = this.currentDataSource;
|
||||
console.log(this.currentDataSource,'currentDataSource');
|
||||
const diff = data.diff[0]
|
||||
const rate = data.rate[0]
|
||||
console.log(this.currentDataSource, 'currentDataSource');
|
||||
|
||||
const salesData = {
|
||||
allPlaceNames: this.locations,
|
||||
@@ -73,12 +75,15 @@ export default {
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
offset: [-30, 0],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{value|完成率}{rate|139%}',
|
||||
formatter: (params) => {
|
||||
// const { rate = 0, diff = 0 } = params.data || {};
|
||||
return `{value|完成率}{rate|${rate}%}`;
|
||||
},
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
@@ -149,12 +154,15 @@ export default {
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
offset: [30, 0],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{rate|139%}{text|差值}',
|
||||
formatter: (params) => {
|
||||
// const { rate = 0, diff = 0 } = params.data || {};
|
||||
return `{rate|${diff}}{text|差值}`;
|
||||
},
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
|
||||
@@ -1,56 +1,62 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container :name="title" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<Container :isShowTab="true" :name="title" icon="cockpitItemIcon" size="opLargeBg" topSize="large"
|
||||
@tabChange="handleChange">
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||
<!-- 横向滚动容器:适配多组件排列 -->
|
||||
<div class="topItem-container" style="display: flex; gap: 8px; ">
|
||||
<!-- 1. 销量组件:传递当前激活数据集中的销量数据 -->
|
||||
<div class="dashboard left">
|
||||
<div class="title">
|
||||
销量·单位/万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
<operatingSingleBar :detailData="salesData"></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 2. 单价组件:传递当前激活数据集中的单价数据 -->
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
单价·单位/万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
<operatingSingleBar :detailData="unitPriceData"></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 3. 成本组件:传递当前激活数据集中的成本数据 -->
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
成本·单位/万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
<operatingSingleBar :detailData="costData"></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 4. 管理费用组件:传递当前激活数据集中的管理费用数据 -->
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
管理费用·单位/万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
<operatingSingleBar :detailData="manageCostData"></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 5. 销售费用组件:传递当前激活数据集中的销售费用数据 -->
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
销售费用·单位/万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
<operatingSingleBar :detailData="salesCostData"></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 6. 财务费用组件:传递当前激活数据集中的财务费用数据 -->
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
财务费用·单位/万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
<operatingSingleBar :detailData="financeCostData"></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,90 +64,139 @@
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Container from '../components/container.vue'
|
||||
import operatingSingleBar from './operatingSingleBar.vue'
|
||||
|
||||
// import * as echarts from 'echarts'
|
||||
// import rawItem from './raw-Item.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, operatingSingleBar },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
itemData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
// 接收父组件传递的 月度+累计 组合数据
|
||||
relatedData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
relatedMon: [], // 月度数据(数组格式,存储销量/单价等数据)
|
||||
relatedTotal: [] // 累计数据(数组格式,存储销量/单价等数据)
|
||||
})
|
||||
},
|
||||
title: { // 接收父组件传递的设备数据数组
|
||||
// 可选:动态标题
|
||||
title: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
},
|
||||
month: { // 接收父组件传递的设备数据数组
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
},
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
|
||||
// 核心:当前激活的数据集(月度/累计),默认初始化月度数据
|
||||
activeData: this.relatedData.relatedMon || []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 1. 销量数据:从当前激活数据集中筛选(依赖 activeData,自动响应变化)
|
||||
salesData() {
|
||||
return (this.activeData.find(item => item.name === "销量")) || {
|
||||
targetValue: 0,
|
||||
value: 0,
|
||||
completed: 0,
|
||||
diffValue: 0
|
||||
};
|
||||
},
|
||||
// 2. 单价数据:从当前激活数据集中筛选
|
||||
unitPriceData() {
|
||||
return (this.activeData.find(item => item.name === "单价")) || {
|
||||
targetValue: 0,
|
||||
value: 0,
|
||||
completed: 0,
|
||||
diffValue: 0
|
||||
};
|
||||
},
|
||||
// 3. 成本数据:从当前激活数据集中筛选
|
||||
costData() {
|
||||
return (this.activeData.find(item => item.name === "成本")) || {
|
||||
targetValue: 0,
|
||||
value: 0,
|
||||
completed: 0,
|
||||
diffValue: 0
|
||||
};
|
||||
},
|
||||
// 4. 管理费用数据:从当前激活数据集中筛选
|
||||
manageCostData() {
|
||||
return (this.activeData.find(item => item.name === "管理费用")) || {
|
||||
targetValue: 0,
|
||||
value: 0,
|
||||
completed: 0,
|
||||
diffValue: 0
|
||||
};
|
||||
},
|
||||
// 5. 销售费用数据:从当前激活数据集中筛选
|
||||
salesCostData() {
|
||||
return (this.activeData.find(item => item.name === "销售费用")) || {
|
||||
targetValue: 0,
|
||||
value: 0,
|
||||
completed: 0,
|
||||
diffValue: 0
|
||||
};
|
||||
},
|
||||
// 6. 财务费用数据:从当前激活数据集中筛选
|
||||
financeCostData() {
|
||||
return (this.activeData.find(item => item.name === "财务费用")) || {
|
||||
targetValue: 0,
|
||||
value: 0,
|
||||
completed: 0,
|
||||
diffValue: 0
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
itemData: {
|
||||
handler(newValue, oldValue) {
|
||||
// this.updateChart()
|
||||
// 可选:监听 relatedData 初始变化(若父组件异步传递数据,确保 activeData 同步更新)
|
||||
relatedData: {
|
||||
handler(newVal) {
|
||||
this.activeData = newVal.relatedMon || [];
|
||||
},
|
||||
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
// computed: {
|
||||
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||
// sortedItemData() {
|
||||
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||
// const totalCostItems = this.itemData.filter(item =>
|
||||
// item.name && item.name.includes('总成本')
|
||||
// );
|
||||
// // 过滤出不包含“总成本”的项
|
||||
// const otherItems = this.itemData.filter(item =>
|
||||
// !item.name || !item.name.includes('总成本')
|
||||
// );
|
||||
// // 合并:总成本项在前,其他项在后
|
||||
// return [...totalCostItems, ...otherItems];
|
||||
// }
|
||||
// },
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
console.log('组件挂载时的激活数据:', this.activeData);
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Tab 切换处理函数
|
||||
* @param {String} value 切换值('month' = 月度,'total' = 累计,可根据实际Tab值调整)
|
||||
*/
|
||||
handleChange(value) {
|
||||
console.log('Tab 切换值:', value);
|
||||
// 根据 Tab 值更新当前激活的数据集
|
||||
if (value === 'month') {
|
||||
// 切换为月度数据
|
||||
this.activeData = this.relatedData.relatedMon || [];
|
||||
} else {
|
||||
// 切换为累计数据(非 month 均视为累计,可根据实际需求调整判断条件)
|
||||
this.activeData = this.relatedData.relatedTotal || [];
|
||||
}
|
||||
console.log('当前激活数据集:', this.activeData);
|
||||
}
|
||||
}
|
||||
}
|
||||
</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;
|
||||
}
|
||||
|
||||
@@ -150,9 +205,10 @@ export default {
|
||||
height: 205px;
|
||||
background: #F9FCFF;
|
||||
padding: 16px 0 0 16px;
|
||||
/* 固定宽度,避免组件变形 */
|
||||
flex-shrink: 0;
|
||||
|
||||
.title {
|
||||
// width: 190px;
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
@@ -163,13 +219,14 @@ export default {
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 16px;
|
||||
/* 增加标题与图表的间距 */
|
||||
}
|
||||
|
||||
.number {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
// width: 190px;
|
||||
height: 32px;
|
||||
font-family: YouSheBiaoTiHei;
|
||||
font-size: 32px;
|
||||
@@ -192,41 +249,22 @@ export default {
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 230px;
|
||||
/* 适配 dashboard 宽度,避免图表溢出 */
|
||||
height: 150px;
|
||||
/* 合理设置图表高度 */
|
||||
}
|
||||
}
|
||||
|
||||
// .line {
|
||||
// width: 500px;
|
||||
// height: 205px;
|
||||
// background: #F9FCFF;
|
||||
// }
|
||||
/* 隐藏横向滚动条(美观优化) */
|
||||
.topItem-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// .leftTitle {
|
||||
// .item {
|
||||
// width: 67px;
|
||||
// height: 180px;
|
||||
// padding: 37px 23px;
|
||||
// background: #F9FCFF;
|
||||
// font-family: PingFangSC, PingFang SC;
|
||||
// font-weight: 400;
|
||||
// font-size: 18px;
|
||||
// color: #000000;
|
||||
// line-height: 25px;
|
||||
// letter-spacing: 1px;
|
||||
// // text-align: left;
|
||||
// font-style: normal;
|
||||
// }
|
||||
// }
|
||||
.topItem-container {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- <style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style> -->
|
||||
|
||||
@@ -11,21 +11,22 @@
|
||||
</div>
|
||||
<div class="number">
|
||||
<div class="yield">
|
||||
90%
|
||||
{{ formatRate(factoryData?.completeRate) }}%
|
||||
</div>
|
||||
<div class="mom">
|
||||
环比10%
|
||||
<img class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||
环比{{ formatRate(factoryData?.thb) }}%
|
||||
<img v-if="factoryData?.thb >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="上升">
|
||||
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="下降">
|
||||
</div>
|
||||
</div>
|
||||
<div class="electricityGauge">
|
||||
<electricityGauge id="totalGauge"></electricityGauge>
|
||||
<!-- 传递包含flag的factoryData给仪表盘组件 -->
|
||||
<electricityGauge id="month" :detailData="factoryData"></electricityGauge>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line" style="padding: 0px;">
|
||||
<verticalBarChart>
|
||||
</verticalBarChart>
|
||||
|
||||
<!-- 传递包含flag的factoryData给柱状图组件 -->
|
||||
<verticalBarChart :detailData="factoryData"></verticalBarChart>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -43,55 +44,60 @@ import verticalBarChart from './verticalBarChart.vue'
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, electricityGauge, verticalBarChart },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
itemData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
totalData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
title: { // 接收父组件传递的设备数据数组
|
||||
title: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
month: { // 接收父组件传递的设备数据数组
|
||||
month: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
itemData: {
|
||||
handler(newValue, oldValue) {
|
||||
// this.updateChart()
|
||||
},
|
||||
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||
computed: {
|
||||
/**
|
||||
* 自动提取monData中的工厂数据,并新增flag字段
|
||||
*/
|
||||
factoryData() { // 整合原始数据 + 计算flag
|
||||
return {
|
||||
completeRate: Number(this.totalData.proportion).toFixed(2),
|
||||
diff: this.totalData.diffValue,
|
||||
real: this.totalData.value,
|
||||
target: this.totalData.targetValue,
|
||||
thb: this.totalData.thb,
|
||||
// ...rawData,
|
||||
flag: this.getRateFlag(this.totalData.completed) // 新增flag字段
|
||||
};
|
||||
}
|
||||
},
|
||||
// computed: {
|
||||
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||
// sortedItemData() {
|
||||
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||
// const totalCostItems = this.itemData.filter(item =>
|
||||
// item.name && item.name.includes('总成本')
|
||||
// );
|
||||
// // 过滤出不包含“总成本”的项
|
||||
// const otherItems = this.itemData.filter(item =>
|
||||
// !item.name || !item.name.includes('总成本')
|
||||
// );
|
||||
// // 合并:总成本项在前,其他项在后
|
||||
// return [...totalCostItems, ...otherItems];
|
||||
// }
|
||||
// },
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 格式化百分比数值:处理空值/非数字,兜底为0
|
||||
*/
|
||||
formatRate(value) {
|
||||
if (isNaN(value) || value === null || value === undefined) {
|
||||
return 0;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
/**
|
||||
* 判断完成率对应的flag值(<100为0,≥100为1)
|
||||
* @param {number} rate 完成率(原始值,如89代表89%)
|
||||
* @returns {0|1} flag值
|
||||
*/
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return rate >= 100 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div ref="verticalBarChart" id="coreLineChart" style="width: 100%; height: 210px;"></div>
|
||||
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 210px;"></div>
|
||||
</template>
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
@@ -8,80 +8,70 @@ export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 存储图表实例,避免重复创建
|
||||
myChart: null
|
||||
};
|
||||
},
|
||||
props: {
|
||||
// 明确接收的props结构,增强可读性
|
||||
chartData: {
|
||||
refName: {
|
||||
type: String,
|
||||
default: 'verticalBarChart',
|
||||
},
|
||||
detailData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
series: [],
|
||||
allPlaceNames: []
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0,
|
||||
flag: 0
|
||||
}),
|
||||
// 校验数据格式
|
||||
validator: (value) => {
|
||||
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
this.$nextTick(() => this.updateChart());
|
||||
},
|
||||
|
||||
// 新增:监听 chartData 变化
|
||||
watch: {
|
||||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||
chartData: {
|
||||
detailData: {
|
||||
handler() {
|
||||
console.log(this.chartData, 'chartData');
|
||||
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
immediate: true // 初始化时立即执行
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateChart() {
|
||||
const chartDom = this.$refs.verticalBarChart;
|
||||
const chartDom = this.$refs[this.refName];
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
console.log('this.detailData', this.detailData);
|
||||
|
||||
// 修复:优化实例销毁逻辑,避免重复dispose
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart.clear(); // 先清空,再重新渲染
|
||||
} else {
|
||||
this.myChart = echarts.init(chartDom);
|
||||
}
|
||||
|
||||
this.myChart = echarts.init(chartDom);
|
||||
const { allPlaceNames, series } = this.chartData || {};
|
||||
|
||||
// 处理空数据
|
||||
const xData = allPlaceNames || [];
|
||||
const chartSeries = series || []; // 父组件传递的 series
|
||||
// 解构数据,避免重复取值
|
||||
const { diff, completeRate, real, target, flag } = this.detailData;
|
||||
// 确保数值为数字类型
|
||||
const realValue = Number(real) || 0;
|
||||
const targetValue = Number(target) || 0;
|
||||
const diffValue = Number(diff) || 0;
|
||||
const rateValue = Number(completeRate) || 0;
|
||||
const flagValue = Number(flag) || 0;
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
},
|
||||
// formatter: (params) => {
|
||||
// let html = `${params[0].axisValue}<br/>`;
|
||||
// params.forEach(item => {
|
||||
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||
// );
|
||||
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||
// });
|
||||
// return html;
|
||||
// }
|
||||
label: { backgroundColor: '#6a7985' }
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 10,
|
||||
@@ -89,217 +79,144 @@ export default {
|
||||
right: 50,
|
||||
left: 30,
|
||||
containLabel: true,
|
||||
show: false // 隐藏grid背景,避免干扰
|
||||
show: false
|
||||
},
|
||||
xAxis: {
|
||||
// 横向柱状图的x轴必须设为数值轴,否则无法正常展示数值
|
||||
type: 'value',
|
||||
// offset: 0,
|
||||
// boundaryGap: true ,
|
||||
// boundaryGap: [10, 0], // 可根据需要开启,控制轴的留白
|
||||
axisTick: { show: false },
|
||||
min: 0,
|
||||
// scale: true,
|
||||
splitNumber: 4,
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0]
|
||||
},
|
||||
// data: xData // 数值轴不需要手动设置data,由series的数据自动生成
|
||||
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLabel: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 12, interval: 0, padding: [5, 0, 0, 0] }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.75)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0]
|
||||
},
|
||||
axisLine: {
|
||||
show: true, // 显示Y轴轴线(关键)
|
||||
lineStyle: {
|
||||
color: '#E5E6EB', // 轴线颜色(浅灰色,可自定义)
|
||||
width: 1, // 轴线宽度
|
||||
type: 'solid' // 实线(可选:dashed虚线、dotted点线)
|
||||
}
|
||||
},
|
||||
axisLabel: { color: 'rgba(0, 0, 0, 0.75)', fontSize: 12, interval: 0, padding: [5, 0, 0, 0] },
|
||||
axisLine: { show: true, lineStyle: { color: '#E5E6EB', width: 1, type: 'solid' } },
|
||||
axisTick: { show: false },
|
||||
// padding: [300, 100, 100, 100],
|
||||
data: ['实际', '预算'] // y轴分类:实际、预算
|
||||
data: ['实际', '预算']
|
||||
},
|
||||
series: [
|
||||
{
|
||||
// name: '预算',
|
||||
type: 'bar',
|
||||
barWidth: 24,
|
||||
// barCategoryGap: '50', // 柱子之间的间距(相对于柱子宽度)
|
||||
// 数据长度与yAxis的分类数量匹配(实际、预算各一个值)
|
||||
data: [{
|
||||
value: 131744,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
offset: [-60, 25],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{rate|139%}{text|差值}',
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' }, // 顶部0px位置:阴影最强
|
||||
// { offset: 0.1, color: 'rgba(205, 215, 224, 0.4)' }, // 1px位置:阴影减弱(对应1px)
|
||||
// { offset: 0.15, color: 'rgba(205, 215, 224, 0.6)' }, // 3px位置:阴影几乎消失(对应3px扩散)
|
||||
{ offset: 0.2, color: '#ffffff' }, // 主体白色
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
// 外阴影:0px 2px 2px 0px rgba(191,203,215,0.5)
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
// 圆角:4px
|
||||
borderRadius: 4,
|
||||
// 移除边框
|
||||
borderColor: '#BFCBD577',
|
||||
borderWidth: 0,
|
||||
// 文字垂直居中(针对富文本)
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
text: {
|
||||
// 缩小宽度和内边距,适配68px容器
|
||||
width: 'auto', // 自动宽度,替代固定40px
|
||||
padding: [5, 10, 5, 0], // 缩小内边距
|
||||
align: 'center',
|
||||
color: '#464646', // 文字灰色
|
||||
fontSize: 11, // 缩小字体,适配小尺寸
|
||||
lineHeight: 20 // 垂直居中
|
||||
// 修复:拆分数据项,确保每个柱子的样式独立生效
|
||||
data: [
|
||||
// 实际值柱子(核心:绑定flag颜色)
|
||||
{
|
||||
value: realValue,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
offset: [-60, 25],
|
||||
width: 68,
|
||||
height: 20,
|
||||
formatter: `{rate|${diffValue}}{text|差值}`,
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||
{ offset: 0.2, color: '#ffffff' },
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
rate: {
|
||||
width: 'auto',
|
||||
padding: [5, 0, 5, 10],
|
||||
align: 'center',
|
||||
color: '#30B590',
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
borderRadius: 4,
|
||||
borderWidth: 0,
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
text: { width: 'auto', padding: [5, 10, 5, 0], align: 'center', color: '#464646', fontSize: 11, lineHeight: 20 },
|
||||
rate: { width: 'auto', padding: [5, 0, 5, 10], align: 'center', color: '#30B590', fontSize: 11, lineHeight: 20 }
|
||||
}
|
||||
},
|
||||
// 修复:flag颜色判断独立绑定到实际值柱子
|
||||
itemStyle: {
|
||||
color: flagValue === 1
|
||||
? {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||
]
|
||||
}
|
||||
: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
// 实际的渐变颜色(绿系渐变,与预算区分)
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#AEEFE0' }, // 浅绿
|
||||
{ offset: 1, color: '#76DABE' } // 深绿
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0,
|
||||
},
|
||||
}, {
|
||||
value: 630230,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
offset: [-70, 25],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{value|完成率}{rate|139%}',
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' }, // 顶部0px位置:阴影最强
|
||||
// { offset: 0.1, color: 'rgba(205, 215, 224, 0.4)' }, // 1px位置:阴影减弱(对应1px)
|
||||
// { offset: 0.15, color: 'rgba(205, 215, 224, 0.6)' }, // 3px位置:阴影几乎消失(对应3px扩散)
|
||||
{ offset: 0.2, color: '#ffffff' }, // 主体白色
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
// 外阴影:0px 2px 2px 0px rgba(191,203,215,0.5)
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
// 圆角:4px
|
||||
borderRadius: 4,
|
||||
// 移除边框
|
||||
borderColor: '#BFCBD577',
|
||||
borderWidth: 0,
|
||||
// 文字垂直居中(针对富文本)
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
value: {
|
||||
// 缩小宽度和内边距,适配68px容器
|
||||
width: 'auto', // 自动宽度,替代固定40px
|
||||
padding: [5, 0, 5, 10], // 缩小内边距
|
||||
align: 'center',
|
||||
color: '#464646', // 文字灰色
|
||||
fontSize: 11, // 缩小字体,适配小尺寸
|
||||
lineHeight: 20 // 垂直居中
|
||||
// 预算值柱子(固定蓝色渐变)
|
||||
{
|
||||
value: targetValue,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
offset: [0, 25],
|
||||
width: 68,
|
||||
height: 20,
|
||||
formatter: `{value|完成率}{rate|${rateValue}%}`,
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||
{ offset: 0.2, color: '#ffffff' },
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
rate: {
|
||||
width: 'auto',
|
||||
padding: [5, 10, 5, 0],
|
||||
align: 'center',
|
||||
color: '#0B58FF', // 数字蓝色
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
borderRadius: 4,
|
||||
borderWidth: 0,
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
value: { width: 'auto', padding: [5, 0, 5, 10], align: 'center', color: '#464646', fontSize: 11, lineHeight: 20 },
|
||||
rate: { width: 'auto', padding: [5, 10, 5, 0], align: 'center', color: '#0B58FF', fontSize: 11, lineHeight: 20 }
|
||||
}
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
// 预算的渐变颜色(蓝系渐变)
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 1, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#82CCFF' }, // 浅蓝
|
||||
{ offset: 1, color: '#4B9DFF' } // 深蓝
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
},],
|
||||
|
||||
},
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 1, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#82CCFF' },
|
||||
{ offset: 1, color: '#4B9DFF' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
option && this.myChart.setOption(option);
|
||||
this.myChart.setOption(option, true); // 新增:true表示替换所有配置,避免缓存
|
||||
|
||||
// 窗口缩放适配和销毁逻辑保持不变
|
||||
window.addEventListener('resize', () => {
|
||||
// 优化:防抖resize,避免频繁触发
|
||||
const resizeHandler = () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
};
|
||||
window.removeEventListener('resize', resizeHandler); // 先移除再添加,避免重复绑定
|
||||
window.addEventListener('resize', resizeHandler);
|
||||
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
window.removeEventListener('resize', resizeHandler);
|
||||
this.myChart && this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -187,7 +187,7 @@ export default {
|
||||
this.beilv = _this.clientWidth / 1920;
|
||||
})();
|
||||
};
|
||||
this.factory = this.$route.query.factory ? this.$route.query.factory : this.factory
|
||||
this.factory = this.$route.query.factory ? Number(this.$route.query.factory) : this.factory
|
||||
},
|
||||
methods: {
|
||||
handleChange(value) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||
<ReportHeader top-title="采购增效额分析" :is-full-screen="isFullScreen" @screenfullChange="screenfullChange"
|
||||
@timeRangeChange="handleTimeChange" :leftMargin=" '300px' " />
|
||||
@timeRangeChange="handleTimeChange" />
|
||||
<div class="main-body" style="
|
||||
flex: 1;
|
||||
display: flex;
|
||||
@@ -16,7 +16,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns:1624px;
|
||||
">
|
||||
<operatingLineChart :salesTrendMap="salesTrendMap" :grossMarginTrendMap="grossMarginTrendMap" />
|
||||
<operatingLineChart :thisMonData="thisMonData" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||
@@ -25,7 +25,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<operatingLineChartCumulative :salesTrendMap="salesTrendMap" :grossMarginTrendMap="grossMarginTrendMap" />
|
||||
<operatingLineChartCumulative :totalData="totalData" />
|
||||
<!-- <keyWork /> -->
|
||||
</div>
|
||||
</div>
|
||||
@@ -50,7 +50,7 @@ import { mapState } from "vuex";
|
||||
import operatingLineChart from "../procurementGainAnalysisComponents/operatingLineChart";
|
||||
import operatingLineChartCumulative from "../procurementGainAnalysisComponents/operatingLineChartCumulative.vue";
|
||||
|
||||
import { getSalesRevenueData } from '@/api/cockpit'
|
||||
import { getUnitPriceAnalysisGroupData } from '@/api/cockpit'
|
||||
import moment from "moment";
|
||||
export default {
|
||||
name: "DayReport",
|
||||
@@ -68,11 +68,9 @@ export default {
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
value: 100,
|
||||
saleData: {},
|
||||
premiumProduct: {},
|
||||
salesTrendMap: {},
|
||||
grossMarginTrendMap: {},
|
||||
salesProportion:{},
|
||||
selectDate: {},
|
||||
thisMonData: {},
|
||||
totalData: {},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -141,22 +139,27 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
getData(obj) {
|
||||
getSalesRevenueData({
|
||||
startTime: obj.startTime,
|
||||
endTime: obj.endTime,
|
||||
timeDim: obj.mode
|
||||
getUnitPriceAnalysisGroupData({
|
||||
startTime: this.selectDate.startTime,
|
||||
endTime: this.selectDate.endTime,
|
||||
paramName: '增效额'
|
||||
// timeDim: this.selectDate.mode
|
||||
}).then((res) => {
|
||||
console.log(res);
|
||||
this.saleData = res.data.SaleData
|
||||
this.premiumProduct = res.data.premiumProduct
|
||||
this.salesTrendMap = res.data.salesTrendMap
|
||||
this.grossMarginTrendMap = res.data.grossMarginTrendMap
|
||||
this.salesProportion = res.data.salesProportion ? res.data.salesProportion : {}
|
||||
this.thisMonData = res.data.thisMonData
|
||||
this.totalData = res.data.totalData
|
||||
|
||||
// this.saleData = res.data.SaleData
|
||||
// this.premiumProduct = res.data.premiumProduct
|
||||
// this.salesTrendMap = res.data.salesTrendMap
|
||||
// this.grossMarginTrendMap = res.data.grossMarginTrendMap
|
||||
// this.salesProportion = res.data.salesProportion ? res.data.salesProportion : {}
|
||||
})
|
||||
},
|
||||
handleTimeChange(obj) {
|
||||
console.log(obj, 'obj');
|
||||
this.getData(obj)
|
||||
// console.log(obj, 'obj');
|
||||
this.selectDate = obj
|
||||
this.getData()
|
||||
},
|
||||
handleClickOutside() {
|
||||
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
||||
@@ -228,12 +231,14 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
@import "~@/assets/styles/mixin.scss";
|
||||
@import "~@/assets/styles/variables.scss";
|
||||
|
||||
.dayReport {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
background: url("../../../assets/img/backp.png") no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||
<ReportHeader size="psi" @timeRangeChange="handleTimeChange" top-title="基地采购增效额分析" :is-full-screen="isFullScreen"
|
||||
@screenfullChange="screenfullChange" :leftMargin="'300px'" />
|
||||
@screenfullChange="screenfullChange" />
|
||||
<div class="main-body" style="
|
||||
margin-top: -20px;
|
||||
flex: 1;
|
||||
@@ -17,7 +17,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<changeBase @selectChange="selectChange" />
|
||||
<changeBase :factory="factory" @baseChange="selectChange" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px;margin-top: -20px;">
|
||||
@@ -26,8 +26,8 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 804px 804px;
|
||||
">
|
||||
<monthlyOverview :month="month" :itemData="renderList" :title="'月度概览'" />
|
||||
<totalOverview :itemData="renderList" :title="'累计概览'" />
|
||||
<monthlyOverview :month="month" :monData="monData" :title="'月度概览'" />
|
||||
<totalOverview :totalData="totalData" :title="'累计概览'" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,8 +37,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<!-- <monthlyRelatedMetrics :itemData="renderList" :title="'月度·相关指标分析'" /> -->
|
||||
<relatedIndicatorsAnalysis :month="month" :itemData="renderList" :title="'相关指标分析'" />
|
||||
<relatedIndicatorsAnalysis :relatedData="relatedData" :title="'相关指标分析·单位/万元'" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,7 +47,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<dataTrend :itemData="renderList" :title="'数据趋势'" />
|
||||
<dataTrend :trendData="trend" :title="'数据趋势'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,13 +69,10 @@ import changeBase from "../components/changeBase.vue";
|
||||
import monthlyOverview from "../procurementGainAnalysisComponents/monthlyOverview.vue";
|
||||
import totalOverview from "../procurementGainAnalysisComponents/totalOverview.vue";
|
||||
// import totalOverview from "../operatingComponents/totalOverview.vue";
|
||||
// import monthlyRelatedMetrics from "../procurementGainAnalysisComponents/monthlyRelatedMetrics.vue";
|
||||
import relatedIndicatorsAnalysis from "../procurementGainAnalysisComponents/relatedIndicatorsAnalysis.vue";
|
||||
import relatedIndicatorsAnalysis from "../procurementGainAnalysisComponents/relatedIndicatorsAnalysis";
|
||||
import dataTrend from "../procurementGainAnalysisComponents/dataTrend.vue";
|
||||
|
||||
import profitLineChart from "../costComponents/profitLineChart.vue";
|
||||
import { mapState } from "vuex";
|
||||
import { getCostAnalysisXXCostList } from '@/api/cockpit'
|
||||
import { getUnitPriceAnalysisBaseData } from '@/api/cockpit'
|
||||
// import PSDO from "./components/PSDO.vue";
|
||||
// import psiLineChart from "./components/psiLineChart.vue";
|
||||
|
||||
@@ -91,12 +87,11 @@ export default {
|
||||
components: {
|
||||
ReportHeader,
|
||||
changeBase,
|
||||
profitLineChart,
|
||||
monthlyOverview,
|
||||
Sidebar,
|
||||
totalOverview,
|
||||
relatedIndicatorsAnalysis,
|
||||
dataTrend
|
||||
dataTrend,
|
||||
relatedIndicatorsAnalysis
|
||||
// psiLineChart
|
||||
},
|
||||
data() {
|
||||
@@ -104,16 +99,15 @@ export default {
|
||||
isFullScreen: false,
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
month:'',
|
||||
month: '',
|
||||
value: 100,
|
||||
dateData:{},
|
||||
levelId:undefined,
|
||||
itemData: [],
|
||||
trendData: [],
|
||||
parentItemList: [
|
||||
{ name: "燃料成本", target: 0, value: 0, proportion: 0, flag: 1 },
|
||||
{ name: "天然气", target: 0, value: 0, proportion: 0, flag: 1 }
|
||||
],
|
||||
factory: 5,
|
||||
dateData: {},
|
||||
monData: {},
|
||||
totalData: {},
|
||||
trend: [],
|
||||
relatedData: {},
|
||||
// cusProData: {},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -130,12 +124,6 @@ export default {
|
||||
needTagsView: (state) => state.settings.tagsView,
|
||||
fixedHeader: (state) => state.settings.fixedHeader,
|
||||
}),
|
||||
renderList() {
|
||||
if (this.itemData && this.itemData.length > 0) {
|
||||
return this.itemData;
|
||||
}
|
||||
return this.parentItemList;
|
||||
},
|
||||
classObj() {
|
||||
return {
|
||||
hideSidebar: !this.sidebar.opened,
|
||||
@@ -185,28 +173,46 @@ export default {
|
||||
this.beilv = _this.clientWidth / 1920;
|
||||
})();
|
||||
};
|
||||
this.factory = this.$route.query.factory ? Number(this.$route.query.factory) : this.factory
|
||||
},
|
||||
methods: {
|
||||
handleChange(value) {
|
||||
this.index = value
|
||||
this.getData()
|
||||
},
|
||||
getData() {
|
||||
const requestParams = {
|
||||
// startTime: this.startTime,
|
||||
// endTime: this.endTime,
|
||||
// mode: this.mode,
|
||||
startTime: this.dateData.startTime,
|
||||
endTime: this.dateData.endTime,
|
||||
mode: this.dateData.mode,
|
||||
trendName: "燃料成本",
|
||||
levelId: this.levelId ? this.levelId :1
|
||||
// index: this.index,
|
||||
// sort: 1,
|
||||
paramName: '增效额',
|
||||
paramList: ['大宗增效额', '石料增效额', '材料增效额', '动力增效额', '技术服务增效额'],
|
||||
baseId: this.factory,
|
||||
// baseId: Number(this.factory),
|
||||
};
|
||||
// 调用接口
|
||||
getCostAnalysisXXCostList(requestParams).then((res) => {
|
||||
this.itemData = res.data[0].map((item) => {
|
||||
return {
|
||||
...item,
|
||||
route: 'singleFuelAnalysis'
|
||||
}
|
||||
})
|
||||
this.trendData= res.data[1]
|
||||
getUnitPriceAnalysisBaseData(requestParams).then((res) => {
|
||||
this.monData = res.data.monData
|
||||
this.totalData = res.data.totalData
|
||||
// this.relatedMon = res.data.relatedMon
|
||||
this.relatedData = {
|
||||
relatedTotal: res.data.relatedTotal,
|
||||
relatedMon: res.data.relatedMon,
|
||||
}
|
||||
this.trend = res.data.trend
|
||||
// this.relatedTotal = res.data.relatedTotal
|
||||
// this.cusProData = {
|
||||
// customerPriceMon: res.data.customerPriceMon,
|
||||
// customerPriceTotal: res.data.customerPriceTotal,
|
||||
// customerSaleMon: res.data.customerSaleMon,
|
||||
// customerSaleTotal: res.data.customerSaleTotal,
|
||||
// productMonSale: res.data.productMonSale,
|
||||
// productPriceMon: res.data.productPriceMon,
|
||||
// productPriceTotal: res.data.productPriceTotal,
|
||||
// productTotalSale: res.data.productTotalSale,
|
||||
// }
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
@@ -215,14 +221,14 @@ export default {
|
||||
this.dateData = {
|
||||
startTime: obj.startTime,
|
||||
endTime: obj.endTime,
|
||||
mode: obj.mode,
|
||||
// mode: obj.mode,
|
||||
}
|
||||
|
||||
this.getData()
|
||||
},
|
||||
selectChange(data) {
|
||||
console.log('选中的数据:', data);
|
||||
this.levelId = data
|
||||
this.factory = data
|
||||
if (this.dateData.startTime && this.dateData.endTime) {
|
||||
this.getData();
|
||||
}
|
||||
@@ -307,12 +313,14 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
@import "~@/assets/styles/mixin.scss";
|
||||
@import "~@/assets/styles/variables.scss";
|
||||
|
||||
.dayReport {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
background: url("../../../assets/img/backp.png") no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="数据趋势" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||
<div class="right" style="
|
||||
height: 191px;
|
||||
@@ -9,8 +8,8 @@
|
||||
width: 1595px;
|
||||
background-color: rgba(249, 252, 255, 1);
|
||||
">
|
||||
<!-- <top-item /> -->
|
||||
<dataTrendBar :chartData="chartData" />
|
||||
<!-- 传递 trend 数据给子组件 -->
|
||||
<dataTrendBar :trendData="trendData" />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
@@ -24,306 +23,20 @@ export default {
|
||||
name: "ProductionStatus",
|
||||
components: { Container, dataTrendBar },
|
||||
props: {
|
||||
salesTrendMap: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
grossMarginTrendMap: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
trendData: {
|
||||
type: Object, // 接收 trend 数据(单价/运费/净价)
|
||||
default: () => ({})
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartData: null, // 初始化 chartData 为 null
|
||||
chartData: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
grossMarginTrendMap: {
|
||||
handler() {
|
||||
this.processChartData();
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
salesTrendMap: {
|
||||
handler() {
|
||||
this.processChartData();
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
* 通用数据处理函数(纯函数)
|
||||
* @param {Array} locations - 某个指标的地点数组
|
||||
* @param {Object} dataMap - 该指标的原始数据映射
|
||||
* @returns {Object} - 格式化后的数据对象
|
||||
*/
|
||||
processSingleDataset(locations, dataMap) {
|
||||
const rates = [];
|
||||
const reals = [];
|
||||
const targets = [];
|
||||
const flags = [];
|
||||
|
||||
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;
|
||||
|
||||
rates.push(rate);
|
||||
reals.push(data.real ?? 0); // 使用空值合并运算符
|
||||
targets.push(data.target ?? 0);
|
||||
|
||||
// 优化:更清晰的逻辑
|
||||
if (data.target === 0) {
|
||||
flags.push(1); // 如果目标为0,默认达标
|
||||
} else {
|
||||
flags.push(rate >= 100 ? 1 : 0);
|
||||
}
|
||||
});
|
||||
|
||||
return { rates, reals, targets, flags };
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
</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 {
|
||||
width: 21px;
|
||||
height: 23px;
|
||||
background: rgba(0, 106, 205, 0.22);
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
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;
|
||||
color: #ffffff;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eqStatus {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #ffffff;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.splitLine {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid #adadad;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #00ffff;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.proBarLine {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #acacac 100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.proBarLineTop {
|
||||
position: absolute;
|
||||
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%
|
||||
);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
/* 进度变化时添加过渡动画,更流畅 */
|
||||
}
|
||||
|
||||
/* 图表相关样式保留 */
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 57px;
|
||||
bottom: 42px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #00e8ff;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
<!-- 样式部分不变,省略 -->
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<div class="item-button category-btn">
|
||||
<span class="item-text">展示顺序</span>
|
||||
<span class="item-text">类目选择</span>
|
||||
</div>
|
||||
<div class="dropdown-container">
|
||||
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||
@@ -59,48 +59,81 @@ import * as echarts from 'echarts';
|
||||
export default {
|
||||
name: "Container",
|
||||
components: { operatingLineBar },
|
||||
props: ["chartData"],
|
||||
props: [
|
||||
"trendData" // 接收父组件传递的 trend 数据(单价/运费/净价)
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
activeButton: 0,
|
||||
isDropdownShow: false,
|
||||
selectedProfit: null, // 选中的名称,初始为null
|
||||
profitOptions: [
|
||||
'采购增效额分析',
|
||||
'大宗类',
|
||||
'石料类',
|
||||
'材料类',
|
||||
'动力类',
|
||||
'技术服务类',
|
||||
]
|
||||
selectedProfit: '增效额', // 关键修改:默认赋值为「净价」,初始化即展示该类目数据
|
||||
profitOptions: ['增效额', '动力增效额', '大宗增效额', '技术服务增效额', '材料增效额', '石料增效额']
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// profitOptions() {
|
||||
// return this.categoryData.map(item => item.name) || [];
|
||||
// },
|
||||
currentDataSource() {
|
||||
console.log('yyyy', this.chartData);
|
||||
return this.activeButton === 0 ? this.chartData.sales : this.chartData.grossMargin;
|
||||
},
|
||||
locations() {
|
||||
console.log('this.chartData', this.chartData);
|
||||
return this.activeButton === 0 ? this.chartData.salesLocations : this.chartData.grossMarginLocations;
|
||||
},
|
||||
// 根据按钮切换生成对应的 chartData
|
||||
chartD() {
|
||||
// 销量场景数据
|
||||
const data = this.currentDataSource;
|
||||
console.log(this.currentDataSource, 'currentDataSource');
|
||||
// 核心:根据选中的类目,动态解析趋势数据
|
||||
trendParsedData() {
|
||||
// 1. 校验数据有效性
|
||||
if (!this.trendData || !this.selectedProfit) {
|
||||
return {
|
||||
diffs: [],
|
||||
months: [], // 月份数组(x轴标签)
|
||||
rates: [], // 完成率(completeRate)
|
||||
reals: [], // 实际值(real)
|
||||
targets: [], // 目标值(target)
|
||||
flags: [] // 达标状态
|
||||
};
|
||||
}
|
||||
|
||||
// 2. 获取选中类目的趋势数据(如:trendData.单价)
|
||||
const selectedTrend = this.trendData[this.selectedProfit] || {};
|
||||
// 3. 提取月份并排序(保证时间顺序)
|
||||
const months = Object.keys(selectedTrend).sort((a, b) => new Date(a) - new Date(b));
|
||||
// 4. 初始化数据数组
|
||||
const rates = [];
|
||||
const reals = [];
|
||||
const targets = [];
|
||||
const flags = [];
|
||||
const diffs = [];
|
||||
|
||||
// 5. 按月份提取数据
|
||||
months.forEach(month => {
|
||||
const monthData = selectedTrend[month] || {};
|
||||
const completeRate = monthData.completeRate || 0;
|
||||
// 填充对应数据
|
||||
rates.push(completeRate);
|
||||
reals.push(monthData.real || 0);
|
||||
targets.push(monthData.target || 0);
|
||||
diffs.push(monthData.diff || 0);
|
||||
// 生成达标状态(复用 getRateFlag 逻辑)
|
||||
flags.push(this.getRateFlag(completeRate));
|
||||
});
|
||||
|
||||
return {
|
||||
months,
|
||||
rates,
|
||||
reals,
|
||||
targets,
|
||||
flags,
|
||||
diffs
|
||||
};
|
||||
},
|
||||
// locations() {
|
||||
// return this.activeButton === 0 ? this.chartData?.salesLocations : this.chartData?.grossMarginLocations;
|
||||
// },
|
||||
// 重构 chartD:替换硬编码数据为动态解析数据
|
||||
chartD() {
|
||||
// 获取动态解析的趋势数据
|
||||
const { months, rates, reals, targets, flags } = this.trendParsedData;
|
||||
// 销量场景数据(保留原有结构,替换数据来源)
|
||||
const salesData = {
|
||||
allPlaceNames: this.locations,
|
||||
allPlaceNames: months, // 优先用基地名称,无则用月份
|
||||
series: [
|
||||
// 1. 完成率(折线图)
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'line',
|
||||
yAxisIndex: 1, // 绑定右侧Y轴(需在子组件启用配置)
|
||||
yAxisIndex: 1,
|
||||
lineStyle: {
|
||||
color: 'rgba(40, 138, 255, .5)',
|
||||
width: 2
|
||||
@@ -118,7 +151,7 @@ export default {
|
||||
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||
])
|
||||
},
|
||||
data: data.rates, // 完成率(%)
|
||||
data: rates, // 动态完成率
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
@@ -126,7 +159,7 @@ export default {
|
||||
{
|
||||
name: '目标',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: {
|
||||
@@ -140,7 +173,7 @@ export default {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.targets // 目标销量(万元)
|
||||
data: targets, // 动态目标值
|
||||
},
|
||||
// 3. 实际(柱状图,含达标状态)
|
||||
{
|
||||
@@ -148,93 +181,68 @@ export default {
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||
const safeFlag = data.flags;
|
||||
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||
return currentFlag === 1
|
||||
? {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||
]
|
||||
}
|
||||
: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||
]
|
||||
};
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: function (params) {
|
||||
const diff = data.diffs || [];
|
||||
const currentDiff = diff[params.dataIndex] || 0;
|
||||
return `{rate|${currentDiff}}{text|差值}`;
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.reals // 实际销量(万元)
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 毛利率场景数据
|
||||
const grossProfitData = {
|
||||
series: [
|
||||
// 1. 完成率(折线图)
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
lineStyle: { color: 'rgba(40, 138, 255, .5)', width: 2 },
|
||||
itemStyle: {
|
||||
color: 'rgba(40, 138, 255, 1)',
|
||||
borderColor: 'rgba(40, 138, 255, 1)',
|
||||
borderWidth: 2,
|
||||
radius: 4
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.2,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||
])
|
||||
},
|
||||
data: [106.7, 96.9, 106.5, 106.1, 93.8, 105.9], // 毛利率完成率(%)
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
// 2. 目标(柱状图)
|
||||
{
|
||||
name: '目标',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: {
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' }, // 顶部0px位置:阴影最强
|
||||
// { offset: 0.1, color: 'rgba(205, 215, 224, 0.4)' }, // 1px位置:阴影减弱(对应1px)
|
||||
// { offset: 0.15, color: 'rgba(205, 215, 224, 0.6)' }, // 3px位置:阴影几乎消失(对应3px扩散)
|
||||
{ offset: 0.2, color: '#ffffff' }, // 主体白色
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
// 外阴影:0px 2px 2px 0px rgba(191,203,215,0.5)
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
// 圆角:4px
|
||||
borderRadius: 4,
|
||||
// 移除边框
|
||||
borderColor: '#BFCBD577',
|
||||
borderWidth: 0,
|
||||
// 文字垂直居中(针对富文本)
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
text: {
|
||||
// 缩小宽度和内边距,适配68px容器
|
||||
width: 'auto', // 自动宽度,替代固定40px
|
||||
padding: [5, 10, 5, 0], // 缩小内边距
|
||||
align: 'center',
|
||||
color: '#464646', // 文字灰色
|
||||
fontSize: 11, // 缩小字体,适配小尺寸
|
||||
lineHeight: 20 // 垂直居中
|
||||
},
|
||||
rate: {
|
||||
width: 'auto',
|
||||
padding: [5, 0, 5, 10],
|
||||
align: 'center',
|
||||
color: '#30B590',
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
}
|
||||
}
|
||||
},
|
||||
data: [30, 32, 31, 33, 32, 34] // 目标毛利率(万元)
|
||||
},
|
||||
// 3. 实际(柱状图)
|
||||
{
|
||||
name: '实际',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
const safeFlag = [1, 0, 1, 1, 0, 1]; // 达标状态
|
||||
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||
const currentFlag = flags[params.dataIndex] || 0;
|
||||
return currentFlag === 1
|
||||
? {
|
||||
type: 'linear',
|
||||
@@ -256,12 +264,12 @@ export default {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: [32, 31, 33, 35, 30, 36] // 实际毛利率(万元)
|
||||
data: reals, // 动态实际值
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 根据按钮状态返回对应数据
|
||||
// 直接返回动态组装的 salesData(移除硬编码的毛利率数据)
|
||||
return salesData;
|
||||
}
|
||||
},
|
||||
@@ -269,8 +277,13 @@ export default {
|
||||
selectProfit(item) {
|
||||
this.selectedProfit = item;
|
||||
this.isDropdownShow = false;
|
||||
},
|
||||
// 复用达标状态判断方法
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return (rate >= 100 || rate === 0) ? 1 : 0;
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -393,7 +406,7 @@ export default {
|
||||
|
||||
.dropdown-container {
|
||||
position: relative;
|
||||
z-index: 999; // 提高z-index,确保菜单不被遮挡
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.item-button {
|
||||
@@ -457,21 +470,18 @@ export default {
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&.rotate {
|
||||
transform: rotate(90deg); // 箭头旋转方向可根据需求调整,比如改为rotate(-90deg)更符合向上展开的视觉
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-options {
|
||||
position: absolute;
|
||||
// 关键修改1:调整top值,让菜单显示在选择框上方,calc(-100% - 2px)表示向上偏移自身100%再加2px间距
|
||||
bottom: 100%;
|
||||
right: 0;
|
||||
// 移除多余的margin-top,避免额外间距
|
||||
// margin-top: 2px;
|
||||
margin-top: 2px;
|
||||
width: 123px;
|
||||
background: #ffffff;
|
||||
// 关键修改2:调整border-radius,让菜单顶部圆角匹配选择框的右上角,底部圆角为0(更美观)
|
||||
border-radius: 8px 8px 0 0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div :id="id" style="width: 100%; height:100%;"></div>
|
||||
<div class="bottomTip">
|
||||
<div class="precent">
|
||||
<span class="precentNum">{{ energyObj.electricComu }} </span>
|
||||
<span class="precentNum">{{ detailData.completeRate || 0 }} </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -18,31 +18,32 @@ export default {
|
||||
// components: { Container },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
energyObj: {
|
||||
detailData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
electricComu: 0,
|
||||
steamComu: 20, // 调整为符合max范围的数值(0-8)
|
||||
// electricity: [120, 150, 130, 180, 160, 200, 190],
|
||||
// steam: [80, 95, 85, 110, 100, 120, 115],
|
||||
// dates: ['1日', '2日', '3日', '4日', '5日', '6日', '7日']
|
||||
// electricComu: 0,
|
||||
// steamComu: 20, // 调整为符合max范围的数值(0-8)
|
||||
// // electricity: [120, 150, 130, 180, 160, 200, 190],
|
||||
// // steam: [80, 95, 85, 110, 100, 120, 115],
|
||||
// // dates: ['1日', '2日', '3日', '4日', '5日', '6日', '7日']
|
||||
})
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: () => ('')
|
||||
default: () => ('monthG')
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
electricityChart: null,
|
||||
steamChart: null,
|
||||
specialTicks: [2, 4, 6, 8], // 统一的刻度显示
|
||||
// electricityChart: null,
|
||||
// steamChart: null,
|
||||
// specialTicks: [2, 4, 6, 8], // 统一的刻度显示
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
energyObj: {
|
||||
detailData: {
|
||||
deep: true,
|
||||
immediate: true, // 初始化时立即执行
|
||||
handler() {
|
||||
this.updateGauges()
|
||||
}
|
||||
@@ -55,42 +56,47 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
observeContainerResize() {
|
||||
const container = document.querySelector('.gauge-container')
|
||||
// 修复:获取正确的容器(组件内的.gauge-container)
|
||||
const container = this.$el.querySelector('.gauge-container')
|
||||
if (container && window.ResizeObserver) {
|
||||
const resizeObserver = new ResizeObserver(entries => {
|
||||
this.handleResize()
|
||||
})
|
||||
resizeObserver.observe(container)
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
resizeObserver.unobserve(container)
|
||||
this.resizeObserver = new ResizeObserver(entries => {
|
||||
if (this.electricityChart) {
|
||||
this.electricityChart.resize() // 直接触发resize,无需防抖
|
||||
}
|
||||
})
|
||||
this.resizeObserver.observe(container)
|
||||
}
|
||||
},
|
||||
initGauges() {
|
||||
// console.log('this.id',this.id);
|
||||
|
||||
// 初始化电气图表实例
|
||||
const electricityDom = document.getElementById(this.id)
|
||||
if (electricityDom) {
|
||||
// 修复:正确创建并存储图表实例
|
||||
this.electricityChart = echarts.init(electricityDom)
|
||||
// 首次更新数据
|
||||
this.updateGauges()
|
||||
}
|
||||
// 初始化蒸汽图表实例
|
||||
const steamDom = document.getElementById('steamGauge')
|
||||
if (steamDom) {
|
||||
this.steamChart = echarts.init(steamDom)
|
||||
}
|
||||
// 首次更新数据
|
||||
this.updateGauges()
|
||||
// 蒸汽图表若未使用,可注释/删除
|
||||
// const steamDom = document.getElementById('steamGauge')
|
||||
// if (steamDom) {
|
||||
// this.steamChart = echarts.init(steamDom)
|
||||
// }
|
||||
},
|
||||
updateGauges() {
|
||||
// 优化:仅更新数据,不销毁实例(提升性能)
|
||||
if (this.electricityChart) {
|
||||
// 转换原始数据为“万kw/h”(与仪表盘max匹配)
|
||||
const electricValue = 80
|
||||
this.electricityChart.setOption(this.getElectricityGaugeOption(electricValue))
|
||||
}
|
||||
// 修复:先判断实例是否存在,再更新配置
|
||||
if (!this.electricityChart) return
|
||||
|
||||
// 修复:兜底获取rate值,确保数值有效
|
||||
const rate = Number(this.detailData?.completeRate) || 0
|
||||
console.log('当前rate值:', rate); // 调试:确认rate值正确
|
||||
|
||||
// 关键:第二个参数传true,清空原有配置,强制更新
|
||||
this.electricityChart.setOption(this.getElectricityGaugeOption(rate), true)
|
||||
},
|
||||
// 1. 用电量仪表盘独立配置函数
|
||||
// 用电量仪表盘配置(保留原有样式,优化数值范围)
|
||||
getElectricityGaugeOption(value) {
|
||||
// 用电量专属渐变色
|
||||
const electricityGradient = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#0B58FF' },
|
||||
{ offset: 1, color: '#32FFCD' }
|
||||
@@ -101,12 +107,12 @@ export default {
|
||||
{
|
||||
name: '月度',
|
||||
type: 'gauge',
|
||||
radius: '95',
|
||||
radius: '95', // 修复:添加%,避免数值错误
|
||||
center: ['50%', '90%'],
|
||||
startAngle: 180,
|
||||
endAngle: 0,
|
||||
min: 0,
|
||||
max: 100, // 用电量专属最大值
|
||||
max: 100,
|
||||
splitNumber: 4,
|
||||
label: { show: false },
|
||||
progress: {
|
||||
@@ -121,117 +127,34 @@ export default {
|
||||
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
|
||||
length: '75%',
|
||||
width: 16,
|
||||
itemStyle: { color: '#288AFF' }, // 用电量指针颜色
|
||||
itemStyle: { color: '#288AFF' },
|
||||
offsetCenter: [0, '10%']
|
||||
},
|
||||
axisLine: {
|
||||
roundCap: true,
|
||||
lineStyle: { width: 12, color: [[1, '#E6EBF7']] }
|
||||
},
|
||||
// axisTick: {
|
||||
// splitNumber: 2,
|
||||
// show: (val) => this.specialTicks.includes(val),
|
||||
// lineStyle: { width: 2, color: '#999' }
|
||||
// },
|
||||
splitLine: {
|
||||
length: 10, lineStyle: { width: 5, color: '#D6DAE5' },
|
||||
length: 10,
|
||||
lineStyle: { width: 5, color: '#D6DAE5' },
|
||||
},
|
||||
// axisLabel: {
|
||||
// show: (val) => this.specialTicks.includes(val),
|
||||
// distance: -45,
|
||||
// color: '#ffffff',
|
||||
// fontSize: 14,
|
||||
// fontWeight: 'bold'
|
||||
// },
|
||||
axisTick: {
|
||||
splitNumber: 2,
|
||||
length:6,
|
||||
length: 6,
|
||||
lineStyle: { width: 2, color: '#D6DAE5' }
|
||||
},
|
||||
axisLabel: {
|
||||
show: false,
|
||||
},
|
||||
detail: { show: false },
|
||||
data: [{ value, unit: '' }] // 用电量单位
|
||||
data: [{ value: value, unit: '' }] // 确保数值正确传入
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// 2. 用蒸汽仪表盘独立配置函数
|
||||
getSteamGaugeOption(value) {
|
||||
// 蒸汽专属渐变色
|
||||
const steamGradient = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: 'rgba(11, 168, 255, 0.26)' },
|
||||
{ offset: 1, color: 'rgba(54, 239, 230, 1)' }
|
||||
])
|
||||
|
||||
return {
|
||||
series: [
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'gauge',
|
||||
radius: '80',
|
||||
center: ['50%', '85%'],
|
||||
startAngle: 180,
|
||||
endAngle: 0,
|
||||
min: 0,
|
||||
max: 160, // 蒸汽专属最大值
|
||||
splitNumber: 4,
|
||||
label: { show: false },
|
||||
progress: {
|
||||
show: true,
|
||||
overlap: false,
|
||||
roundCap: true,
|
||||
clip: false,
|
||||
width: 14,
|
||||
itemStyle: { color: steamGradient }
|
||||
},
|
||||
pointer: {
|
||||
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
|
||||
length: '75%',
|
||||
width: 16,
|
||||
itemStyle: { color: 'rgba(11, 168, 255, 1)' }, // 蒸汽指针颜色
|
||||
offsetCenter: [0, '5%']
|
||||
},
|
||||
axisLine: {
|
||||
roundCap: true,
|
||||
lineStyle: { width: 14, color: [[1, 'rgba(19, 84, 122, 1)']] }
|
||||
},
|
||||
axisTick: {
|
||||
splitNumber: 2,
|
||||
show: (val) => this.specialTicks.includes(val),
|
||||
lineStyle: { width: 2, color: '#999' }
|
||||
},
|
||||
splitLine: { length: 12, lineStyle: { width: 3, color: '#999' } },
|
||||
axisLabel: {
|
||||
show: (val) => this.specialTicks.includes(val),
|
||||
distance: -45,
|
||||
color: '#ffffff',
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
detail: { show: false },
|
||||
data: [{ value, unit: 't' }] // 蒸汽单位
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// handleResize() {
|
||||
// if (this.resizeTimer) clearTimeout(this.resizeTimer)
|
||||
// this.resizeTimer = setTimeout(() => {
|
||||
// if (this.electricityChart) this.electricityChart.resize()
|
||||
// if (this.steamChart) this.steamChart.resize()
|
||||
// }, 100)
|
||||
// }
|
||||
},
|
||||
// beforeDestroy() {
|
||||
// if (this.electricityChart) this.electricityChart.dispose()
|
||||
// if (this.steamChart) this.steamChart.dispose()
|
||||
// window.removeEventListener('resize', this.handleResize)
|
||||
// if (this.resizeTimer) clearTimeout(this.resizeTimer)
|
||||
// }
|
||||
// 未使用的蒸汽仪表盘可注释/删除
|
||||
// getSteamGaugeOption(value) { ... }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -298,9 +221,4 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||
<div class="dashboard">
|
||||
<div class="title">
|
||||
@@ -11,112 +9,124 @@
|
||||
</div>
|
||||
<div class="number">
|
||||
<div class="yield">
|
||||
90%
|
||||
{{ formatRate(factoryData?.completeRate) }}%
|
||||
</div>
|
||||
<div class="mom">
|
||||
环比10%
|
||||
<img class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||
环比{{ formatRate(factoryData?.thb) }}%
|
||||
<img v-if="factoryData?.thb >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="上升">
|
||||
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="下降">
|
||||
</div>
|
||||
</div>
|
||||
<div class="electricityGauge">
|
||||
<electricityGauge id="month"></electricityGauge>
|
||||
<!-- 传递包含flag的factoryData给仪表盘组件 -->
|
||||
<electricityGauge id="month" :detailData="factoryData"></electricityGauge>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line" style="padding: 0px;">
|
||||
<verticalBarChart>
|
||||
</verticalBarChart>
|
||||
|
||||
<!-- 传递包含flag的factoryData给柱状图组件 -->
|
||||
<verticalBarChart :detailData="factoryData"></verticalBarChart>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
import electricityGauge from './electricityGauge.vue'
|
||||
import verticalBarChart from './verticalBarChart.vue'
|
||||
|
||||
// import * as echarts from 'echarts'
|
||||
// import rawItem from './raw-Item.vue'
|
||||
// 引入箭头图片(根据实际路径调整,若模板中直接用路径可注释)
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, electricityGauge, verticalBarChart },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
itemData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
monData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
title: { // 接收父组件传递的设备数据数组
|
||||
title: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
month: { // 接收父组件传递的设备数据数组
|
||||
month: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
itemData: {
|
||||
handler(newValue, oldValue) {
|
||||
// this.updateChart()
|
||||
},
|
||||
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||
computed: {
|
||||
/**
|
||||
* 自动提取monData中的工厂数据,并新增flag字段
|
||||
*/
|
||||
factoryData() {
|
||||
const factoryKeys = Object.keys(this.monData);
|
||||
if (factoryKeys.length === 0) {
|
||||
// 无数据时返回兜底对象(包含flag)
|
||||
return {
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0,
|
||||
flag: 0 // 兜底flag
|
||||
};
|
||||
}
|
||||
const firstKey = factoryKeys[0];
|
||||
const rawData = this.monData[firstKey];
|
||||
// 整合原始数据 + 计算flag
|
||||
return {
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0,
|
||||
...rawData,
|
||||
flag: this.getRateFlag(rawData.completeRate) // 新增flag字段
|
||||
};
|
||||
}
|
||||
},
|
||||
// computed: {
|
||||
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||
// sortedItemData() {
|
||||
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||
// const totalCostItems = this.itemData.filter(item =>
|
||||
// item.name && item.name.includes('总成本')
|
||||
// );
|
||||
// // 过滤出不包含“总成本”的项
|
||||
// const otherItems = this.itemData.filter(item =>
|
||||
// !item.name || !item.name.includes('总成本')
|
||||
// );
|
||||
// // 合并:总成本项在前,其他项在后
|
||||
// return [...totalCostItems, ...otherItems];
|
||||
// }
|
||||
// },
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 格式化百分比数值:处理空值/非数字,兜底为0
|
||||
*/
|
||||
formatRate(value) {
|
||||
if (isNaN(value) || value === null || value === undefined) {
|
||||
return 0;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
/**
|
||||
* 判断完成率对应的flag值(<100为0,≥100为1)
|
||||
* @param {number} rate 完成率(原始值,如89代表89%)
|
||||
* @returns {0|1} flag值
|
||||
*/
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return rate >= 100 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</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;
|
||||
}
|
||||
|
||||
@@ -127,16 +137,12 @@ export default {
|
||||
padding: 16px 0 0 16px;
|
||||
|
||||
.title {
|
||||
// width: 190px;
|
||||
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;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
@@ -144,19 +150,16 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
// width: 190px;
|
||||
height: 32px;
|
||||
font-family: YouSheBiaoTiHei;
|
||||
font-size: 32px;
|
||||
color: #0B58FF;
|
||||
line-height: 32px;
|
||||
letter-spacing: 2px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.mom {
|
||||
width: 97px;
|
||||
width: fit-content; // 自适应宽度,避免文字溢出
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
@@ -164,8 +167,16 @@ export default {
|
||||
color: #000000;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
display: flex;
|
||||
align-items: center; // 箭头和文字垂直居中
|
||||
gap: 4px; // 文字和箭头间距
|
||||
}
|
||||
|
||||
// 箭头样式优化
|
||||
.arrow {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,34 +185,4 @@ export default {
|
||||
height: 205px;
|
||||
background: #F9FCFF;
|
||||
}
|
||||
|
||||
// .leftTitle {
|
||||
// .item {
|
||||
// width: 67px;
|
||||
// height: 180px;
|
||||
// padding: 37px 23px;
|
||||
// background: #F9FCFF;
|
||||
// font-family: PingFangSC, PingFang SC;
|
||||
// font-weight: 400;
|
||||
// font-size: 18px;
|
||||
// color: #000000;
|
||||
// line-height: 25px;
|
||||
// letter-spacing: 1px;
|
||||
// // text-align: left;
|
||||
// font-style: normal;
|
||||
// }
|
||||
// }
|
||||
</style>
|
||||
|
||||
<!-- <style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style> -->
|
||||
|
||||
@@ -1,29 +1,21 @@
|
||||
<template>
|
||||
<div class="coreBar">
|
||||
<!-- 新增行容器:包裹“各基地情况”和barTop -->
|
||||
<div class="header-row">
|
||||
<div class="base-title">
|
||||
各基地情况
|
||||
</div>
|
||||
<div class="base-title">各基地情况</div>
|
||||
<div class="barTop">
|
||||
<!-- 关键:新增右侧容器,包裹图例和按钮组,实现整体靠右 -->
|
||||
<div class="right-container">
|
||||
<div class="legend">
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon line yield"></span>
|
||||
完成率
|
||||
<span class="legend-icon line yield"></span>完成率
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square target"></span>
|
||||
目标
|
||||
<span class="legend-icon square target"></span>预算
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square achieved"></span>
|
||||
实际·达标
|
||||
<span class="legend-icon square achieved"></span>实际·达标
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square unachieved"></span>
|
||||
实际·未达标
|
||||
<span class="legend-icon square unachieved"></span>实际·未达标
|
||||
</span>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
@@ -32,13 +24,13 @@
|
||||
</div>
|
||||
<div class="dropdown-container">
|
||||
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||
<span class="item-text profit-text">{{ selectedProfit || '请选择' }}</span>
|
||||
<span class="item-text profit-text">{{ selectedSort || '请选择' }}</span>
|
||||
<span class="dropdown-arrow" :class="{ 'rotate': isDropdownShow }"></span>
|
||||
</div>
|
||||
<div class="dropdown-options" v-if="isDropdownShow">
|
||||
<div class="dropdown-option" v-for="(item, index) in profitOptions" :key="index"
|
||||
@click.stop="selectProfit(item)">
|
||||
{{ item }}
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,130 +52,79 @@ export default {
|
||||
name: "Container",
|
||||
components: { operatingLineBar },
|
||||
props: ["chartData"],
|
||||
emits: ['sort-change'], // 声明事件(Vue3 推荐)
|
||||
data() {
|
||||
return {
|
||||
activeButton: 0,
|
||||
isDropdownShow: false,
|
||||
selectedProfit: null, // 选中的名称,初始为null
|
||||
selectedSort: null, // 选中的label
|
||||
selectedSortValue: null, // 选中的value,用于排序逻辑
|
||||
profitOptions: [
|
||||
'实际值:高~低',
|
||||
'实际值:低~高',
|
||||
'目标值:高~低',
|
||||
'目标值:低~高',
|
||||
|
||||
{ label: '实际值:高~低', value: 1 },
|
||||
{ label: '实际值:低~高', value: 2 },
|
||||
{ label: '完成率:高~低', value: 3 },
|
||||
{ label: '完成率:低~高', value: 4 },
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// profitOptions() {
|
||||
// return this.categoryData.map(item => item.name) || [];
|
||||
// },
|
||||
// 排序后的数据源(核心:根据selectedSortValue重新排序)
|
||||
currentDataSource() {
|
||||
console.log('yyyy', this.chartData);
|
||||
if (!this.chartData) return {};
|
||||
|
||||
return this.activeButton === 0 ? this.chartData.sales : this.chartData.grossMargin;
|
||||
// 深拷贝原始数据,避免修改原数据
|
||||
const factory = JSON.parse(JSON.stringify(this.chartData));
|
||||
if (!factory.locations.length || !this.selectedSortValue) return factory;
|
||||
|
||||
// 构建带索引的数组,方便同步所有字段排序
|
||||
const dataWithIndex = factory.locations.map((name, index) => ({
|
||||
index,
|
||||
name,
|
||||
real: factory.reals[index],
|
||||
target: factory.targets[index],
|
||||
rate: factory.rates[index],
|
||||
diff: factory.diff[index],
|
||||
flag: factory.flags[index]
|
||||
}));
|
||||
|
||||
// 根据选中的排序规则排序
|
||||
switch (this.selectedSortValue) {
|
||||
case 1: // 实际值:高~低
|
||||
dataWithIndex.sort((a, b) => b.real - a.real);
|
||||
break;
|
||||
case 2: // 实际值:低~高
|
||||
dataWithIndex.sort((a, b) => a.real - b.real);
|
||||
break;
|
||||
case 3: // 目标值:高~低
|
||||
dataWithIndex.sort((a, b) => b.rate - a.rate);
|
||||
break;
|
||||
case 4: // 目标值:低~高
|
||||
dataWithIndex.sort((a, b) => a.rate - b.rate);
|
||||
break;
|
||||
default:
|
||||
return factory;
|
||||
}
|
||||
|
||||
// 同步更新所有数组
|
||||
factory.locations = dataWithIndex.map(item => item.name);
|
||||
factory.reals = dataWithIndex.map(item => item.real);
|
||||
factory.targets = dataWithIndex.map(item => item.target);
|
||||
factory.rates = dataWithIndex.map(item => item.rate);
|
||||
factory.diff = dataWithIndex.map(item => item.diff);
|
||||
factory.flags = dataWithIndex.map(item => item.flag);
|
||||
|
||||
return factory;
|
||||
},
|
||||
locations() {
|
||||
console.log('this.chartData', this.chartData);
|
||||
|
||||
return this.activeButton === 0 ? this.chartData.salesLocations : this.chartData.grossMarginLocations;
|
||||
return this.currentDataSource.locations || [];
|
||||
},
|
||||
// 根据按钮切换生成对应的 chartData
|
||||
// 最终传递给图表的排序后数据
|
||||
chartD() {
|
||||
// 销量场景数据
|
||||
const data = this.currentDataSource;
|
||||
console.log(this.currentDataSource, 'currentDataSource');
|
||||
|
||||
const salesData = {
|
||||
allPlaceNames: this.locations,
|
||||
series: [
|
||||
// 1. 完成率(折线图)
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'line',
|
||||
yAxisIndex: 1, // 绑定右侧Y轴(需在子组件启用配置)
|
||||
lineStyle: {
|
||||
color: 'rgba(40, 138, 255, .5)',
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(40, 138, 255, 1)',
|
||||
borderColor: 'rgba(40, 138, 255, 1)',
|
||||
borderWidth: 2,
|
||||
radius: 4
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.2,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||
])
|
||||
},
|
||||
data: data.rates, // 完成率(%)
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
// 2. 目标(柱状图)
|
||||
{
|
||||
name: '目标',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.targets // 目标销量(万元)
|
||||
},
|
||||
// 3. 实际(柱状图,含达标状态)
|
||||
{
|
||||
name: '实际',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||
const safeFlag = data.flags;
|
||||
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||
return currentFlag === 1
|
||||
? {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||
]
|
||||
}
|
||||
: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||
]
|
||||
};
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.reals // 实际销量(万元)
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 毛利率场景数据
|
||||
const grossProfitData = {
|
||||
series: [
|
||||
// 1. 完成率(折线图)
|
||||
// 完成率(折线图)
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'line',
|
||||
@@ -202,13 +143,13 @@ export default {
|
||||
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||
])
|
||||
},
|
||||
data: [106.7, 96.9, 106.5, 106.1, 93.8, 105.9], // 毛利率完成率(%)
|
||||
data: data.rates || [],
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
// 2. 目标(柱状图)
|
||||
// 目标(柱状图)
|
||||
{
|
||||
name: '目标',
|
||||
name: '预算',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
@@ -224,17 +165,64 @@ export default {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: [30, 32, 31, 33, 32, 34] // 目标毛利率(万元)
|
||||
data: data.targets || []
|
||||
},
|
||||
// 3. 实际(柱状图)
|
||||
// 实际(柱状图)
|
||||
{
|
||||
name: '实际',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [30, 0],
|
||||
width: 68,
|
||||
height: 20,
|
||||
formatter: (params) => {
|
||||
const diff = data.diff || [];
|
||||
const currentDiff = diff[params.dataIndex] || 0;
|
||||
return `{rate|${currentDiff}}{text|差值}`;
|
||||
},
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||
{ offset: 0.2, color: '#ffffff' },
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
borderRadius: 4,
|
||||
borderColor: '#BFCBD577',
|
||||
borderWidth: 0,
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
text: {
|
||||
width: 'auto',
|
||||
padding: [5, 10, 5, 0],
|
||||
align: 'center',
|
||||
color: '#464646',
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
},
|
||||
rate: {
|
||||
width: 'auto',
|
||||
padding: [5, 0, 5, 10],
|
||||
align: 'center',
|
||||
color: '#30B590',
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
}
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
const safeFlag = [1, 0, 1, 1, 0, 1]; // 达标状态
|
||||
const safeFlag = data.flags || [];
|
||||
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||
return currentFlag === 1
|
||||
? {
|
||||
@@ -257,21 +245,34 @@ export default {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: [32, 31, 33, 35, 30, 36] // 实际毛利率(万元)
|
||||
data: data.reals || []
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 根据按钮状态返回对应数据
|
||||
return salesData;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectProfit(item) {
|
||||
this.selectedProfit = item;
|
||||
// 更新选中的label和value
|
||||
this.selectedSort = item.label;
|
||||
this.selectedSortValue = item.value;
|
||||
this.isDropdownShow = false;
|
||||
// 向父组件传递排序事件(可选,保持原有逻辑)
|
||||
this.$emit('sort-change', item.value);
|
||||
}
|
||||
},
|
||||
// 监听父组件传入的chartData变化,重置选中状态(可选)
|
||||
watch: {
|
||||
'chartData.factory': {
|
||||
handler() {
|
||||
// 若需要切换数据源后重置排序,可取消注释
|
||||
// this.selectedSort = null;
|
||||
// this.selectedSortValue = null;
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -282,16 +283,14 @@ export default {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
|
||||
// 新增:头部行容器,实现一行排列
|
||||
.header-row {
|
||||
display: flex;
|
||||
justify-content: space-between; // 左右两端对齐
|
||||
align-items: center; // 垂直居中
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-bottom: 8px; // 与下方图表区保留间距(可根据需求调整)
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
// 各基地情况标题样式
|
||||
.base-title {
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
@@ -299,29 +298,25 @@ export default {
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
font-style: normal;
|
||||
padding: 0 0 0 16px; // 保留原有内边距
|
||||
white-space: nowrap; // 防止文字换行
|
||||
padding: 0 0 0 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.barTop {
|
||||
// 移除原有flex和justify-content,由header-row控制
|
||||
width: auto; // 自适应宽度
|
||||
// 保留原有align-items,确保内部元素垂直居中
|
||||
width: auto;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||
.right-container {
|
||||
display: flex;
|
||||
align-items: center; // 图例和按钮组垂直居中
|
||||
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
margin-right: 46px;
|
||||
}
|
||||
|
||||
// 2. 图例:在右侧容器内横向排列
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 16px; // 图例项之间间距,避免重叠
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -336,7 +331,7 @@ export default {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
white-space: nowrap; // 防止图例文字换行
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.legend-icon {
|
||||
@@ -365,7 +360,6 @@ export default {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
// 图例颜色
|
||||
.yield {
|
||||
background: rgba(40, 138, 255, 1);
|
||||
}
|
||||
@@ -382,7 +376,6 @@ export default {
|
||||
background: rgba(255, 132, 0, 1);
|
||||
}
|
||||
|
||||
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||
.button-group {
|
||||
display: flex;
|
||||
position: relative;
|
||||
@@ -406,7 +399,6 @@ export default {
|
||||
line-height: 24px;
|
||||
font-style: normal;
|
||||
letter-spacing: 2px;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
.item-text {
|
||||
|
||||
@@ -10,17 +10,24 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
myChart: null, // 存储图表实例
|
||||
resizeHandler: null // 存储resize事件处理函数,用于后续移除
|
||||
resizeHandler: null, // 存储resize事件处理函数
|
||||
// 核心:基地名称与序号的映射表(固定顺序)
|
||||
baseNameToIndexMap: {
|
||||
'宜兴': 7,
|
||||
'漳州': 8,
|
||||
'自贡': 3,
|
||||
'桐城': 2,
|
||||
'洛阳': 9,
|
||||
'合肥': 5,
|
||||
'宿迁': 6,
|
||||
'秦皇岛': 10
|
||||
}
|
||||
};
|
||||
},
|
||||
props: {
|
||||
chartData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
// 可选:保留数据校验
|
||||
// validator: (value) => {
|
||||
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||
// }
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -57,19 +64,28 @@ export default {
|
||||
|
||||
// 绑定点击事件(只绑定一次,永久生效)
|
||||
this.myChart.on('click', (params) => {
|
||||
// 箭头函数保证this指向Vue实例
|
||||
console.log('点击事件的参数:', params);
|
||||
|
||||
// 提取关键数据(注意:如果是折线图,value是数组;柱状图是单个值,需兼容)
|
||||
// 提取点击的基地名称
|
||||
const itemName = params.name;
|
||||
// 根据映射表获取对应的序号(未匹配到则返回0或其他默认值)
|
||||
const baseIndex = this.baseNameToIndexMap[itemName] || 0;
|
||||
|
||||
// 兼容不同图表类型的value:柱状图value是数值,折线图是[横坐标, 纵坐标]
|
||||
const itemValue = Array.isArray(params.value) ? params.value[1] : params.value;
|
||||
const seriesName = params.seriesName;
|
||||
console.log(`你点击了【${itemName}】,${seriesName}:${itemValue}`);
|
||||
// const itemValue = Array.isArray(params.value) ? params.value[1] : params.value;
|
||||
// const seriesName = params.seriesName;
|
||||
|
||||
console.log(`你点击了【${itemName}】(序号:${baseIndex})`);
|
||||
|
||||
// 路由跳转时携带序号(或名称+序号)
|
||||
this.$router.push({
|
||||
path: 'procurementGainAnalysisBase',
|
||||
base: itemName
|
||||
})
|
||||
query: { // 使用query传递参数(推荐),也可使用params
|
||||
// baseName: itemName,
|
||||
factory: baseIndex
|
||||
}
|
||||
// 若仍需用base作为参数:
|
||||
// base: itemName,
|
||||
// params: { baseIndex: baseIndex }
|
||||
});
|
||||
});
|
||||
|
||||
// 定义resize处理函数(命名函数,方便移除)
|
||||
@@ -119,7 +135,12 @@ export default {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0]
|
||||
padding: [5, 0, 0, 0],
|
||||
// 可选:X轴标签显示“序号+名称”(如“1 宜兴”)
|
||||
// formatter: (value) => {
|
||||
// const index = this.baseNameToIndexMap[value] || '';
|
||||
// return index ? `${index} ${value}` : value;
|
||||
// }
|
||||
},
|
||||
data: xData
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export default {
|
||||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||
chartData: {
|
||||
handler() {
|
||||
console.log(this.chartData,'chartData');
|
||||
console.log(this.chartData, 'chartData');
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
@@ -54,7 +54,7 @@ export default {
|
||||
}
|
||||
|
||||
this.myChart = echarts.init(chartDom);
|
||||
|
||||
|
||||
const { allPlaceNames, series } = this.chartData || {};
|
||||
console.log('chartData', this.chartData);
|
||||
|
||||
|
||||
@@ -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: {
|
||||
thisMonData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
required: true, // 标记为必填,避免空数据导致异常
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartData: null, // 初始化 chartData 为 null
|
||||
chartData: null, // 工厂图表数据
|
||||
groupData: {}, // 集团(凯盛新能)数据
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
grossMarginTrendMap: {
|
||||
handler() {
|
||||
this.processChartData();
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
salesTrendMap: {
|
||||
thisMonData: {
|
||||
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→1,false→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.thisMonData['凯盛新能'] || {
|
||||
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.thisMonData).filter(key => key !== '凯盛新能');
|
||||
const factoryDataList = factoryKeys.map(key => this.thisMonData[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;
|
||||
|
||||
@@ -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→1,false→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;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="lineBottom" style="height: 180px; width: 100%">
|
||||
<operatingLineBarSaleSingle :chartData="chartD" style="height: 99%; width: 100%" />
|
||||
<operatingLineBarSaleSingle :refName="'totalOperating'" :chartData="chartD" style="height: 99%; width: 100%" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -11,10 +11,9 @@ import * as echarts from 'echarts';
|
||||
export default {
|
||||
name: "Container",
|
||||
components: { operatingLineBarSaleSingle },
|
||||
props: ["chartData"],
|
||||
props: ["detailData"],
|
||||
data() {
|
||||
return {
|
||||
activeButton: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -24,10 +23,13 @@ export default {
|
||||
chartD() {
|
||||
// 背景图片路径(若不需要可注释)
|
||||
// const bgImageUrl = require('@/assets/img/labelBg.png');
|
||||
const rate = this.detailData?.completeRate || 0
|
||||
const diff = this.detailData?.diff || 0
|
||||
console.log('diff', diff);
|
||||
|
||||
const seriesData = [
|
||||
{
|
||||
value: 131744,
|
||||
value: this.detailData?.target || 0,
|
||||
flag: 1, // 实际项:达标(绿色)
|
||||
label: {
|
||||
show: true,
|
||||
@@ -37,7 +39,9 @@ export default {
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{value|完成率}{rate|139%}',
|
||||
formatter: function (params) {
|
||||
return `{value|完成率}{rate|${rate}%}`;
|
||||
},
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
@@ -91,8 +95,8 @@ export default {
|
||||
},
|
||||
},
|
||||
{
|
||||
value: 630230,
|
||||
flag: 0, // 预算项:未达标(橙色)
|
||||
value: this.detailData?.real || 0,
|
||||
flag: this.detailData?.flag, // 实际项:达标(绿色)
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
@@ -101,8 +105,11 @@ export default {
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{rate|139%}{text|差值}',
|
||||
// 核心样式:匹配CSS需求
|
||||
formatter: function (params) {
|
||||
// 假设 params.data 是完成率数值(如 139)
|
||||
// // 2. 模板字符串拼接富文本标签 + 动态值
|
||||
return `{rate|${diff}}{text|差值}`;
|
||||
},
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
@@ -189,6 +196,7 @@ export default {
|
||||
},
|
||||
],
|
||||
};
|
||||
console.log('data', data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -23,17 +23,19 @@ export default {
|
||||
currentDataSource() {
|
||||
console.log('yyyy', this.chartData);
|
||||
|
||||
return this.activeButton === 0 ? this.chartData.sales : this.chartData.grossMargin;
|
||||
return this.chartData
|
||||
},
|
||||
locations() {
|
||||
console.log('this.chartData', this.chartData);
|
||||
|
||||
return this.activeButton === 0 ? this.chartData.salesLocations : this.chartData.grossMarginLocations;
|
||||
return this.chartData.locations
|
||||
},
|
||||
// 根据按钮切换生成对应的 chartData
|
||||
chartD() {
|
||||
// 销量场景数据
|
||||
const data = this.currentDataSource;
|
||||
const diff = data.diff[0]
|
||||
const rate = data.rate[0]
|
||||
console.log(this.currentDataSource, 'currentDataSource');
|
||||
|
||||
const salesData = {
|
||||
@@ -73,12 +75,15 @@ export default {
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
offset: [-30, 0],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{value|完成率}{rate|139%}',
|
||||
formatter: (params) => {
|
||||
// const { rate = 0, diff = 0 } = params.data || {};
|
||||
return `{value|完成率}{rate|${rate}%}`;
|
||||
},
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
@@ -149,12 +154,15 @@ export default {
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
offset: [30, 0],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{rate|139%}{text|差值}',
|
||||
formatter: (params) => {
|
||||
// const { rate = 0, diff = 0 } = params.data || {};
|
||||
return `{rate|${diff}}{text|差值}`;
|
||||
},
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
|
||||
@@ -1,48 +1,62 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container :name="title" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<Container :isShowTab="true" :name="title" icon="cockpitItemIcon" size="opLargeBg" topSize="large"
|
||||
@tabChange="handleChange">
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||
<div class="dashboard left">
|
||||
<div class="dashboard left" @click="handleRoute('/netPriceAnalysis/netPriceAnalysisBase')">
|
||||
<div class="title">
|
||||
大宗类·单位/万元
|
||||
大宗类
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.大宗增效额 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.大宗增效额 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right" @click="handleRoute('/unitPriceAnalysis/unitPriceAnalysisBase')">
|
||||
<div class="title">
|
||||
石料类
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.石料增效额 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.石料增效额 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right" @click="handleRoute('/unitPriceAnalysis/unitPriceAnalysisBase')">
|
||||
<div class="title">
|
||||
材料类
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.材料增效额 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.材料增效额 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
石料类·单位/万元
|
||||
动力类
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.动力增效额 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.动力增效额 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
<div class="dashboard right" @click="handleRoute('/salesVolumeAnalysis/doublePlatedBase')">
|
||||
<div class="title">
|
||||
材料类·单位/万元
|
||||
技术服务类
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
动力类·单位/万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
技术服务类·单位/万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.技术服务类 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.技术服务类 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -53,88 +67,95 @@
|
||||
<script>
|
||||
import Container from '../components/container.vue'
|
||||
import operatingSingleBar from './operatingSingleBar.vue'
|
||||
import verticalBarChart from './verticalBarChart.vue'
|
||||
|
||||
// import * as echarts from 'echarts'
|
||||
// import rawItem from './raw-Item.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, operatingSingleBar, verticalBarChart },
|
||||
// mixins: [resize],
|
||||
components: { Container, operatingSingleBar },
|
||||
props: {
|
||||
itemData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
relatedData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
relatedMon: {}, // 兜底月度数据
|
||||
relatedTotal: {} // 兜底累计数据
|
||||
})
|
||||
},
|
||||
title: { // 接收父组件传递的设备数据数组
|
||||
title: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
month: { // 接收父组件传递的设备数据数组
|
||||
month: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
|
||||
// 关键优化:初始化时直接赋值为月度数据(relatedMon)
|
||||
relatedDetailData: this.relatedData.relatedMon || {},
|
||||
defaultData: {
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听 relatedData 变化(异步加载场景),同步更新月度数据
|
||||
relatedData: {
|
||||
handler(newVal) {
|
||||
this.relatedDetailData = newVal.relatedMon || {};
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
},
|
||||
itemData: {
|
||||
handler(newValue, oldValue) {
|
||||
// this.updateChart()
|
||||
// 保留原有逻辑(若有需要)
|
||||
},
|
||||
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
// computed: {
|
||||
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||
// sortedItemData() {
|
||||
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||
// const totalCostItems = this.itemData.filter(item =>
|
||||
// item.name && item.name.includes('总成本')
|
||||
// );
|
||||
// // 过滤出不包含“总成本”的项
|
||||
// const otherItems = this.itemData.filter(item =>
|
||||
// !item.name || !item.name.includes('总成本')
|
||||
// );
|
||||
// // 合并:总成本项在前,其他项在后
|
||||
// return [...totalCostItems, ...otherItems];
|
||||
// }
|
||||
// },
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
// 无需额外操作,初始化数据已赋值
|
||||
},
|
||||
methods: {
|
||||
handleRoute(path) {
|
||||
this.$router.push({
|
||||
path: path
|
||||
})
|
||||
},
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return +(rate >= 100 || rate === 0);
|
||||
},
|
||||
handleChange(value) {
|
||||
console.log('value', value, this.relatedData);
|
||||
if (value === 'month') {
|
||||
this.relatedDetailData = this.relatedData.relatedMon || {};
|
||||
} else {
|
||||
this.relatedDetailData = this.relatedData.relatedTotal || {};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</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;
|
||||
}
|
||||
|
||||
@@ -145,7 +166,6 @@ export default {
|
||||
padding: 16px 0 0 16px;
|
||||
|
||||
.title {
|
||||
// width: 190px;
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
@@ -162,7 +182,6 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
// width: 190px;
|
||||
height: 32px;
|
||||
font-family: YouSheBiaoTiHei;
|
||||
font-size: 32px;
|
||||
@@ -186,40 +205,4 @@ export default {
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
|
||||
// .line {
|
||||
// width: 500px;
|
||||
// height: 205px;
|
||||
// background: #F9FCFF;
|
||||
// }
|
||||
|
||||
// .leftTitle {
|
||||
// .item {
|
||||
// width: 67px;
|
||||
// height: 180px;
|
||||
// padding: 37px 23px;
|
||||
// background: #F9FCFF;
|
||||
// font-family: PingFangSC, PingFang SC;
|
||||
// font-weight: 400;
|
||||
// font-size: 18px;
|
||||
// color: #000000;
|
||||
// line-height: 25px;
|
||||
// letter-spacing: 1px;
|
||||
// // text-align: left;
|
||||
// font-style: normal;
|
||||
// }
|
||||
// }
|
||||
</style>
|
||||
|
||||
<!-- <style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style> -->
|
||||
|
||||
@@ -11,19 +11,20 @@
|
||||
</div>
|
||||
<div class="number">
|
||||
<div class="yield">
|
||||
90%
|
||||
{{ formatRate(factoryData?.completeRate) }}%
|
||||
</div>
|
||||
<div class="mom">
|
||||
环比10%
|
||||
<img class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||
环比{{ formatRate(factoryData?.thb) }}%
|
||||
<img v-if="factoryData?.thb >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="">
|
||||
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="electricityGauge">
|
||||
<electricityGauge id="totalGauge"></electricityGauge>
|
||||
<electricityGauge id="year" :detailData="factoryData"></electricityGauge>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line" style="padding: 0px;">
|
||||
<verticalBarChart>
|
||||
<verticalBarChart :detailData="factoryData">
|
||||
</verticalBarChart>
|
||||
|
||||
</div>
|
||||
@@ -43,55 +44,75 @@ import verticalBarChart from './verticalBarChart.vue'
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, electricityGauge, verticalBarChart },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
itemData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
totalData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
title: { // 接收父组件传递的设备数据数组
|
||||
title: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
month: { // 接收父组件传递的设备数据数组
|
||||
month: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
itemData: {
|
||||
handler(newValue, oldValue) {
|
||||
// this.updateChart()
|
||||
},
|
||||
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||
computed: {
|
||||
/**
|
||||
* 自动提取monData中的工厂数据,并新增flag字段
|
||||
*/
|
||||
factoryData() {
|
||||
const factoryKeys = Object.keys(this.totalData);
|
||||
if (factoryKeys.length === 0) {
|
||||
// 无数据时返回兜底对象(包含flag)
|
||||
return {
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0,
|
||||
flag: 0 // 兜底flag
|
||||
};
|
||||
}
|
||||
const firstKey = factoryKeys[0];
|
||||
const rawData = this.totalData[firstKey];
|
||||
// 整合原始数据 + 计算flag
|
||||
return {
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0,
|
||||
...rawData,
|
||||
flag: this.getRateFlag(rawData.completeRate) // 新增flag字段
|
||||
};
|
||||
}
|
||||
},
|
||||
// computed: {
|
||||
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||
// sortedItemData() {
|
||||
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||
// const totalCostItems = this.itemData.filter(item =>
|
||||
// item.name && item.name.includes('总成本')
|
||||
// );
|
||||
// // 过滤出不包含“总成本”的项
|
||||
// const otherItems = this.itemData.filter(item =>
|
||||
// !item.name || !item.name.includes('总成本')
|
||||
// );
|
||||
// // 合并:总成本项在前,其他项在后
|
||||
// return [...totalCostItems, ...otherItems];
|
||||
// }
|
||||
// },
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 格式化百分比数值:处理空值/非数字,兜底为0
|
||||
*/
|
||||
formatRate(value) {
|
||||
if (isNaN(value) || value === null || value === undefined) {
|
||||
return 0;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
/**
|
||||
* 判断完成率对应的flag值(<100为0,≥100为1)
|
||||
* @param {number} rate 完成率(原始值,如89代表89%)
|
||||
* @returns {0|1} flag值
|
||||
*/
|
||||
getRateFlag(rate) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return rate >= 100 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -156,7 +177,7 @@ export default {
|
||||
}
|
||||
|
||||
.mom {
|
||||
width: 97px;
|
||||
// width: 97px;
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div ref="verticalBarChart" id="coreLineChart" style="width: 100%; height: 210px;"></div>
|
||||
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 210px;"></div>
|
||||
</template>
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
@@ -8,80 +8,69 @@ export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 存储图表实例,避免重复创建
|
||||
myChart: null
|
||||
};
|
||||
},
|
||||
props: {
|
||||
// 明确接收的props结构,增强可读性
|
||||
chartData: {
|
||||
refName: {
|
||||
type: String,
|
||||
default: 'verticalBarChart',
|
||||
},
|
||||
detailData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
series: [],
|
||||
allPlaceNames: []
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0,
|
||||
flag: 0
|
||||
}),
|
||||
// 校验数据格式
|
||||
validator: (value) => {
|
||||
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
this.$nextTick(() => this.updateChart());
|
||||
},
|
||||
|
||||
// 新增:监听 chartData 变化
|
||||
watch: {
|
||||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||
chartData: {
|
||||
detailData: {
|
||||
handler() {
|
||||
console.log(this.chartData, 'chartData');
|
||||
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
immediate: true // 初始化时立即执行
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateChart() {
|
||||
const chartDom = this.$refs.verticalBarChart;
|
||||
const chartDom = this.$refs[this.refName];
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
|
||||
// 修复:优化实例销毁逻辑,避免重复dispose
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart.clear(); // 先清空,再重新渲染
|
||||
} else {
|
||||
this.myChart = echarts.init(chartDom);
|
||||
}
|
||||
|
||||
this.myChart = echarts.init(chartDom);
|
||||
const { allPlaceNames, series } = this.chartData || {};
|
||||
|
||||
// 处理空数据
|
||||
const xData = allPlaceNames || [];
|
||||
const chartSeries = series || []; // 父组件传递的 series
|
||||
// 解构数据,避免重复取值
|
||||
const { diff, completeRate: rate, real, target, flag } = this.detailData;
|
||||
// 确保数值为数字类型
|
||||
const realValue = Number(real) || 0;
|
||||
const targetValue = Number(target) || 0;
|
||||
const diffValue = Number(diff) || 0;
|
||||
const rateValue = Number(completeRate) || 0;
|
||||
const flagValue = Number(flag) || 0;
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
},
|
||||
// formatter: (params) => {
|
||||
// let html = `${params[0].axisValue}<br/>`;
|
||||
// params.forEach(item => {
|
||||
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||
// );
|
||||
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||
// });
|
||||
// return html;
|
||||
// }
|
||||
label: { backgroundColor: '#6a7985' }
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 10,
|
||||
@@ -89,217 +78,144 @@ export default {
|
||||
right: 50,
|
||||
left: 30,
|
||||
containLabel: true,
|
||||
show: false // 隐藏grid背景,避免干扰
|
||||
show: false
|
||||
},
|
||||
xAxis: {
|
||||
// 横向柱状图的x轴必须设为数值轴,否则无法正常展示数值
|
||||
type: 'value',
|
||||
// offset: 0,
|
||||
// boundaryGap: true ,
|
||||
// boundaryGap: [10, 0], // 可根据需要开启,控制轴的留白
|
||||
axisTick: { show: false },
|
||||
min: 0,
|
||||
// scale: true,
|
||||
splitNumber: 4,
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0]
|
||||
},
|
||||
// data: xData // 数值轴不需要手动设置data,由series的数据自动生成
|
||||
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLabel: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 12, interval: 0, padding: [5, 0, 0, 0] }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.75)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0]
|
||||
},
|
||||
axisLine: {
|
||||
show: true, // 显示Y轴轴线(关键)
|
||||
lineStyle: {
|
||||
color: '#E5E6EB', // 轴线颜色(浅灰色,可自定义)
|
||||
width: 1, // 轴线宽度
|
||||
type: 'solid' // 实线(可选:dashed虚线、dotted点线)
|
||||
}
|
||||
},
|
||||
axisLabel: { color: 'rgba(0, 0, 0, 0.75)', fontSize: 12, interval: 0, padding: [5, 0, 0, 0] },
|
||||
axisLine: { show: true, lineStyle: { color: '#E5E6EB', width: 1, type: 'solid' } },
|
||||
axisTick: { show: false },
|
||||
// padding: [300, 100, 100, 100],
|
||||
data: ['实际', '预算'] // y轴分类:实际、预算
|
||||
data: ['实际', '预算']
|
||||
},
|
||||
series: [
|
||||
{
|
||||
// name: '预算',
|
||||
type: 'bar',
|
||||
barWidth: 24,
|
||||
// barCategoryGap: '50', // 柱子之间的间距(相对于柱子宽度)
|
||||
// 数据长度与yAxis的分类数量匹配(实际、预算各一个值)
|
||||
data: [{
|
||||
value: 131744,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
offset: [-60, 25],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{rate|139%}{text|差值}',
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' }, // 顶部0px位置:阴影最强
|
||||
// { offset: 0.1, color: 'rgba(205, 215, 224, 0.4)' }, // 1px位置:阴影减弱(对应1px)
|
||||
// { offset: 0.15, color: 'rgba(205, 215, 224, 0.6)' }, // 3px位置:阴影几乎消失(对应3px扩散)
|
||||
{ offset: 0.2, color: '#ffffff' }, // 主体白色
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
// 外阴影:0px 2px 2px 0px rgba(191,203,215,0.5)
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
// 圆角:4px
|
||||
borderRadius: 4,
|
||||
// 移除边框
|
||||
borderColor: '#BFCBD577',
|
||||
borderWidth: 0,
|
||||
// 文字垂直居中(针对富文本)
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
text: {
|
||||
// 缩小宽度和内边距,适配68px容器
|
||||
width: 'auto', // 自动宽度,替代固定40px
|
||||
padding: [5, 10, 5, 0], // 缩小内边距
|
||||
align: 'center',
|
||||
color: '#464646', // 文字灰色
|
||||
fontSize: 11, // 缩小字体,适配小尺寸
|
||||
lineHeight: 20 // 垂直居中
|
||||
// 修复:拆分数据项,确保每个柱子的样式独立生效
|
||||
data: [
|
||||
// 实际值柱子(核心:绑定flag颜色)
|
||||
{
|
||||
value: realValue,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
offset: [-60, 25],
|
||||
width: 68,
|
||||
height: 20,
|
||||
formatter: `{rate|${diffValue}}{text|差值}`,
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||
{ offset: 0.2, color: '#ffffff' },
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
rate: {
|
||||
width: 'auto',
|
||||
padding: [5, 0, 5, 10],
|
||||
align: 'center',
|
||||
color: '#30B590',
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
borderRadius: 4,
|
||||
borderWidth: 0,
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
text: { width: 'auto', padding: [5, 10, 5, 0], align: 'center', color: '#464646', fontSize: 11, lineHeight: 20 },
|
||||
rate: { width: 'auto', padding: [5, 0, 5, 10], align: 'center', color: '#30B590', fontSize: 11, lineHeight: 20 }
|
||||
}
|
||||
},
|
||||
// 修复:flag颜色判断独立绑定到实际值柱子
|
||||
itemStyle: {
|
||||
color: flagValue === 1
|
||||
? {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||
]
|
||||
}
|
||||
: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
// 实际的渐变颜色(绿系渐变,与预算区分)
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#AEEFE0' }, // 浅绿
|
||||
{ offset: 1, color: '#76DABE' } // 深绿
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0,
|
||||
},
|
||||
}, {
|
||||
value: 630230,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
offset: [-70, 25],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: '{value|完成率}{rate|139%}',
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' }, // 顶部0px位置:阴影最强
|
||||
// { offset: 0.1, color: 'rgba(205, 215, 224, 0.4)' }, // 1px位置:阴影减弱(对应1px)
|
||||
// { offset: 0.15, color: 'rgba(205, 215, 224, 0.6)' }, // 3px位置:阴影几乎消失(对应3px扩散)
|
||||
{ offset: 0.2, color: '#ffffff' }, // 主体白色
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
// 外阴影:0px 2px 2px 0px rgba(191,203,215,0.5)
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
// 圆角:4px
|
||||
borderRadius: 4,
|
||||
// 移除边框
|
||||
borderColor: '#BFCBD577',
|
||||
borderWidth: 0,
|
||||
// 文字垂直居中(针对富文本)
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
value: {
|
||||
// 缩小宽度和内边距,适配68px容器
|
||||
width: 'auto', // 自动宽度,替代固定40px
|
||||
padding: [5, 0, 5, 10], // 缩小内边距
|
||||
align: 'center',
|
||||
color: '#464646', // 文字灰色
|
||||
fontSize: 11, // 缩小字体,适配小尺寸
|
||||
lineHeight: 20 // 垂直居中
|
||||
// 预算值柱子(固定蓝色渐变)
|
||||
{
|
||||
value: targetValue,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
offset: [0, 25],
|
||||
width: 68,
|
||||
height: 20,
|
||||
formatter: `{value|完成率}{rate|${rateValue}%}`,
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||
{ offset: 0.2, color: '#ffffff' },
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
rate: {
|
||||
width: 'auto',
|
||||
padding: [5, 10, 5, 0],
|
||||
align: 'center',
|
||||
color: '#0B58FF', // 数字蓝色
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
borderRadius: 4,
|
||||
borderWidth: 0,
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
value: { width: 'auto', padding: [5, 0, 5, 10], align: 'center', color: '#464646', fontSize: 11, lineHeight: 20 },
|
||||
rate: { width: 'auto', padding: [5, 10, 5, 0], align: 'center', color: '#0B58FF', fontSize: 11, lineHeight: 20 }
|
||||
}
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
// 预算的渐变颜色(蓝系渐变)
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 1, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#82CCFF' }, // 浅蓝
|
||||
{ offset: 1, color: '#4B9DFF' } // 深蓝
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
},],
|
||||
|
||||
},
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 1, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#82CCFF' },
|
||||
{ offset: 1, color: '#4B9DFF' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
option && this.myChart.setOption(option);
|
||||
this.myChart.setOption(option, true); // 新增:true表示替换所有配置,避免缓存
|
||||
|
||||
// 窗口缩放适配和销毁逻辑保持不变
|
||||
window.addEventListener('resize', () => {
|
||||
// 优化:防抖resize,避免频繁触发
|
||||
const resizeHandler = () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
};
|
||||
window.removeEventListener('resize', resizeHandler); // 先移除再添加,避免重复绑定
|
||||
window.addEventListener('resize', resizeHandler);
|
||||
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
window.removeEventListener('resize', resizeHandler);
|
||||
this.myChart && this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||
<ReportHeader size="psi" @timeRangeChange="handleTimeChange" top-title="单项原片原料成本分析" :is-full-screen="isFullScreen"
|
||||
@screenfullChange="screenfullChange" :leftMargin="'280px'" />
|
||||
@screenfullChange="screenfullChange" :leftMargin=" '280px' " />
|
||||
<div class="main-body" style="
|
||||
margin-top: -20px;
|
||||
flex: 1;
|
||||
@@ -17,7 +17,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<changeBase @selectChange="selectChange" />
|
||||
<changeBase :factory="factory" @baseChange="selectChange" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px;margin-top: -20px;">
|
||||
@@ -26,9 +26,8 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 804px 804px;
|
||||
">
|
||||
<monthlyOverview :month="month" :itemData="renderList" :title="'月度概览'" />
|
||||
<totalOverview :itemData="renderList" :title="'累计概览'" />
|
||||
|
||||
<monthlyOverview :month="month" :monData="monData" :title="'月度概览'" />
|
||||
<totalOverview :totalData="totalData" :title="'累计概览'" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="middle" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||
@@ -38,7 +37,7 @@
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<!-- <monthlyRelatedMetrics :itemData="renderList" :title="'月度·相关指标分析'" /> -->
|
||||
<relateSingleFuelCostAnalysis :month="month" :itemData="renderList" :title="'相关指标分析'" />
|
||||
<relateSingleFuelCostAnalysis :relatedData="relatedData" :title="'相关指标分析'" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,7 +47,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<dataTrend :itemData="renderList" :title="'数据趋势'" />
|
||||
<dataTrend @getData="changeItem" :trendData="trend" :title="'数据趋势'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,7 +73,7 @@ import totalOverview from "../productionCostAnalysisComponents/totalOverview.vue
|
||||
import relateSingleFuelCostAnalysis from "../productionCostAnalysisComponents/relateSingleFuelCostAnalysis.vue";
|
||||
import dataTrend from "../productionCostAnalysisComponents/dataTrendSingleFuel.vue";
|
||||
import { mapState } from "vuex";
|
||||
import { getCostAnalysisXXCostList } from '@/api/cockpit'
|
||||
import { getSingleMaterialAnalysis } from '@/api/cockpit'
|
||||
// import PSDO from "./components/PSDO.vue";
|
||||
// import psiLineChart from "./components/psiLineChart.vue";
|
||||
|
||||
@@ -101,16 +100,17 @@ export default {
|
||||
isFullScreen: false,
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
month:'',
|
||||
month: '',
|
||||
value: 100,
|
||||
dateData:{},
|
||||
levelId:undefined,
|
||||
itemData: [],
|
||||
trendData: [],
|
||||
parentItemList: [
|
||||
{ name: "燃料成本", target: 0, value: 0, proportion: 0, flag: 1 },
|
||||
{ name: "天然气", target: 0, value: 0, proportion: 0, flag: 1 }
|
||||
],
|
||||
factory: 5,
|
||||
dateData: {},
|
||||
monData: {},
|
||||
totalData: {},
|
||||
trend: [],
|
||||
relatedData: {},
|
||||
trendName: '采购单价',
|
||||
// monthRelatedData: [],
|
||||
// totalRelatedData: [],
|
||||
};
|
||||
},
|
||||
|
||||
@@ -182,28 +182,52 @@ export default {
|
||||
this.beilv = _this.clientWidth / 1920;
|
||||
})();
|
||||
};
|
||||
console.log(this.$route.query.name, 'name');
|
||||
},
|
||||
methods: {
|
||||
changeItem(item) {
|
||||
console.log('item', item);
|
||||
|
||||
this.trendName = item
|
||||
this.getData()
|
||||
},
|
||||
getData() {
|
||||
const requestParams = {
|
||||
// startTime: this.startTime,
|
||||
// endTime: this.endTime,
|
||||
// mode: this.mode,
|
||||
startTime: this.dateData.startTime,
|
||||
endTime: this.dateData.endTime,
|
||||
mode: this.dateData.mode,
|
||||
trendName: "燃料成本",
|
||||
levelId: this.levelId ? this.levelId :1
|
||||
// index: this.index,
|
||||
// sort: 1,
|
||||
trendName: this.trendName,
|
||||
analysisObject: [
|
||||
this.$route.query.name ? this.$route.query.name + '成本' : '硅砂成本'
|
||||
],
|
||||
// paramList: ['制造成本', '财务费用', '销售费用', '管理费用', '运费'],
|
||||
levelId: this.factory,
|
||||
// baseId: Number(this.factory),
|
||||
};
|
||||
// 调用接口
|
||||
getCostAnalysisXXCostList(requestParams).then((res) => {
|
||||
this.itemData = res.data[0].map((item) => {
|
||||
return {
|
||||
...item,
|
||||
route: 'singleFuelAnalysis'
|
||||
}
|
||||
})
|
||||
this.trendData= res.data[1]
|
||||
getSingleMaterialAnalysis(requestParams).then((res) => {
|
||||
this.monData = res.data.currentMonthData.find(item => {
|
||||
return item.name === "硅砂成本";
|
||||
});
|
||||
console.log('this.monData', this.monData);
|
||||
|
||||
this.totalData = res.data.totalMonthData.find(item => {
|
||||
return item.name === "硅砂成本";
|
||||
});
|
||||
// this.relatedMon = res.data.relatedMon
|
||||
this.relatedData = {
|
||||
relatedMon: res.data.currentMonthData.filter(item => {
|
||||
return item.name !== "硅砂成本";
|
||||
}), // 兜底月度数据
|
||||
relatedTotal: res.data.totalMonthData.filter(item => {
|
||||
return item.name !== "硅砂成本";
|
||||
}) // 兜底累计数据
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
this.trend = res.data.dataTrend
|
||||
});
|
||||
},
|
||||
|
||||
@@ -219,7 +243,7 @@ export default {
|
||||
},
|
||||
selectChange(data) {
|
||||
console.log('选中的数据:', data);
|
||||
this.levelId = data
|
||||
this.factory = data
|
||||
if (this.dateData.startTime && this.dateData.endTime) {
|
||||
this.getData();
|
||||
}
|
||||
@@ -304,12 +328,14 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
@import "~@/assets/styles/mixin.scss";
|
||||
@import "~@/assets/styles/variables.scss";
|
||||
|
||||
.dayReport {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
background: url("../../../assets/img/backp.png") no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<changeBase @selectChange="selectChange" />
|
||||
<changeBase :factory="factory" @baseChange="selectChange" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px;margin-top: -20px;">
|
||||
@@ -26,9 +26,8 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 804px 804px;
|
||||
">
|
||||
<monthlyOverview :month="month" :itemData="renderList" :title="'月度概览'" />
|
||||
<totalOverview :itemData="renderList" :title="'累计概览'" />
|
||||
|
||||
<monthlyOverview :month="month" :monData="monData" :title="'月度概览'" />
|
||||
<totalOverview :totalData="totalData" :title="'累计概览'" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="middle" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||
@@ -38,7 +37,7 @@
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<!-- <monthlyRelatedMetrics :itemData="renderList" :title="'月度·相关指标分析'" /> -->
|
||||
<relatedIndicatorsAnalysis :month="month" :itemData="renderList" :title="'相关指标分析'" />
|
||||
<relatedIndicatorsAnalysis :relatedData="relatedData" :title="'相关指标分析'" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,7 +47,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<dataTrend :itemData="renderList" :title="'数据趋势'" />
|
||||
<dataTrend @getData="changeItem" :trendData="trend" :title="'数据趋势'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,7 +73,7 @@ import totalOverview from "../productionCostAnalysisComponents/totalOverview.vue
|
||||
import relatedIndicatorsAnalysis from "../productionCostAnalysisComponents/relateCombustibleCostAnalysis.vue";
|
||||
import dataTrend from "../productionCostAnalysisComponents/dataTrendCombustible.vue";
|
||||
import { mapState } from "vuex";
|
||||
import { getCostAnalysisXXCostList } from '@/api/cockpit'
|
||||
import { getCostAnalysisData } from '@/api/cockpit'
|
||||
// import PSDO from "./components/PSDO.vue";
|
||||
// import psiLineChart from "./components/psiLineChart.vue";
|
||||
|
||||
@@ -101,16 +100,17 @@ export default {
|
||||
isFullScreen: false,
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
month:'',
|
||||
month: '',
|
||||
value: 100,
|
||||
dateData:{},
|
||||
levelId:undefined,
|
||||
itemData: [],
|
||||
trendData: [],
|
||||
parentItemList: [
|
||||
{ name: "燃料成本", target: 0, value: 0, proportion: 0, flag: 1 },
|
||||
{ name: "天然气", target: 0, value: 0, proportion: 0, flag: 1 }
|
||||
],
|
||||
factory: 5,
|
||||
dateData: {},
|
||||
monData: {},
|
||||
totalData: {},
|
||||
trend: [],
|
||||
relatedData: {},
|
||||
trendName: '天然气',
|
||||
// monthRelatedData: [],
|
||||
// totalRelatedData: [],
|
||||
};
|
||||
},
|
||||
|
||||
@@ -184,26 +184,46 @@ export default {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
changeItem(item) {
|
||||
console.log('item', item);
|
||||
|
||||
this.trendName = item
|
||||
this.getData()
|
||||
},
|
||||
getData() {
|
||||
const requestParams = {
|
||||
// startTime: this.startTime,
|
||||
// endTime: this.endTime,
|
||||
// mode: this.mode,
|
||||
startTime: this.dateData.startTime,
|
||||
endTime: this.dateData.endTime,
|
||||
mode: this.dateData.mode,
|
||||
trendName: "燃料成本",
|
||||
levelId: this.levelId ? this.levelId :1
|
||||
// index: this.index,
|
||||
// sort: 1,
|
||||
trendName: this.trendName,
|
||||
analysisObject: [
|
||||
"原片燃料成本",
|
||||
],
|
||||
// paramList: ['制造成本', '财务费用', '销售费用', '管理费用', '运费'],
|
||||
levelId: this.factory,
|
||||
// baseId: Number(this.factory),
|
||||
};
|
||||
// 调用接口
|
||||
getCostAnalysisXXCostList(requestParams).then((res) => {
|
||||
this.itemData = res.data[0].map((item) => {
|
||||
return {
|
||||
...item,
|
||||
route: 'singleFuelAnalysis'
|
||||
}
|
||||
})
|
||||
this.trendData= res.data[1]
|
||||
getCostAnalysisData(requestParams).then((res) => {
|
||||
this.monData = res.data.currentMonthData.find(item => {
|
||||
return item.name === "原片燃料成本";
|
||||
});
|
||||
console.log('this.monData', this.monData);
|
||||
|
||||
this.totalData = res.data.totalMonthData.find(item => {
|
||||
return item.name === "原片燃料成本";
|
||||
});
|
||||
// this.relatedMon = res.data.relatedMon
|
||||
this.relatedData = {
|
||||
relatedMon: res.data.currentMonthData.filter(item => {
|
||||
return item.name !== "原片燃料成本";
|
||||
}), // 兜底月度数据
|
||||
relatedTotal: res.data.totalMonthData.filter(item => {
|
||||
return item.name !== "原片燃料成本";
|
||||
}) // 兜底累计数据
|
||||
}
|
||||
this.trend = res.data.dataTrend
|
||||
});
|
||||
},
|
||||
|
||||
@@ -219,7 +239,7 @@ export default {
|
||||
},
|
||||
selectChange(data) {
|
||||
console.log('选中的数据:', data);
|
||||
this.levelId = data
|
||||
this.factory = data
|
||||
if (this.dateData.startTime && this.dateData.endTime) {
|
||||
this.getData();
|
||||
}
|
||||
@@ -304,12 +324,14 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
@import "~@/assets/styles/mixin.scss";
|
||||
@import "~@/assets/styles/variables.scss";
|
||||
|
||||
.dayReport {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
background: url("../../../assets/img/backp.png") no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<changeBase @selectChange="selectChange" />
|
||||
<changeBase :factory="factory" @baseChange="selectChange" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px;margin-top: -20px;">
|
||||
@@ -26,9 +26,8 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 804px 804px;
|
||||
">
|
||||
<monthlyOverview :month="month" :itemData="renderList" :title="'月度概览'" />
|
||||
<totalOverview :itemData="renderList" :title="'累计概览'" />
|
||||
|
||||
<monthlyOverview :month="month" :monData="monData" :title="'月度概览'" />
|
||||
<totalOverview :totalData="totalData" :title="'累计概览'" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="middle" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||
@@ -38,7 +37,7 @@
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<!-- <monthlyRelatedMetrics :itemData="renderList" :title="'月度·相关指标分析'" /> -->
|
||||
<relatedIndicatorsAnalysis :month="month" :itemData="renderList" :title="'相关指标分析'" />
|
||||
<relatedIndicatorsAnalysis :relatedData="relatedData" :title="'相关指标分析'" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,7 +47,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<dataTrend :itemData="renderList" :title="'数据趋势'" />
|
||||
<dataTrend @getData="changeItem" :trendData="trend" :title="'数据趋势'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,7 +73,7 @@ import totalOverview from "../productionCostAnalysisComponents/totalOverview.vue
|
||||
import relatedIndicatorsAnalysis from "../productionCostAnalysisComponents/relateFuelCostAnalysis.vue";
|
||||
import dataTrend from "../productionCostAnalysisComponents/dataTrendFuel.vue";
|
||||
import { mapState } from "vuex";
|
||||
import { getCostAnalysisXXCostList } from '@/api/cockpit'
|
||||
import { getCostAnalysisData } from '@/api/cockpit'
|
||||
// import PSDO from "./components/PSDO.vue";
|
||||
// import psiLineChart from "./components/psiLineChart.vue";
|
||||
|
||||
@@ -101,16 +100,17 @@ export default {
|
||||
isFullScreen: false,
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
month:'',
|
||||
month: '',
|
||||
value: 100,
|
||||
dateData:{},
|
||||
levelId:undefined,
|
||||
itemData: [],
|
||||
trendData: [],
|
||||
parentItemList: [
|
||||
{ name: "燃料成本", target: 0, value: 0, proportion: 0, flag: 1 },
|
||||
{ name: "天然气", target: 0, value: 0, proportion: 0, flag: 1 }
|
||||
],
|
||||
factory: 5,
|
||||
dateData: {},
|
||||
monData: {},
|
||||
totalData: {},
|
||||
trend: [],
|
||||
relatedData: {},
|
||||
trendName: '原片原料成本',
|
||||
// monthRelatedData: [],
|
||||
// totalRelatedData: [],
|
||||
};
|
||||
},
|
||||
|
||||
@@ -184,26 +184,46 @@ export default {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
changeItem(item) {
|
||||
console.log('item', item);
|
||||
|
||||
this.trendName = item
|
||||
this.getData()
|
||||
},
|
||||
getData() {
|
||||
const requestParams = {
|
||||
// startTime: this.startTime,
|
||||
// endTime: this.endTime,
|
||||
// mode: this.mode,
|
||||
startTime: this.dateData.startTime,
|
||||
endTime: this.dateData.endTime,
|
||||
mode: this.dateData.mode,
|
||||
trendName: "燃料成本",
|
||||
levelId: this.levelId ? this.levelId :1
|
||||
// index: this.index,
|
||||
// sort: 1,
|
||||
trendName: this.trendName,
|
||||
analysisObject: [
|
||||
"原片原料成本",
|
||||
],
|
||||
// paramList: ['制造成本', '财务费用', '销售费用', '管理费用', '运费'],
|
||||
levelId: this.factory,
|
||||
// baseId: Number(this.factory),
|
||||
};
|
||||
// 调用接口
|
||||
getCostAnalysisXXCostList(requestParams).then((res) => {
|
||||
this.itemData = res.data[0].map((item) => {
|
||||
return {
|
||||
...item,
|
||||
route: 'singleFuelAnalysis'
|
||||
}
|
||||
})
|
||||
this.trendData= res.data[1]
|
||||
getCostAnalysisData(requestParams).then((res) => {
|
||||
this.monData = res.data.currentMonthData.find(item => {
|
||||
return item.name === "原片原料成本";
|
||||
});
|
||||
console.log('this.monData', this.monData);
|
||||
|
||||
this.totalData = res.data.totalMonthData.find(item => {
|
||||
return item.name === "原片原料成本";
|
||||
});
|
||||
// this.relatedMon = res.data.relatedMon
|
||||
this.relatedData = {
|
||||
relatedMon: res.data.currentMonthData.filter(item => {
|
||||
return item.name !== "原片原料成本";
|
||||
}), // 兜底月度数据
|
||||
relatedTotal: res.data.totalMonthData.filter(item => {
|
||||
return item.name !== "原片原料成本";
|
||||
}) // 兜底累计数据
|
||||
}
|
||||
this.trend = res.data.dataTrend
|
||||
});
|
||||
},
|
||||
|
||||
@@ -219,7 +239,7 @@ export default {
|
||||
},
|
||||
selectChange(data) {
|
||||
console.log('选中的数据:', data);
|
||||
this.levelId = data
|
||||
this.factory = data
|
||||
if (this.dateData.startTime && this.dateData.endTime) {
|
||||
this.getData();
|
||||
}
|
||||
@@ -304,12 +324,14 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
@import "~@/assets/styles/mixin.scss";
|
||||
@import "~@/assets/styles/variables.scss";
|
||||
|
||||
.dayReport {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
background: url("../../../assets/img/backp.png") no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||
<ReportHeader size="psi" @timeRangeChange="handleTimeChange" top-title="原片制造费用成本分析" :is-full-screen="isFullScreen"
|
||||
@screenfullChange="screenfullChange" :leftMargin="'270px'" />
|
||||
@screenfullChange="screenfullChange" :leftMargin=" '270px'" />
|
||||
<div class="main-body" style="
|
||||
margin-top: -20px;
|
||||
flex: 1;
|
||||
@@ -17,7 +17,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<changeBase @selectChange="selectChange" />
|
||||
<changeBase :factory="factory" @baseChange="selectChange" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px;margin-top: -20px;">
|
||||
@@ -26,9 +26,8 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 804px 804px;
|
||||
">
|
||||
<monthlyOverview :month="month" :itemData="renderList" :title="'月度概览'" />
|
||||
<totalOverview :itemData="renderList" :title="'累计概览'" />
|
||||
|
||||
<monthlyOverview :month="month" :monData="monData" :title="'月度概览'" />
|
||||
<totalOverview :totalData="totalData" :title="'累计概览'" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="middle" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||
@@ -38,7 +37,7 @@
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<!-- <monthlyRelatedMetrics :itemData="renderList" :title="'月度·相关指标分析'" /> -->
|
||||
<relatedIndicatorsAnalysis :month="month" :itemData="renderList" :title="'相关指标分析'" />
|
||||
<relatedIndicatorsAnalysis :relatedData="relatedData" :title="'相关指标分析'" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,7 +47,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<dataTrend :itemData="renderList" :title="'数据趋势'" />
|
||||
<dataTrend @getData="changeItem" :trendData="trend" :title="'数据趋势'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,7 +73,7 @@ import totalOverview from "../productionCostAnalysisComponents/totalOverview.vue
|
||||
import relatedIndicatorsAnalysis from "../productionCostAnalysisComponents/relateFactoryBurdenCostAnalysis.vue";
|
||||
import dataTrend from "../productionCostAnalysisComponents/dataTrendFactoryBurden.vue";
|
||||
import { mapState } from "vuex";
|
||||
import { getCostAnalysisXXCostList } from '@/api/cockpit'
|
||||
import { getCostAnalysisData } from '@/api/cockpit'
|
||||
// import PSDO from "./components/PSDO.vue";
|
||||
// import psiLineChart from "./components/psiLineChart.vue";
|
||||
|
||||
@@ -101,16 +100,17 @@ export default {
|
||||
isFullScreen: false,
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
month:'',
|
||||
month: '',
|
||||
value: 100,
|
||||
dateData:{},
|
||||
levelId:undefined,
|
||||
itemData: [],
|
||||
trendData: [],
|
||||
parentItemList: [
|
||||
{ name: "燃料成本", target: 0, value: 0, proportion: 0, flag: 1 },
|
||||
{ name: "天然气", target: 0, value: 0, proportion: 0, flag: 1 }
|
||||
],
|
||||
factory: 5,
|
||||
dateData: {},
|
||||
monData: {},
|
||||
totalData: {},
|
||||
trend: [],
|
||||
relatedData: {},
|
||||
trendName: '原片制造费用成本',
|
||||
// monthRelatedData: [],
|
||||
// totalRelatedData: [],
|
||||
};
|
||||
},
|
||||
|
||||
@@ -184,26 +184,49 @@ export default {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
changeItem(item) {
|
||||
console.log('item', item);
|
||||
|
||||
this.trendName = item
|
||||
this.getData()
|
||||
},
|
||||
getData() {
|
||||
const requestParams = {
|
||||
// startTime: this.startTime,
|
||||
// endTime: this.endTime,
|
||||
// mode: this.mode,
|
||||
startTime: this.dateData.startTime,
|
||||
endTime: this.dateData.endTime,
|
||||
mode: this.dateData.mode,
|
||||
trendName: "燃料成本",
|
||||
levelId: this.levelId ? this.levelId :1
|
||||
// index: this.index,
|
||||
// sort: 1,
|
||||
trendName: this.trendName,
|
||||
analysisObject: [
|
||||
"原片制造费用成本",
|
||||
],
|
||||
// paramList: ['制造成本', '财务费用', '销售费用', '管理费用', '运费'],
|
||||
levelId: this.factory,
|
||||
// baseId: Number(this.factory),
|
||||
};
|
||||
// 调用接口
|
||||
getCostAnalysisXXCostList(requestParams).then((res) => {
|
||||
this.itemData = res.data[0].map((item) => {
|
||||
return {
|
||||
...item,
|
||||
route: 'singleFuelAnalysis'
|
||||
}
|
||||
})
|
||||
this.trendData= res.data[1]
|
||||
getCostAnalysisData(requestParams).then((res) => {
|
||||
this.monData = res.data.currentMonthData.find(item => {
|
||||
return item.name === "原片制造费用成本";
|
||||
});
|
||||
console.log('this.monData', this.monData);
|
||||
|
||||
this.totalData = res.data.totalMonthData.find(item => {
|
||||
return item.name === "原片制造费用成本";
|
||||
});
|
||||
// this.relatedMon = res.data.relatedMon
|
||||
this.relatedData = {
|
||||
relatedMon: res.data.currentMonthData.filter(item => {
|
||||
return item.name !== "原片制造费用成本";
|
||||
}), // 兜底月度数据
|
||||
relatedTotal: res.data.totalMonthData.filter(item => {
|
||||
return item.name !== "原片制造费用成本";
|
||||
}) // 兜底累计数据
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
this.trend = res.data.dataTrend
|
||||
});
|
||||
},
|
||||
|
||||
@@ -219,7 +242,7 @@ export default {
|
||||
},
|
||||
selectChange(data) {
|
||||
console.log('选中的数据:', data);
|
||||
this.levelId = data
|
||||
this.factory = data
|
||||
if (this.dateData.startTime && this.dateData.endTime) {
|
||||
this.getData();
|
||||
}
|
||||
@@ -304,12 +327,14 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
@import "~@/assets/styles/mixin.scss";
|
||||
@import "~@/assets/styles/variables.scss";
|
||||
|
||||
.dayReport {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
background: url("../../../assets/img/backp.png") no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<changeBase @selectChange="selectChange" />
|
||||
<changeBase :factory="factory" @baseChange="selectChange" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px;margin-top: -20px;">
|
||||
@@ -26,9 +26,8 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 804px 804px;
|
||||
">
|
||||
<monthlyOverview :month="month" :itemData="renderList" :title="'月度概览'" />
|
||||
<totalOverview :itemData="renderList" :title="'累计概览'" />
|
||||
|
||||
<monthlyOverview :month="month" :monData="monData" :title="'月度概览'" />
|
||||
<totalOverview :totalData="totalData" :title="'累计概览'" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||
@@ -37,7 +36,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<dataTrend :itemData="renderList" :title="'数据趋势'" />
|
||||
<dataTrend :trendData="trend" :title="'数据趋势'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,12 +57,10 @@ import screenfull from "screenfull";
|
||||
import changeBase from "../components/changeBase.vue";
|
||||
import monthlyOverview from "../productionCostAnalysisComponents/monthlyOverview.vue";
|
||||
import totalOverview from "../productionCostAnalysisComponents/totalOverview.vue";
|
||||
// import totalOverview from "../operatingComponents/totalOverview.vue";
|
||||
// import monthlyRelatedMetrics from "../procurementGainAnalysisComponents/monthlyRelatedMetrics.vue";
|
||||
import relatedIndicatorsAnalysis from "../productionCostAnalysisComponents/relateProcessCostAnalysis.vue";
|
||||
|
||||
import dataTrend from "../productionCostAnalysisComponents/dataTrendProcessingLabor.vue";
|
||||
import { mapState } from "vuex";
|
||||
import { getCostAnalysisXXCostList } from '@/api/cockpit'
|
||||
import { getSingleMaterialCostAnalysis } from '@/api/cockpit'
|
||||
// import PSDO from "./components/PSDO.vue";
|
||||
// import psiLineChart from "./components/psiLineChart.vue";
|
||||
|
||||
@@ -81,7 +78,6 @@ export default {
|
||||
monthlyOverview,
|
||||
Sidebar,
|
||||
totalOverview,
|
||||
relatedIndicatorsAnalysis,
|
||||
dataTrend
|
||||
// psiLineChart
|
||||
},
|
||||
@@ -92,14 +88,13 @@ export default {
|
||||
beilv: 1,
|
||||
month: '',
|
||||
value: 100,
|
||||
factory: 5,
|
||||
dateData: {},
|
||||
levelId: undefined,
|
||||
itemData: [],
|
||||
trendData: [],
|
||||
parentItemList: [
|
||||
{ name: "燃料成本", target: 0, value: 0, proportion: 0, flag: 1 },
|
||||
{ name: "天然气", target: 0, value: 0, proportion: 0, flag: 1 }
|
||||
],
|
||||
monData: {},
|
||||
totalData: {},
|
||||
trend: [],
|
||||
relatedData: {},
|
||||
trendName: '制造费用',
|
||||
};
|
||||
},
|
||||
|
||||
@@ -173,26 +168,49 @@ export default {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
changeItem(item) {
|
||||
console.log('item', item);
|
||||
|
||||
this.trendName = item
|
||||
this.getData()
|
||||
},
|
||||
getData() {
|
||||
const requestParams = {
|
||||
// startTime: this.startTime,
|
||||
// endTime: this.endTime,
|
||||
// mode: this.mode,
|
||||
startTime: this.dateData.startTime,
|
||||
endTime: this.dateData.endTime,
|
||||
mode: this.dateData.mode,
|
||||
trendName: "燃料成本",
|
||||
levelId: this.levelId ? this.levelId : 1
|
||||
// index: this.index,
|
||||
// sort: 1,
|
||||
trendName: this.trendName,
|
||||
analysisObject: [
|
||||
this.$route.query.name ? this.$route.query.name + '成本' : '包材成本'
|
||||
],
|
||||
// paramList: ['制造成本', '财务费用', '销售费用', '管理费用', '运费'],
|
||||
levelId: this.factory,
|
||||
// baseId: Number(this.factory),
|
||||
};
|
||||
// 调用接口
|
||||
getCostAnalysisXXCostList(requestParams).then((res) => {
|
||||
this.itemData = res.data[0].map((item) => {
|
||||
return {
|
||||
...item,
|
||||
route: 'singleFuelAnalysis'
|
||||
}
|
||||
})
|
||||
this.trendData = res.data[1]
|
||||
getSingleMaterialCostAnalysis(requestParams).then((res) => {
|
||||
this.monData = res.data.currentMonthData.find(item => {
|
||||
return item.name === "包材成本";
|
||||
});
|
||||
console.log('this.monData', this.monData);
|
||||
|
||||
this.totalData = res.data.totalMonthData.find(item => {
|
||||
return item.name === "包材成本";
|
||||
});
|
||||
// this.relatedMon = res.data.relatedMon
|
||||
this.relatedData = {
|
||||
relatedMon: res.data.currentMonthData.filter(item => {
|
||||
return item.name !== "包材成本";
|
||||
}), // 兜底月度数据
|
||||
relatedTotal: res.data.totalMonthData.filter(item => {
|
||||
return item.name !== "包材成本";
|
||||
}) // 兜底累计数据
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
this.trend = res.data.dataTrend
|
||||
});
|
||||
},
|
||||
|
||||
@@ -208,7 +226,7 @@ export default {
|
||||
},
|
||||
selectChange(data) {
|
||||
console.log('选中的数据:', data);
|
||||
this.levelId = data
|
||||
this.factory = data
|
||||
if (this.dateData.startTime && this.dateData.endTime) {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||
<ReportHeader top-title="原片成本分析" :is-full-screen="isFullScreen" @screenfullChange="screenfullChange"
|
||||
@timeRangeChange="handleTimeChange" :leftMargin=" '300px' " />
|
||||
@timeRangeChange="handleTimeChange" :leftMargin="'300px'" />
|
||||
<div class="main-body" style="
|
||||
flex: 1;
|
||||
display: flex;
|
||||
@@ -16,7 +16,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns:1624px;
|
||||
">
|
||||
<operatingLineChart :salesTrendMap="salesTrendMap" :grossMarginTrendMap="grossMarginTrendMap" />
|
||||
<operatingLineChart :baseUrl="baseUrl" :monData="monData" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||
@@ -25,7 +25,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<operatingLineChartCumulative :salesTrendMap="salesTrendMap" :grossMarginTrendMap="grossMarginTrendMap" />
|
||||
<operatingLineChartCumulative :baseUrl="baseUrl" :totalData="totalData" />
|
||||
<!-- <keyWork /> -->
|
||||
</div>
|
||||
</div>
|
||||
@@ -50,7 +50,7 @@ import { mapState } from "vuex";
|
||||
import operatingLineChart from "../productionCostAnalysisComponents/operatingLineChart";
|
||||
import operatingLineChartCumulative from "../productionCostAnalysisComponents/operatingLineChartCumulative.vue";
|
||||
|
||||
import { getSalesRevenueData } from '@/api/cockpit'
|
||||
import { getCostAnalysisData } from '@/api/cockpit'
|
||||
import moment from "moment";
|
||||
export default {
|
||||
name: "DayReport",
|
||||
@@ -68,11 +68,10 @@ export default {
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
value: 100,
|
||||
saleData: {},
|
||||
premiumProduct: {},
|
||||
salesTrendMap: {},
|
||||
grossMarginTrendMap: {},
|
||||
salesProportion:{},
|
||||
baseUrl: 'originalSheetCostBase/originalSheetCostBase',
|
||||
selectDate: {},
|
||||
monData: [],
|
||||
totalData: [],
|
||||
};
|
||||
},
|
||||
|
||||
@@ -140,23 +139,31 @@ export default {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getData(obj) {
|
||||
getSalesRevenueData({
|
||||
startTime: obj.startTime,
|
||||
endTime: obj.endTime,
|
||||
timeDim: obj.mode
|
||||
getData() {
|
||||
getCostAnalysisData({
|
||||
startTime: this.selectDate.startTime,
|
||||
endTime: this.selectDate.endTime,
|
||||
analysisObject: [
|
||||
"原片成本"
|
||||
],
|
||||
levelId: 1,
|
||||
// timeDim: this.selectDate.mode
|
||||
}).then((res) => {
|
||||
console.log(res);
|
||||
this.saleData = res.data.SaleData
|
||||
this.premiumProduct = res.data.premiumProduct
|
||||
this.salesTrendMap = res.data.salesTrendMap
|
||||
this.grossMarginTrendMap = res.data.grossMarginTrendMap
|
||||
this.salesProportion = res.data.salesProportion ? res.data.salesProportion : {}
|
||||
this.monData = res.data.currentMonthData
|
||||
this.totalData = res.data.totalMonthData
|
||||
// this.totalData = res.data.totalData
|
||||
// this.saleData = res.data.SaleData
|
||||
// this.premiumProduct = res.data.premiumProduct
|
||||
// this.salesTrendMap = res.data.salesTrendMap
|
||||
// this.grossMarginTrendMap = res.data.grossMarginTrendMap
|
||||
// this.salesProportion = res.data.salesProportion ? res.data.salesProportion : {}
|
||||
})
|
||||
},
|
||||
handleTimeChange(obj) {
|
||||
console.log(obj, 'obj');
|
||||
this.getData(obj)
|
||||
// console.log(obj, 'obj');
|
||||
this.selectDate = obj
|
||||
this.getData()
|
||||
},
|
||||
handleClickOutside() {
|
||||
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
||||
@@ -228,12 +235,14 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
@import "~@/assets/styles/mixin.scss";
|
||||
@import "~@/assets/styles/variables.scss";
|
||||
|
||||
.dayReport {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
background: url("../../../assets/img/backp.png") no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user