处理监听图表的函数,确保及时移除&生产环境不打印log
This commit is contained in:
@@ -1,124 +1,131 @@
|
||||
<template>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="height: 184px; width: 100%;"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'Container',
|
||||
components: {},
|
||||
// 接收父组件传递的 series 数据
|
||||
props: {
|
||||
chartSeries: {
|
||||
type: Object,
|
||||
default: () => {{}} // 默认空数组,避免报错
|
||||
},
|
||||
xAxisData: {
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
dateData: {
|
||||
type: Object,
|
||||
default: () => {} // 默认空数组,避免报错
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 存储图表实例,方便后续操作
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initData();
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
// 监听 series 数据变化,实时更新图表
|
||||
chartSeries: {
|
||||
handler() {
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true // 深度监听数组内元素变化
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
this.myChart = echarts.init(chartDom);
|
||||
this.updateChart(); // 初始化时渲染图表
|
||||
|
||||
// 监听窗口缩放
|
||||
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();
|
||||
});
|
||||
},
|
||||
// 单独提取更新图表的方法,方便复用
|
||||
updateChart() {
|
||||
if (!this.myChart) return;
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: { backgroundColor: '#6a7985' }
|
||||
}
|
||||
},
|
||||
grid: { top: 35, bottom: 3, right: 15, left: 18, containLabel: true},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
onZero: false,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
// width: 38,
|
||||
overflow: 'break',
|
||||
formatter: (value) => {
|
||||
const dateParts = value.split('-'); // ["2025", "07", "01"]
|
||||
if (dateParts.length < 2) return value;
|
||||
|
||||
// 去掉月份前面的0,然后加上"月"
|
||||
const month = dateParts[1].replace(/^0+/, '');
|
||||
return `${month}月`;
|
||||
}
|
||||
},
|
||||
data: this.xAxisData
|
||||
}
|
||||
],
|
||||
yAxis: {
|
||||
name: this.chartSeries.unit,
|
||||
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' },
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 12 },
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }
|
||||
},
|
||||
series: this.chartSeries.series // 使用父组件传递的 series 数据
|
||||
};
|
||||
|
||||
this.myChart.setOption(option, true); // 第二个参数 true 表示替换现有配置
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="height: 184px; width: 100%;"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'Container',
|
||||
components: {},
|
||||
// 接收父组件传递的 series 数据
|
||||
props: {
|
||||
chartSeries: {
|
||||
type: Object,
|
||||
default: () => {{}} // 默认空数组,避免报错
|
||||
},
|
||||
xAxisData: {
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
dateData: {
|
||||
type: Object,
|
||||
default: () => {} // 默认空数组,避免报错
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
myChart: null, // 存储图表实例,方便后续操作
|
||||
resizeHandler: null // 窗口resize事件处理器
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initData();
|
||||
});
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize();
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
watch: {
|
||||
// 监听 series 数据变化,实时更新图表
|
||||
chartSeries: {
|
||||
handler() {
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true // 深度监听数组内元素变化
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
this.myChart = echarts.init(chartDom);
|
||||
this.updateChart(); // 初始化时渲染图表
|
||||
},
|
||||
// 单独提取更新图表的方法,方便复用
|
||||
updateChart() {
|
||||
if (!this.myChart) return;
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: { backgroundColor: '#6a7985' }
|
||||
}
|
||||
},
|
||||
grid: { top: 35, bottom: 3, right: 15, left: 18, containLabel: true},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
onZero: false,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
// width: 38,
|
||||
overflow: 'break',
|
||||
formatter: (value) => {
|
||||
const dateParts = value.split('-'); // ["2025", "07", "01"]
|
||||
if (dateParts.length < 2) return value;
|
||||
|
||||
// 去掉月份前面的0,然后加上"月"
|
||||
const month = dateParts[1].replace(/^0+/, '');
|
||||
return `${month}月`;
|
||||
}
|
||||
},
|
||||
data: this.xAxisData
|
||||
}
|
||||
],
|
||||
yAxis: {
|
||||
name: this.chartSeries.unit,
|
||||
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' },
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 12 },
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }
|
||||
},
|
||||
series: this.chartSeries.series // 使用父组件传递的 series 数据
|
||||
};
|
||||
|
||||
this.myChart.setOption(option, true); // 第二个参数 true 表示替换现有配置
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
this.resizeHandler = null;
|
||||
}
|
||||
// 销毁图表,避免内存泄漏
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,401 +1,412 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<!-- 传入点击切换的状态到Container组件 -->
|
||||
<Container name="财务重点指标" nameTwo="费用重点指标" icon="cockpitItemIcon" size="topBasic" @switchTab="handleTabSwitch" @tabChange='tabChange'>
|
||||
<div class="bottom-left-content" style="display: flex;gap: 0px;padding: 14px 16px;flex-direction: column;">
|
||||
<!-- 根据activeTab状态,切换显示采购/存货内容 -->
|
||||
<template v-if="activeTab === 'purchase'">
|
||||
<!-- 财务重点指标应的内容 -->
|
||||
<coreBottomLeftItem :dateData="dateData" :finance="financeData"></coreBottomLeftItem>
|
||||
<div class="bottom"
|
||||
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
||||
<coreBottomBar :dateData="dateData" :line="finance.line"></coreBottomBar>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="activeTab === 'inventory'">
|
||||
<!-- 费用重点指标对应的内容 -->
|
||||
<costItem :dateData="dateData" :cost="costData" :currentTap='currentTap'></costItem>
|
||||
<div class="bottom"
|
||||
style="display: flex; width: 100%;margin-top: 4px;background-color: rgba(249, 252, 255, 1);">
|
||||
<CostsBottomBar :line="cost.line" :dateData="dateData">
|
||||
</CostsBottomBar>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Container from './financeCostsContainer.vue'
|
||||
import * as echarts from 'echarts'
|
||||
import coreBottomLeftItem from './purchase-Item.vue'
|
||||
import coreBottomBar from './financeCostsBottomBar.vue'
|
||||
import costItem from './cost-Item.vue'
|
||||
import CostsBottomBar from './CostsBottomBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, coreBottomLeftItem, coreBottomBar, costItem, CostsBottomBar },
|
||||
props: {
|
||||
finance: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
cost: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
dateData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'purchase', // 激活的标签:purchase=采购,inventory=存货
|
||||
showChart: true, // 控制图表是否显示
|
||||
chart: null, // 图表实例
|
||||
financeData:{},
|
||||
costData:{},
|
||||
currentTap: 'month'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 切换标签时更新图表
|
||||
activeTab(newVal) {
|
||||
this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
productionOverviewVo: {
|
||||
handler() {
|
||||
this.updateChart()
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
finance: {
|
||||
handler() {
|
||||
if(this.currentTap === 'month') {
|
||||
this.financeData = this.finance.mon
|
||||
}else{
|
||||
this.financeData = this.finance.total
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
cost: {
|
||||
handler() {
|
||||
if(this.currentTap === 'month') {
|
||||
this.costData = this.cost.mon
|
||||
}else{
|
||||
this.costData = this.cost.total
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
|
||||
},
|
||||
mounted() {
|
||||
// 初始化图表
|
||||
this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 销毁图表,避免内存泄漏
|
||||
if (this.chart) {
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
tabChange(val) {
|
||||
if(val === 'month') {
|
||||
this.currentTap = 'month'
|
||||
this.financeData = this.finance.mon
|
||||
this.costData = this.cost.mon
|
||||
}else{
|
||||
this.currentTap = 'total'
|
||||
this.financeData = this.finance.total
|
||||
this.costData = this.cost.total
|
||||
}
|
||||
},
|
||||
// 处理标题点击切换标签
|
||||
handleTabSwitch(tabType) {
|
||||
this.activeTab = tabType // tabType由Container组件传递:'purchase'或'inventory'
|
||||
this.showChart = true // 切换时默认显示图表(可根据需求调整)
|
||||
},
|
||||
// 更新图表内容
|
||||
updateChart() {
|
||||
const chartDom = document.getElementById('productionStatusChart')
|
||||
if (!chartDom) return
|
||||
|
||||
// 销毁已有图表实例
|
||||
if (this.chart) this.chart.dispose()
|
||||
this.chart = echarts.init(chartDom)
|
||||
|
||||
// 根据当前激活的标签,设置对应图表数据(示例:可根据实际需求修改)
|
||||
const data = this.activeTab === 'purchase'
|
||||
? [
|
||||
this.productionOverviewVo.purchaseInput || 0,
|
||||
this.productionOverviewVo.purchaseOutput || 0,
|
||||
this.productionOverviewVo.purchaseNg || 0,
|
||||
0, 0, 0, 0 // 补充默认值,避免图表报错
|
||||
]
|
||||
: [
|
||||
this.productionOverviewVo.inventoryInput || 0,
|
||||
this.productionOverviewVo.inventoryOutput || 0,
|
||||
this.productionOverviewVo.inventoryNg || 0,
|
||||
0, 0, 0, 0
|
||||
]
|
||||
|
||||
const option = {
|
||||
type: 'bar',
|
||||
grid: { left: 51, right: 40, top: 50, bottom: 45 },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
className: 'production-status-chart-tooltip'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
offset: 8,
|
||||
data: this.activeTab === 'purchase'
|
||||
? ['采购投入', '采购产出', '采购待判', '低价值', '报废', '在制', '实验片']
|
||||
: ['存货投入', '存货产出', '存货待判', '低价值', '报废', '在制', '实验片'],
|
||||
axisTick: { show: false },
|
||||
axisLine: { show: true, onZero: false, lineStyle: { color: '#00E8FF' } },
|
||||
axisLabel: {
|
||||
color: 'rgba(255,255,255,0.7)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
width: 38,
|
||||
overflow: 'break'
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '单位/片',
|
||||
nameTextStyle: { color: 'rgba(255,255,255,0.7)', fontSize: 14, align: 'left' },
|
||||
min: 0,
|
||||
max: (value) => Math.ceil(value.max),
|
||||
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: 'rgba(255,255,255,0.7)', fontSize: 12 },
|
||||
splitLine: { lineStyle: { color: 'RGBA(24, 88, 100, 0.6)', type: 'dashed' } },
|
||||
axisLine: { show: true, lineStyle: { color: '#00E8FF' } }
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pictorialBar',
|
||||
label: { show: true, position: 'top', distance: -3, color: '#89CDFF', fontSize: 11 },
|
||||
symbolSize: [20, 8],
|
||||
symbolOffset: [0, 5],
|
||||
z: 20,
|
||||
itemStyle: {
|
||||
borderColor: '#3588C7',
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'RGBA(22, 89, 98, 1)' },
|
||||
{ offset: 1, color: '#3588C7' }
|
||||
])
|
||||
},
|
||||
data: data
|
||||
},
|
||||
{
|
||||
type: 'bar',
|
||||
barWidth: 20,
|
||||
itemStyle: {
|
||||
borderWidth: 1,
|
||||
borderColor: '#3588C7',
|
||||
opacity: 0.8,
|
||||
color: {
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
type: 'linear',
|
||||
global: false,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(73,178,255,0)' },
|
||||
{ offset: 0.5, color: 'rgba(0, 232, 255, .5)' },
|
||||
{ offset: 1, color: 'rgba(0, 232, 255, 1)' }
|
||||
]
|
||||
}
|
||||
},
|
||||
tooltip: { show: false },
|
||||
data: data
|
||||
},
|
||||
{
|
||||
type: 'pictorialBar',
|
||||
symbolSize: [20, 8],
|
||||
symbolOffset: [0, -4],
|
||||
z: 12,
|
||||
symbolPosition: 'end',
|
||||
itemStyle: {
|
||||
borderColor: 'rgba(0, 232, 255, 1)',
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'RGBA(22, 89, 98, 1)' },
|
||||
{ offset: 1, color: '#3588C7' }
|
||||
])
|
||||
},
|
||||
tooltip: { show: false },
|
||||
data: data
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
this.chart.setOption(option)
|
||||
// 监听窗口 resize,自适应图表
|
||||
window.addEventListener('resize', this.chart.resize)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* 原有样式保留,新增内容容器样式 */
|
||||
.bottom-left-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 其他原有样式... */
|
||||
.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>
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<!-- 传入点击切换的状态到Container组件 -->
|
||||
<Container name="财务重点指标" nameTwo="费用重点指标" icon="cockpitItemIcon" size="topBasic" @switchTab="handleTabSwitch" @tabChange='tabChange'>
|
||||
<div class="bottom-left-content" style="display: flex;gap: 0px;padding: 14px 16px;flex-direction: column;">
|
||||
<!-- 根据activeTab状态,切换显示采购/存货内容 -->
|
||||
<template v-if="activeTab === 'purchase'">
|
||||
<!-- 财务重点指标应的内容 -->
|
||||
<coreBottomLeftItem :dateData="dateData" :finance="financeData"></coreBottomLeftItem>
|
||||
<div class="bottom"
|
||||
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
||||
<coreBottomBar :dateData="dateData" :line="finance.line"></coreBottomBar>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="activeTab === 'inventory'">
|
||||
<!-- 费用重点指标对应的内容 -->
|
||||
<costItem :dateData="dateData" :cost="costData" :currentTap='currentTap'></costItem>
|
||||
<div class="bottom"
|
||||
style="display: flex; width: 100%;margin-top: 4px;background-color: rgba(249, 252, 255, 1);">
|
||||
<CostsBottomBar :line="cost.line" :dateData="dateData">
|
||||
</CostsBottomBar>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Container from './financeCostsContainer.vue'
|
||||
import * as echarts from 'echarts'
|
||||
import coreBottomLeftItem from './purchase-Item.vue'
|
||||
import coreBottomBar from './financeCostsBottomBar.vue'
|
||||
import costItem from './cost-Item.vue'
|
||||
import CostsBottomBar from './CostsBottomBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, coreBottomLeftItem, coreBottomBar, costItem, CostsBottomBar },
|
||||
props: {
|
||||
finance: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
cost: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
dateData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'purchase', // 激活的标签:purchase=采购,inventory=存货
|
||||
showChart: true, // 控制图表是否显示
|
||||
chart: null, // 图表实例
|
||||
financeData:{},
|
||||
costData:{},
|
||||
currentTap: 'month',
|
||||
resizeHandler: null // 窗口resize事件处理器
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 切换标签时更新图表
|
||||
activeTab(newVal) {
|
||||
this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
productionOverviewVo: {
|
||||
handler() {
|
||||
this.updateChart()
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
finance: {
|
||||
handler() {
|
||||
if(this.currentTap === 'month') {
|
||||
this.financeData = this.finance.mon
|
||||
}else{
|
||||
this.financeData = this.finance.total
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
cost: {
|
||||
handler() {
|
||||
if(this.currentTap === 'month') {
|
||||
this.costData = this.cost.mon
|
||||
}else{
|
||||
this.costData = this.cost.total
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
|
||||
},
|
||||
mounted() {
|
||||
// 初始化图表
|
||||
this.$nextTick(() => this.updateChart())
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.chart) {
|
||||
this.chart.resize()
|
||||
}
|
||||
}
|
||||
window.addEventListener('resize', this.resizeHandler)
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler)
|
||||
this.resizeHandler = null
|
||||
}
|
||||
// 销毁图表,避免内存泄漏
|
||||
if (this.chart) {
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
tabChange(val) {
|
||||
if(val === 'month') {
|
||||
this.currentTap = 'month'
|
||||
this.financeData = this.finance.mon
|
||||
this.costData = this.cost.mon
|
||||
}else{
|
||||
this.currentTap = 'total'
|
||||
this.financeData = this.finance.total
|
||||
this.costData = this.cost.total
|
||||
}
|
||||
},
|
||||
// 处理标题点击切换标签
|
||||
handleTabSwitch(tabType) {
|
||||
this.activeTab = tabType // tabType由Container组件传递:'purchase'或'inventory'
|
||||
this.showChart = true // 切换时默认显示图表(可根据需求调整)
|
||||
},
|
||||
// 更新图表内容
|
||||
updateChart() {
|
||||
const chartDom = document.getElementById('productionStatusChart')
|
||||
if (!chartDom) return
|
||||
|
||||
// 销毁已有图表实例
|
||||
if (this.chart) this.chart.dispose()
|
||||
this.chart = echarts.init(chartDom)
|
||||
|
||||
// 根据当前激活的标签,设置对应图表数据(示例:可根据实际需求修改)
|
||||
const data = this.activeTab === 'purchase'
|
||||
? [
|
||||
this.productionOverviewVo.purchaseInput || 0,
|
||||
this.productionOverviewVo.purchaseOutput || 0,
|
||||
this.productionOverviewVo.purchaseNg || 0,
|
||||
0, 0, 0, 0 // 补充默认值,避免图表报错
|
||||
]
|
||||
: [
|
||||
this.productionOverviewVo.inventoryInput || 0,
|
||||
this.productionOverviewVo.inventoryOutput || 0,
|
||||
this.productionOverviewVo.inventoryNg || 0,
|
||||
0, 0, 0, 0
|
||||
]
|
||||
|
||||
const option = {
|
||||
type: 'bar',
|
||||
grid: { left: 51, right: 40, top: 50, bottom: 45 },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
className: 'production-status-chart-tooltip'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
offset: 8,
|
||||
data: this.activeTab === 'purchase'
|
||||
? ['采购投入', '采购产出', '采购待判', '低价值', '报废', '在制', '实验片']
|
||||
: ['存货投入', '存货产出', '存货待判', '低价值', '报废', '在制', '实验片'],
|
||||
axisTick: { show: false },
|
||||
axisLine: { show: true, onZero: false, lineStyle: { color: '#00E8FF' } },
|
||||
axisLabel: {
|
||||
color: 'rgba(255,255,255,0.7)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
width: 38,
|
||||
overflow: 'break'
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '单位/片',
|
||||
nameTextStyle: { color: 'rgba(255,255,255,0.7)', fontSize: 14, align: 'left' },
|
||||
min: 0,
|
||||
max: (value) => Math.ceil(value.max),
|
||||
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: 'rgba(255,255,255,0.7)', fontSize: 12 },
|
||||
splitLine: { lineStyle: { color: 'RGBA(24, 88, 100, 0.6)', type: 'dashed' } },
|
||||
axisLine: { show: true, lineStyle: { color: '#00E8FF' } }
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pictorialBar',
|
||||
label: { show: true, position: 'top', distance: -3, color: '#89CDFF', fontSize: 11 },
|
||||
symbolSize: [20, 8],
|
||||
symbolOffset: [0, 5],
|
||||
z: 20,
|
||||
itemStyle: {
|
||||
borderColor: '#3588C7',
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'RGBA(22, 89, 98, 1)' },
|
||||
{ offset: 1, color: '#3588C7' }
|
||||
])
|
||||
},
|
||||
data: data
|
||||
},
|
||||
{
|
||||
type: 'bar',
|
||||
barWidth: 20,
|
||||
itemStyle: {
|
||||
borderWidth: 1,
|
||||
borderColor: '#3588C7',
|
||||
opacity: 0.8,
|
||||
color: {
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
type: 'linear',
|
||||
global: false,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(73,178,255,0)' },
|
||||
{ offset: 0.5, color: 'rgba(0, 232, 255, .5)' },
|
||||
{ offset: 1, color: 'rgba(0, 232, 255, 1)' }
|
||||
]
|
||||
}
|
||||
},
|
||||
tooltip: { show: false },
|
||||
data: data
|
||||
},
|
||||
{
|
||||
type: 'pictorialBar',
|
||||
symbolSize: [20, 8],
|
||||
symbolOffset: [0, -4],
|
||||
z: 12,
|
||||
symbolPosition: 'end',
|
||||
itemStyle: {
|
||||
borderColor: 'rgba(0, 232, 255, 1)',
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'RGBA(22, 89, 98, 1)' },
|
||||
{ offset: 1, color: '#3588C7' }
|
||||
])
|
||||
},
|
||||
tooltip: { show: false },
|
||||
data: data
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
this.chart.setOption(option)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* 原有样式保留,新增内容容器样式 */
|
||||
.bottom-left-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 其他原有样式... */
|
||||
.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>
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,7 +8,8 @@ export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 存储图表实例,避免重复创建
|
||||
myChart: null, // 存储图表实例,避免重复创建
|
||||
resizeHandler: null // 窗口resize事件处理器
|
||||
};
|
||||
},
|
||||
props: {
|
||||
@@ -21,6 +22,13 @@ export default {
|
||||
this.$nextTick(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize();
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
|
||||
// 新增:监听 chartData 变化
|
||||
@@ -139,19 +147,19 @@ export default {
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
this.resizeHandler = null;
|
||||
}
|
||||
// 销毁图表实例
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,176 +1,184 @@
|
||||
<template>
|
||||
<div ref="cockpitEffChipBottom" id="cockpitEffChipBottom" style="width: 100%; height: 400px;"></div>
|
||||
</template>
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 存储图表实例,避免重复创建
|
||||
};
|
||||
},
|
||||
props: {
|
||||
// 明确接收的props结构,增强可读性
|
||||
chartData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
series: [],
|
||||
allPlaceNames: []
|
||||
}),
|
||||
// 校验数据格式
|
||||
validator: (value) => {
|
||||
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
},
|
||||
|
||||
// 新增:监听 chartData 变化
|
||||
watch: {
|
||||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||
chartData: {
|
||||
handler() {
|
||||
console.log(this.chartData,'chartData');
|
||||
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
immediate: true // 初始化时立即执行
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateChart() {
|
||||
const chartDom = this.$refs.cockpitEffChipBottom;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
}
|
||||
|
||||
this.myChart = echarts.init(chartDom);
|
||||
const { allPlaceNames, series } = this.chartData || {};
|
||||
|
||||
// 处理空数据
|
||||
const xData = allPlaceNames || [];
|
||||
const chartSeries = series || []; // 父组件传递的 series
|
||||
|
||||
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: 30,
|
||||
bottom: 30,
|
||||
right: 20,
|
||||
left: 60,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: true,
|
||||
axisTick: { show: false },
|
||||
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
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
// 左侧Y轴:营业收入、成本(单位万元)
|
||||
{
|
||||
type: 'value',
|
||||
splitNumber: 4,
|
||||
name: '万元',
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'right'
|
||||
},
|
||||
// min: 0,
|
||||
// max: (value) => Math.ceil((value.max || 0) * 1.1),
|
||||
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
formatter: '{value}'
|
||||
},
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
splitNumber: 4
|
||||
},
|
||||
// 右侧Y轴:利润占比(百分比)
|
||||
{
|
||||
type: 'value',
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'left'
|
||||
},
|
||||
// min: 0,
|
||||
// max: 100,
|
||||
scale:true,
|
||||
splitNumber: 4,
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
formatter: '{value}%'
|
||||
},
|
||||
splitLine: { show: false },
|
||||
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
splitNumber: 4
|
||||
}
|
||||
],
|
||||
series: chartSeries // 直接使用父组件传递的 series
|
||||
};
|
||||
|
||||
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>
|
||||
<template>
|
||||
<div ref="cockpitEffChipBottom" id="cockpitEffChipBottom" style="width: 100%; height: 400px;"></div>
|
||||
</template>
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null, // 存储图表实例,避免重复创建
|
||||
resizeHandler: null // 窗口resize事件处理器
|
||||
};
|
||||
},
|
||||
props: {
|
||||
// 明确接收的props结构,增强可读性
|
||||
chartData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
series: [],
|
||||
allPlaceNames: []
|
||||
}),
|
||||
// 校验数据格式
|
||||
validator: (value) => {
|
||||
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize();
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
|
||||
// 新增:监听 chartData 变化
|
||||
watch: {
|
||||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||
chartData: {
|
||||
handler() {
|
||||
console.log(this.chartData,'chartData');
|
||||
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
immediate: true // 初始化时立即执行
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateChart() {
|
||||
const chartDom = this.$refs.cockpitEffChipBottom;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
}
|
||||
|
||||
this.myChart = echarts.init(chartDom);
|
||||
const { allPlaceNames, series } = this.chartData || {};
|
||||
|
||||
// 处理空数据
|
||||
const xData = allPlaceNames || [];
|
||||
const chartSeries = series || []; // 父组件传递的 series
|
||||
|
||||
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: 30,
|
||||
bottom: 30,
|
||||
right: 20,
|
||||
left: 60,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: true,
|
||||
axisTick: { show: false },
|
||||
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
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
// 左侧Y轴:营业收入、成本(单位万元)
|
||||
{
|
||||
type: 'value',
|
||||
splitNumber: 4,
|
||||
name: '万元',
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'right'
|
||||
},
|
||||
// min: 0,
|
||||
// max: (value) => Math.ceil((value.max || 0) * 1.1),
|
||||
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
formatter: '{value}'
|
||||
},
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
splitNumber: 4
|
||||
},
|
||||
// 右侧Y轴:利润占比(百分比)
|
||||
{
|
||||
type: 'value',
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'left'
|
||||
},
|
||||
// min: 0,
|
||||
// max: 100,
|
||||
scale:true,
|
||||
splitNumber: 4,
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
formatter: '{value}%'
|
||||
},
|
||||
splitLine: { show: false },
|
||||
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
splitNumber: 4
|
||||
}
|
||||
],
|
||||
series: chartSeries // 直接使用父组件传递的 series
|
||||
};
|
||||
|
||||
option && this.myChart.setOption(option);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
this.resizeHandler = null;
|
||||
}
|
||||
// 销毁图表,避免内存泄漏
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,172 +1,180 @@
|
||||
<template>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||
</template>
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 存储图表实例,避免重复创建
|
||||
};
|
||||
},
|
||||
props: {
|
||||
// 明确接收的props结构,增强可读性
|
||||
chartData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
series: [],
|
||||
allPlaceNames: []
|
||||
}),
|
||||
// 校验数据格式
|
||||
validator: (value) => {
|
||||
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
},
|
||||
|
||||
// 新增:监听 chartData 变化
|
||||
watch: {
|
||||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||
chartData: {
|
||||
handler() {
|
||||
console.log(this.chartData,'chartData');
|
||||
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
immediate: true // 初始化时立即执行
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateChart() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
}
|
||||
|
||||
this.myChart = echarts.init(chartDom);
|
||||
const { allPlaceNames, series } = this.chartData || {};
|
||||
|
||||
// 处理空数据
|
||||
const xData = allPlaceNames || [];
|
||||
const chartSeries = series || []; // 父组件传递的 series
|
||||
|
||||
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: 30,
|
||||
bottom: 30,
|
||||
right: 20,
|
||||
left: 60,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: true,
|
||||
axisTick: { show: false },
|
||||
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
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
// 左侧Y轴:营业收入、成本(单位万元)
|
||||
{
|
||||
type: 'value',
|
||||
name: '万元',
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'right'
|
||||
},
|
||||
|
||||
splitNumber: 4,
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
formatter: '{value}'
|
||||
},
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
splitNumber: 4
|
||||
},
|
||||
// 右侧Y轴:利润占比(百分比)
|
||||
// {
|
||||
// type: 'value',
|
||||
// nameTextStyle: {
|
||||
// color: 'rgba(0, 0, 0, 0.45)',
|
||||
// fontSize: 12,
|
||||
// align: 'left'
|
||||
// },
|
||||
// min: 0,
|
||||
// max: 100,
|
||||
// axisTick: { show: false },
|
||||
// axisLabel: {
|
||||
// color: 'rgba(0, 0, 0, 0.45)',
|
||||
// fontSize: 12,
|
||||
// formatter: '{value}%'
|
||||
// },
|
||||
// splitLine: { show: false },
|
||||
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
// splitNumber: 4
|
||||
// }
|
||||
],
|
||||
series: chartSeries // 直接使用父组件传递的 series
|
||||
};
|
||||
|
||||
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>
|
||||
<template>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||
</template>
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null, // 存储图表实例,避免重复创建
|
||||
resizeHandler: null // 窗口resize事件处理器
|
||||
};
|
||||
},
|
||||
props: {
|
||||
// 明确接收的props结构,增强可读性
|
||||
chartData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
series: [],
|
||||
allPlaceNames: []
|
||||
}),
|
||||
// 校验数据格式
|
||||
validator: (value) => {
|
||||
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize();
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
|
||||
// 新增:监听 chartData 变化
|
||||
watch: {
|
||||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||
chartData: {
|
||||
handler() {
|
||||
console.log(this.chartData,'chartData');
|
||||
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
immediate: true // 初始化时立即执行
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateChart() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
}
|
||||
|
||||
this.myChart = echarts.init(chartDom);
|
||||
const { allPlaceNames, series } = this.chartData || {};
|
||||
|
||||
// 处理空数据
|
||||
const xData = allPlaceNames || [];
|
||||
const chartSeries = series || []; // 父组件传递的 series
|
||||
|
||||
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: 30,
|
||||
bottom: 30,
|
||||
right: 20,
|
||||
left: 60,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: true,
|
||||
axisTick: { show: false },
|
||||
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
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
// 左侧Y轴:营业收入、成本(单位万元)
|
||||
{
|
||||
type: 'value',
|
||||
name: '万元',
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'right'
|
||||
},
|
||||
|
||||
splitNumber: 4,
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
formatter: '{value}'
|
||||
},
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
splitNumber: 4
|
||||
},
|
||||
// 右侧Y轴:利润占比(百分比)
|
||||
// {
|
||||
// type: 'value',
|
||||
// nameTextStyle: {
|
||||
// color: 'rgba(0, 0, 0, 0.45)',
|
||||
// fontSize: 12,
|
||||
// align: 'left'
|
||||
// },
|
||||
// min: 0,
|
||||
// max: 100,
|
||||
// axisTick: { show: false },
|
||||
// axisLabel: {
|
||||
// color: 'rgba(0, 0, 0, 0.45)',
|
||||
// fontSize: 12,
|
||||
// formatter: '{value}%'
|
||||
// },
|
||||
// splitLine: { show: false },
|
||||
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
// splitNumber: 4
|
||||
// }
|
||||
],
|
||||
series: chartSeries // 直接使用父组件传递的 series
|
||||
};
|
||||
|
||||
option && this.myChart.setOption(option);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
this.resizeHandler = null;
|
||||
}
|
||||
// 销毁图表,避免内存泄漏
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -8,7 +8,8 @@ export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 存储图表实例,避免重复创建
|
||||
myChart: null, // 存储图表实例,避免重复创建
|
||||
resizeHandler: null // 窗口resize事件处理器
|
||||
};
|
||||
},
|
||||
props: {
|
||||
@@ -27,6 +28,13 @@ export default {
|
||||
this.$nextTick(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize();
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
|
||||
// 新增:监听 chartData 变化
|
||||
@@ -152,19 +160,19 @@ export default {
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
this.resizeHandler = null;
|
||||
}
|
||||
// 销毁图表实例
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -24,7 +24,10 @@ export default {
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
return {
|
||||
myChart: null,
|
||||
resizeHandler: null
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
watch: {
|
||||
@@ -41,6 +44,13 @@ export default {
|
||||
this.$nextTick(() => {
|
||||
this.initChart(); // 只负责初始化图表实例
|
||||
});
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize();
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
@@ -266,21 +276,20 @@ export default {
|
||||
]
|
||||
};
|
||||
|
||||
option && myChart.setOption(option);
|
||||
|
||||
// 窗口缩放监听
|
||||
window.addEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
|
||||
// 组件销毁清理
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
myChart.dispose();
|
||||
});
|
||||
option && this.myChart.setOption(option);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
this.resizeHandler = null;
|
||||
}
|
||||
// 销毁图表实例
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,286 +1,294 @@
|
||||
<template>
|
||||
<div style="position: relative;">
|
||||
<div class="legend">
|
||||
<span class="legend-item-line">
|
||||
<span class="line target"></span>
|
||||
预算
|
||||
</span>
|
||||
<span class="legend-item-line">
|
||||
<span class="line real"></span>
|
||||
实际
|
||||
</span>
|
||||
</div>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="height: 219px; width: 100%;"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'Container',
|
||||
props: ["chartData",'dateData','unit'],
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null, // 存储 echarts 实例
|
||||
};
|
||||
},
|
||||
// 关键:监听 chartData 变化
|
||||
watch: {
|
||||
chartData: {
|
||||
handler(newData) {
|
||||
this.updateChart(newData);
|
||||
},
|
||||
immediate: true, // 组件初始化时立即执行一次
|
||||
deep: true, // 深度监听对象内部变化
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initChart();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
// 初始化图表实例
|
||||
initChart() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
this.myChart = echarts.init(chartDom);
|
||||
|
||||
// 初始化时调用一次更新
|
||||
this.updateChart(this.chartData);
|
||||
|
||||
// 监听窗口缩放
|
||||
window.addEventListener('resize', () => {
|
||||
this.myChart?.resize();
|
||||
});
|
||||
},
|
||||
|
||||
// 核心:根据数据更新图表
|
||||
updateChart(data) {
|
||||
if (!this.myChart) {
|
||||
// 如果实例还未初始化,则等待 initChart 完成后再更新
|
||||
setTimeout(() => this.updateChart(data), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 处理数据,如果 data 无效则清空图表
|
||||
if (!data || typeof data !== 'object' || (!data.real && !data.target)) {
|
||||
this.myChart.setOption({
|
||||
xAxis: { data: [] },
|
||||
series: [{ data: [] }, { data: [] }]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 提取 X 轴数据(从 real 或 target 中取键名)
|
||||
const xAxisData = data.real ? Object.keys(data.real) : Object.keys(data.target);
|
||||
console.log('xAxisData', xAxisData);
|
||||
|
||||
// 3. 提取 "实际" 和 "目标" 系列的数据
|
||||
const realData = data.real ? Object.values(data.real) : [];
|
||||
const targetData = data.target ? Object.values(data.target) : [];
|
||||
|
||||
// 4. 准备 echarts 的 option 配置
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 35,
|
||||
bottom: 20,
|
||||
right: 13,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
onZero: false,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
// width: 38,
|
||||
overflow: 'break',
|
||||
formatter: (value) => {
|
||||
const dateParts = value.split('-'); // ["2025", "07", "01"]
|
||||
if (dateParts.length < 2) return value;
|
||||
|
||||
// 去掉月份前面的0,然后加上"月"
|
||||
const month = dateParts[1].replace(/^0+/, '');
|
||||
return `${month}月`;
|
||||
}
|
||||
},
|
||||
data: xAxisData
|
||||
}
|
||||
],
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: this.unit,
|
||||
// nameLocation:'center',
|
||||
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' },
|
||||
min: 0,
|
||||
// max: function (value) { return Math.ceil(value.max * 1.1); }, // 增加一点余量
|
||||
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.15)',
|
||||
}
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '实际',
|
||||
type: 'line',
|
||||
// stack: 'Total', // 趋势图通常不需要堆叠
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
lineStyle: {
|
||||
color: 'rgba(255, 132, 0, 1)', // 加深颜色
|
||||
width: 2,
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(255, 132, 0, 1)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.3,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(255, 132, 0, .5)' },
|
||||
{ offset: 1, color: 'rgba(255, 132, 0, 0)' },
|
||||
]),
|
||||
},
|
||||
data: realData // 使用提取出的 "实际" 数据
|
||||
},
|
||||
{
|
||||
name: '预算',
|
||||
type: 'line',
|
||||
// stack: 'Total',
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
lineStyle: {
|
||||
color: 'rgba(98, 213, 180, 1)', // 加深颜色
|
||||
width: 2,
|
||||
// type: 'dashed' // 目标线使用虚线
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(98, 213, 180, 1)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.3,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(98, 213, 180, .5)' },
|
||||
{ offset: 1, color: 'rgba(98, 213, 180, 0)' },
|
||||
]),
|
||||
},
|
||||
data: targetData // 使用提取出的 "目标" 数据
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
// 5. 应用配置项更新图表
|
||||
this.myChart.setOption(option, true);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 组件销毁时清理
|
||||
window.removeEventListener('resize', () => {
|
||||
this.myChart?.resize();
|
||||
});
|
||||
this.myChart?.dispose();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
/* (你的样式代码保持不变) */
|
||||
.legend {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 0px;
|
||||
display: flex;
|
||||
/* 使用 flex 布局让两个图例项并排且对齐 */
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.legend-item-line {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
position: relative;
|
||||
padding-left: 24px;
|
||||
/* 为圆点和线条留出空间 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.line {
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
/* 线条从圆点右侧开始 */
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
/* 线条长度 */
|
||||
height: 2px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.target {
|
||||
background: rgba(98, 213, 180, 1);
|
||||
}
|
||||
|
||||
.real {
|
||||
background: rgba(255, 132, 0, 1);
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
background-color: rgba(255, 132, 0, 1);
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.legend-item-line:nth-child(1) {
|
||||
&::before {
|
||||
background-color: rgba(98, 213, 180, 1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div style="position: relative;">
|
||||
<div class="legend">
|
||||
<span class="legend-item-line">
|
||||
<span class="line target"></span>
|
||||
预算
|
||||
</span>
|
||||
<span class="legend-item-line">
|
||||
<span class="line real"></span>
|
||||
实际
|
||||
</span>
|
||||
</div>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="height: 219px; width: 100%;"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'Container',
|
||||
props: ["chartData",'dateData','unit'],
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null, // 存储 echarts 实例
|
||||
resizeHandler: null // 窗口resize事件处理器
|
||||
};
|
||||
},
|
||||
// 关键:监听 chartData 变化
|
||||
watch: {
|
||||
chartData: {
|
||||
handler(newData) {
|
||||
this.updateChart(newData);
|
||||
},
|
||||
immediate: true, // 组件初始化时立即执行一次
|
||||
deep: true, // 深度监听对象内部变化
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initChart();
|
||||
});
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize();
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
methods: {
|
||||
// 初始化图表实例
|
||||
initChart() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
this.myChart = echarts.init(chartDom);
|
||||
|
||||
// 初始化时调用一次更新
|
||||
this.updateChart(this.chartData);
|
||||
},
|
||||
|
||||
// 核心:根据数据更新图表
|
||||
updateChart(data) {
|
||||
if (!this.myChart) {
|
||||
// 如果实例还未初始化,则等待 initChart 完成后再更新
|
||||
setTimeout(() => this.updateChart(data), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 处理数据,如果 data 无效则清空图表
|
||||
if (!data || typeof data !== 'object' || (!data.real && !data.target)) {
|
||||
this.myChart.setOption({
|
||||
xAxis: { data: [] },
|
||||
series: [{ data: [] }, { data: [] }]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 提取 X 轴数据(从 real 或 target 中取键名)
|
||||
const xAxisData = data.real ? Object.keys(data.real) : Object.keys(data.target);
|
||||
console.log('xAxisData', xAxisData);
|
||||
|
||||
// 3. 提取 "实际" 和 "目标" 系列的数据
|
||||
const realData = data.real ? Object.values(data.real) : [];
|
||||
const targetData = data.target ? Object.values(data.target) : [];
|
||||
|
||||
// 4. 准备 echarts 的 option 配置
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 35,
|
||||
bottom: 20,
|
||||
right: 13,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
onZero: false,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
// width: 38,
|
||||
overflow: 'break',
|
||||
formatter: (value) => {
|
||||
const dateParts = value.split('-'); // ["2025", "07", "01"]
|
||||
if (dateParts.length < 2) return value;
|
||||
|
||||
// 去掉月份前面的0,然后加上"月"
|
||||
const month = dateParts[1].replace(/^0+/, '');
|
||||
return `${month}月`;
|
||||
}
|
||||
},
|
||||
data: xAxisData
|
||||
}
|
||||
],
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: this.unit,
|
||||
// nameLocation:'center',
|
||||
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' },
|
||||
min: 0,
|
||||
// max: function (value) { return Math.ceil(value.max * 1.1); }, // 增加一点余量
|
||||
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.15)',
|
||||
}
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '实际',
|
||||
type: 'line',
|
||||
// stack: 'Total', // 趋势图通常不需要堆叠
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
lineStyle: {
|
||||
color: 'rgba(255, 132, 0, 1)', // 加深颜色
|
||||
width: 2,
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(255, 132, 0, 1)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.3,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(255, 132, 0, .5)' },
|
||||
{ offset: 1, color: 'rgba(255, 132, 0, 0)' },
|
||||
]),
|
||||
},
|
||||
data: realData // 使用提取出的 "实际" 数据
|
||||
},
|
||||
{
|
||||
name: '预算',
|
||||
type: 'line',
|
||||
// stack: 'Total',
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
lineStyle: {
|
||||
color: 'rgba(98, 213, 180, 1)', // 加深颜色
|
||||
width: 2,
|
||||
// type: 'dashed' // 目标线使用虚线
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(98, 213, 180, 1)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.3,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(98, 213, 180, .5)' },
|
||||
{ offset: 1, color: 'rgba(98, 213, 180, 0)' },
|
||||
]),
|
||||
},
|
||||
data: targetData // 使用提取出的 "目标" 数据
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
// 5. 应用配置项更新图表
|
||||
this.myChart.setOption(option, true);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
this.resizeHandler = null;
|
||||
}
|
||||
// 销毁图表,避免内存泄漏
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
/* (你的样式代码保持不变) */
|
||||
.legend {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 0px;
|
||||
display: flex;
|
||||
/* 使用 flex 布局让两个图例项并排且对齐 */
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.legend-item-line {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
position: relative;
|
||||
padding-left: 24px;
|
||||
/* 为圆点和线条留出空间 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.line {
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
/* 线条从圆点右侧开始 */
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
/* 线条长度 */
|
||||
height: 2px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.target {
|
||||
background: rgba(98, 213, 180, 1);
|
||||
}
|
||||
|
||||
.real {
|
||||
background: rgba(255, 132, 0, 1);
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
background-color: rgba(255, 132, 0, 1);
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.legend-item-line:nth-child(1) {
|
||||
&::before {
|
||||
background-color: rgba(98, 213, 180, 1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user