This commit is contained in:
‘937886381’
2025-11-25 09:30:51 +08:00
parent 694beb5851
commit a907c7273e
25 changed files with 1203 additions and 1231 deletions

View File

@@ -60,3 +60,11 @@ export function getSalesRevenueData(data) {
});
}
export function getOperateCockpit(data) {
return request({
url: "/lb/operate-cockpit/get",
method: "post",
data: data,
});
}

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1763973887412" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5048" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M538.622846 950.291255c257.534448-171.658591 308.738643-462.119963-75.504648-453.027887l0 225.514173L121.893676 381.529483 463.118198 40.281425l0 220.701564c218.259956-5.67321 470.02705 96.270601 470.02705 280.69596C933.146271 813.119105 768.337691 874.762047 538.622846 950.291255" fill="#1296db" p-id="5049"></path></svg>

After

Width:  |  Height:  |  Size: 656 B

View File

@@ -10,10 +10,10 @@
</div>
</div>
</div>
<div class="lineBottom" style="height: 214px; width: 100%">
<!-- 传递 series 数据给子组件添加 key 确保数据更新时重新渲染 -->
<coreLineChart style="height: 214px; width: 680px" :chart-series="chartSeries"
:key="JSON.stringify(chartSeries)" />
<div class="lineBottom" style="height: 213px; width: 100%">
<!-- 传递动态生成的 series 数据 xAxis 数据给子组件 -->
<coreLineChart style="height: 213px; width: 680px" :chart-series="chartSeries" :x-axis-data="xAxisData"
:dateData="dateData" />
</div>
</div>
</template>
@@ -25,80 +25,97 @@ import * as echarts from 'echarts';
export default {
name: "Container",
components: { coreLineChart },
props: ["name", "size", "icon"],
props: {
line: { // 接收父组件传递的 cost 数据对象
type: Object,
default: () => ({})
},
dateData: {
type: Object,
default: () => ({})
}
},
data() {
return {
activeButton: undefined,
itemList: [
{ unit: "单价·元/m²", targetValue: 16, currentValue: 14.5, progress: 90 },
{ unit: "净价·元/m²", targetValue: 16, currentValue: 15.2, progress: 85 },
{ unit: "销量·万m²", targetValue: 20, currentValue: 16, progress: 80 },
{ unit: "双镀面板·万m²", targetValue: 15, currentValue: 13.8, progress: 92 },
],
// 定义要传递的 series 数据(对应图例的三个费用类型)
chartSeries: [
{
// 图表样式配置项,可以抽离出来方便管理
chartConfig: {
manageCost: {
name: '管理费用',
type: 'line',
stack: 'Total',
symbol: 'circle',
lineStyle: { color: 'rgba(11, 88, 255, .5)' },
itemStyle: {
color: 'rgba(11, 88, 255, .5)',
borderColor: 'rgba(11, 88, 255, 1)',
borderWidth: 1
},
areaStyle: {
opacity: 0.5,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(11, 88, 255, .4)' },
{ offset: 1, color: 'rgba(18, 255, 245, 0)' },
]),
},
data: [140, 232, 101, 264, 90, 340] // 与 xAxis 数据长度一致6个月份
lineColor: 'rgba(11, 88, 255, .5)',
itemColor: 'rgba(11, 88, 255, .5)',
borderColor: 'rgba(11, 88, 255, 1)',
areaGradient: [
{ offset: 0, color: 'rgba(11, 88, 255, .4)' },
{ offset: 1, color: 'rgba(18, 255, 245, 0)' },
]
},
{
saleCost: {
name: '销售费用',
type: 'line',
stack: 'Total',
symbol: 'circle',
lineStyle: { color: 'rgba(54, 181, 138, .5)' },
itemStyle: {
color: 'rgba(54, 181, 138, .5)',
borderColor: 'rgba(54, 181, 138, 1)',
borderWidth: 1
},
areaStyle: {
opacity: 0.5,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(54, 181, 138, .4)' },
{ offset: 1, color: 'rgba(18, 255, 245, 0)' },
]),
},
data: [120, 282, 111, 234, 220, 340]
lineColor: 'rgba(54, 181, 138, .5)',
itemColor: 'rgba(54, 181, 138, .5)',
borderColor: 'rgba(54, 181, 138, 1)',
areaGradient: [
{ offset: 0, color: 'rgba(54, 181, 138, .4)' },
{ offset: 1, color: 'rgba(18, 255, 245, 0)' },
]
},
{
financeCost: {
name: '财务费用',
lineColor: 'rgba(255, 132, 0, .5)',
itemColor: 'rgba(255, 132, 0, .5)',
borderColor: 'rgba(255, 132, 0, 1)',
areaGradient: [
{ offset: 0, color: 'rgba(255, 132, 0, .4)' },
{ offset: 1, color: 'rgba(18, 255, 245, 0)' },
]
}
}
};
},
computed: {
// 动态生成 X 轴数据
xAxisData() {
// 从 cost.line 中获取任意一个有数据的键的 keys 作为 X 轴
const lineData = this.line || {};
const firstKey = Object.keys(lineData)[0];
return firstKey ? Object.keys(lineData[firstKey]) : [];
},
// 动态生成 series 数据
chartSeries() {
const lineData = this.line || {};
const xAxisKeys = this.xAxisData;
// 如果没有 X 轴数据,则返回空数组
if (xAxisKeys.length === 0) {
return [];
}
// 遍历配置项,生成 series
return Object.keys(this.chartConfig).map(key => {
const config = this.chartConfig[key];
// 确保数据顺序和 X 轴一致
const dataValues = xAxisKeys.map(date => lineData[key] ? lineData[key][date] : 0);
return {
name: config.name,
type: 'line',
stack: 'Total',
symbol: 'circle',
lineStyle: { color: 'rgba(255, 132, 0, .5)' },
symbolSize: 6,
lineStyle: { color: config.lineColor },
itemStyle: {
color: 'rgba(255, 132, 0, .5)',
borderColor: 'rgba(255, 132, 0, 1)',
color: config.itemColor,
borderColor: config.borderColor,
borderWidth: 1
},
areaStyle: {
opacity: 0.5,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255, 132, 0, .4)' },
{ offset: 1, color: 'rgba(18, 255, 245, 0)' },
]),
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, config.areaGradient),
},
data: [90, 150, 180, 120, 250, 280]
}
]
};
data: dataValues
};
});
}
},
methods: {}
};

View File

@@ -40,6 +40,7 @@
</template>
<script>
import moment from 'moment'
export default {
name: 'Header',
props: {

View File

@@ -6,6 +6,8 @@
<span class="title-text">
{{ name }}
</span>
<svg-icon @click="handleShow" v-if="tableShow" class="title-icon" style="position: absolute;right: 20;" :icon-class="'orderReturn'" />
</div>
</div>
<div class="container-body">
@@ -21,12 +23,16 @@ export default {
name: 'Container',
components: {},
// eslint-disable-next-line vue/require-prop-types
props: ['name', 'size', 'icon', 'nameTwo'],
props: ['name', 'size', 'icon', 'nameTwo','tableShow'],
data() {
return {};
},
computed: {},
methods: {},
methods: {
handleShow() {
this.$emit('handleShow',false)
}
},
};
</script>
@@ -83,7 +89,7 @@ export default {
position: absolute;
top: 0;
left: 0;
width: 100%;
width: 50%;
height: 100%;
border: 1px solid;
border-image: linear-gradient(277deg, rgba(255, 255, 255, 0), rgba(92, 140, 255, 1)) 1 1;

View File

@@ -42,40 +42,48 @@
export default {
name: "Container",
components: {},
props: ["name", "size", "icon"],
props: ["purchase"],
data() {
return {
progress: 90,
itemList: [
{
unit: "本月增效额·万元",
targetValue: 16,
currentValue: 14.5,
progress: 90
},
{
unit: "累计增效额·万元",
targetValue: 16,
currentValue: 15.2,
progress: 85
},
// {
// unit: "销量·万㎡",
// targetValue: 20,
// currentValue: 16,
// progress: 80
// },
// {
// unit: "双镀面板·万㎡",
// targetValue: 15,
// currentValue: 13.8,
// progress: 92
// }
]
itemList: []
};
},
computed: {},
watch: {
purchase: {
handler(newVal) {
if (newVal) {
this.itemList = this.transformData(newVal);
}
},
immediate: true,
deep: true
}
},
methods: {
transformData(rawData) {
const dataMap = [
{
key: 'increase',
unit: '本月增效额·万元'
},
{
key: 'totalIncrease',
unit: '累计增效额·万元'
}
];
return dataMap.map(itemInfo => {
const rawItem = rawData[itemInfo.key] || {};
const progress = Math.max(0, Math.round((rawItem.rate || 0)));
return {
unit: itemInfo.unit,
targetValue: rawItem.target || 0,
currentValue: rawItem.real || 0,
progress: progress
};
});
},
getTargetColor(currentValue, targetValue) {
return currentValue >= targetValue
? "rgba(98, 213, 180, 1)"

View File

@@ -29,8 +29,8 @@
</div>
<div class="lineBottom" style="height: 219px; width: 100%">
<!-- 传递当前选中的 series 数据给子组件key 确保数据更新时重新渲染 -->
<coreLineChart style="height: 219px; width: 500px" :chart-series="currentSeries"
:key="activeButton + JSON.stringify(currentSeries)" />
<coreLineChart style="height: 219px; width: 500px" :chart-series="currentSeries" :x-axis-data="xAxisData"
:dateData="dateData" :key="activeButton + JSON.stringify(currentSeries)" />
</div>
</div>
</template>
@@ -42,204 +42,102 @@ import * as echarts from 'echarts';
export default {
name: "Container",
components: { coreLineChart },
props: ["name", "size", "icon"],
props: {
line: { // 接收父组件传递的 sale 数据对象
type: Object,
default: () => ({})
},
dateData: { // 接收父组件传递的 sale 数据对象
type: Object,
default: () => ({})
}
},
data() {
return {
activeButton: 0,
itemList: [
{ unit: "单价·元/m²", targetValue: 16, currentValue: 14.5, progress: 90 },
{ unit: "净价·元/m²", targetValue: 16, currentValue: 15.2, progress: 85 },
{ unit: "销量·万m²", targetValue: 20, currentValue: 16, progress: 80 },
{ unit: "双镀面板·万m²", targetValue: 15, currentValue: 13.8, progress: 92 },
],
// 4个按钮对应的 series 数据(目标+实际两条线)
seriesMap: [
// 0: 单价(元/m²
[
{
name: '目标',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(91, 230, 190, 1)', width: 2 },
itemStyle: {
color: 'rgba(91, 230, 190, 1)',
borderColor: 2
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(91, 230, 190, 0.4)' },
{ offset: 1, color: 'rgba(91, 230, 190, 0)' },
]),
},
data: [16, 16.2, 15.8, 16.1, 15.9, 16] // 6-11月目标数据
},
{
name: '实际',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(255, 132, 0, 1)', width: 2 },
itemStyle: {
color: 'rgba(255, 132, 0, 1)',
borderColor: 'rgba(255, 132, 0, 1)',
borderWidth: 2,
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255, 132, 0, 0.4)' },
{ offset: 1, color: 'rgba(255, 132, 0, 0)' },
]),
},
data: [14.5, 14.8, 15.2, 14.6, 15, 14.7] // 6-11月实际数据
}
],
// 1: 净价(元/m²
[
{
name: '目标',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(91, 230, 190, 1)', width: 2 },
itemStyle: {
color: 'rgba(91, 230, 190, 1)',
borderWidth: 2
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(91, 230, 190, 0.4)' },
{ offset: 1, color: 'rgba(91, 230, 190, 0)' },
]),
},
data: [16, 16.1, 15.9, 16.2, 16, 16.1]
},
{
name: '实际',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(255, 132, 0, 1)', width: 2 },
itemStyle: {
color: 'rgba(255, 132, 0, 1)',
//
borderWidth: 2
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255, 132, 0, 0.4)' },
{ offset: 1, color: 'rgba(255, 132, 0, 0)' },
]),
},
data: [15.2, 15.5, 15.3, 15.6, 15.4, 15.5]
}
],
// 2: 销量万m²
[
{
name: '目标',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(91, 230, 190, 1)', width: 2 },
itemStyle: {
color: 'rgba(91, 230, 190, 1)',
borderWidth: 2
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(91, 230, 190, 0.4)' },
{ offset: 1, color: 'rgba(91, 230, 190, 0)' },
]),
},
data: [20, 20.5, 19.8, 21, 20.2, 20.8]
},
{
name: '实际',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(255, 132, 0, 1)', width: 2 },
itemStyle: {
color: 'rgba(255, 132, 0, 1)',
borderWidth: 2
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255, 132, 0, 0.4)' },
{ offset: 1, color: 'rgba(255, 132, 0, 0)' },
]),
},
data: [16, 16.8, 17.2, 16.5, 17, 17.5]
}
],
// 3: 双镀产品万m²
[
{
name: '目标',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(91, 230, 190, 1)', width: 2 },
itemStyle: {
color: 'rgba(91, 230, 190, 1)',
borderWidth: 2
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(91, 230, 190, 0.4)' },
{ offset: 1, color: 'rgba(91, 230, 190, 0)' },
]),
},
data: [15, 15.2, 14.8, 15.5, 15.1, 15.3]
},
{
name: '实际',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(255, 132, 0, 1)', width: 2 },
itemStyle: {
color: 'rgba(255, 132, 0, 1)',
borderWidth: 2
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255, 132, 0, 0.4)' },
{ offset: 1, color: 'rgba(255, 132, 0, 0)' },
]),
},
data: [13.8, 14.2, 14, 14.5, 14.3, 14.6]
}
]
// 定义按钮与 line 数据中 key 的映射关系
buttonToDataKey: [
'单价',
'净价',
'销量',
'双镀面板' // 注意:数据中的 key 是“双镀面板”,按钮显示的是“双镀产品”
]
};
},
computed: {
// 动态生成 X 轴数据
xAxisData() {
const lineData = this.line || {};
// 获取当前激活按钮对应的数据
const currentDataKey = this.buttonToDataKey[this.activeButton];
const currentIndicatorData = lineData[currentDataKey];
// 使用 'target' 的键作为 x 轴,如果 'target' 不存在,则使用 'real' 的键
if (currentIndicatorData && currentIndicatorData.target) {
return Object.keys(currentIndicatorData.target);
} else if (currentIndicatorData && currentIndicatorData.real) {
return Object.keys(currentIndicatorData.real);
}
return [];
},
// 根据激活按钮动态返回对应 series 数据
currentSeries() {
return this.seriesMap[this.activeButton] || [];
const lineData = this.line || {};
const currentDataKey = this.buttonToDataKey[this.activeButton];
const chartData = lineData[currentDataKey];
if (!chartData) {
return [];
}
// 提取目标和实际数据的值,并确保顺序与 X 轴一致
const xAxisKeys = this.xAxisData;
const targetDataValues = xAxisKeys.map(date => chartData.target ? chartData.target[date] : 0);
const realDataValues = xAxisKeys.map(date => chartData.real ? chartData.real[date] : 0);
return [
{
name: '目标',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(91, 230, 190, 1)', width: 2 },
itemStyle: {
color: 'rgba(91, 230, 190, 1)',
borderColor: 2
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(91, 230, 190, 0.4)' },
{ offset: 1, color: 'rgba(91, 230, 190, 0)' },
]),
},
data: targetDataValues
},
{
name: '实际',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(255, 132, 0, 1)', width: 2 },
itemStyle: {
color: 'rgba(255, 132, 0, 1)',
borderColor: 'rgba(255, 132, 0, 1)',
borderWidth: 2,
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255, 132, 0, 0.4)' },
{ offset: 1, color: 'rgba(255, 132, 0, 0)' },
]),
},
data: realDataValues
}
];
}
}
};

View File

@@ -3,7 +3,7 @@
<Container name="采购重点指标" nameTwo="存货重点指标" icon="cockpitItemIcon" size="bottomBasic">
<!-- 1. 移除 .kpi-content 的固定高度改为自适应 -->
<div class="bottom-left-content" style="display: flex;gap: 9px;padding: 14px 16px;">
<coreBottomLeftItem>
<coreBottomLeftItem :purchase="purchase">
</coreBottomLeftItem>
<div class="content-right" style="background: #F9FCFF;padding: 15px 12px;">
<base-table style="height: 180px;width: 260px;" :page="1" :limit="10" :show-index="true" :beilv="1"
@@ -25,11 +25,11 @@ export default {
components: { Container, coreBottomLeftItem, baseTable },
// mixins: [resize],
props: {
leftEqInfoData: { // 接收父组件传递的设备数据数组
type: Array,
default: () => [] // 默认空数组,避免报错
purchase: { // 接收父组件传递的设备数据数组
type: Object,
default: () => {} // 默认空数组,避免报错
},
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
inventory: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
type: Object,
default: () => ({})
}
@@ -37,12 +37,12 @@ export default {
data() {
return {
maintenanceTasks: [
{ id: 1, eqName: '纯碱', taskName: '1313,252', },
{ id: 2, eqName: '硅砂', taskName: '14,252', },
{ id: 2, eqName: '白云石', taskName: '23,252', },
{ id: 2, eqName: '石灰石', taskName: '34,421', },
{ id: 2, eqName: '氧化铝', taskName: '1,251.34', },
{ id: 2, eqName: '氢氧化铝', taskName: '14,252', },
{ id: 1, name: '纯碱', number: '1313,252', },
{ id: 2, name: '硅砂', number: '14,252', },
{ id: 2, name: '白云石', number: '23,252', },
{ id: 2, name: '石灰石', number: '34,421', },
{ id: 2, name: '氧化铝', number: '1,251.34', },
{ id: 2, name: '氢氧化铝', number: '14,252', },
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
@@ -53,22 +53,26 @@ export default {
],
tableProps: [
// { prop: 'id', label: '序号', width: 50, align: 'center' },
{ prop: 'eqName', label: '物料', align: 'left' },
{ prop: 'taskName', label: '库存/吨', align: 'left' },
{ prop: 'name', label: '物料', align: 'left' },
{ prop: 'number', label: '库存/吨', align: 'left' },
]
}
},
watch: {
productionOverviewVo: {
handler(newValue, oldValue) {
this.updateChart()
inventory: {
handler(newInventoryData) {
// 当 inventory 数据变化时,执行转换函数
this.maintenanceTasks = this.transformInventoryData(newInventoryData);
},
deep: true // 若对象内属性变化需触发,需加 deep: true
immediate: true, // 组件初始化时立即执行一次
deep: true // 深度监听对象内部属性的变化
}
},
mounted() {
// 初始化图表(若需展示图表,需在模板中添加对应 DOM
// this.$nextTick(() => this.updateChart())
// 组件挂载时,如果有初始 inventory 数据,也执行一次转换
if (this.inventory) {
this.maintenanceTasks = this.transformInventoryData(this.inventory);
}
},
beforeDestroy() {
// 销毁图表,避免内存泄漏
@@ -78,115 +82,25 @@ export default {
}
},
methods: {
updateChart() {
// 注意:原代码中图表依赖 id 为 "productionStatusChart" 的 DOM需在模板中补充否则会报错
// 示例:在 Container 内添加 <div id="productionStatusChart" style="height: 200px;"></div>
if (!document.getElementById('productionStatusChart')) return
if (this.chart) this.chart.dispose()
this.chart = echarts.init(document.getElementById('productionStatusChart'))
const data = [
this.productionOverviewVo.input || 0,
this.productionOverviewVo.output || 0,
this.productionOverviewVo.ng || 0,
this.productionOverviewVo.lowValue || 0,
this.productionOverviewVo.scrap || 0,
this.productionOverviewVo.inProcess || 0,
this.productionOverviewVo.engineer || 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: ['投入', '产出', '待判', '低价值', '报废', '在制', '实验片'],
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),
scale: true,
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
}
]
/**
* 核心转换函数:将 inventory 对象转换为 maintenanceTasks 数组
* @param {Object} inventoryData - 格式如: { "纯碱": 0, "硅砂": 0, ... }
* @returns {Array} - 格式如: [ { id: 1, name: '纯碱', number: '0' }, ... ]
*/
transformInventoryData(inventoryData) {
// 检查输入是否为有效的非空对象
if (!inventoryData || typeof inventoryData !== 'object' || Object.keys(inventoryData).length === 0) {
return []; // 如果无效,返回空数组
}
this.chart.setOption(option)
// 使用 Object.entries() 和 map() 进行转换
return Object.entries(inventoryData).map(([name, value], index) => {
return {
id: index + 1, // id 从 1 开始自增
name: name, // 物料名称
number: String(value) // 将数值转换为字符串,以匹配 "number" 字段
};
});
}
}
}

View File

@@ -5,12 +5,12 @@
<div class="kpi-content" style="padding: 14px 16px; display: flex;flex-direction: column; width: 100%;">
<!-- 2. .top 保持 flex无需固定高度自动跟随子元素拉伸 -->
<div class="top" style="display: flex; width: 100%;">
<top-item />
<top-item :sale="sale" :dateData="dateData" />
</div>
<div class="bottom"
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
<!-- <top-item /> -->
<coreBottomBar />
<coreBottomBar :line="sale.line" :dateData="dateData" />
</div>
</div>
@@ -28,11 +28,11 @@ export default {
components: { Container, topItem, coreBottomBar },
// mixins: [resize],
props: {
leftEqInfoData: { // 接收父组件传递的设备数据数组
type: Array,
default: () => [] // 默认空数组,避免报错
sale: { // 接收父组件传递的设备数据数组
type: Object,
default: () => ({})
},
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
dateData: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
type: Object,
default: () => ({})
}
@@ -43,135 +43,12 @@ export default {
}
},
watch: {
productionOverviewVo: {
handler(newValue, oldValue) {
this.updateChart()
},
deep: true // 若对象内属性变化需触发,需加 deep: true
}
},
mounted() {
// 初始化图表(若需展示图表,需在模板中添加对应 DOM
// this.$nextTick(() => this.updateChart())
},
beforeDestroy() {
// 销毁图表,避免内存泄漏
if (this.chart) {
this.chart.dispose()
this.chart = null
}
},
methods: {
updateChart() {
// 注意:原代码中图表依赖 id 为 "productionStatusChart" 的 DOM需在模板中补充否则会报错
// 示例:在 Container 内添加 <div id="productionStatusChart" style="height: 200px;"></div>
if (!document.getElementById('productionStatusChart')) return
if (this.chart) this.chart.dispose()
this.chart = echarts.init(document.getElementById('productionStatusChart'))
const data = [
this.productionOverviewVo.input || 0,
this.productionOverviewVo.output || 0,
this.productionOverviewVo.ng || 0,
this.productionOverviewVo.lowValue || 0,
this.productionOverviewVo.scrap || 0,
this.productionOverviewVo.inProcess || 0,
this.productionOverviewVo.engineer || 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: ['投入', '产出', '待判', '低价值', '报废', '在制', '实验片'],
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),
scale: true,
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>

View File

@@ -12,9 +12,16 @@ export default {
props: {
chartSeries: {
type: Array,
required: true,
default: () => [] // 默认空数组,避免报错
}
},
xAxisData: {
type: Array,
default: () => [] // 默认空数组,避免报错
},
dateData: {
type: Object,
default: () => {} // 默认空数组,避免报错
},
},
data() {
return {
@@ -61,6 +68,7 @@ export default {
// 单独提取更新图表的方法,方便复用
updateChart() {
if (!this.myChart) return;
console.log('this.chartSeries', this.chartSeries,this.xAxisData);
const option = {
tooltip: {
@@ -85,10 +93,28 @@ export default {
color: 'rgba(0, 0, 0, 0.45)',
fontSize: 12,
interval: 0,
width: 38,
overflow: 'break'
// width: 38,
overflow: 'break',
formatter: (value) => {
const dateParts = value.split('-'); // ["2025", "07", "01"]
if (dateParts.length < 2) return value; // 处理异常格式
switch (this.dateData.mode) {
case 1: // 日模式,显示“月-日”
// 确保有日的部分
return dateParts.length >= 3
? `${dateParts[1]}${dateParts[2]}`
: `${dateParts[1]}`; // fallback
case 2: // 月模式,显示“月”
return `${dateParts[1]}`;
case 3: // 年模式,显示“年”
return `${dateParts[0]}`;
default: // 默认月模式
return `${dateParts[1]}`;
}
}
},
data: ['6月', '7月', '8月', '9月', '10月', '11月']
data: this.xAxisData
}
],
yAxis: {

View File

@@ -2,7 +2,7 @@
<div class="coreItem">
<!-- 动态生成每个 item -->
<div class="item" v-for="(item, index) in itemList" :key="index">
<div class="unit">{{ item.unit }}</div>
<div class="unit">{{ item.name }}</div>
<div class="item-content">
<!-- 左右内容容器 -->
<div class="content-wrapper">
@@ -24,7 +24,7 @@
<div class="right">
<!-- 环比额计算差值并动态绑定颜色 -->
<div class="number" :style="{ color: getColor(item.currentValue, item.targetValue) }">
{{ get环比额(item.currentValue, item.targetValue) }}
{{ item.hbe }}
</div>
<div class="title">环比额</div>
</div>
@@ -37,46 +37,75 @@
export default {
name: "Container",
components: {},
props: ["name", "size", "icon"],
props: {
cost: { // 接收父组件传递的 cost 数据对象
type: Object,
default: () => ({})
}
},
data() {
return {
itemList: [
{
unit: "管理费用·万元",
targetValue: 16, // 上月值
currentValue: 14.5, // 本月值(小于上月,应显示橙色)
},
{
unit: "销售费用·万元",
targetValue: 16,
currentValue: 17, // 大于上月,应显示绿色
},
{
unit: "财务费用·万元",
targetValue: 16,
currentValue: 16, // 等于上月,应显示绿色
},
]
itemList: [] // 初始化为空数组,等待数据加载
};
},
watch: {
// 监听 cost 数据变化,实时更新 itemList
cost: {
handler(newVal) {
if (newVal) {
this.itemList = this.transformData(newVal);
}
},
immediate: true, // 组件初始化时立即执行一次
deep: true // 深度监听对象内部属性变化
}
},
methods: {
/**
* 核心转换函数:将 cost 对象转换为 itemList 数组
* @param {Object} rawData - 原始的 cost 数据对象
* @returns {Array} - 转换后的 itemList 数组
*/
transformData(rawData) {
// 定义费用类型映射关系(键名、显示名称、单位)
const costMapping = [
{ key: 'manageCost', name: '管理费用·万元' },
{ key: 'saleCost', name: '销售费用·万元' },
{ key: 'financeCost', name: '财务费用·万元' }
];
// 遍历映射关系,转换数据
return costMapping.map(mappingItem => {
// 获取对应费用类型的数据,若不存在则使用默认值
const costData = rawData[mappingItem.key] || { last: 0, this: 0, hbe: 0 };
return {
name: mappingItem.name,
targetValue: costData.last, // 上月值
currentValue: costData.this // 本月值
};
});
},
// 颜色判断:本月 >= 上月 绿色,否则 橙色
getColor(current, target) {
return current >= target
? "rgba(98, 213, 180, 1)"
: "rgba(249, 164, 74, 1)";
? "rgba(98, 213, 180, 1)" // 绿色:增长或持平
: "rgba(249, 164, 74, 1)"; // 橙色:下降
},
// 计算环比额(本月 - 上月),保留一位小数
get环比额(current, target) {
const diff = current - target;
// 正数加"+"号,负数和零保持原样
return diff > 0 ? `${diff.toFixed(1)}` : diff.toFixed(1);
}
// getData(current, target) {
// const diff = current - target;
// // 正数加"+"号,负数和零保持原样
// return diff > 0 ? `+${diff.toFixed(1)}` : diff.toFixed(1);
// }
},
};
</script>
<style scoped lang="scss">
/* 样式保持不变 */
.coreItem {
display: flex;
gap: 8px;

View File

@@ -6,18 +6,18 @@
<!-- 根据activeTab状态切换显示采购/存货内容 -->
<template v-if="activeTab === 'purchase'">
<!-- 采购重点指标对应的内容 -->
<coreBottomLeftItem></coreBottomLeftItem>
<coreBottomLeftItem :dateData="dateData" :finance="finance"></coreBottomLeftItem>
<div class="bottom"
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
<coreBottomBar></coreBottomBar>
<coreBottomBar :dateData="dateData" :line="finance.line"></coreBottomBar>
</div>
</template>
<template v-else-if="activeTab === 'inventory'">
<!-- 存货重点指标对应的内容 -->
<costItem></costItem>
<costItem :cost="cost"></costItem>
<div class="bottom"
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
<CostsBottomBar>
<CostsBottomBar :line="cost.line" :dateData="dateData">
</CostsBottomBar>
</div>
</template>
@@ -41,14 +41,18 @@ export default {
name: 'ProductionStatus',
components: { Container, coreBottomLeftItem, coreBottomBar, costItem, CostsBottomBar },
props: {
leftEqInfoData: {
type: Array,
default: () => []
finance: {
type: Object,
default: () => {}
},
productionOverviewVo: {
cost: {
type: Object,
default: () => ({})
}
},
dateData: {
type: Object,
default: () => ({})
},
},
data() {
return {

View File

@@ -30,9 +30,9 @@
</div>
</div>
<div class="lineBottom" style="height: 210px; width: 100%">
<!-- 传递当前选中的 series 数据给子组件 -->
<coreLineChart style="height: 210px; width: 680px" :chart-series="currentSeries"
:key="activeButton + JSON.stringify(currentSeries)" />
<!-- 传递当前选中的 series 数据 xAxis 数据给子组件 -->
<coreLineChart style="height: 210px; width: 680px" :chart-series="currentSeries" :x-axis-data="xAxisData"
:dateData="dateData" />
</div>
</div>
</template>
@@ -44,205 +44,93 @@ import * as echarts from 'echarts';
export default {
name: "Container",
components: { coreLineChart },
props: ["name", "size", "icon"],
props: {
line: { // 接收父组件传递的 line 数据
type: Object,
default: () => ({})
},
dateData: { // 接收父组件传递的 line 数据
type: Object,
default: () => ({})
},
},
data() {
return {
activeButton: 0, // 初始激活第一个按钮索引0
itemList: [
{ unit: "单价·元/m²", targetValue: 16, currentValue: 14.5, progress: 90 },
{ unit: "净价·元/m²", targetValue: 16, currentValue: 15.2, progress: 85 },
{ unit: "销量·万m²", targetValue: 20, currentValue: 16, progress: 80 },
{ unit: "双镀面板·万m²", targetValue: 15, currentValue: 13.8, progress: 92 },
],
// 存储4个按钮对应的 series 数据(每个按钮对应「目标+实际」两条线)
seriesMap: [
// 0: 营业收入
[
{
name: '目标',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(91, 230, 190, 1)' },
itemStyle: {
color: 'rgba(91, 230, 190, 1)',
borderColor: 'rgba(91, 230, 190, 1)',
borderWidth: 2
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(91, 230, 190, 0.4)' },
{ offset: 1, color: 'rgba(91, 230, 190, 0)' },
]),
},
data: [500, 620, 580, 720, 650, 800] // 6-11月目标数据
},
{
name: '实际',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(255, 132, 0, 1)' },
itemStyle: {
color: 'rgba(255, 132, 0, 1)',
borderColor: 'rgba(255, 132, 0, 1)',
borderWidth: 2
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255, 132, 0, 0.4)' },
{ offset: 1, color: 'rgba(255, 132, 0, 0)' },
]),
},
data: [480, 590, 610, 680, 700, 750] // 6-11月实际数据
}
],
// 1: 经营性利润
[
{
name: '目标',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(91, 230, 190, 1)' },
itemStyle: {
color: 'rgba(91, 230, 190, 1)',
borderWidth: 2
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(91, 230, 190, 0.4)' },
{ offset: 1, color: 'rgba(91, 230, 190, 0)' },
]),
},
data: [150, 180, 160, 200, 190, 220]
},
{
name: '实际',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(255, 132, 0, 1)' },
itemStyle: {
color: 'rgba(255, 132, 0, 1)',
borderWidth: 2
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255, 132, 0, 0.4)' },
{ offset: 1, color: 'rgba(255, 132, 0, 0)' },
]),
},
data: [140, 170, 180, 190, 210, 200]
}
],
// 2: 利润总额
[
{
name: '目标',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(91, 230, 190, 1)' },
itemStyle: {
color: 'rgba(91, 230, 190, 1)',
borderWidth: 2
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(91, 230, 190, 0.4)' },
{ offset: 1, color: 'rgba(91, 230, 190, 0)' },
]),
},
data: [120, 150, 140, 170, 160, 190]
},
{
name: '实际',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(255, 132, 0, 1)' },
itemStyle: {
color: 'rgba(255, 132, 0, 1)',
borderWidth: 2
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255, 132, 0, 0.4)' },
{ offset: 1, color: 'rgba(255, 132, 0, 0)' },
]),
},
data: [110, 140, 150, 160, 180, 170]
}
],
// 3: 毛利率(百分比数据)
[
{
name: '目标',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(91, 230, 190, 1)' },
itemStyle: {
color: 'rgba(91, 230, 190, 1)',
borderWidth: 2
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(91, 230, 190, 0.4)' },
{ offset: 1, color: 'rgba(91, 230, 190, 0)' },
]),
},
data: [35, 36, 35.5, 37, 36.5, 38]
},
{
name: '实际',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(255, 132, 0, 1)' },
itemStyle: {
color: 'rgba(255, 132, 0, 1)',
borderWidth: 2
},
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255, 132, 0, 0.4)' },
{ offset: 1, color: 'rgba(255, 132, 0, 0)' },
]),
},
data: [34, 35.5, 36, 36.2, 37, 37.5]
}
]
// 定义按钮与 line 数据中 key 的映射关系
buttonToDataKey: [
'营业收入',
'经营收入', // 注意:数据中的 key 是“经营收入”,按钮显示的是“经营性利润”
'利润总额',
'毛利率'
]
};
},
computed: {
// 根据当前激活的按钮,返回对应的 series 数据
// 根据当前激活的按钮,动态生成对应的 series 数据
currentSeries() {
return this.seriesMap[this.activeButton] || [];
const dataKey = this.buttonToDataKey[this.activeButton];
const chartData = this.line[dataKey];
console.log('this.line[dataKey', this.buttonToDataKey[this.activeButton]);
if (!chartData) {
return [];
}
// 提取目标和实际数据的值
const targetDataValues = Object.values(chartData.target || {});
const realDataValues = Object.values(chartData.real || {});
console.log('realDataValues', realDataValues);
return [
{
name: '目标',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(91, 230, 190, 1)' },
itemStyle: { color: 'rgba(91, 230, 190, 1)', borderColor: 'rgba(91, 230, 190, 1)', borderWidth: 2 },
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(91, 230, 190, 0.4)' },
{ offset: 1, color: 'rgba(91, 230, 190, 0)' },
]),
},
data: targetDataValues
},
{
name: '实际',
type: 'line',
stack: 'Total',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: 'rgba(255, 132, 0, 1)' },
itemStyle: { color: 'rgba(255, 132, 0, 1)', borderColor: 'rgba(255, 132, 0, 1)', borderWidth: 2 },
areaStyle: {
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255, 132, 0, 0.4)' },
{ offset: 1, color: 'rgba(255, 132, 0, 0)' },
]),
},
data: realDataValues
}
];
},
// 提取 x 轴数据(日期)
xAxisData() {
const dataKey = this.buttonToDataKey[this.activeButton];
const chartData = this.line[dataKey];
// 使用 'target' 的键作为 x 轴,如果 'target' 不存在,则使用 'real' 的键
if (chartData && chartData.target) {
return Object.keys(chartData.target);
} else if (chartData && chartData.real) {
return Object.keys(chartData.real);
}
return [];
}
},
methods: {}

View File

@@ -5,11 +5,11 @@
<div class="kpi-content" style="padding: 14px 16px; display: flex;flex-direction: column; width: 100%;">
<!-- 2. .top 保持 flex无需固定高度自动跟随子元素拉伸 -->
<div class="top" style="display: flex; width: 100%;">
<top-item />
<top-item :rawItemList='productData' :dateData="dateData" />
</div>
<div class="bottom" style="display: flex;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
<!-- <top-item /> -->
<coreBottomBar />
<coreBottomBar :lineData="productData.line" :dateData="dateData" />
</div>
</div>
@@ -27,14 +27,14 @@ export default {
components: { Container, topItem, coreBottomBar },
// mixins: [resize],
props: {
leftEqInfoData: { // 接收父组件传递的设备数据数组
type: Array,
default: () => [] // 默认空数组,避免报错
},
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
productData: {
type: Object,
default: () => ({})
}
default: () => {} // 默认空数组,避免报错
},
dateData: {
type: Object,
default: () => { } // 默认空数组,避免报错
},
},
data() {
return {
@@ -42,12 +42,6 @@ export default {
}
},
watch: {
productionOverviewVo: {
handler(newValue, oldValue) {
// this.updateChart()
},
deep: true // 若对象内属性变化需触发,需加 deep: true
}
},
mounted() {
// 初始化图表(若需展示图表,需在模板中添加对应 DOM

View File

@@ -1,7 +1,6 @@
<template>
<div style="flex: 1">
<Container name="重点工作/三大攻坚战" icon="cockpitItemIcon" size="bottomBasic" topSize="basic">
<!-- 1. 移除 .kpi-content 的固定高度改为自适应 -->
<div class="kpi-content"
style="padding: 14px 16px; display: flex;flex-direction: column; width: 100%;height: 280px;">
<div class="bottom"
@@ -16,79 +15,78 @@
<span class="legend-text">未完成</span>
</div>
</div>
<base-table style="height: 180px;" :page="1" :limit="10" :show-index="true" :beilv="1" :tableConfig="tableProps"
:table-data="maintenanceTasks" />
<base-table style="height: 180px;" :page="1" :limit="10" :show-index="true" :beilv="1"
:tableConfig="tableProps" :table-data="tableData" />
</div>
</div>
</Container>
</div>
</template>
<script>
import Container from './container.vue'
// import * as echarts from 'echarts'
import topItem from './top-product-item.vue'
import coreBottomBar from './productBottomBar.vue'
import baseTable from './baseTable.vue'
import finishDiv from './finishDiv.vue'
export default {
name: 'ProductionStatus',
components: { Container, topItem, coreBottomBar, baseTable },
// mixins: [resize],
components: { Container, baseTable },
props: {
leftEqInfoData: { // 接收父组件传递的设备数据数组
type: Array,
default: () => [] // 默认空数组,避免报错
},
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
importantWork: {
type: Object,
default: () => ({})
}
},
data() {
return {
maintenanceTasks: [
{
id: 1, eqName: '应收账款(亿元)', taskName: '10', monthlyActual: '12.45', accumulated: '12.45', status: 'done' }, // 已完成-绿色
{ id: 2, eqName: '存货/亿元', taskName: '0.57', monthlyActual: '0.72', accumulated: '0.72', status: 'pending' }, // 未完成-橙色
{ id: 3, eqName: '三年以上应收款/亿元', taskName: '0.57', monthlyActual: '0.72', accumulated: '0.72', status: 'done' },
{ id: 4, eqName: '非经营性资产处置到账金额/万元', taskName: '11000', monthlyActual: '0', accumulated: '0', status: 'pending' },
{ id: 1, eqName: '研发经费入强度/%', taskName: '3.07', monthlyActual: '2.37', accumulated: '3.14', status: 'done' }, // 已完成-绿色
{ id: 4, eqName: '研发经费投入/万元', taskName: '19500', monthlyActual: '1084', accumulated: '2797', status: 'pending' },
{ id: 4, eqName: '经营性现金流/万元', taskName: '2898', monthlyActual: '13472', accumulated: '-30490', status: 'pending' },
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
],
tableData: [],
tableProps: [
// { prop: 'id', label: '序号', width: 50, align: 'center' },
{ prop: 'eqName', label: '攻坚标', align: 'center' },
{ prop: 'taskName', label: '攻坚目标', align: 'center' },
{ prop: 'taskName', label: '当月实际', align: 'center' },
{ prop: 'taskName', label: '累计', align: 'center', subcomponent: finishDiv },
{ prop: 'name', label: '攻坚指标', align: 'center' },
{ prop: 'target', label: '攻坚标', align: 'center' },
{ prop: 'monthlyActual', label: '当月实际', align: 'center' },
{ prop: 'accumulated', label: '累计', align: 'center', subcomponent: finishDiv },
]
}
},
watch: {
productionOverviewVo: {
handler(newValue, oldValue) {
this.updateChart()
importantWork: {
handler(newVal) {
this.tableData = this.transformData(newVal);
},
deep: true // 若对象内属性变化需触发,需加 deep: true
immediate: true,
deep: true
}
},
mounted() {
// 初始化图表(若需展示图表,需在模板中添加对应 DOM
// this.$nextTick(() => this.updateChart())
},
beforeDestroy() {
// 销毁图表,避免内存泄漏
},
methods: {
transformData(rawData) {
if (!rawData || typeof rawData !== 'object') return [];
const mapping = [
{ key: 'jyxxjl', name: '经营现金流', unit: '万元' },
{ key: 'yszk', name: '应收账款', unit: '万元' },
{ key: 'ch', name: '存货', unit: '万元' },
{ key: 'yysr', name: '营业收入', unit: '万元' },
{ key: 'snysysk', name: '三年以上应收款', unit: '万元' },
{ key: 'dzje', name: '非经营性资产处置到账金额', unit: '万元' },
{ key: 'yfjftr', name: '研发经费投入', unit: '万元' },
{ key: 'yfjftrqd', name: '研发经费投入强度', unit: '%' }
];
return mapping.map((item, index) => {
const data = rawData[item.key] || { monValue: 0, real: 0, target: 0 };
const accumulated = data.real || 0;
const target = data.target || 0;
return {
id: index + 1,
name: item.name + '/' + item.unit,
target: target,
monthlyActual: data.monValue,
accumulated: accumulated,
status: accumulated > 0 && target > 0 && accumulated / target >= 1 ? 'done' : 'pending'
};
});
}
}
}
</script>
@@ -98,41 +96,31 @@ export default {
display: flex;
align-items: center;
gap: 10px;
/* 两个图例项之间的间距 */
}
/* 单个图例项 */
.legend-item {
display: flex;
align-items: center;
}
/* 图例小方块 */
.legend-dot {
display: inline-block;
width: 12px;
height: 12px;
margin-right: 5px;
border-radius: 2px;
/* 可选:轻微圆角 */
}
/* 已完成(绿色) */
.legend-dot.done {
background-color: #4CAF50;
/* 绿色,可根据需求调整色值 */
}
/* 未完成(橙色) */
.legend-dot.pending {
background-color: #FF9800;
/* 橙色,可根据需求调整色值 */
}
/* 图例文字 */
.legend-text {
font-size: 14px;
color: #333;
}
</style>

View File

@@ -71,7 +71,7 @@
<!-- 动态渲染城市进度循环 item.cities -->
<div class="right-city" v-for="(city, cityIdx) in item.cities" :key="cityIdx"
:style="{ marginTop: cityIdx > 0 ? '2px' : '0' }">
:style="{ marginTop: cityIdx > 0 ? '2px' : '0' }" @click="getTableData(city.num)" style="cursor: pointer;">
<div class="city">{{ city.name }}</div> <!-- 动态城市名 -->
<div class="city-progress-group">
<div class="city-progress-container">
@@ -102,72 +102,145 @@
export default {
name: "Container",
components: {},
props: ["name", "size", "icon"],
props: ["orderOutput"],
data() {
return {
progress: 90, // 进度值基础参数
itemList: [
// {
// unit: "总进度",
// targetValue: 16,
// currentValue: 14.5,
// progress: 90,
// cities: [] // 总进度无需城市数据,留空
// },
// {
// unit: "一组",
// targetValue: 16,
// currentValue: 17,
// progress: 106,
// cities: [
// { name: "桐城", completed: 12, total: 13, progress: 92 },
// { name: "自贡", completed: 15, total: 16, progress: 93 } // 新增城市示例
// ]
// },
// {
// unit: "二组",
// targetValue: 16,
// currentValue: 16,
// progress: 100,
// cities: [
// { name: "蚌埠", completed: 10, total: 12, progress: 83 },
// { name: "合肥", completed: 8, total: 10, progress: 80 }
// ]
// },
// // 其他组同理,按需添加 cities 数据
// {
// unit: "三组",
// targetValue: 16,
// currentValue: 15.2,
// progress: 85,
// cities: [{ name: "宜兴", completed: 9, total: 11, progress: 81 }]
// },
// {
// unit: "四组",
// targetValue: 16,
// currentValue: 18,
// progress: 112,
// cities: [
// { name: "漳州", completed: 14, total: 15, progress: 93 },
// { name: "洛阳", completed: 12, total: 14, progress: 85 }
// ]
// },
// {
// unit: "五组",
// targetValue: 16,
// currentValue: 14,
// progress: 80,
// cities: [{ name: "桐城", completed: 7, total: 9, progress: 77 }]
// }
]
};
},
watch: {
orderOutput: {
handler(newValue, oldValue) {
this.getItemData(newValue)
},
deep: true // 若对象内属性变化需触发,需加 deep: true
}
},
methods: {
getItemData(data) {
this.itemList = [
{
unit: "总进度",
targetValue: 16,
currentValue: 14.5,
progress: 90,
targetValue: data.totalProgress.target,
currentValue: data.totalProgress.real,
progress: data.totalProgress.rate,
cities: [] // 总进度无需城市数据,留空
},
{
unit: "一组",
targetValue: 16,
currentValue: 17,
progress: 106,
targetValue: data.group1.target,
currentValue: data.group1.real,
progress: data.group1.rate,
cities: [
{ name: "桐城", completed: 12, total: 13, progress: 92 },
{ name: "自贡", completed: 15, total: 16, progress: 93 } // 新增城市示例
{ name: "桐城", completed: data[2].real, total: data[2].target, progress: data[2].rate,num:2 },
{ name: "自贡", completed: data[3].real, total: data[3].target, progress: data[3].rate, num: 3 } // 新增城市示例
]
},
{
unit: "二组",
targetValue: 16,
currentValue: 16,
progress: 100,
targetValue: data.group2.target,
currentValue: data.group2.real,
progress: data.group2.rate,
cities: [
{ name: "蚌埠", completed: 10, total: 12, progress: 83 },
{ name: "合肥", completed: 8, total: 10, progress: 80 }
{ name: "蚌埠", completed: data[4].real, total: data[4].target, progress: data[4].rate, num: 4 },
{ name: "合肥", completed: data[5].real, total: data[5].target, progress: data[5].rate, num: 5 }
]
},
// 其他组同理,按需添加 cities 数据
{
unit: "三组",
targetValue: 16,
currentValue: 15.2,
progress: 85,
cities: [{ name: "宜兴", completed: 9, total: 11, progress: 81 }]
targetValue: data.group3.target,
currentValue: data.group3.real,
progress: data.group3.rate,
cities: [{ name: "江苏凯盛", completed: data[6].real, total: data[6].target, progress: data[6].rate, num: 6 },
{ name: "宜兴", completed: data[7].real, total: data[7].target, progress: data[7].rate, num: 7 }
]
},
{
unit: "四组",
targetValue: 16,
currentValue: 18,
progress: 112,
targetValue: data.group4.target,
currentValue: data.group4.real,
progress: data.group4.rate,
cities: [
{ name: "漳州", completed: 14, total: 15, progress: 93 },
{ name: "洛阳", completed: 12, total: 14, progress: 85 }
{ name: "漳州", completed: data[8].real, total: data[8].target, progress: data[8].rate, num: 8 },
{ name: "洛阳", completed: data[9].real, total: data[9].target, progress: data[9].rate, num: 9 }
]
},
{
unit: "五组",
targetValue: 16,
currentValue: 14,
progress: 80,
cities: [{ name: "桐城", completed: 7, total: 9, progress: 77 }]
targetValue: data.group5.target,
currentValue: data.group5.real,
progress: data.group5.rate,
cities: [{ name: "秦皇岛", completed: data[10].real, total: data[10].target, progress: data[10].rate, num: 10 },
// { name: "秦皇岛", completed: 7, total: 9, progress: 77 }
]
}
]
};
},
methods: {
},
// 颜色判断核心方法:实际值≥目标值返回绿色,否则返回橙色
getColor(currentValue, targetValue) {
return currentValue >= targetValue
? "rgba(98, 213, 180, 1)"
: "rgba(249, 164, 74, 1)";
},
getTableData(data) {
console.log(data, 'data');
this.$emit('handleShowTable',data)
}
}
};
@@ -466,15 +539,16 @@ export default {
}
/* 右上角折现边框(主边框) */
.groupData::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
clip-path: polygon(0 0, calc(100% - 20px) 0, 100% 20px, 100% 100%, 0 100%);
}
// .groupData::before {
// content: "";
// position: absolute;
// top: 0;
// left: 0;
// width: 100%;
// height: 100%;
// background-color: #000000;
// clip-path: polygon(0 0, calc(100% - 20px) 0, 100% 20px, 100% 100%, 0 100%);
// }
/* 右上角折现细节 */
.groupData::after {

View File

@@ -0,0 +1,38 @@
<template>
<!-- 对象语法根据status的值动态添加类名 -->
<div class="accumulated-value" :class="{ pending: injectData.status === 1, done: injectData.status === 2 }">
<!-- 同样可添加状态文本可选 -->
{{ injectData.status === 2 ? '已完成' : injectData.status === 1 ? '生产中' : '' }}
</div>
</template>
<script>
export default {
name: 'FinishDiv',
props: {
injectData: {
type: Object,
default: () => ({})
}
}
}
</script>
<!-- style部分与方式1完全一致 -->
<style scoped>
.accumulated-value {
font-size: 14px;
padding: 2px 4px;
border-radius: 4px;
}
.accumulated-value.done {
color: #4CAF50;
/* background-color: rgba(76, 175, 80, 0.1); */
}
.accumulated-value.pending {
color: #FF9800;
/* background-color: rgba(255, 152, 0, 0.1); */
}
</style>

View File

@@ -1,9 +1,12 @@
<template>
<div style="flex: 1">
<bottomMiddleContainer name="订单产量跟踪·万m²" icon="cockpitItemIcon" size="bottomBasic">
<bottomMiddleContainer name="订单产量跟踪·万m²" icon="cockpitItemIcon" size="bottomBasic" :tableShow="tableShow"
@handleShow="showTable">
<!-- 1. 移除 .kpi-content 的固定高度改为自适应 -->
<div style="display: flex;gap: 9px;padding: 14px 16px;">
<orderItem />
<orderItem v-show="!tableShow" :orderOutput="orderOutput" @handleShowTable="getTable" />
<base-table v-show="tableShow" style="height: 252px;width: 100%;" :page="1" :limit="10" :show-index="true"
:beilv="1" :tableConfig="tableProps" :table-data="tableData" />
</div>
</bottomMiddleContainer>
</div>
@@ -11,52 +14,56 @@
<script>
import bottomMiddleContainer from './bottomMiddleContainer.vue'
// import * as echarts from 'echarts'
import topItem from './top-product-item.vue'
import coreBottomBar from './productBottomBar.vue'
import orderItem from './order-bottom-leftItem.vue'
import baseTable from './baseTable.vue'
import orderColor from './orderColor.vue'
import proColor from './proColor.vue'
import moment from 'moment'
export default {
name: 'ProductionStatus',
components: { bottomMiddleContainer, topItem, coreBottomBar, orderItem },
components: { bottomMiddleContainer, orderItem, baseTable },
// mixins: [resize],
props: {
leftEqInfoData: { // 接收父组件传递的设备数据数组
type: Array,
default: () => [] // 默认空数组,避免报错
},
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
orderOutput: { // 接收父组件传递的设备数据数组
type: Object,
default: () => ({})
}
},
baseOrder: { // 接收父组件传递的设备数据数组
type: Array,
default: () => []
},
},
data() {
return {
maintenanceTasks: [
{ id: 1, eqName: '连续化', taskName: '例行维护', },
{ id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
{ id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
],
tableData: [],
tableShow:false,
tableProps: [
// { prop: 'id', label: '序号', width: 50, align: 'center' },
{ prop: 'eqName', label: '攻坚指标', align: 'left' },
{ prop: 'taskName', label: '攻坚目标', align: 'left' },
{ prop: 'taskName', label: '当月实际', align: 'left' },
{ prop: 'taskName', label: '累计', align: 'left' },
{ prop: 'code', label: '订单编号', align: 'center' },
{ prop: 'customerName', label: '客户名称', align: 'center' },
{
prop: 'deliveryTime', label: '交货时间', align: 'center',
filter: (val) => moment(val).format('yyyy-MM-DD HH:mm:ss'),
},
{ prop: 'spec', label: '规格(mm)', align: 'center', },
{ prop: 'num', label: '生产量(平方米)', align: 'center', },
{ prop: 'progress', label: '生产进度', align: 'center', subcomponent: proColor },
{ prop: 'status', label: '订单状态', align: 'center', subcomponent: orderColor },
]
}
},
watch: {
productionOverviewVo: {
baseOrder: {
handler(newValue, oldValue) {
this.updateChart()
this.tableData = newValue
},
deep: true // 若对象内属性变化需触发,需加 deep: true
}
},
// orderOutput: {
// handler(newValue, oldValue) {
// this.tableData = newValue
// },
// deep: true // 若对象内属性变化需触发,需加 deep: true
// }
},
mounted() {
// 初始化图表(若需展示图表,需在模板中添加对应 DOM
@@ -66,6 +73,16 @@ export default {
// 销毁图表,避免内存泄漏
},
methods: {
showTable(flag) {
this.tableShow = flag
console.log('this.tableShow', this.tableShow);
},
getTable(num) {
this.tableShow = true
console.log('num', num);
this.$emit('getData', num)
}
}
}
</script>

View File

@@ -0,0 +1,41 @@
<template>
<!-- 对象语法根据status的值动态添加类名 -->
<div class="accumulated-value" :class="{
pending: injectData.progress < 100, // 进度小于100%时,添加 pending 类
done: injectData.progress >= 100
}">
<!-- 同样可添加状态文本可选 -->
{{ injectData.progress }}
</div>
</template>
<script>
export default {
name: 'FinishDiv',
props: {
injectData: {
type: Object,
default: () => ({})
}
}
}
</script>
<!-- style部分与方式1完全一致 -->
<style scoped>
.accumulated-value {
font-size: 14px;
padding: 2px 4px;
border-radius: 4px;
}
.accumulated-value.done {
color: #4CAF50;
/* background-color: rgba(76, 175, 80, 0.1); */
}
.accumulated-value.pending {
color: #FF9800;
/* background-color: rgba(255, 152, 0, 0.1); */
}
</style>

View File

@@ -19,30 +19,74 @@ import * as echarts from 'echarts';
export default {
name: 'Container',
props: ["chartData",'dateData'],
components: {},
data() {
return {};
return {
myChart: null, // 存储 echarts 实例
};
},
// 关键:监听 chartData 变化
watch: {
chartData: {
handler(newData) {
this.updateChart(newData);
},
immediate: true, // 组件初始化时立即执行一次
deep: true, // 深度监听对象内部变化
}
},
computed: {},
mounted() {
this.$nextTick(() => {
this.initData();
this.initChart();
});
},
methods: {
initData() {
// 优先使用 ref 获取 DOM避免 id 冲突
// 初始化图表实例
initChart() {
const chartDom = this.$refs.cockpitEffChip;
if (!chartDom) {
console.error('图表容器未找到!');
return;
}
const myChart = echarts.init(chartDom);
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 = {
// color: ['#80FFA5', '#00DDFF', '#37A2FF', '#FF0087', '#FFBF00'],
// title: {
// text: 'Gradient Stacked Area Chart'
// },
tooltip: {
trigger: 'axis',
axisPointer: {
@@ -55,60 +99,56 @@ export default {
grid: {
top: 20,
bottom: 20,
// top: 10,
// bottom: 20,
right: 25,
},
// legend: {
// data: ['Line 1', 'Line 2', 'Line 3', 'Line 4', 'Line 5']
// },
// toolbox: {
// feature: {
// saveAsImage: {}
// }
// },
xAxis: [
{
type: 'category',
boundaryGap: false,
axisTick: {
show: false
},
axisTick: { show: false },
axisLine: {
show: true,
onZero: false,
lineStyle: {
color: 'rgba(0, 0, 0, 0.15)'
}
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; // 处理异常格式
switch (this.dateData.mode) {
case 1: // 日模式,显示“月-日”
// 确保有日的部分
return dateParts.length >= 3
? `${dateParts[1]}${dateParts[2]}`
: `${dateParts[1]}`; // fallback
case 2: // 月模式,显示“月”
return `${dateParts[1]}`;
case 3: // 年模式,显示“年”
return `${dateParts[0]}`;
default: // 默认月模式
return `${dateParts[1]}`;
}
}
},
data: ['6月', '7月', '8月', '9月', '10月', '11月']
data: xAxisData // 使用提取出的 X 轴数据
}
],
yAxis: {
type: 'value',
// name: '单位/片',
nameTextStyle: {
color: 'rgba(0, 0, 0, 0.45)',
fontSize: 14,
align: 'left'
},
min: function (value) {
return 0
},
max: function (value) {
return Math.ceil(value.max)
},
min: 0,
// max: function (value) { return Math.ceil(value.max * 1.1); }, // 增加一点余量
scale: true,
axisTick: {
show: false
},
axisTick: { show: false },
axisLabel: {
color: 'rgba(0, 0, 0, 0.45)',
fontSize: 12
@@ -116,101 +156,88 @@ export default {
splitLine: {
lineStyle: {
color: 'rgba(0, 0, 0, 0.15)',
// type: 'dashed'
}
},
axisLine: {
show: true,
lineStyle: {
color: 'rgba(0, 0, 0, 0.15)'
}
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
}
},
series: [
{
name: '实际',
type: 'line',
stack: 'Total',
symbol: 'circle', // 点的形状circle为圆形
// stack: 'Total', // 趋势图通常不需要堆叠
symbol: 'circle',
symbolSize: 8,
lineStyle: {
color: 'rgba(255, 132, 0, .5)',
color: 'rgba(255, 132, 0, 1)', // 加深颜色
width: 2,
},
itemStyle: {
color: 'rgba(255, 132, 0, .5)',
borderColor: 'rgba(255, 132, 0, .5)', // 数据点边框色(白色)
borderWidth: 2, // 数据点边框宽度
color: 'rgba(255, 132, 0, 1)',
borderColor: '#fff',
borderWidth: 2,
},
areaStyle: {
opacity: 0.5,
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(255, 132, 0, .4)',
},
{
offset: 1,
color: 'rgba(18, 255, 245, 0)',
},
{ offset: 0, color: 'rgba(255, 132, 0, .5)' },
{ offset: 1, color: 'rgba(255, 132, 0, 0)' },
]),
},
// emphasis: { focus: 'series' },
data: [140, 232, 101, 264, 90, 340, 250]
data: realData // 使用提取出的 "实际" 数据
},
{
name: '目标',
type: 'line',
stack: 'Total',
symbol: 'circle', // 点的形状circle为圆形
// stack: 'Total',
symbol: 'circle',
symbolSize: 8,
lineStyle: {
color: 'rgba(98, 213, 180, .5)',
color: 'rgba(98, 213, 180, 1)', // 加深颜色
width: 2,
type: 'dashed' // 目标线使用虚线
},
itemStyle: {
color: 'rgba(98, 213, 180, .5)',
borderColor: 'rgba(98, 213, 180, .5)', // 数据点边框色(白色)
borderWidth: 2, // 数据点边框宽度
color: 'rgba(98, 213, 180, 1)',
borderColor: '#fff',
borderWidth: 2,
},
areaStyle: {
opacity: 0.5,
opacity: 0.3,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(98, 213, 180,.4)',
},
{
offset: 1,
color: 'rgba(18, 255, 245, 0)',
},
{ offset: 0, color: 'rgba(98, 213, 180, .5)' },
{ offset: 1, color: 'rgba(98, 213, 180, 0)' },
]),
},
// emphasis: { focus: 'series' },
data: [120, 282, 111, 234, 220, 340, 310]
data: targetData // 使用提取出的 "目标" 数据
},
]
};
option && myChart.setOption(option);
// 监听窗口缩放
window.addEventListener('resize', () => {
myChart.resize();
});
// 组件销毁时清理
this.$once('hook:destroyed', () => {
window.removeEventListener('resize', () => {
myChart.resize();
});
myChart.dispose();
});
// 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: 10px;
top: -5px;
display: flex;
/* 使用 flex 布局让两个图例项并排且对齐 */
gap: 20px;
}
.legend-item-line {
@@ -220,25 +247,27 @@ export default {
color: rgba(0, 0, 0, 0.8);
text-align: left;
font-style: normal;
margin-right: 20px;
/* 增加两个图例项之间的间距 */
position: relative;
padding-left: 8px;
/* 给文字左侧增加内边距 */
padding-left: 20px;
/* 为圆点和线条留出空间 */
display: flex;
align-items: center;
.line {
position: absolute;
left: 5px;
/* 调整线条位置 */
top: 10px;
left: 6px;
/* 线条从圆点右侧开始 */
top: 50%;
transform: translateY(-50%);
display: inline-block;
width: 12px;
width: 16px;
/* 线条长度 */
height: 2px;
margin-right: 4px;
}
.target {
background: rgba(91, 230, 190, 1);
background: rgba(98, 213, 180, 1);
}
.real {
@@ -251,23 +280,18 @@ export default {
width: 6px;
height: 6px;
border-radius: 50%;
margin-right: 10px;
/* 关键:增加图例圆点和文字之间的间距 */
margin-bottom: 2px;
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 {
content: "";
display: inline-block;
width: 6px;
height: 6px;
margin-right: 10px;
/* 关键:增加图例圆点和文字之间的间距 */
margin-bottom: 2px;
background-color: rgba(91, 230, 190, 1);
background-color: rgba(98, 213, 180, 1);
}
}
</style>

View File

@@ -1,107 +1,81 @@
<template>
<div class="coreBar" style="width: 100%;">
<!-- 循环渲染itemdata中配置的每一项对应一个卡片 -->
<div class="barTop">
<div class="title">生产指标趋势</div>
<div class="button-group">
<!-- 按钮1单价 -->
<div class="item-button" :class="{ active: activeButton === 0 }" @click="activeButton = 0">
总成本
</div>
<!-- 分割线0单价右侧 -->
<div class="item-button" :class="{ active: activeButton === 0 }" @click="activeButton = 0">总成本</div>
<div class="button-line lineOne" v-if="activeButton !== 0 && activeButton !== 1"></div>
<!-- 按钮2净价 -->
<div class="item-button" :class="{ active: activeButton === 1 }" @click="activeButton = 1">
原片成本
</div>
<!-- 分割线1净价右侧 -->
<div class="item-button" :class="{ active: activeButton === 1 }" @click="activeButton = 1">原片成本</div>
<div class="button-line lineTwo" v-if="activeButton !== 1 && activeButton !== 2"></div>
<!-- 按钮3销量 -->
<div class="item-button" :class="{ active: activeButton === 2 }" @click="activeButton = 2">
加工成本
</div>
<!-- 分割线2销量右侧 -->
<div class="item-button" :class="{ active: activeButton === 2 }" @click="activeButton = 2">加工成本</div>
<div class="button-line lineThree" v-if="activeButton !== 2 && activeButton !== 3"></div>
<!-- 按钮4双镀产品 -->
<div class="item-button" style="width: 75px;" :class="{ active: activeButton === 3 }" @click="activeButton = 3">
原片成品率
</div>
原片成品率</div>
<div class="button-line lineFour" v-if="activeButton !== 3 && activeButton !== 4"></div>
<!-- 按钮5投入产出率 -->
<div class="item-button" style="width: 75px;" :class="{ active: activeButton === 4 }" @click="activeButton = 4">
投入产出率
</div>
投入产出率</div>
</div>
</div>
<div class="lineBottom" style="height: 219px; width: 100%">
<coreLineChart style="height: 219px; width: 500px" />
<div class="lineBottom" style="height: 219px; width: 100%" v-if="isLineDataReady">
<!-- 核心改动动态传递数据给子组件 -->
<coreLineChart style="height: 219px; width: 100%" :chart-data="selectedChartData" :dateData="dateData" />
</div>
</div>
</template>
<script>
import coreLineChart from './productBar.vue';
export default {
name: "Container",
components: { coreLineChart },
props: ["name", "size", "icon"],
props: ["lineData",'dateData'], // 接收父组件传递的完整line数据对象
data() {
return {
// 所有item的数据配置后续修改直接操作这个数组即可
activeButton: 0, // 初始激活第一个按钮索引0
itemList: [
{
unit: "单价·元/m²", // 标题
targetValue: 16, // 左侧目标值
currentValue: 14.5, // 右侧当前值(可根据实际需求修改)
progress: 90, // 进度百分比
},
{
unit: "净价·元/m²",
targetValue: 16,
currentValue: 15.2,
progress: 85,
},
{
unit: "销量·万m²",
targetValue: 20,
currentValue: 16,
progress: 80,
},
{
unit: "双镀面板·万m²",
targetValue: 15,
currentValue: 13.8,
progress: 92,
},
],
};
},
computed: {},
methods: {
computed: {
isLineDataReady() {
// 确保 lineData 是一个对象,而不是 null 或 undefined
return this.lineData && typeof this.lineData === 'object';
},
// 核心改动计算属性根据activeButton动态返回选中的数据
selectedChartData() {
// 定义按钮索引与lineData中key的映射关系
const dataKeyMap = [
'总成本',
'原片成本',
'加工成本',
'原片成品率',
'投入产出率'
];
// 根据当前激活的按钮索引获取对应的数据key
const selectedKey = dataKeyMap[this.activeButton];
console.log(this.lineData[selectedKey]);
// 从lineData中获取对应的数据如果不存在则返回一个空对象以防止报错
return this.lineData ? this.lineData[selectedKey] || {} : {};
}
},
methods: {},
};
</script>
<style scoped lang="scss">
/* (你的样式代码保持不变) */
.coreBar {
display: flex;
flex-direction: column;
padding: 12px;
// grid-template-columns: 1fr 1fr;
// grid-template-rows: 1fr 1fr;
// gap: 8px;
.barTop {
display: flex;
gap: 40px;
.title {
// width: 124px;
height: 18px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
@@ -116,42 +90,40 @@ export default {
.button-group {
display: flex;
position: relative;
// justify-content: space-around;
gap: 2px;
width: 327px;
align-items: center;
height: 24px;
background: #ecf4fe;
border-radius: 12px;
.button-line {
position: absolute;
// top: 5px;
// left: 54px;
width: 1px;
height: 14px;
border: 1px solid rgba(11, 88, 255, 0.25);
}
.button-line {
position: absolute;
width: 1px;
height: 14px;
border: 1px solid rgba(11, 88, 255, 0.25);
}
.lineOne {
top: 5px;
left: 57px;
}
.lineTwo {
top: 5px;
left: 118px;
}
.lineThree {
top: 5px;
left: 177px;
}
.lineFour {
top: 5px;
left: 268px;
}
// .button-line:nth-child(3) {
// top: 5px;
// left: 216px;
// }
.lineFour {
top: 5px;
left: 268px;
}
.item-button {
cursor: pointer;
width: 59px;
@@ -164,21 +136,14 @@ export default {
text-align: center;
font-style: normal;
}
// .item-button:nth-child(6){
// width: 75px;
// }
// .item-button:nth-child(8) {
// width: 75px;
// }
.item-button.active {
width: 59px;
height: 24px;
background: #3071ff;
border-radius: 12px;
color: #ffffff; // 文字变白,提高对比度
font-weight: 500; // 文字加粗,突出激活状态
color: #ffffff;
font-weight: 500;
}
}
}

View File

@@ -1,7 +1,7 @@
<template>
<div class="coreItem">
<div class="item" @click="handleRoute(item.route)" v-for="(item, index) in itemList" :key="index">
<div class="unit">{{ item.unit }}</div>
<div class="name">{{ item.name }}</div>
<div class="item-content">
<div class="content-wrapper">
<div class="left">
@@ -54,45 +54,83 @@
export default {
name: "Container",
components: {},
props: ["name", "size", "icon"],
props: ["finance",'dateData'],
data() {
return {
itemList: [
{
unit: "营业收入·万元",
targetValue: 16,
currentValue: 17.2, // 大于目标值(绿色)
progress: 107.5,
route: 'operatingRevenue'
},
{
unit: "经营性利润·万元",
targetValue: 16,
currentValue: 16, // 等于目标值(绿色)
progress: 100,
route: 'profitAnalysis'
},
{
unit: "利润总额·万元",
targetValue: 16,
currentValue: 14.8, // 小于目标值(黄色)
progress: 92.5,
route: 'profitAnalysis'
},
{
unit: "毛利率·%",
targetValue: 16,
currentValue: 15.5, // 小于目标值(黄色)
progress: 96.875,
route: 'profitAnalysis'
}
]
// itemList: [
// {
// name: "营业收入·万元",
// targetValue: 16,
// currentValue: 17.2, // 大于目标值(绿色)
// progress: 107.5,
// route: 'operatingRevenue'
// },
// {
// name: "经营性利润·万元",
// targetValue: 16,
// currentValue: 16, // 等于目标值(绿色)
// progress: 100,
// route: 'profitAnalysis'
// },
// {
// name: "利润总额·万元",
// targetValue: 16,
// currentValue: 14.8, // 小于目标值(黄色)
// progress: 92.5,
// route: 'profitAnalysis'
// },
// {
// name: "毛利率·%",
// targetValue: 16,
// currentValue: 15.5, // 小于目标值(黄色)
// progress: 96.875,
// route: 'profitAnalysis'
// }
// ]
};
},
watch: {
finance: {
handler(newVal) {
if (newVal) {
this.itemList = this.transformData(newVal);
}
},
immediate: true,
deep: true
}
},
methods: {
transformData(rawData) {
// 定义指标映射关系,包括名称、对应的数据键和路由
const Mapping = [
{ key: 'operatingRevenue', name: '营业收入·万元', route: 'operatingRevenue' },
{ key: 'operatingIncome', name: '经营性利润·万元', route: 'profitAnalysis' },
{ key: 'totalProfit', name: '利润总额·万元', route: 'profitAnalysis' },
{ key: 'grossMargin', name: '毛利率·%', route: 'profitAnalysis' }
];
// 遍历映射关系,转换数据
return Mapping.map(mappingItem => {
const data = rawData[mappingItem.key] || { rate: 0, real: 0, target: 0 };
return {
name: mappingItem.name,
targetValue: data.target,
currentValue: data.real,
progress: Math.round(data.rate), // 将小数率转换为百分比并四舍五入
route: mappingItem.route
};
});
},
handleRoute(route) {
if (route) {
this.$router.push({ path: route });
this.$router.push({
path: route,
query: {
dateData: this.dateData
}
});
}
}
}
@@ -120,7 +158,7 @@ export default {
transform: translateY(-2px);
}
.unit {
.name {
height: 18px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;

View File

@@ -47,46 +47,63 @@
export default {
name: "Container",
components: {},
props: ["name", "size", "icon"],
props: ["sale", "dateData"],
data() {
return {
itemList: [
{
unit: "单价·元/㎡",
targetValue: 16,
currentValue: 14.5, // 小于目标值(黄色)
progress: 90,
path: "/cost/cost"
},
{
unit: "净价·元/㎡",
targetValue: 16,
currentValue: 16, // 等于目标值(默认灰色)
progress: 100,
path: "/cost/cost"
},
{
unit: "销量·万㎡",
targetValue: 20,
currentValue: 22, // 大于目标值(绿色)
progress: 110,
path: "PSIAnal"
},
{
unit: "双镀面板·万㎡",
targetValue: 15,
currentValue: 13.8, // 小于目标值(黄色)
progress: 92,
path: "PSIAnal"
}
]
itemList: [] // 初始化为空数组,等待数据加载
};
},
watch: {
// 监听 sale 数据变化,实时更新 itemList
sale: {
handler(newVal) {
if (newVal) {
this.itemList = this.transformData(newVal);
}
},
immediate: true, // 组件初始化时立即执行一次
deep: true // 深度监听对象内部属性变化
}
},
methods: {
/**
* 核心转换函数:将 sale 对象转换为 itemList 数组
* @param {Object} rawData - 原始的 sale 数据对象
* @returns {Array} - 转换后的 itemList 数组
*/
transformData(rawData) {
// 定义销售指标映射关系(键名、显示名称、单位、路由路径)
const saleMapping = [
{ key: 'unitPrice', unit: '单价·元/㎡', path: '/cost/cost' },
{ key: 'netPrice', unit: '净价·元/㎡', path: '/cost/cost' },
{ key: 'sales', unit: '销量·万㎡', path: 'PSIAnal' },
{ key: 'panel', unit: '双镀面板·万㎡', path: 'PSIAnal' }
];
// 遍历映射关系,转换数据
return saleMapping.map(mappingItem => {
// 获取对应指标的数据,若不存在则使用默认值
const indicatorData = rawData[mappingItem.key] || { rate: 0, real: 0, target: 0 };
return {
unit: mappingItem.unit,
targetValue: indicatorData.target, // 目标值
currentValue: indicatorData.real, // 实际值
progress: Math.round(indicatorData.rate * 100), // 完成率(百分比,四舍五入)
path: mappingItem.path // 路由路径
};
});
},
// 处理路由跳转
handleRouter(obj) {
if (obj.path) {
this.$router.push({ path: obj.path });
this.$router.push({
path: obj.path,
query: {
dateData:this.dateData
}
});
}
}
}

View File

@@ -40,69 +40,114 @@
export default {
name: "Container",
components: {},
props: ["name", "size", "icon"],
// 接收父组件传递过来的原始 itemList 对象
props: ['rawItemList','dateData'],
data() {
return {
itemList: [
{
unit: "总成本·元/㎡",
route: "cost/cost",
target: 16,
actual: 16,
progress: 100
},
{
unit: "原片成本·元/㎡",
route: "cost/cost",
target: 16,
actual: 16,
progress: 110
},
{
unit: "加工成本·元/㎡",
route: "cost/cost",
target: 16,
actual: 16,
progress: 85
},
{
unit: "原片成品率·%",
target: 95,
actual: 92,
progress: 97
},
{
unit: "投入产出率·%",
target: 88,
actual: 90,
progress: 102
}
],
// 组件内部用于渲染的数组
itemList: [],
activeIndex: -1
};
},
// 监听 props 中的 rawItemList 变化
watch: {
rawItemList: {
handler(newVal) {
if (newVal) {
this.itemList = this.transformData(newVal);
}
},
immediate: true, // 组件初始化时立即执行一次
deep: true // 深度监听对象内部变化
}
},
methods: {
/**
* 核心转换函数:将对象转换为组件需要的数组格式
* @param {Object} rawData - 父组件传递的原始数据对象
* @returns {Array} - 转换后的数组
*/
transformData(rawData) {
// 定义一个映射关系,将后端字段名与前端显示信息关联起来
const dataMap = [
{
key: 'totalCost',
unit: '总成本·元/㎡',
route: 'cost/cost'
},
{
key: 'rawCost',
unit: '原片成本·元/㎡',
route: 'cost/cost'
},
{
key: 'processCost',
unit: '加工成本·元/㎡',
route: 'cost/cost'
},
{
key: 'rawYield',
unit: '原片成品率·%',
route: '' // 假设这个没有路由
},
{
key: 'ioYield',
unit: '投入产出率·%',
route: '' // 假设这个没有路由
}
];
// 使用 map 方法将 dataMap 数组转换为组件需要的 itemList 数组
return dataMap.map(itemInfo => {
const rawItem = rawData[itemInfo.key] || {};
// 计算进度百分比确保不小于0
const progress = Math.max(0, Math.round((rawItem.rate || 0)));
return {
unit: itemInfo.unit,
route: itemInfo.route,
target: rawItem.target || 0,
actual: rawItem.real || 0,
progress: progress
};
});
},
handleItemClick(index) {
const currentItem = this.itemList[index];
console.log(`点击了第${index + 1}个item:`, currentItem.unit);
this.$emit('item-click', { index, ...currentItem });
this.activeIndex = index;
if (currentItem.route) {
this.$router.push({ path: currentItem.route });
this.$router.push({
path: currentItem.route,
query: {
dateData: this.dateData
}
});
}
},
// 判断颜色的方法
getColor(index) {
const { actual, target } = this.itemList[index];
return actual >= target
? "rgba(98, 213, 180, 1)"
: "rgba(249, 164, 74, 1)";
const { actual, target, progress } = this.itemList[index];
// 新增条件如果实际值、目标值和进度都为0则显示绿色
if (actual === 0 && target === 0 && progress === 0) {
return "rgba(98, 213, 180, 1)"; // 绿色
}
// 原有的通用判断逻辑
return progress >= 100
? "rgba(98, 213, 180, 1)" // 绿色
: "rgba(249, 164, 74, 1)"; // 橙色
}
}
};
</script>
<style scoped lang="scss">
/* (你的样式代码保持不变) */
.coreItem {
display: flex;
flex-wrap: wrap;

View File

@@ -1,7 +1,7 @@
<template>
<div id="dayReport" class="dayReport" :style="styles">
<ReportHeader top-title="洛玻集团运营驾驶舱" :is-full-screen="isFullScreen" @screenfullChange="screenfullChange"
@timeRangeChange="handleTimeChange" />
@timeRangeChange="handleTimeChange" />
<div class="main-body"
style="margin-top: -20px; flex: 1; display: flex;padding: 0px 16px 0;flex-direction: column;">
<div class="top" style="display: flex;gap: 16px;">
@@ -10,9 +10,9 @@
gap: 12px;
grid-template-columns: 560px 745px 560px ;
">
<coreSalesKPIs />
<financeCosts />
<keyProductionIndicators />
<coreSalesKPIs :sale="sale" :dateData="dateData" />
<financeCosts :finance="finance" :dateData="dateData" :cost="cost" />
<keyProductionIndicators :productData="productData" :dateData="dateData" />
</div>
</div>
<div class="top" style="display: flex;gap: 16px;margin-top: 6px;">
@@ -21,9 +21,9 @@
gap: 12px;
grid-template-columns: 560px 745px 560px ;
">
<coreBottomLeft />
<orderProgress />
<keyWork />
<coreBottomLeft :purchase="purchase" :inventory="inventory" />
<orderProgress @getData="getOrderData" :baseOrder="baseOrder" :orderOutput="orderOutput" />
<keyWork :importantWork="importantWork" />
</div>
</div>
</div>
@@ -38,7 +38,8 @@ import keyProductionIndicators from './components/keyProductionIndicators.vue'
import coreBottomLeft from './components/coreBottomLeft.vue'
import orderProgress from './components/orderProgress.vue'
import keyWork from './components/keyWork.vue'
import moment from 'moment'
// import moment from 'moment'
import { getOperateCockpit } from '@/api/cockpit'
export default {
name: 'DayReport',
components: { ReportHeader, coreSalesKPIs, keyProductionIndicators, coreBottomLeft, keyWork, orderProgress, financeCosts },
@@ -48,6 +49,16 @@ export default {
timer: null,
beilv: 1,
value: 100,
productData: {},
purchase: {},
dateData:{},
inventory: {},
importantWork: {},
finance: {},
cost: {},
sale: {},
orderOutput: {},
baseOrder:[],
}
},
@@ -96,9 +107,52 @@ export default {
}
},
methods: {
getOrderData(num) {
this.getData({
startTime: this.dateData.startTime,
endTime: this.dateData.endTime,
timeDim: this.dateData.mode,
baseId: num
})
},
getData(obj) {
console.log('obj', obj);
this.dateData= obj
getOperateCockpit({
startTime: obj.startTime,
endTime: obj.endTime,
timeDim: obj.mode ? obj.mode : obj.timeDim,
baseId: obj.baseId ? obj.baseId : 1
}).then((res) => {
console.log(res)
this.productData = res.data.product
this.purchase = res.data.purchase
this.inventory = res.data.inventory
this.importantWork = res.data.importantWork
this.finance = res.data.finance
this.cost = res.data.cost
this.sale = res.data.sale
this.orderOutput = res.data.orderOutput
this.baseOrder = res.data.baseOrder
// this.saleData = res.data.SaleData
// this.premiumProduct = res.data.premiumProduct
// this.salesTrendMap = res.data.salesTrendMap
// this.grossMarginTrendMap = res.data.grossMarginTrendMap
// this.salesProportion = res.data.salesProportion ? res.data.salesProportion : {}
})
},
handleTimeChange(obj) {
console.log(obj, 'obj');
this.getData(obj)
},
change() {
this.isFullScreen = screenfull.isFullscreen
},
windowWidth(value) {
this.clientWidth = value;
this.beilv2 = this.clientWidth / 1920;
},
init() {
if (!screenfull.isEnabled) {
this.$message({