运营驾驶舱对接

This commit is contained in:
2026-03-31 15:13:13 +08:00
parent 05fe91618c
commit 161d6a1bdf
44 changed files with 625 additions and 522 deletions

View File

@@ -87,11 +87,14 @@ export default {
// 如果没有 X 轴数据,则返回空数组
if (xAxisKeys.length === 0) {
return [];
return {};
}
let obj = {
unit:'万元',
series:[]
}
// 遍历配置项,生成 series
return Object.keys(this.chartConfig).map(key => {
obj.series = Object.keys(this.chartConfig).map(key => {
const config = this.chartConfig[key];
// 确保数据顺序和 X 轴一致
const dataValues = xAxisKeys.map(date => lineData[key] ? lineData[key].real[date] : 0);
@@ -114,6 +117,7 @@ export default {
data: dataValues
};
});
return obj;
}
},
methods: {}

View File

@@ -11,8 +11,8 @@
@mouseleave.native="autoScroll(false)" v-loading="isLoading"
:header-cell-style="{ background: 'rgba(218, 226, 237, 1)', color: 'rgba(0, 0, 0, .6)',padding:'3px 2px'}" :row-style="setRowStyle"
:data="renderData" border style="width: 100%; background: transparent">
<el-table-column v-if="page && limit && showIndex" prop="_pageIndex" label="序号" :width="70" align="center" />
<el-table-column v-for="item in renderTableHeadList" :key="item.prop" :show-overflow-tooltip="showOverflow"
<el-table-column v-if="page && limit && showIndex" prop="_pageIndex" label="序号" :width="60" align="center" />
<el-table-column v-for="item in renderTableHeadList" :key="item.prop" :show-overflow-tooltip="showOverflow" :width='item.width || ""'
v-bind="item">
<template slot-scope="scope">
@@ -39,7 +39,7 @@ export default {
maxHeight: {
type: [Number, String], // 支持数字如300或字符串如'300px'
required: false,
default: 200 // 原固定值,作为默认 fallback
default: 200 // 原固定值,作为默认 fallback
},
tableData: {
type: Array,

View File

@@ -90,7 +90,7 @@
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileForm"> </el-button>
<el-button @click="upload.open = false"> </el-button>
<el-button @click="cancelBtn"> </el-button>
</div>
</el-dialog>
</Container>
@@ -512,6 +512,10 @@ export default {
this.$message.error('上传失败!')
}
},
cancelBtn() {
this.upload.open = false
this.$refs.upload.clearFiles();
}
}
}
</script>

View File

@@ -57,10 +57,10 @@ export default {
activeButton: 0,
// 定义按钮与 line 数据中 key 的映射关系
buttonToDataKey: [
'单价',
'净价',
'销量',
'双镀销量' // 注意:数据中的 key 是“双镀面板”,按钮显示的是“双镀产品”
{name:'单价',unit:'元/㎡'},
{name:'净价',unit:'元/㎡'},
{name:'销量',unit:'万㎡'},
{name:'双镀销量',unit:'万㎡'}
]
};
},
@@ -69,7 +69,7 @@ export default {
xAxisData() {
const lineData = this.line || {};
// 获取当前激活按钮对应的数据
const currentDataKey = this.buttonToDataKey[this.activeButton];
const currentDataKey = this.buttonToDataKey[this.activeButton].name;
const currentIndicatorData = lineData[currentDataKey];
// 使用 'target' 的键作为 x 轴,如果 'target' 不存在,则使用 'real' 的键
@@ -83,19 +83,20 @@ export default {
// 根据激活按钮动态返回对应 series 数据
currentSeries() {
const lineData = this.line || {};
const currentDataKey = this.buttonToDataKey[this.activeButton];
const currentDataKey = this.buttonToDataKey[this.activeButton].name;
const chartData = lineData[currentDataKey];
if (!chartData) {
return [];
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 [
let obj = {
unit:this.buttonToDataKey[this.activeButton].unit,
series:[
{
name: '预算',
type: 'line',
@@ -136,7 +137,8 @@ export default {
},
data: realDataValues
}
];
]}
return obj;
}
}
};

View File

@@ -6,8 +6,8 @@
<coreBottomLeftItem :dateData="dateData" :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"
:tableConfig="tableProps" :table-data="maintenanceTasks" />
<base-table style="height: 221px;width: 260px;" :page="1" :limit="10" :show-index="true" :beilv="1"
:tableConfig="tableProps" :table-data="maintenanceTasks" :maxHeight='220'/>
</div>
</div>
</Container>
@@ -43,20 +43,13 @@ export default {
maintenanceTasks: [
{ 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: '例行维护', },
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
{ id: 3, name: '白云石', number: '23,252', },
{ id: 4, name: '石灰石', number: '34,421', },
{ id: 5, name: '氧化铝', number: '1,251.34', },
{ id: 6, name: '氢氧化铝', number: '14,252', }
],
tableProps: [
// { prop: 'id', label: '序号', width: 50, align: 'center' },
{ prop: 'name', label: '物料', align: 'left' },
{ prop: 'number', label: '库存/吨', align: 'left' },
]
@@ -102,7 +95,7 @@ export default {
return {
id: index + 1, // id 从 1 开始自增
name: name, // 物料名称
number: String(value) // 将数值转换为字符串,以匹配 "number" 字段
number: value ? String(value) : '-' // 将数值转换为字符串,以匹配 "number" 字段
};
});
}

View File

@@ -1,5 +1,5 @@
<template>
<div ref="cockpitEffChip" id="coreLineChart" style="height: 219px; width: 100%;"></div>
<div ref="cockpitEffChip" id="coreLineChart" style="height: 184px; width: 100%;"></div>
</template>
<script>
@@ -11,8 +11,8 @@ export default {
// 接收父组件传递的 series 数据
props: {
chartSeries: {
type: Array,
default: () => [] // 默认空数组,避免报错
type: Object,
default: () => {{}} // 默认空数组,避免报错
},
xAxisData: {
type: Array,
@@ -68,7 +68,6 @@ export default {
// 单独提取更新图表的方法,方便复用
updateChart() {
if (!this.myChart) return;
console.log('this.chartSeries', this.chartSeries,this.xAxisData);
const option = {
tooltip: {
@@ -78,7 +77,7 @@ export default {
label: { backgroundColor: '#6a7985' }
}
},
grid: { top: 35, bottom: 3, right: 15, left: 10, containLabel: true},
grid: { top: 35, bottom: 3, right: 15, left: 15, containLabel: true},
xAxis: [
{
type: 'category',
@@ -108,19 +107,14 @@ export default {
}
],
yAxis: {
// type: 'value',
name: '元/㎡',
// nameLocation:'center',
name: this.chartSeries.unit,
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' },
// min: () => 0,
// max: (value) => Math.ceil(value.max),
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.chartSeries.series // 使用父组件传递的 series 数据
};
this.myChart.setOption(option, true); // 第二个参数 true 表示替换现有配置

View File

@@ -24,7 +24,7 @@
<div class="right">
<!-- 环比额计算差值并动态绑定颜色 -->
<div class="number" :style="{ color: getColor(item.currentValue, item.targetValue) }">
{{ item.hbe }}
{{ item.thbe }}
</div>
<div class="title">环比额</div>
</div>
@@ -101,12 +101,12 @@ export default {
// 遍历映射关系,转换数据
return costMapping.map(mappingItem => {
// 获取对应费用类型的数据,若不存在则使用默认值
const costData = rawData[mappingItem.key] || { last: 0, this: 0, hbe: 0 };
const costData = rawData[mappingItem.key] || { last: 0, this: 0, thbe: 0 };
return {
name: mappingItem.name,
path: mappingItem.path,
hbe: costData.hbe,
thbe: costData.thbe,
targetValue: costData.last, // 上月值
currentValue: costData.this // 本月值
};

View File

@@ -2,10 +2,10 @@
<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: 9px;padding: 14px 16px;flex-direction: column;">
<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);">
@@ -13,10 +13,10 @@
</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: 8px;background-color: rgba(249, 252, 255, 1);">
style="display: flex; width: 100%;margin-top: 4px;background-color: rgba(249, 252, 255, 1);">
<CostsBottomBar :line="cost.line" :dateData="dateData">
</CostsBottomBar>
</div>

View File

@@ -27,11 +27,21 @@
<div class="item-button" :class="{ active: activeButton === 3 }" @click="activeButton = 3">
毛利率
</div>
<div class="button-line lineFour" v-if="activeButton !== 3 && activeButton !== 4"></div>
<div class="item-button" :class="{ active: activeButton === 4 }" @click="activeButton = 4">
应收账款
</div>
<div class="button-line lineFive" v-if="activeButton !== 4 && activeButton !== 5"></div>
<div class="item-button" :class="{ active: activeButton === 5 }" @click="activeButton = 5">
存货
</div>
</div>
</div>
<div class="lineBottom" style="height: 210px; width: 100%">
<div class="lineBottom" style="height: 184px; width: 100%">
<!-- 传递当前选中的 series 数据和 xAxis 数据给子组件 -->
<coreLineChart style="height: 210px; width: 680px" :chart-series="currentSeries" :x-axis-data="xAxisData"
<coreLineChart style="height: 184px; width: 680px" :chart-series="currentSeries" :x-axis-data="xAxisData"
:dateData="dateData" />
</div>
</div>
@@ -59,10 +69,12 @@ export default {
activeButton: 0, // 初始激活第一个按钮索引0
// 定义按钮与 line 数据中 key 的映射关系
buttonToDataKey: [
'营业收入',
'经营性利润', // 注意:数据中的 key 是“经营收入”,按钮显示的是“经营性利润”
'利润总额',
'毛利率'
{name:'营业收入',unit:'万元'},
{name:'经营性利润',unit:'万元'},
{name:'利润总额',unit:'万元'},
{name:'毛利率',unit:'%'},
{name:'应收账款',unit:'万元'},
{name:'存货',unit:'万元'}
]
};
},
@@ -70,20 +82,19 @@ export default {
// 根据当前激活的按钮,动态生成对应的 series 数据
currentSeries() {
const dataKey = this.buttonToDataKey[this.activeButton];
const dataKey = this.buttonToDataKey[this.activeButton].name;
const chartData = this.line[dataKey];
console.log('this.line[dataKey', this.buttonToDataKey[this.activeButton]);
if (!chartData) {
return [];
return {};
}
// 提取目标和实际数据的值
const targetDataValues = Object.values(chartData.target || {});
const realDataValues = Object.values(chartData.real || {});
console.log('realDataValues', realDataValues);
return [
let obj = {
unit: this.buttonToDataKey[this.activeButton].unit,
series:[
{
name: '预算',
type: 'line',
@@ -116,11 +127,12 @@ export default {
},
data: realDataValues
}
];
]}
return obj;
},
// 提取 x 轴数据(日期)
xAxisData() {
const dataKey = this.buttonToDataKey[this.activeButton];
const dataKey = this.buttonToDataKey[this.activeButton].name;
const chartData = this.line[dataKey];
// 使用 'target' 的键作为 x 轴,如果 'target' 不存在,则使用 'real' 的键
if (chartData && chartData.target) {
@@ -205,7 +217,7 @@ export default {
display: flex;
position: relative;
gap: 2px;
width: 252px;
width: 350px;
align-items: center;
height: 24px;
background: #ecf4fe;
@@ -219,18 +231,27 @@ export default {
}
.lineOne {
top: 5px;
top: 6px;
left: 59px;
}
.lineTwo {
top: 5px;
left: 134px;
top: 6px;
left: 131px;
}
.lineThree {
top: 5px;
left: 193px;
top: 6px;
left: 190px;
}
.lineFour {
top: 6px;
left: 238px;
}
.lineFive {
top: 6px;
left: 302px;
}
.item-button {

View File

@@ -1,7 +1,7 @@
<template>
<!-- 显示累计值并绑定颜色类 -->
<div class="accumulated-value" :class="injectData.status">
{{ injectData.accumulated }} <!-- 显示累计数据 -->
{{ injectData.total }} <!-- 显示累计数据 -->
</div>
</template>

View File

@@ -15,10 +15,6 @@ export default {
lineData: {
type: Object,
default: () => ({}),
},
xData: {
type: Array,
default: () => []
}
},
mounted() {
@@ -32,7 +28,6 @@ export default {
// 深度监听数据变化,仅更新图表配置(不销毁实例)
lineData: {
handler() {
console.log(this.lineData,'lineData');
this.updateChart();
},
deep: true,
@@ -52,8 +47,12 @@ export default {
}
this.myChart = echarts.init(chartDom);
const entries = Object.entries(this.lineData);
entries.sort((item1, item2) => item2[1] - item1[1]);
const sortedObj = Object.fromEntries(entries);
let xData = Object.keys(sortedObj);
let yData = Object.values(sortedObj);
console.log('linedaata',this.lineData)
const option = {
tooltip: {
@@ -86,7 +85,7 @@ export default {
interval: 0,
padding: [5, 0, 0, 0]
},
data:this.xData
data:xData
}
],
yAxis: [
@@ -112,7 +111,31 @@ export default {
splitNumber: 4
},
],
series: []
series: [
{
name: '实际',
type: 'bar',
yAxisIndex: 0,
barWidth: 40,
label: {
show: true,
position: 'top'
},
itemStyle: {
color:{
type: 'linear',
x: 0, y: 0, x2: 0, y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
]
},
borderRadius: [4, 4, 0, 0],
borderWidth: 0
},
data: yData
}
]
};
option && this.myChart.setOption(option);

View File

@@ -77,7 +77,7 @@ font-style: normal;">指标详情</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileForm"> </el-button>
<el-button @click="upload.open = false"> </el-button>
<el-button @click="cancelBtn"> </el-button>
</div>
</el-dialog>
</Container>
@@ -387,6 +387,10 @@ export default {
console.error('文件上传出错:', error)
this.$message.error('上传失败!')
}
},
cancelBtn(){
this.upload.open = false
this.$refs.upload.clearFiles();
}
}
}

View File

@@ -17,10 +17,10 @@
<div class="bottom" style="background-color: rgba(249, 252, 255, 1);">
<div class="bottom" style="margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
<div style='text-align: center;margin: 5px;font-size: 18px;font-weight: 400;color: #000;'>1200t/d</div>
<heatBar :lineData="heatData['1200t']" :xData="['桐城','洛阳','江苏','秦皇岛']" :dateData="dateData" />
<heatBar :lineData="heatData['1200t']"/>
</div>
<div style='text-align: center;margin: 5px;font-size: 18px;font-weight: 400;color: #000;'>650t/d</div>
<heatBar :lineData="heatData['650t']" :xData="['宜兴','自贡','漳州']" :dateData="dateData" />
<heatBar :lineData="heatData['650t']"/>
</div>
</template>
</div>
@@ -51,10 +51,6 @@ export default {
type: Object,
default: () => { } // 默认空数组,避免报错
},
heat: {
type: Object,
default: () => { }
}
},
data() {
return {

View File

@@ -15,7 +15,7 @@
<span class="legend-text">未完成</span>
</div>
</div>
<base-table style="height: 180px;" :page="1" :limit="10" :show-index="true" :beilv="1"
<base-table style="height: 204px;" :page="1" :limit="10" :show-index="true" :beilv="1"
:tableConfig="tableProps" :table-data="tableData" />
</div>
</div>
@@ -33,18 +33,18 @@ export default {
components: { Container, baseTable },
props: {
importantWork: {
type: Object,
default: () => ({})
type: Array,
default: () => ([])
}
},
data() {
return {
tableData: [],
tableProps: [
{ prop: 'name', label: '攻坚指标', align: 'center' },
{ prop: 'index', label: '攻坚指标', align: 'center',width:150 },
{ prop: 'target', label: '攻坚预算', align: 'center' },
{ prop: 'monthlyActual', label: '当月实际', align: 'center' },
{ prop: 'accumulated', label: '累计', align: 'center', subcomponent: finishDiv },
{ prop: 'real', label: '当月实际', align: 'center' },
{ prop: 'total', label: '累计', align: 'center', subcomponent: finishDiv },
]
}
},
@@ -59,33 +59,13 @@ export default {
},
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 rawData.map((item, index) =>{
return {
...item,
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'
status: (item.real > 0 && item.target > 0 && item.real / item.target >= 1 || item.real >= item.target) ? 'done' : 'pending'
};
});
})
}
}
}

View File

@@ -1,18 +1,18 @@
<template>
<div class="coreItem">
<!-- 单独渲染第一个item -->
<div class="item" v-if="itemList.length > 0">
<div class="unit">{{ itemList[0].unit }}</div>
<div class="item" v-if="itemList.length > 0" v-for="(item, index) in itemList">
<div class="unit">{{ item.unit }}</div>
<div class="item-content">
<div class="content-wrapper">
<div class="left">
<div class="number">{{ itemList[0].targetValue }}</div>
<div class="number">{{ item.targetValue }}</div>
<div class="title">预算值</div>
</div>
<div class="line"></div>
<div class="right">
<div class="number" :style="{ color: getColor(itemList[0].currentValue, itemList[0].targetValue) }">
{{ itemList[0].currentValue }}
<div class="number" :style="{ color: getColor(item.currentValue, item.targetValue) }">
{{ item.currentValue }}
</div>
<div class="title">实际值</div>
</div>
@@ -23,7 +23,7 @@
<!-- 进度条颜色和宽度动态绑定 -->
<div class="progress-bar" :style="{
width: itemList[0].progress + '%',
background: getColor(itemList[0].currentValue, itemList[0].targetValue)
background: getColor(item.currentValue, item.targetValue)
}"></div>
</div>
</div>
@@ -32,67 +32,9 @@
<div class="progress-percent">完成率</div>
<!-- 百分比颜色动态绑定 -->
<div class="progress-percent" :style="{
color: getColor(itemList[0].currentValue, itemList[0].targetValue)
color: getColor(item.currentValue, item.targetValue)
}">
{{ itemList[0].progress }}%
</div>
</div>
</div>
</div>
<!-- 循环渲染剩余的item从索引1开始 -->
<div class="item groupData" style="display: flex;padding: 0;" v-for="(item, index) in itemList.slice(1)"
:key="index">
<!-- 左侧预算值/实际值部分不变 -->
<div class="left" style="display: flex;align-items: start;gap: 4px;padding: 12px 0 0 12px;">
<div class="groupName">{{ item.unit }}</div>
<div class="left-target">
<div class="number">{{ item.targetValue }}</div>
<div class="title">预算值</div>
</div>
<div class="left-real">
<div class="number" :style="{ color: getColor(item.currentValue, item.targetValue) }">
{{ item.currentValue }}
</div>
<div class="title">实际值</div>
</div>
</div>
<div class="cityLine"></div>
<div class="right">
<!-- 顶部完成率部分不变 -->
<div class="groupName" :class="{
'bg-default': item.currentValue < item.targetValue,
'bg-green': item.currentValue >= item.targetValue
}" style="font-size: 12px;display: flex;align-items: center;justify-content: flex-end;">
<div class="title">完成率</div>
<div class="yield" style="font-size: 22px;margin-bottom: 4px;">
{{ item.progress }}
</div>
<div class="unit">%</div>
</div>
<!-- 动态渲染城市进度循环 item.cities -->
<div class="right-city" v-for="(city, cityIdx) in item.cities" :key="cityIdx"
: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">
<!-- 动态城市进度条颜色按城市进度判断 -->
<div class="city-progress-bar" :style="{
width: city.progress + '%',
background: getColor(city.completed, city.total) // 用城市已完成/总数判断颜色
}"></div>
</div>
</div>
<div class="city-progress-yield" style="display: flex;justify-content: space-between;">
<!-- 动态比值已完成/总数 -->
<div class="numerator" :style="{ color: getColor(city.completed, city.total) }">
{{ city.completed }}/{{ city.total }}
</div>
<!-- 动态城市完成率 -->
<div class="city-yield" :style="{ color: getColor(city.completed, city.total) }">
{{ city.progress }}%
</div>
{{ item.progress }}%
</div>
</div>
</div>
@@ -108,60 +50,7 @@ export default {
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 }]
// }
]
itemList: []
};
},
watch: {
@@ -180,56 +69,36 @@ export default {
targetValue: data.totalProgress.target,
currentValue: data.totalProgress.real,
progress: data.totalProgress.rate,
cities: [] // 总进度无需城市数据,留空
},
{
unit: "一组",
targetValue: data.group1.target,
currentValue: data.group1.real,
progress: data.group1.rate,
cities: [
{ 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 } // 新增城市示例
]
progress: data.group1.rate
},
{
unit: "二组",
targetValue: data.group2.target,
currentValue: data.group2.real,
progress: data.group2.rate,
cities: [
{ 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 }
]
progress: data.group2.rate
},
// 其他组同理,按需添加 cities 数据
{
unit: "三组",
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 }
]
progress: data.group3.rate
},
{
unit: "四组",
targetValue: data.group4.target,
currentValue: data.group4.real,
progress: data.group4.rate,
cities: [
{ 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 }
]
progress: data.group4.rate
},
{
unit: "五组",
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 }
]
progress: data.group5.rate
}
]
},
@@ -240,7 +109,6 @@ export default {
: "rgba(249, 164, 74, 1)";
},
getTableData(data) {
console.log(data, 'data');
this.$emit('handleShowTable',data)
}
@@ -257,11 +125,12 @@ export default {
.coreItem {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 8px;
}
.item {
width: 220px;
width: 227px;
height: 122px;
background: #f9fcff;
padding: 12px;

View File

@@ -19,7 +19,7 @@ import * as echarts from 'echarts';
export default {
name: 'Container',
props: ["chartData",'dateData'],
props: ["chartData",'dateData','unit'],
components: {},
data() {
return {
@@ -131,7 +131,7 @@ export default {
],
yAxis: {
type: 'value',
name: '元/㎡',
name: this.unit,
// nameLocation:'center',
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' },
min: 0,

View File

@@ -14,18 +14,28 @@
<div class="button-line lineFour" v-if="activeButton !== 3 && activeButton !== 4"></div>
<div class="item-button" style="width: 75px;" :class="{ active: activeButton === 4 }" @click="activeButton = 4">
投入产出率</div>
<div class="button-line lineFive" v-if="activeButton !== 4 && activeButton !== 5"></div>
<div class="item-button" style="width: 45px;" :class="{ active: activeButton === 5 }" @click="activeButton = 5">
折旧</div>
</div>
</div>
<div class="lineBottom" style="height: 219px; width: 100%" v-if="isLineDataReady">
<!-- 核心改动动态传递数据给子组件 -->
<coreLineChart style="height: 219px; width: 100%" :chart-data="selectedChartData" :dateData="dateData" />
<coreLineChart style="height: 219px; width: 100%" :chart-data="selectedChartData" :unit='unit' :dateData="dateData" />
</div>
</div>
</template>
<script>
import coreLineChart from './productBar.vue';
const dataKeyMap = [
{name:'制造成本',unit:'元/㎡'},
{name:'原片成本',unit:'元/㎡'},
{name:'加工成本',unit:'元/㎡'},
{name:'原片成品率',unit:'%'},
{name:'投入产出率',unit:'%'},
{name:'折旧',unit:'万元'}
];
export default {
name: "Container",
components: { coreLineChart },
@@ -33,6 +43,7 @@ export default {
data() {
return {
activeButton: 0, // 初始激活第一个按钮索引0
dataKeyMap
};
},
computed: {
@@ -42,22 +53,14 @@ export default {
},
// 核心改动计算属性根据activeButton动态返回选中的数据
selectedChartData() {
// 定义按钮索引与lineData中key的映射关系
const dataKeyMap = [
'制造成本',
'原片成本',
'加工成本',
'原片成品率',
'投入产出率'
];
// 根据当前激活的按钮索引获取对应的数据key
const selectedKey = dataKeyMap[this.activeButton];
console.log(this.lineData[selectedKey]);
const selectedKey = this.dataKeyMap[this.activeButton].name;
// 从lineData中获取对应的数据如果不存在则返回一个空对象以防止报错
return this.lineData ? this.lineData[selectedKey] || {} : {};
},
unit() {
return this.dataKeyMap[this.activeButton].unit;
}
},
methods: {},
@@ -73,7 +76,7 @@ export default {
.barTop {
display: flex;
gap: 40px;
gap: 20px;
.title {
height: 18px;
@@ -91,7 +94,7 @@ export default {
display: flex;
position: relative;
gap: 2px;
width: 327px;
width: 356px;
align-items: center;
height: 24px;
background: #ecf4fe;
@@ -105,23 +108,27 @@ export default {
}
.lineOne {
top: 5px;
left: 57px;
top: 6px;
left: 55px;
}
.lineTwo {
top: 5px;
left: 118px;
top: 6px;
left: 111px;
}
.lineThree {
top: 5px;
left: 177px;
top: 6px;
left: 169px;
}
.lineFour {
top: 5px;
left: 252px;
top: 6px;
left: 240px;
}
.lineFive {
top: 6px;
left: 314px;
}
.item-button {

View File

@@ -1,52 +1,96 @@
<template>
<div class="coreItem">
<div class="item" @click="handleRoute(item.route)" v-for="(item, index) in itemList" :key="index">
<div class="name">{{ item.name }}</div>
<div class="item-content">
<div class="content-wrapper">
<div class="left">
<div class="number" style="color: rgba(103, 103, 103, 0.79);">{{ item.targetValue }}</div>
<div class="title" style="color: rgba(134, 134, 135, 1);">目标值</div>
<div>
<div class="coreItem">
<div class="item" @click="handleRoute(item.route)" v-for="(item, index) in itemList" :key="index" v-if='index<4'>
<div class="name">{{ item.name }}</div>
<div class="item-content">
<div class="content-wrapper">
<div class="left">
<div class="number" style="color: rgba(103, 103, 103, 0.79);">{{ item.targetValue }}</div>
<div class="title" style="color: rgba(134, 134, 135, 1);">目标值</div>
</div>
<div class="line"></div>
<!-- 实际值根据 实际值目标值 动态绑定类名 -->
<div class="right">
<div class="number" :class="{
'number-exceed': item.currentValue >= item.targetValue,
'number-below': item.currentValue < item.targetValue
}">
{{ item.currentValue }}
</div>
<div class="title" style="color: rgba(134, 134, 135, 1);">实际值</div>
</div>
</div>
<div class="line"></div>
<!-- 实际值根据 实际值目标值 动态绑定类名 -->
<div class="right">
<div class="number" :class="{
'number-exceed': item.currentValue >= item.targetValue,
'number-below': item.currentValue < item.targetValue
}">
{{ item.currentValue }}
<!-- 进度条同步绑定类名 -->
<div class="progress-group">
<div class="progress-container">
<div class="progress-bar" :style="{ width: item.progressWidth + '%' }" :class="{
'bar-exceed': item.currentValue >= item.targetValue,
'bar-below': item.currentValue < item.targetValue
}"></div>
</div>
<div class="title" style="color: rgba(134, 134, 135, 1);">实际值</div>
</div>
</div>
<div class="line"></div>
<!-- 进度条同步绑定类名 -->
<div class="progress-group">
<div class="progress-container">
<div class="progress-bar" :style="{ width: item.progressWidth + '%' }" :class="{
'bar-exceed': item.currentValue >= item.targetValue,
'bar-below': item.currentValue < item.targetValue
}"></div>
</div>
</div>
<!-- 完成率同步绑定类名 -->
<div class="yield" style="display: flex;justify-content: space-between;">
<div class="progress-percent" :class="{
'percent-exceed': item.currentValue >= item.targetValue,
'percent-below': item.currentValue < item.targetValue
}">完成率</div>
<div class="progress-percent" :class="{
'percent-exceed': item.currentValue >= item.targetValue,
'percent-below': item.currentValue < item.targetValue
}">
{{ item.progressDisplay }}
<!-- 完成率同步绑定类名 -->
<div class="yield" style="display: flex;justify-content: space-between;">
<div class="progress-percent" :class="{
'percent-exceed': item.currentValue >= item.targetValue,
'percent-below': item.currentValue < item.targetValue
}">完成率</div>
<div class="progress-percent" :class="{
'percent-exceed': item.currentValue >= item.targetValue,
'percent-below': item.currentValue < item.targetValue
}">
{{ item.progressDisplay }}
</div>
</div>
</div>
</div>
</div>
<div class="itemBottom">
<div class="item" v-for="(item, index) in itemList" :key="index" @click="handleRoute(item.route)" v-if='index>=4'>
<div class="unit">{{ item.name }}</div>
<div class="item-content">
<div class="content-wrapper">
<div class="left">
<div class="number">{{ item.targetValue }}</div>
<div class="title">目标值</div>
</div>
<div class="line"></div>
<!-- 实际值:根据与目标值的比较动态变色 -->
<div class="right">
<div class="number" :class="{
'exceed-target': item.currentValue > item.targetValue,
'below-target': item.currentValue < item.targetValue,
'equal-target': item.currentValue === item.targetValue
}">
{{ item.currentValue }}
</div>
<div class="title">实际值</div>
</div>
</div>
<!-- 进度条和百分比:同步变色逻辑 -->
<div class="progress-group">
<div class="progress-container">
<div class="progress-bar" :style="{ width: item.progress + '%' }" :class="{
'exceed-pro-target': item.currentValue > item.targetValue,
'below-pro-target': item.currentValue < item.targetValue,
'equal-pro-target': item.currentValue === item.targetValue
}"></div>
</div>
<div class="progress-percent" :class="{
'exceed-target': item.currentValue > item.targetValue,
'below-target': item.currentValue < item.targetValue,
'equal-target': item.currentValue === item.targetValue
}">
{{ item.progressDisplay }}
</div>
</div>
</div>
</div>
</div>
</div>
</template>
@@ -160,6 +204,18 @@ export default {
name: '毛利率·%',
route: '/grossMargin/grossMargin',
isPercentage: true // 需要加%符号
},
{
key: 'accountsReceivable',
name: '应收账款·万元',
route: '/accountsReceivable/accountsReceivableIndex',
isPercentage: false // 需要加%符号
},
{
key: 'inventory',
name: '存货·万元',
route: '/inventoryAnalysis/inventoryAnalysisIndex',
isPercentage: false // 需要加%符号
}
];
@@ -219,24 +275,152 @@ export default {
.coreItem {
display: flex;
gap: 8px;
// padding: 8px; // 避免边缘item hover阴影被截断
}
.item {
width: 170px;
height: 168px;
background: #f9fcff;
padding: 12px 0px 0px 12px;
box-sizing: border-box;
cursor: pointer;
transition: all 0.3s ease;
.item {
width: 170px;
height: 228px;
&:hover {
box-shadow: 0px 4px 12px 2px #B5CDE5;
transform: translateY(-2px);
}
.name {
height: 18px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 18px;
color: #000000;
line-height: 18px;
letter-spacing: 1px;
text-align: left;
font-style: normal;
margin-bottom: 2px;
}
.item-content {
display: flex;
flex-direction: column;
justify-content: space-between;
height: calc(100% - 26px);
}
.content-wrapper {
display: flex;
flex-direction: column;
gap: 2px;
}
.line {
width: 149px;
height: 1px;
background: linear-gradient(to left, rgba(255, 0, 0, 0), #cbcbcb);
}
.left,
.right {
margin-top: 0px;
display: flex;
flex-direction: column;
gap: 2px;
width: 100%;
}
/* 实际值 - 基础样式(无颜色) */
.number {
height: 22px;
font-family: PingFangSC, PingFang SC;
font-weight: 600;
font-size: 24px;
line-height: 22px;
text-align: left;
font-style: normal;
}
/* 实际值 - 实际值≥目标值(绿色) */
.number-exceed {
color: rgba(54, 181, 138, 1) !important;
}
/* 实际值 - 实际值<目标值(黄色) */
.number-below {
color: rgba(249, 164, 74, 1) !important;
}
.title {
height: 14px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 12px;
color: #868687;
line-height: 14px;
text-align: left;
font-style: normal;
}
.progress-group {
display: flex;
align-items: center;
gap: 8px;
margin-top: 2px;
}
.progress-container {
width: 138px;
height: 10px;
background: #ECEFF7;
border-radius: 8px;
overflow: hidden;
}
/* 进度条 - 基础样式(无颜色) */
.progress-bar {
height: 100%;
border-radius: 8px;
transition: width 0.5s ease;
}
/* 进度条 - 实际值≥目标值(绿色) */
.bar-exceed {
background: rgba(98, 213, 180, 1) !important;
opacity: 1 !important;
}
/* 进度条 - 实际值<目标值(黄色) */
.bar-below {
background: rgba(249, 164, 74, 1) !important;
opacity: 1 !important;
}
/* 百分比 - 基础样式(无颜色) */
.progress-percent {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 12px;
line-height: 1;
}
/* 百分比 - 实际值≥目标值(绿色) */
.percent-exceed {
color: rgba(54, 181, 138, 1) !important;
}
/* 百分比 - 实际值<目标值(黄色) */
.percent-below {
color: rgba(249, 164, 74, 1) !important;
}
.yield {
width: 138px;
margin-top: 3px;
}
}
}
.itemBottom {
display: flex;
gap: 8px;
margin-top: 5px;
.item {
width: 350px;
height: 90px;
background: #f9fcff;
padding: 12px 0px 17px 12px;
padding: 8px 8px 0px;
box-sizing: border-box;
cursor: pointer;
transition: all 0.3s ease;
cursor: pointer; // 提示可点击
transition: all 0.3s ease; // 动画过渡
&:hover {
box-shadow: 0px 4px 12px 2px #B5CDE5;
transform: translateY(-2px);
transform: translateY(-2px); // 轻微上浮增强交互感
}
.name {
.unit {
height: 18px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
@@ -246,57 +430,51 @@ export default {
letter-spacing: 1px;
text-align: left;
font-style: normal;
margin-bottom: 2px;
}
.item-content {
display: flex;
flex-direction: column;
justify-content: space-between;
height: calc(100% - 26px);
height: calc(100% - 29px);
}
.content-wrapper {
display: flex;
flex-direction: column;
gap: 10px;
align-items: center;
justify-content: space-around;
flex: 1;
}
.line {
width: 149px;
height: 1px;
background: linear-gradient(to left, rgba(255, 0, 0, 0), #cbcbcb);
width: 1px;
height: 46px;
background: linear-gradient(to bottom, rgba(255, 0, 0, 0), #cbcbcb);
}
.left,
.right {
margin-top: 11px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 2px;
width: 100%;
flex: 1;
}
/* 实际值 - 基础样式(无颜色) */
.number {
height: 22px;
font-family: PingFangSC, PingFang SC;
font-weight: 600;
font-size: 24px;
color: rgba(103, 103, 103, 0.79);
/* 默认颜色(等于目标值时) */
line-height: 22px;
text-align: left;
text-align: center;
font-style: normal;
}
/* 实际值 - 实际值≥目标值(绿色) */
.number-exceed {
color: rgba(54, 181, 138, 1) !important;
}
/* 实际值 - 实际值<目标值(黄色) */
.number-below {
color: rgba(249, 164, 74, 1) !important;
}
.title {
height: 14px;
font-family: PingFangSC, PingFang SC;
@@ -304,7 +482,7 @@ export default {
font-size: 12px;
color: #868687;
line-height: 14px;
text-align: left;
text-align: center;
font-style: normal;
}
@@ -312,57 +490,83 @@ export default {
display: flex;
align-items: center;
gap: 8px;
margin-top: 15px;
}
.progress-container {
width: 138px;
width: 280px;
height: 10px;
background: #ECEFF7;
border-radius: 8px;
overflow: hidden;
}
/* 进度条 - 基础样式(无颜色) */
.progress-bar {
height: 100%;
background: rgba(98, 213, 180, 1);
/* 默认进度条颜色(等于目标值时) */
border-radius: 8px;
transition: width 0.5s ease;
opacity: 0.7;
transition: width 0.5s ease; // 进度条动画
}
/* 进度条 - 实际值≥目标值(绿色) */
.bar-exceed {
background: rgba(98, 213, 180, 1) !important;
opacity: 1 !important;
}
/* 进度条 - 实际值<目标值(黄色) */
.bar-below {
background: rgba(249, 164, 74, 1) !important;
opacity: 1 !important;
}
/* 百分比 - 基础样式(无颜色) */
.progress-percent {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 12px;
color: #868687;
/* 默认百分比颜色(等于目标值时) */
line-height: 1;
}
/* 百分比 - 实际值≥目标值绿色 */
.percent-exceed {
color: rgba(54, 181, 138, 1) !important;
/* 实际值 > 目标值绿色样式 */
.exceed-target {
color: rgba(98, 213, 180, 1) !important;
/* 文字绿色 */
// background: rgba(98, 213, 180, 1) !important;
/* 进度条绿色 */
opacity: 1 !important;
}
/* 百分比 - 实际值<目标值黄色 */
.percent-below {
/* 实际值 < 目标值黄色样式 */
.below-target {
color: rgba(249, 164, 74, 1) !important;
/* 文字黄色 */
// background: rgba(249, 164, 74, 1) !important;
/* 进度条黄色 */
opacity: 1 !important;
}
.yield {
width: 138px;
margin-top: 3px;
.exceed-pro-target {
// color: rgba(98, 213, 180, 1) !important;
/* 文字绿色 */
background: rgba(98, 213, 180, 1) !important;
/* 进度条绿色 */
opacity: 1 !important;
}
/* 实际值 < 目标值:黄色样式 */
.below-pro-target {
// color: rgba(249, 164, 74, 1) !important;
/* 文字黄色 */
background: rgba(249, 164, 74, 1) !important;
/* 进度条黄色 */
opacity: 1 !important;
}
/* 实际值 = 目标值:默认灰色(可自定义) */
.equal-target{
color: rgba(98, 213, 180, 1) !important;
/* 文字绿色 */
// background: rgba(98, 213, 180, 1) !important;
/* 进度条绿色 */
opacity: 1 !important;
}
.equal-pro-target {
// color: rgba(98, 213, 180, 1) !important;
/* 文字绿色 */
background: rgba(98, 213, 180, 1) !important;
/* 进度条绿色 */
opacity: 1 !important;
}
}
}
</style>

View File

@@ -1,15 +1,15 @@
<template>
<div class="coreItem">
<div class="item" :class="`item${index + 1}`" @click="handleItemClick(index)" v-for="(item, index) in itemList"
<div class="item" @click="handleItemClick(index)" v-for="(item, index) in itemList"
:key="index">
<div class="unit">{{ item.unit }}</div>
<div class="item-content">
<div class="content-wrapper">
<div class="left">
<div class="left" v-if="item.unit !== '折旧·万元'">
<div class="number">{{ item.target }}</div>
<div class="title">预算值</div>
</div>
<div class="line"></div>
<div class="line" v-if="item.unit !== '折旧·万元'"></div>
<div class="right">
<!-- 实际值颜色动态绑定 -->
<div class="number" :style="{ color: getColor(index) }">
@@ -18,7 +18,7 @@
<div class="title">实际值</div>
</div>
</div>
<div class="progress-group">
<div class="progress-group" v-if="item.unit !== '折旧·万元'">
<div class="progress-container">
<!-- 进度条样式动态绑定 -->
<div class="progress-bar" :style="{
@@ -88,12 +88,17 @@ export default {
{
key: 'rawYield',
unit: '原片成品率·%',
route: '/rawSheetYield/rawSheetYield' // 假设这个没有路由
route: '/rawSheetYield/rawSheetYield'
},
{
key: 'ioYield',
unit: '投入产出率·%',
route: '/inputOutputRatio/inputOutputRatio' // 假设这个没有路由
route: '/inputOutputRatio/inputOutputRatio'
},
{
key: 'depreciation',
unit: '折旧·万元',
route: '/depreciationAnalysis/depreciationAnalysisIndex'
}
];
@@ -194,7 +199,7 @@ export default {
}
.item {
width: 252px;
width: 166px;
height: 110px;
background: #f9fcff;
padding: 12px;
@@ -300,10 +305,4 @@ export default {
line-height: 1;
}
}
.item1,
.item2,
.item3 {
width: 166px;
}
</style>