达标函数删除,图表label置于最顶层

This commit is contained in:
2026-04-14 10:17:38 +08:00
parent 0d74e762ce
commit 7135ab0e4b
208 changed files with 3567 additions and 12787 deletions

View File

@@ -1,298 +0,0 @@
<template>
<div style="flex: 1">
<Container name="数据趋势" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
<div class="right" style="
height: 191px;
display: flex;
width: 1595px;
background-color: rgba(249, 252, 255, 1);
">
<dataTrendBar @changeItem="handleChange" :chartData="chartData" />
</div>
</div>
</Container>
</div>
</template>
<script>
import Container from "../components/container.vue";
import dataTrendBar from "./dataTrendBar.vue";
export default {
name: "ProductionStatus",
components: { Container, dataTrendBar },
props: {
trend: {
type: Array,
// 默认值与实际数据结构一致12个月
default: () => [
// { title: "2025年01月", budget: 0, real: 0, rate: 0, diff: 0 },
// { title: "2025年02月", budget: 0, real: 0, rate: 0, diff: 0 },
// { title: "2025年03月", budget: 0, real: 0, rate: 0, diff: 0 },
// { title: "2025年04月", budget: 0, real: 0, rate: 0, diff: 0 },
// { title: "2025年05月", budget: 0, real: 0, rate: 0, diff: 0 },
// { title: "2025年06月", budget: 0, real: 0, rate: 0, diff: 0 },
// { title: "2025年07月", budget: 0, real: 0, rate: 0, diff: 0 },
// { title: "2025年08月", budget: 0, real: 0, rate: 0, diff: 0 },
// { title: "2025年09月", budget: 0, real: 0, rate: 0, diff: 0 },
// { title: "2025年10月", budget: 0, real: 0, rate: 0, diff: 0 },
// { title: "2025年11月", budget: 0, real: 0, rate: 0, diff: 0 },
// { title: "2025年12月", budget: 0, real: 0, rate: 0, diff: 0 }
]
},
},
data() {
return {
chartData: {
months: [], // 月份数组2025年01月 - 2025年12月
rates: [], // 每月完成率(百分比)
reals: [], // 每月实际值
budgets: [],// 每月预算值
diffs: [], // 每月差值
flags: [] // 每月达标标识≥100 → 1<100 → 0
}
};
},
watch: {
trend: {
handler(newVal) {
this.processTrendData(newVal);
},
immediate: true,
deep: true,
},
},
mounted() {
this.processTrendData(this.trend);
},
methods: {
handleChange(value) {
this.$emit("handleChange", value);
},
/**
* 处理趋势数据适配12个月的数组结构
* @param {Array} trendData - 原始趋势数组12个月
*/
processTrendData(trendData) {
// 数据兜底确保是数组且长度为12
const validTrend = Array.isArray(trendData)
? trendData
: []
// 初始化空数组
const months = [];
const rates = [];
const reals = [];
const budgets = [];
const diffs = [];
const flags = [];
// 遍历12个月数据
validTrend.forEach(item => {
// 基础数据提取(兜底处理)
const month = item.title ?? '';
const budget = Number(item.budget) || 0;
const real = Number(item.real) || 0;
const rate = Number(item.rate) || 0;
const diff = Number(item.diff) || 0;
// 计算达标标识≥100 → 1<100 → 0
const flag = this.getRateFlag(rate, real, budget);
// 填充数组
months.push(month);
rates.push(rate); // 转为百分比并取整
reals.push(real);
budgets.push(budget);
diffs.push(diff);
flags.push(flag);
});
// 更新chartData响应式
this.chartData = {
months,
rates,
reals,
budgets,
diffs,
flags
};
console.log('处理后的趋势数据:', this.chartData);
},
/**
* 计算达标标识
* @param {Number} rate - 完成率原始值如1.2 → 120%
* @returns {Number} 1: 达标≥100%0: 未达标(<100%
*/
getRateFlag(rate, real, target) {
if (isNaN(rate) || rate === null || rate === undefined) return 0;
// 1. 完成率 >= 100 => 达标
if (rate >= 100) return 1;
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
if (rate === 0 && target === 0) return 1;
// 其他情况 => 未达标
return 0;
},
},
};
</script>
<style lang="scss" scoped>
/* 滚动容器样式 */
.scroll-container {
max-height: 210px;
overflow-y: auto;
overflow-x: hidden;
padding: 10px 0;
&::-webkit-scrollbar {
display: none;
}
scrollbar-width: none;
-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 样式 */
.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,477 +0,0 @@
<template>
<div class="coreBar">
<!-- 新增行容器包裹各基地情况和barTop -->
<div class="header-row">
<div class="barTop">
<!-- 关键新增右侧容器包裹图例和按钮组实现整体靠右 -->
<div class="right-container">
<div class="legend">
<span class="legend-item">
<span class="legend-icon line yield"></span>
完成率
</span>
<span class="legend-item">
<span class="legend-icon square target"></span>
预算
</span>
<span class="legend-item">
<span class="legend-icon square achieved"></span>
实际·达标
</span>
<span class="legend-item">
<span class="legend-icon square unachieved"></span>
实际·未达标
</span>
</div>
<div class="button-group">
<div class="item-button category-btn">
<span class="item-text">类目选择</span>
</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="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 }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="lineBottom" style="height: 100%; width: 100%">
<operatingLineBar :chartData="chartD" style="height: 99%; width: 100%" />
</div>
</div>
</template>
<script>
import operatingLineBar from './operatingLineBarSale.vue';
import * as echarts from 'echarts';
export default {
name: "Container",
components: { operatingLineBar },
props: ["chartData"],
data() {
return {
activeButton: 0,
isDropdownShow: false,
selectedProfit: '营业收入', // 选中的名称初始为null
profitOptions: [
'营业收入',
'单价',
'销量',
]
};
},
computed: {
// profitOptions() {
// return this.categoryData.map(item => item.name) || [];
// },
currentDataSource() {
console.log('yyyy', this.chartData);
return this.chartData
},
locations() {
console.log('this.chartData', this.chartData);
return this.chartData.months
},
// 根据按钮切换生成对应的 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.budgets // 目标销量(万元)
},
// 3. 实际(柱状图,含达标状态)
{
name: '实际',
type: 'bar',
yAxisIndex: 0,
barWidth: 14,
label: {
show: true,
position: 'top',
offset: [0, 0],
// 固定label尺寸68px×20px
width: 68,
height: 20,
// 关键:去掉换行,让文字在一行显示,适配小尺寸
formatter: (params) => {
const diff = data.diffs || [];
const flags = data.flags || [];
const currentDiff = diff[params.dataIndex] || 0;
const currentFlag = flags[params.dataIndex] || 0;
const prefix = currentFlag === 1 ? '+' : '-';
// 根据标志位选择不同的样式类
if (currentFlag === 1) {
// 达标 - 使用 rate-achieved 样式
return `{achieved|${currentDiff}}{text|差值}`;
} else {
// 未达标 - 使用 rate-unachieved 样式
return `{unachieved|${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
},
achieved: {
width: 'auto',
padding: [5, 0, 5, 10],
align: 'center',
color: '#76DABE', // 与达标的 offset: 1 颜色一致
fontSize: 11,
lineHeight: 20
},
// 未达标样式
unachieved: {
width: 'auto',
padding: [5, 0, 5, 10],
align: 'center',
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
fontSize: 11,
lineHeight: 20
}
}
},
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 // 实际销量(万元)
}
]
};
// 根据按钮状态返回对应数据
return salesData;
}
},
methods: {
selectProfit(item) {
this.selectedProfit = item;
this.isDropdownShow = false;
this.$emit("changeItem", item);
}
},
};
</script>
<style scoped lang="scss">
.coreBar {
display: flex;
flex-direction: column;
width: 100%;
padding: 12px;
// 新增:头部行容器,实现一行排列
.header-row {
display: flex;
justify-content: flex-end; // 左右两端对齐
align-items: center; // 垂直居中
// width: 100%;
margin-bottom: 8px; // 与下方图表区保留间距(可根据需求调整)
}
// 各基地情况标题样式
.base-title {
font-weight: 400;
font-size: 18px;
color: #000000;
line-height: 18px;
letter-spacing: 1px;
font-style: normal;
padding: 0 0 0 16px; // 保留原有内边距
white-space: nowrap; // 防止文字换行
}
.barTop {
// 移除原有flex和justify-content由header-row控制
width: auto; // 自适应宽度
// 保留原有align-items确保内部元素垂直居中
align-items: center;
gap: 16px;
// 1. 右侧容器:包裹图例和按钮组,整体靠右
.right-container {
display: flex;
align-items: center; // 图例和按钮组垂直居中
gap: 24px; // 图例与按钮组的间距,避免贴紧
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
}
// 2. 图例:在右侧容器内横向排列
.legend {
display: flex;
gap: 16px; // 图例项之间间距,避免重叠
align-items: center;
margin: 0;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 14px;
color: rgba(0, 0, 0, 0.8);
text-align: left;
font-style: normal;
white-space: nowrap; // 防止图例文字换行
}
.legend-icon {
display: inline-block;
}
.legend-icon.line {
width: 12px;
height: 2px;
position: relative;
&::before {
position: absolute;
content: "";
top: -2px;
left: 3px;
width: 6px;
border-radius: 50%;
height: 6px;
background-color: rgba(40, 138, 255, 1);
}
}
.legend-icon.square {
width: 8px;
height: 8px;
}
// 图例颜色
.yield {
background: rgba(40, 138, 255, 1);
}
.target {
background: #2889FF;
}
.achieved {
background: rgba(40, 203, 151, 1);
}
.unachieved {
background: rgba(255, 132, 0, 1);
}
// 3. 按钮组:在右侧容器内,保留原有样式
.button-group {
display: flex;
position: relative;
gap: 2px;
align-items: center;
height: 24px;
background: #ecf4fe;
margin: 0;
.dropdown-container {
position: relative;
z-index: 10;
}
.item-button {
cursor: pointer;
height: 24px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 12px;
line-height: 24px;
font-style: normal;
letter-spacing: 2px;
overflow: hidden;
.item-text {
display: inline-block;
}
}
.category-btn {
width: 75px;
border-top-left-radius: 12px;
border-bottom-left-radius: 12px;
background: #ffffff;
color: #0b58ff;
text-align: center;
}
.profit-btn {
width: 123px;
border-top-right-radius: 12px;
border-bottom-right-radius: 12px;
position: relative;
padding: 0 18px 0 8px;
background: #ffffff;
color: #0b58ff;
text-align: left;
&.active {
background: #3071ff;
color: rgba(249, 252, 255, .8);
}
.profit-text {
text-align: left;
width: 100%;
}
}
.dropdown-arrow {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
width: 0;
height: 0;
border-left: 6px solid currentColor;
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
border-right: 4px solid transparent;
transition: transform 0.2s ease;
&.rotate {
transform: rotate(90deg);
}
}
.dropdown-options {
position: absolute;
top: 100%;
right: 0;
margin-top: 2px;
width: 123px;
background: #ffffff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
.dropdown-option {
padding: 6px 12px;
font-size: 12px;
color: #333;
cursor: pointer;
text-align: left;
letter-spacing: 1px;
&:hover {
background: #f5f7fa;
color: #3071ff;
}
}
}
}
}
}
</style>

View File

@@ -1,204 +0,0 @@
<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">
{{ month }}月完成率
</div>
<div class="number">
<div class="yield">
{{ monthData?.rate || 0 }}%
</div>
<div class="mom">
环比{{ monthData?.momRate }}%
<img v-if="monthData?.momRate >= 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 :detailData="monthData" id="month"></electricityGauge>
</div> -->
</div>
<div class="line" style="padding: 0px;">
<verticalBarChart :detailData="monthData">
</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: {
monthData: { // 接收父组件传递的设备数据数组
type: Object,
default: () => {} // 默认空数组,避免报错
},
title: { // 接收父组件传递的设备数据数组
type: String,
default: () => '' // 默认空数组,避免报错
},
month: { // 接收父组件传递的设备数据数组
type: String,
default: () => '' // 默认空数组,避免报错
},
},
data() {
return {
chart: null,
}
},
watch: {
// itemData: {
// handler(newValue, oldValue) {
// // this.updateChart()
// },
// 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: {
}
}
</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;
}
.dashboard {
width: 264px;
height: 205px;
background: #F9FCFF;
padding: 16px 0 0 10px;
.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;
}
.number {
font-family: YouSheBiaoTiHei;
font-size: 46px;
color: #0B58FF;
letter-spacing: 2px;
text-align: center;
font-style: normal;
white-space: nowrap;
margin-top: 20px;
}
.mom {
height: 18px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 20px;
color: #000000;
line-height: 18px;
letter-spacing: 1px;
text-align: center;
font-style: normal;
margin-top: 20px;
}
}
.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> -->

View File

@@ -1,221 +0,0 @@
<template>
<div style="flex: 1">
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
<div class="topItem-container" style="display: flex; gap: 8px; width: 100%;">
<!-- 销量模块直接传递整合了flag的salesData -->
<div class="dashboard left" @click="handleDashboardClick('/salesVolumeAnalysis/salesVolumeAnalysisBase')">
<div style='position: relative;'>
<div class="title">
销量·
</div>
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
<span>完成率:<span style='color: #0B58FF;'>{{monthAnalysis[0].rate}}%</span></span>
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:monthAnalysis[0].flags>0?'#30B590':'#FF9423'}" >{{monthAnalysis[0].diff}}</span></span>
</div>
</div>
<div class="chart-wrap">
<operatingSingleBar :detailData="salesData"></operatingSingleBar>
</div>
</div>
<!-- 单价模块直接传递整合了flag的unitPriceData -->
<div class="dashboard right" @click="handleDashboardClick('/unitPriceAnalysis/unitPriceAnalysisBase')">
<div style='position: relative;'>
<div class="title">
单价·/
</div>
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
<span>完成率:<span style='color: #0B58FF;'>{{monthAnalysis[1].rate}}%</span></span>
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:monthAnalysis[1].flags>0?'#30B590':'#FF9423'}" >{{monthAnalysis[1].diff}}</span></span>
</div>
</div>
<div class="chart-wrap">
<operatingSingleBar :detailData="unitPriceData"></operatingSingleBar>
</div>
</div>
</div>
</div>
</Container>
</div>
</template>
<script>
import Container from './container.vue'
import operatingSingleBar from './operatingSingleBar.vue'
export default {
name: 'ProductionStatus',
components: { Container, operatingSingleBar },
props: {
monthAnalysis: {
type: Array,
default: () => [
{ title: "销量", budget: 0, real: 0, rate: 0, diff: 0 },
{ title: "单价", budget: 0, real: 0, rate: 0, diff: 0 }
]
},
dateData: {
type: Object,
default: () => {}
},
title: {
type: String,
default: ''
},
factory: {
type: [String,Number],
default: ''
},
month: {
type: String,
default: ''
},
},
data() {
return {
chart: null,
// 初始化数据包含flag字段
salesData: { title: "销量", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 },
unitPriceData: { title: "单价", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 }
}
},
watch: {
monthAnalysis: {
handler(newVal) {
this.updateChart(newVal)
},
deep: true,
immediate: true
}
},
mounted() {
this.updateChart(this.monthAnalysis)
},
methods: {
handleDashboardClick(path) {
this.$router.push({
path: path,
query: {
factory: this.$route.query.factory ? this.$route.query.factory : this.factory,
dateData: this.dateData
}
})
},
// 判断flag的核心方法
getRateFlag(rate, real, target) {
if (isNaN(rate) || rate === null || rate === undefined) return 0;
// 1. 完成率 >= 100 => 达标
if (rate >= 100) return 1;
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
if (rate === 0 && target === 0) return 1;
// 其他情况 => 未达标
return 0;
},
updateChart(data) {
// 数据兜底
const salesItem = Array.isArray(data) && data[0] ? data[0] : { title: "销量", budget: 0, real: 0, rate: 0, diff: 0 };
const unitPriceItem = Array.isArray(data) && data[1] ? data[1] : { title: "单价", budget: 0, real: 0, rate: 0, diff: 0 };
// 核心修改将flag整合到数据对象中无需单独定义salesFlag/unitPriceFlag
this.salesData = {
...salesItem, // 合并原有字段
flag: this.getRateFlag(salesItem.rate, salesItem.real, salesItem.budget) // 新增flag字段
};
this.unitPriceData = {
...unitPriceItem, // 合并原有字段
flag: this.getRateFlag(unitPriceItem.rate, unitPriceItem.real, unitPriceItem.budget) // 新增flag字段
};
// 调试:确认整合后的数据
console.log('整合flag后的销量数据', this.salesData);
console.log('整合flag后的单价数据', this.unitPriceData);
}
}
}
</script>
<style lang='scss' scoped>
.scroll-container {
max-height: 210px;
overflow-y: auto;
overflow-x: hidden;
padding: 10px 0;
&::-webkit-scrollbar {
display: none;
}
scrollbar-width: none;
-ms-overflow-style: none;
}
.topItem-container {
display: flex;
justify-content: space-between;
}
.dashboard {
flex: 1;
min-width: 300px;
height: 205px;
background: #F9FCFF;
padding: 16px 0 0 10px;
margin: 0 4px;
.title {
height: 18px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 18px;
color: #000000;
line-height: 18px;
letter-spacing: 2px;
text-align: left;
margin-bottom: 12px;
}
.chart-wrap {
width: 100%;
height: calc(100% - 30px);
}
.number {
display: flex;
align-items: center;
gap: 30px;
height: 32px;
font-family: YouSheBiaoTiHei;
font-size: 32px;
color: #0B58FF;
line-height: 32px;
letter-spacing: 2px;
text-align: left;
}
.mom {
width: 97px;
height: 18px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 18px;
color: #000000;
line-height: 18px;
letter-spacing: 1px;
text-align: left;
z-index: 1000;
}
}
.dashboard.left {
margin-left: 0;
}
.dashboard.right {
margin-right: 0;
}
</style>

View File

@@ -174,69 +174,7 @@ export default {
yAxisIndex: 0,
barWidth: 40,
label: {
show: true,
position: 'top',
offset: [32, 0],
width: 100,
height: 22,
formatter: (params) => {
const diff = data.diff || [];
const flags = data.flags || [];
const currentDiff = diff[params.dataIndex] || 0;
const currentFlag = flags[params.dataIndex] || 0;
const prefix = currentFlag === 1 ? '+' : '-';
// 根据标志位选择不同的样式类
if (currentFlag === 1) {
// 达标 - 使用 rate-achieved 样式
return `{achieved|${currentDiff}}{text|差值}`;
} else {
// 未达标 - 使用 rate-unachieved 样式
return `{unachieved|${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: 26,
rich: {
text: {
width: 'auto',
padding: [5, 10, 5, 0],
align: 'center',
color: '#464646',
fontSize: 14,
},
achieved: {
width: 'auto',
padding: [5, 0, 5, 10],
align: 'center',
color: '#76DABE', // 与达标的 offset: 1 颜色一致
fontSize: 14,
},
// 未达标样式
unachieved: {
width: 'auto',
padding: [5, 0, 5, 10],
align: 'center',
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
fontSize: 14,
}
}
show: false
},
itemStyle: {
color: (params) => {
@@ -264,6 +202,78 @@ export default {
borderWidth: 0
},
data: data.reals || []
},
// 实际差值标签独立scatter系列zlevel=1确保标签在最上层
{
name: '__实际差值标签',
type: 'scatter',
yAxisIndex: 0,
zlevel: 1,
symbolSize: 0,
tooltip: {
show: false
},
data: (data.reals || []).map((value, index) => ({
value: value,
label: {
show: true,
position: 'top',
offset: [32, 0],
width: 100,
height: 22,
formatter: () => {
const diff = data.diff || [];
const flags = data.flags || [];
const currentDiff = diff[index] || 0;
const currentFlag = flags[index] || 0;
if (currentFlag === 1) {
return `{achieved|${currentDiff}}{text|差值}`;
} else {
return `{unachieved|${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: 26,
rich: {
text: {
width: 'auto',
padding: [5, 10, 5, 0],
align: 'center',
color: '#464646',
fontSize: 14,
},
achieved: {
width: 'auto',
padding: [5, 0, 5, 10],
align: 'center',
color: '#76DABE',
fontSize: 14,
},
unachieved: {
width: 'auto',
padding: [5, 0, 5, 10],
align: 'center',
color: '#F9A44A',
fontSize: 14,
}
}
}
}))
}
]
};

View File

@@ -32,7 +32,6 @@
width: 1220px;
background-color: rgba(249, 252, 255, 1);
">
<!-- <top-item /> -->
<operatingBar :dateData="dateData" :chartData="chartData" @sort-change="sortChange" />
</div>
</div>
@@ -88,23 +87,6 @@ export default {
sortChange(value) {
this.$emit('sort-change', value);
},
/**
* 判断rate对应的flag值<1为0>1为1
* @param {number} rate 处理后的rate值已*100
* @returns {0|1} flag值
*/
getRateFlag(rate, real, target) {
if (isNaN(rate) || rate === null || rate === undefined) return 0;
// 1. 完成率 >= 100 => 达标
if (rate >= 100) return 1;
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
// if (rate === 0 && target === 0) return 1;
// 其他情况 => 未达标
return 0;
},
/**
* 核心处理函数:在所有数据都准备好后,才组装 chartData
*/
@@ -118,7 +100,7 @@ getRateFlag(rate, real, target) {
const groupReal = [this.groupData.real]; // 实际值数组
const groupRate = [this.groupData.rate]; // 完成率数组
// 新增集团rate对应的flag
const groupFlag = [this.getRateFlag(groupRate[0], groupReal[0], groupTarget[0])];
const groupFlag = [this.groupData.rate >= 100 ? 1 : 0];
console.log('集团数据数组:', {
groupTarget,
@@ -139,7 +121,7 @@ getRateFlag(rate, real, target) {
const factoryRate = this.factoryData.map(item => item.rate || 0);
const factoryDiff = this.factoryData.map(item => item.diff || 0);
// 新增每个工厂rate对应的flag数组
const factoryFlags = this.factoryData.map(item => this.getRateFlag(item.rate, item.real, item.budget));
const factoryFlags = this.factoryData.map(item => item.rate >= 100 ? 1 : 0);
// 3. 组装最终的chartData供子组件使用
this.chartData = {

View File

@@ -32,7 +32,6 @@
width: 1220px;
background-color: rgba(249, 252, 255, 1);
">
<!-- <top-item /> -->
<operatingBar :dateData="dateData" @sort-change="sortChange" :chartData="chartData" />
</div>
</div>
@@ -83,23 +82,6 @@ export default {
sortChange(value) {
this.$emit('sort-change', value);
},
/**
* 判断rate对应的flag值<1为0>1为1
* @param {number} rate 处理后的rate值已*100
* @returns {0|1} flag值
*/
getRateFlag(rate, real, target) {
if (isNaN(rate) || rate === null || rate === undefined) return 0;
// 1. 完成率 >= 100 => 达标
if (rate >= 100) return 1;
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
// if (rate === 0 && target === 0) return 1;
// 其他情况 => 未达标
return 0;
},
/**
* 核心处理函数:在所有数据都准备好后,才组装 chartData
@@ -114,7 +96,7 @@ getRateFlag(rate, real, target) {
const groupReal = [this.groupData.real]; // 实际值数组
const groupRate = [this.groupData.rate]; // 完成率数组
// 新增集团rate对应的flag
const groupFlag = [this.getRateFlag(groupRate[0], groupReal[0], groupTarget[0])];
const groupFlag = [this.groupData.rate >= 100 ? 1 : 0];
console.log('集团数据数组:', {
groupTarget,
@@ -135,7 +117,7 @@ getRateFlag(rate, real, target) {
const factoryRate = this.factoryData.map(item => item.rate || 0);
const factoryDiff = this.factoryData.map(item => item.diff || 0);
// 新增每个工厂rate对应的flag数组
const factoryFlags = this.factoryData.map(item => this.getRateFlag(item.rate, item.real, item.budget));
const factoryFlags = this.factoryData.map(item => item.rate >= 100 ? 1 : 0);
// 3. 组装最终的chartData供子组件使用
this.chartData = {

View File

@@ -1,245 +0,0 @@
<template>
<div class="lineBottom" style="height: 180px; width: 100%">
<operatingLineBarSaleSingle :refName=" 'totalOperating' " :chartData="chartD" style="height: 99%; width: 100%" />
</div>
</template>
<script>
import operatingLineBarSaleSingle from './operatingLineBarSaleSingle.vue';
import * as echarts from 'echarts';
export default {
name: "Container",
components: { operatingLineBarSaleSingle },
props: ["detailData"],
data() {
return {
};
},
computed: {
locations() {
return ['预算', '实际'];
},
chartD() {
// 背景图片路径(若不需要可注释)
// const bgImageUrl = require('@/assets/img/labelBg.png');
console.log('this.detailData', this.detailData);
const rate = this.detailData?.rate || 0
const diff = this.detailData?.diff || 0
console.log('diff', diff);
const seriesData = [
{
value: this.detailData?.budget || 0,
flag: 1, // 实际项:达标(绿色)
label: {
show: true,
position: 'top',
offset: [0, 0],
fontSize: 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
},
},
{
value: this.detailData?.real || 0,
flag: this.detailData?.flag, // 实际项:达标(绿色)
label: {
show: true,
position: 'top',
offset: [0, 0],
fontSize: 14,
},
itemStyle: {
borderRadius: [4, 4, 0, 0],
borderWidth: 0
},
}
];
const data = {
allPlaceNames: ['预算', '实际'],
series: [
{
type: 'bar',
barWidth: 60,
barCategoryGap: '50%',
data: seriesData,
itemStyle: {
color: (params) => {
const currentFlag = params.data.flag || 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)' }
]
};
}
},
},
],
};
console.log('data', data);
return data;
}
},
methods: {},
};
</script>
<style scoped lang="scss">
.coreBar {
display: flex;
flex-direction: column;
width: 100%;
padding: 12px;
.barTop {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 16px;
width: 100%;
.title {
height: 18px;
font-family: PingFangSC, PingFangSC;
font-weight: 400;
font-size: 18px;
color: #000000;
line-height: 18px;
letter-spacing: 1px;
text-align: left;
font-style: normal;
white-space: nowrap;
}
.right-container {
display: flex;
align-items: center;
gap: 24px;
margin-right: 46px;
}
.legend {
display: flex;
gap: 16px;
align-items: center;
margin: 0;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
font-family: PingFangSC;
font-weight: 400;
font-size: 14px;
color: rgba(0, 0, 0, 0.8);
text-align: left;
font-style: normal;
white-space: nowrap;
}
.legend-icon {
display: inline-block;
}
.legend-icon.line {
width: 12px;
height: 2px;
position: relative;
&::before {
position: absolute;
content: "";
top: -2px;
left: 3px;
width: 6px;
border-radius: 50%;
height: 6px;
background-color: rgba(40, 138, 255, 1);
}
}
.legend-icon.square {
width: 8px;
height: 8px;
}
.yield {
background: rgba(40, 138, 255, 1);
}
.target {
background: #2889FF;
}
.achieved {
background: rgba(40, 203, 151, 1);
}
.unachieved {
background: rgba(255, 132, 0, 1);
}
.button-group {
display: flex;
position: relative;
gap: 2px;
width: 283px;
align-items: center;
height: 24px;
background: #ecf4fe;
border-radius: 12px;
margin: 0;
.item-button {
cursor: pointer;
width: 142px;
height: 24px;
font-family: PingFangSC;
font-weight: 400;
font-size: 12px;
color: #0b58ff;
line-height: 24px;
text-align: center;
font-style: normal;
letter-spacing: 8px;
padding-left: 8px;
}
.item-button.active {
width: 142px;
height: 24px;
background: #3071ff;
border-radius: 12px;
color: #ffffff;
font-weight: 500;
}
}
}
}
</style>

View File

@@ -1,202 +0,0 @@
<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">
累计完成率
</div>
<div class="number">
<div class="yield">
{{ ytdData?.rate || 0}}%
</div>
<div class="mom">
同比{{ ytdData?.yoyRate || 0}}%
<img v-if="ytdData?.yoyRate >= 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=" 'totalG' " :detailData="ytdData" id="totalGauge"></electricityGauge>
</div> -->
</div>
<div class="line" style="padding: 0px;">
<verticalBarChart :refName=" 'totalVerticalBarChart' " :detailData="ytdData">
</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: {
ytdData: { // 接收父组件传递的设备数据数组
type: Object,
default: () => {} // 默认空数组,避免报错
},
title: { // 接收父组件传递的设备数据数组
type: String,
default: () => '' // 默认空数组,避免报错
},
month: { // 接收父组件传递的设备数据数组
type: String,
default: () => '' // 默认空数组,避免报错
},
},
data() {
return {
chart: null,
}
},
watch: {
itemData: {
handler(newValue, oldValue) {
// this.updateChart()
},
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: {
}
}
</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;
}
.dashboard {
width: 264px;
height: 205px;
background: #F9FCFF;
padding: 16px 0 0 10px;
.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;
}
.number {
font-family: YouSheBiaoTiHei;
font-size: 46px;
color: #0B58FF;
letter-spacing: 2px;
text-align: center;
font-style: normal;
margin-top: 20px;
}
.mom {
height: 18px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 20px;
color: #000000;
line-height: 18px;
letter-spacing: 1px;
text-align: center;
font-style: normal;
margin-top: 20px;
}
}
.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> -->

View File

@@ -1,226 +0,0 @@
<template>
<div style="width: 100%; height: 210px;position: relative;">
<div style='font-size: 16px;position: absolute;right: 20px;top:10px'>
<span>完成率:<span style='color: #0B58FF;'>{{detailData.rate}}%</span></span>
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:detailData.flags>0?'#30B590':'#FF9423'}" >{{detailData.diff}}</span></span>
</div>
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 210px;"></div>
</div>
</template>
<script>
import * as echarts from 'echarts';
export default {
components: {},
data() {
return {
myChart: null // 存储图表实例,避免重复创建
};
},
props: {
// 明确接收的props结构增强可读性
refName: {
type: String,
default: () => 'verticalBarChart',
},
detailData: {
type: Object,
default: () => ({
}),
}
},
mounted() {
this.$nextTick(() => {
this.updateChart();
});
},
// 新增:监听 chartData 变化
watch: {
// 深度监听数据变化,仅更新图表配置(不销毁实例)
detailData: {
handler() {
console.log(this.chartData, 'chartData');
this.updateChart();
},
deep: true,
immediate: true // 初始化时立即执行
}
},
methods: {
getRateFlag(rate, real, target) {
if (isNaN(rate) || rate === null || rate === undefined) return 0;
// 1. 完成率 >= 100 => 达标
if (rate >= 100) return 1;
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
if (rate === 0 && target === 0) return 1;
// 其他情况 => 未达标
return 0;
},
updateChart() {
const chartDom = this.$refs[this.refName];
if (!chartDom) {
console.error('图表容器未找到!');
return;
}
if (this.myChart) {
this.myChart.dispose();
}
this.myChart = echarts.init(chartDom);
const diff = this.detailData.diff || 0
const rate = this.detailData.rate || 0
const flagValue = this.getRateFlag(this.detailData.rate, this.detailData.real, this.detailData.target) || 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;
// }
},
grid: {
top: 40,
bottom: 15,
right: 80,
left: 10,
containLabel: true,
show: false // 隐藏grid背景避免干扰
},
xAxis: {
// 横向柱状图的x轴必须设为数值轴否则无法正常展示数值
type: 'value',
// offset: 0,
// boundaryGap: true ,
// boundaryGap: [10, 0], // 可根据需要开启,控制轴的留白
axisTick: { show: false },
min: 0,
//
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的数据自动生成
},
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点线
}
},
axisTick: { show: false },
// padding: [300, 100, 100, 100],
data: ['实际', '预算'] // y轴分类实际、预算
},
series: [
{
// name: '预算',
type: 'bar',
barWidth: 24,
// barCategoryGap: '50', // 柱子之间的间距(相对于柱子宽度)
// 数据长度与yAxis的分类数量匹配实际、预算各一个值
data: [{
value: this.detailData.real,
label: {
show: true,
position: 'right',
fontSize: 14,
},
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]
}
}, {
value: this.detailData.target,
label: {
show: true,
position: 'right',
fontSize: 14,
},
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);
// 窗口缩放适配和销毁逻辑保持不变
window.addEventListener('resize', () => {
this.myChart && this.myChart.resize();
});
this.$once('hook:destroyed', () => {
window.removeEventListener('resize', () => {
this.myChart && this.myChart.resize();
});
this.myChart && this.myChart.dispose();
});
}
},
};
</script>

View File

@@ -1,217 +0,0 @@
<template>
<div style="flex: 1">
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
<div class="topItem-container" style="display: flex; gap: 8px; width: 100%;">
<!-- 销量模块直接传递整合了flag的salesData -->
<div class="dashboard left" @click="handleDashboardClick('/salesVolumeAnalysis/salesVolumeAnalysisBase')">
<div style='position: relative;'>
<div class="title">
销量·
</div>
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
<span>完成率:<span style='color: #0B58FF;'>{{ytdAnalysis[0].rate}}%</span></span>
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:ytdAnalysis[0].flags>0?'#30B590':'#FF9423'}" >{{ytdAnalysis[0].diff}}</span></span>
</div>
</div>
<div class="chart-wrap">
<operatingSingleBar :detailData="salesData"></operatingSingleBar>
</div>
</div>
<!-- 单价模块直接传递整合了flag的unitPriceData -->
<div class="dashboard right" @click="handleDashboardClick('/unitPriceAnalysis/unitPriceAnalysisBase')">
<div style='position: relative;'>
<div class="title">
单价·/
</div>
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
<span>完成率:<span style='color: #0B58FF;'>{{ytdAnalysis[1].rate}}%</span></span>
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:ytdAnalysis[1].flags>0?'#30B590':'#FF9423'}" >{{ytdAnalysis[1].diff}}</span></span>
</div>
</div>
<div class="chart-wrap">
<operatingSingleBar :detailData="unitPriceData"></operatingSingleBar>
</div>
</div>
</div>
</div>
</Container>
</div>
</template>
<script>
import Container from './container.vue'
import operatingSingleBar from './operatingSingleBar.vue'
export default {
name: 'ProductionStatus',
components: { Container, operatingSingleBar },
props: {
ytdAnalysis: {
type: Array,
default: () => [
{ title: "销量", budget: 0, real: 0, rate: 0, diff: 0 },
{ title: "单价", budget: 0, real: 0, rate: 0, diff: 0 }
]
},
dateData: {
type: Object,
default: () => {}
},
title: {
type: String,
default: ''
},
month: {
type: String,
default: ''
},
},
data() {
return {
chart: null,
// 初始化数据包含flag字段
salesData: { title: "销量", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 },
unitPriceData: { title: "单价", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 }
}
},
watch: {
ytdAnalysis: {
handler(newVal) {
this.updateChart(newVal)
},
deep: true,
immediate: true
}
},
mounted() {
this.updateChart(this.ytdAnalysis)
},
methods: {
handleDashboardClick(path) {
this.$router.push({
path: path,
query: {
factory: this.$route.query.factory ? this.$route.query.factory : 5,
dateData: this.dateData
}
})
},
// 判断flag的核心方法
getRateFlag(rate, real, target) {
if (isNaN(rate) || rate === null || rate === undefined) return 0;
// 1. 完成率 >= 100 => 达标
if (rate >= 100) return 1;
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
if (rate === 0 && target === 0) return 1;
// 其他情况 => 未达标
return 0;
},
updateChart(data) {
// 数据兜底
const salesItem = Array.isArray(data) && data[0] ? data[0] : { title: "销量", budget: 0, real: 0, rate: 0, diff: 0 };
const unitPriceItem = Array.isArray(data) && data[1] ? data[1] : { title: "单价", budget: 0, real: 0, rate: 0, diff: 0 };
// 核心修改将flag整合到数据对象中无需单独定义salesFlag/unitPriceFlag
this.salesData = {
...salesItem, // 合并原有字段
flag: this.getRateFlag(salesItem.rate, salesItem.real, salesItem.budget) // 新增flag字段
};
this.unitPriceData = {
...unitPriceItem, // 合并原有字段
flag: this.getRateFlag(unitPriceItem.rate, unitPriceItem.real, unitPriceItem.budget) // 新增flag字段
};
// 调试:确认整合后的数据
console.log('整合flag后的销量数据', this.salesData);
console.log('整合flag后的单价数据', this.unitPriceData);
}
}
}
</script>
<style lang='scss' scoped>
.scroll-container {
max-height: 210px;
overflow-y: auto;
overflow-x: hidden;
padding: 10px 0;
&::-webkit-scrollbar {
display: none;
}
scrollbar-width: none;
-ms-overflow-style: none;
}
.topItem-container {
display: flex;
justify-content: space-between;
}
.dashboard {
flex: 1;
min-width: 300px;
height: 205px;
background: #F9FCFF;
padding: 16px 0 0 10px;
margin: 0 4px;
.title {
height: 18px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 18px;
color: #000000;
line-height: 18px;
letter-spacing: 2px;
text-align: left;
margin-bottom: 12px;
}
.chart-wrap {
width: 100%;
height: calc(100% - 30px);
}
.number {
display: flex;
align-items: center;
gap: 30px;
height: 32px;
font-family: YouSheBiaoTiHei;
font-size: 32px;
color: #0B58FF;
line-height: 32px;
letter-spacing: 2px;
text-align: left;
}
.mom {
width: 97px;
height: 18px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 18px;
color: #000000;
line-height: 18px;
letter-spacing: 1px;
text-align: left;
z-index: 1000;
}
}
.dashboard.left {
margin-left: 0;
}
.dashboard.right {
margin-right: 0;
}
</style>