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

View File

@@ -1,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;

View File

@@ -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)
}
},
};

View File

@@ -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>

View File

@@ -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> -->

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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);

View File

@@ -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,
},

View File

@@ -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);
// 遍历有效数据填充locationslevelId→地名
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;

View File

@@ -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);
// 遍历有效数据填充locationslevelId→地名
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;

View File

@@ -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;
}

View File

@@ -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',

View File

@@ -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> -->

View File

@@ -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>

View File

@@ -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;
});
}
},