修改
This commit is contained in:
236
src/views/home/components/CostsBottomBar.vue
Normal file
236
src/views/home/components/CostsBottomBar.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<div class="coreBar">
|
||||
<div class="barTop">
|
||||
<div class="barTop-left" style="display: flex;">
|
||||
<div class="title">费用指标趋势</div>
|
||||
<div class="legend">
|
||||
<span class="legend-item manager">管理费用</span>
|
||||
<span class="legend-item sale">销售费用</span>
|
||||
<span class="legend-item finance">财务费用</span>
|
||||
</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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import coreLineChart from './coresBar.vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: "Container",
|
||||
components: { coreLineChart },
|
||||
props: ["name", "size", "icon"],
|
||||
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: [
|
||||
{
|
||||
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个月份)
|
||||
},
|
||||
{
|
||||
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]
|
||||
},
|
||||
{
|
||||
name: '财务费用',
|
||||
type: 'line',
|
||||
stack: 'Total',
|
||||
symbol: 'circle',
|
||||
lineStyle: { color: 'rgba(255, 132, 0, .5)' },
|
||||
itemStyle: {
|
||||
color: 'rgba(255, 132, 0, .5)',
|
||||
borderColor: 'rgba(255, 132, 0, 1)',
|
||||
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)' },
|
||||
]),
|
||||
},
|
||||
data: [90, 150, 180, 120, 250, 280]
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
methods: {}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 原有样式保持不变 */
|
||||
.coreBar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 12px;
|
||||
|
||||
.barTop {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: space-between;
|
||||
|
||||
.title {
|
||||
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;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
margin-left: 30px;
|
||||
|
||||
.legend-item {
|
||||
position: relative;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -13px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 12px;
|
||||
height: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.manager::before,
|
||||
.manager::after {
|
||||
background-color: rgba(11, 88, 255, 1);
|
||||
}
|
||||
|
||||
.sale::before,
|
||||
.sale::after {
|
||||
background-color: rgba(54, 181, 138, 1);
|
||||
}
|
||||
|
||||
.finance::before,
|
||||
.finance::after {
|
||||
background-color: rgba(255, 132, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
position: relative;
|
||||
gap: 2px;
|
||||
width: 252px;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
background: #ecf4fe;
|
||||
border-radius: 12px;
|
||||
|
||||
.button-line {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid rgba(11, 88, 255, 0.25);
|
||||
}
|
||||
|
||||
.lineOne {
|
||||
top: 5px;
|
||||
left: 59px;
|
||||
}
|
||||
|
||||
.lineTwo {
|
||||
top: 5px;
|
||||
left: 134px;
|
||||
}
|
||||
|
||||
.lineThree {
|
||||
top: 5px;
|
||||
left: 193px;
|
||||
}
|
||||
|
||||
.item-button {
|
||||
cursor: pointer;
|
||||
width: 54px;
|
||||
height: 24px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #0b58ff;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.item-button.active {
|
||||
width: 54px;
|
||||
height: 24px;
|
||||
background: #3071ff;
|
||||
border-radius: 12px;
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
422
src/views/home/components/Header.vue
Normal file
422
src/views/home/components/Header.vue
Normal file
@@ -0,0 +1,422 @@
|
||||
<template>
|
||||
<header class="report-header">
|
||||
<!-- 左侧区域:logo + 标题 -->
|
||||
<div class="left-content">
|
||||
<img style="height: 36px;" src="../../../assets/img/cnbm.png" alt="benmaLogo" >
|
||||
<div class="top-title">{{ topTitle }}</div>
|
||||
</div>
|
||||
|
||||
<div class="center-content">
|
||||
<!-- 循环 pageRoutes,不再硬编码文字 -->
|
||||
<div class="item" v-for="(page, index) in pageRoutes" :key="index" @click="goToPage(page.path, index)">
|
||||
<span class="item-text">{{ page.text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- :class="{ 'no-skew': activeIndex === index }
|
||||
" -->
|
||||
<!-- 右侧区域:全屏按钮 -->
|
||||
<div class="right-content">
|
||||
<el-button type="text" class="screen-btn" :title="isFullScreen ? '退出全屏' : '全屏'" @click="changeFullScreen">
|
||||
<svg-icon style="color: #0B58FF;" v-if="isFullScreen" icon-class="unFullScreenView" />
|
||||
<svg-icon style="color: #0B58FF;" v-else icon-class="fullScreenView" />
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 时间选择区域:日/月/年按钮 + label + 日期选择器 -->
|
||||
<div class="timeType">
|
||||
<div class="item" v-for="(item, index) in timeTypes" :key="index" @click="activeTime = index"
|
||||
:class="{ 'no-skew': activeTime === index }">
|
||||
<span class="item-text">{{ item.text }}</span>
|
||||
</div>
|
||||
<div class="dateP">
|
||||
<div class="label">
|
||||
<span class="label-text">日期选择</span>
|
||||
</div>
|
||||
<el-date-picker v-model="date" :type="getPickerType" :placeholder="getPickerPlaceholder"
|
||||
class="custom-date-picker" style="width: 132px;height: 29px;" @change="emitTimeRange" />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Header',
|
||||
props: {
|
||||
isFullScreen: { type: Boolean, default: false },
|
||||
topTitle: { type: String, default: '' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentTime: '',
|
||||
timeTimer: null,
|
||||
date: undefined,
|
||||
activeIndex: -1,
|
||||
activeTime: 1, // 0=日,1=月,2=年(默认选中“日”)
|
||||
pageRoutes: [
|
||||
{ text: '营业收入', path: '/operatingRevenue' },
|
||||
{ text: '利润分析', path: '/profitAnalysis' },
|
||||
{ text: '产销率库存分析', path: '/PSIAnal' },
|
||||
{ text: '成本分析', path: '/cost/cost' },
|
||||
{ text: '驾驶舱报表', path: '/cockpit' }
|
||||
],
|
||||
// 定义时间类型配置:text=按钮文字,pickerType=选择器类型,placeholder=占位符
|
||||
timeTypes: [
|
||||
{ text: '日', pickerType: 'date', placeholder: '选择日期' },
|
||||
{ text: '月', pickerType: 'month', placeholder: '选择月份' },
|
||||
{ text: '年', pickerType: 'year', placeholder: '选择年份' }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 动态获取日期选择器类型
|
||||
getPickerType() {
|
||||
return this.timeTypes[this.activeTime].pickerType;
|
||||
},
|
||||
// 动态获取日期选择器占位符
|
||||
getPickerPlaceholder() {
|
||||
return this.timeTypes[this.activeTime].placeholder;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goToPage(path, index) {
|
||||
// 1. 跳转到对应路由
|
||||
this.$router.push(path);
|
||||
// 2. 更新activeIndex,让当前点击项高亮
|
||||
this.activeIndex = index;
|
||||
},
|
||||
changeFullScreen() { this.$emit('screenfullChange') },
|
||||
padZero(num) { return num < 10 ? '0' + num : num },
|
||||
/**
|
||||
* 核心方法1:根据维度计算时间区间(首次进入时基于赋值的当月日期,计算“当月第一天0点→次月第一天0点”)
|
||||
* @returns {Object} 包含 start(开始时间)、end(结束时间)、dimension(维度)的区间对象
|
||||
*/
|
||||
calculateTimeRange() {
|
||||
let startTime = 0;
|
||||
let endTime = 0;
|
||||
const mode = this.activeTime + 1; // 1=日,2=月,3=年
|
||||
const defaultMoment = moment(); // 默认当前时间
|
||||
|
||||
// 处理选择的日期:转为moment对象(兼容不同选择器格式)
|
||||
console.log('this.date', this.date);
|
||||
|
||||
const targetMoment = this.date
|
||||
? moment(this.date, this.getPickerType === 'date' ? 'YYYY-MM-DD' : (this.getPickerType === 'month' ? 'YYYY-MM' : 'YYYY'))
|
||||
: defaultMoment;
|
||||
|
||||
// 验证日期有效性
|
||||
if (!targetMoment.isValid()) {
|
||||
console.error('无效日期:', this.date);
|
||||
return { startTime, endTime, mode };
|
||||
}
|
||||
|
||||
// 1. 日维度:当天0点 → 次日0点
|
||||
if (this.activeTime === 0) {
|
||||
startTime = targetMoment.startOf('day').valueOf(); // 当天00:00:00 时间戳
|
||||
endTime = targetMoment.add(1, 'day').startOf('day').valueOf(); // 次日00:00:00 时间戳
|
||||
}
|
||||
|
||||
// 2. 月维度:当月1日0点 → 次月1日0点
|
||||
else if (this.activeTime === 1) {
|
||||
startTime = targetMoment.startOf('month').valueOf(); // 当月1日00:00:00 时间戳
|
||||
endTime = targetMoment.add(1, 'month').startOf('month').valueOf(); // 次月1日00:00:00 时间戳
|
||||
}
|
||||
|
||||
// 3. 年维度:当年1月1日0点 → 次年1月1日0点
|
||||
else if (this.activeTime === 2) {
|
||||
startTime = targetMoment.startOf('year').valueOf(); // 当年1月1日00:00:00 时间戳
|
||||
endTime = targetMoment.add(1, 'year').startOf('year').valueOf(); // 次年1月1日00:00:00 时间戳
|
||||
}
|
||||
|
||||
// 调试输出(格式化显示,便于验证)
|
||||
console.log('时间范围计算结果:', {
|
||||
mode,
|
||||
startTime: moment(startTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
endTime: moment(endTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
startTimeStamp: startTime,
|
||||
endTimeStamp: endTime
|
||||
});
|
||||
|
||||
return { startTime, endTime, mode };
|
||||
},
|
||||
/**
|
||||
* 核心方法2:传递时间区间给父组件(首次进入时触发,传递“当月第一天0点→次月第一天0点”)
|
||||
*/
|
||||
emitTimeRange() {
|
||||
const timeRange = this.calculateTimeRange();
|
||||
this.$emit('timeRangeChange', timeRange);
|
||||
// 调试用:查看首次传递的区间(如{start: "2025-10-01T00:00:00", end: "2025-11-01T00:00:00", dimension: "月"})
|
||||
console.log('当前时间区间:', timeRange);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 维度切换时:清空选择的日期,并传递当前维度的默认区间
|
||||
activeTime(newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
this.date = undefined;
|
||||
// this.emitTimeRange();
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 核心逻辑:首次进入页面,计算当月默认日期并赋值给选择器,同时传递区间
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = this.padZero(now.getMonth() + 1); // 月份从0开始,+1后补零(如1月→01)
|
||||
// 赋值当月默认日期(格式:YYYY-MM,适配month类型选择器)
|
||||
this.date = `${year}-${month}`;
|
||||
// 确保选择器渲染完成后,传递“当月第一天0点→次月第一天0点”的区间
|
||||
this.$nextTick(() => this.emitTimeRange());
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 原有样式不变,仅补充label文字的倾斜抵消样式 */
|
||||
@font-face {
|
||||
font-family: "YouSheBiaoTiHei";
|
||||
src: url('../../../assets/fonts/YouSheBiaoTiHe.ttf') format('truetype');
|
||||
}
|
||||
|
||||
.report-header {
|
||||
height: 117px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
background: url('../../../assets/img/topTitle.png') no-repeat;
|
||||
background-size: cover;
|
||||
background-position: 0 0;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
/* 确保timeType绝对定位生效 */
|
||||
|
||||
.left-content {
|
||||
margin-top: 11px;
|
||||
margin-left: 44px;
|
||||
height: 55px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.top-title {
|
||||
height: 55px;
|
||||
font-family: "YouSheBiaoTiHei", sans-serif;
|
||||
font-size: 42px;
|
||||
color: #1E1651;
|
||||
line-height: 55px;
|
||||
letter-spacing: 6px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.center-content {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 18px;
|
||||
margin-left: 70px;
|
||||
|
||||
.item {
|
||||
width: 180px;
|
||||
height: 50px;
|
||||
background: #E1EEFC;
|
||||
transform: skew(-20deg);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 20px;
|
||||
color: #1E1651;
|
||||
line-height: 50px;
|
||||
letter-spacing: 2px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
box-shadow: 0px 13px 16px 0px rgba(179, 217, 255, 0.43),
|
||||
0px 2px 4px 0px rgba(92, 140, 255, 0.25),
|
||||
inset 0px -43px 13px 0px rgba(255, 255, 255, 0.51);
|
||||
|
||||
.item-text {
|
||||
display: inline-block;
|
||||
transform: skew(20deg);
|
||||
}
|
||||
}
|
||||
|
||||
.item.no-skew {
|
||||
background: none !important;
|
||||
transform: none !important;
|
||||
box-shadow: none !important;
|
||||
color: #1E1651;
|
||||
|
||||
.item-text {
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.timeType {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 垂直居中,避免元素高低错位 */
|
||||
top: 42px;
|
||||
right:10px;
|
||||
margin-top: 18px;
|
||||
gap: 0;
|
||||
/* 清除间隙,让按钮与选择器紧密连接 */
|
||||
}
|
||||
|
||||
.timeType .item {
|
||||
width: 40px;
|
||||
height: 28px;
|
||||
background: rgba(236, 244, 254, 1);
|
||||
transform: skew(-20deg);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: rgba(11, 88, 255, 1);
|
||||
line-height: 28px;
|
||||
letter-spacing: 2px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
/* 选中按钮与未选中按钮倾斜角度统一,避免切换时跳动 */
|
||||
}
|
||||
|
||||
.timeType .item .item-text {
|
||||
display: inline-block;
|
||||
transform: skew(20deg);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.timeType .item.no-skew {
|
||||
background: rgba(11, 88, 255, 1);
|
||||
color: rgba(249, 252, 255, 1);
|
||||
transform: skew(-20deg) !important;
|
||||
/* 统一倾斜角度,修复原30deg的错位 */
|
||||
box-shadow: 0 2px 8px rgba(11, 88, 255, 0.3);
|
||||
}
|
||||
|
||||
.timeType .item.no-skew .item-text {
|
||||
transform: skew(20deg) !important;
|
||||
/* 同步统一文字倾斜角度 */
|
||||
}
|
||||
|
||||
.dateP {
|
||||
position: relative;
|
||||
margin-left: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.dateP .label {
|
||||
width: 70px;
|
||||
height: 28px;
|
||||
background: rgba(236, 244, 254, 1);
|
||||
transform: skew(-25deg);
|
||||
/* 与按钮倾斜角度统一(原30deg改为25deg,避免视觉错位) */
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #0B58FF;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 补充:label文字抵消倾斜(原代码遗漏,导致文字倾斜) */
|
||||
.dateP .label-text {
|
||||
display: inline-block;
|
||||
transform: skew(25deg);
|
||||
/* 与label倾斜角度相反,确保文字正立 */
|
||||
}
|
||||
|
||||
.right-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 12px;
|
||||
margin-right: 4px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.current-time {
|
||||
color: #FFFFFF;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 500;
|
||||
font-size: 22px;
|
||||
line-height: 24px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.screen-btn {
|
||||
width: 26px;
|
||||
margin-left: 300px;
|
||||
color: #00fff0;
|
||||
font-size: 26px;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 日期选择器样式保持不变 */
|
||||
::v-deep .custom-date-picker {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
width: 132px !important;
|
||||
height: 28px !important;
|
||||
position: relative;
|
||||
margin: 0 !important;
|
||||
|
||||
/* 1. 调整输入框文字:确保行高与输入框高度一致,垂直居中 */
|
||||
.el-input__inner {
|
||||
height: 28px !important;
|
||||
width: 132px !important;
|
||||
text-align: center;
|
||||
padding-left: 15px !important;
|
||||
padding-right: 32px !important;
|
||||
/* 给图标留空间,避免文字被遮挡 */
|
||||
font-size: 14px !important;
|
||||
line-height: 28px !important;
|
||||
/* 行高=输入框高度,文字垂直居中 */
|
||||
color: rgba(237, 245, 253, 1) !important;
|
||||
vertical-align: middle !important;
|
||||
/* 强制文字垂直对齐 */
|
||||
clip-path: polygon(18px 0, 100% 0, 100% 100%, 0 100%);
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: rgba(11, 88, 255, 1) !important;
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/* 2. 调整图标容器:让图标与文字在同一水平线上 */
|
||||
.el-input__prefix {
|
||||
left: auto !important;
|
||||
right: 8px !important;
|
||||
top: 50% !important;
|
||||
/* 从40%改为50%,基于输入框垂直居中 */
|
||||
transform: translateY(-50%) !important;
|
||||
/* 向上偏移自身50%,精准居中 */
|
||||
display: inline-flex !important;
|
||||
/* 让容器内图标垂直居中 */
|
||||
align-items: center !important;
|
||||
/* 图标在容器内垂直居中 */
|
||||
height: 28px !important;
|
||||
/* 容器高度=输入框高度,避免偏移 */
|
||||
}
|
||||
|
||||
/* 3. 调整图标本身:确保图标大小和对齐方式 */
|
||||
.el-input__icon {
|
||||
color: #ffffff !important;
|
||||
font-size: 16px !important;
|
||||
line-height: 28px !important;
|
||||
/* 图标行高=输入框高度,与文字对齐 */
|
||||
vertical-align: middle !important;
|
||||
/* 强制图标垂直对齐 */
|
||||
}
|
||||
|
||||
/* 4. 图标伪类:确保颜色和对齐继承 */
|
||||
.el-icon-date::before {
|
||||
color: #ffffff !important;
|
||||
font-size: 16px !important;
|
||||
line-height: inherit !important;
|
||||
/* 继承父级行高,避免错位 */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
263
src/views/home/components/KFAP.vue
Normal file
263
src/views/home/components/KFAP.vue
Normal file
@@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<div style="">
|
||||
<Container name="利润影主要响因素" icon="cockpitItemIcon" size="profitMiddleBasic" topSize="KFAPTopTitle">
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex;width: 100%;">
|
||||
<div class="left" style="width: 382px;">
|
||||
<top-item :itemList="targetItemList" />
|
||||
</div>
|
||||
<div class="bottom"
|
||||
style="height: 227px; margin-left: 16px; display: flex; width: 100%;background-color: rgba(249, 252, 255, 1);">
|
||||
<KFAPMiddleBar :chartData="{
|
||||
allPlaceNames, incomeValueData, profitProportionData, costValueData, costCompletedData
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
import topItem from './profit-item-middle.vue'
|
||||
import KFAPMiddleBar from './KFAPMiddleBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, topItem, KFAPMiddleBar },
|
||||
props: {
|
||||
middleChartData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
middleItemData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
parentItemList: [
|
||||
{ name: "营业收入", targetValue: 0, value: 0, proportion: 0, route: 'operatingRevenue' },
|
||||
{ name: "成本", targetValue: 0, value: 0, proportion: 0, route: 'cost/cost' },
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
allPlaceNames() {
|
||||
if (!Array.isArray(this.middleChartData)) return [];
|
||||
return this.middleChartData.map(item => this.cleanPlaceName(item.name)).filter(Boolean)
|
||||
},
|
||||
PlaceNames() {
|
||||
if (!Array.isArray(this.middleChartData)) return [];
|
||||
return this.middleChartData.map(item => item.name).filter(Boolean);
|
||||
},
|
||||
profitProportionData() {
|
||||
if (!Array.isArray(this.middleChartData)) return [];
|
||||
return this.PlaceNames.map(place => {
|
||||
const target = this.middleChartData.find(item => item.name === place);
|
||||
return target?.profitProportion || 0;
|
||||
});
|
||||
},
|
||||
incomeValueData() {
|
||||
if (!Array.isArray(this.middleChartData)) return [];
|
||||
return this.PlaceNames.map(place => {
|
||||
const target = this.middleChartData.find(item => item.name === place);
|
||||
return target?.income || 0;
|
||||
});
|
||||
},
|
||||
costValueData() {
|
||||
if (!Array.isArray(this.middleChartData)) return [];
|
||||
return this.PlaceNames.map(place => {
|
||||
const target = this.middleChartData.find(item => item.name === place);
|
||||
return target?.cost || 0;
|
||||
});
|
||||
},
|
||||
|
||||
costCompletedData() {
|
||||
if (!Array.isArray(this.middleChartData)) return [];
|
||||
return this.PlaceNames.map(place => {
|
||||
const target = this.middleChartData.find(item => item.name === place);
|
||||
return target?.costCompleted || 0;
|
||||
});
|
||||
},
|
||||
|
||||
targetItemList() {
|
||||
// 基于 parentItemList 的名称,匹配 productAndSaleData 中对应名称的数据
|
||||
return this.parentItemList.map(parentItem => {
|
||||
// 按名称精准匹配(name 完全一致才关联)
|
||||
const productItem = this.middleItemData.find(
|
||||
item => item.name === parentItem.name
|
||||
) || {};
|
||||
|
||||
// 优先使用匹配到的 productItem 数据,缺失则用 parentItem 的默认值(0)
|
||||
return {
|
||||
name: parentItem.name, // 保留原名称(销量、产量等)
|
||||
targetValue: productItem.targetValue ?? parentItem.targetValue ?? 0,
|
||||
value: productItem.value ?? parentItem.value ?? 0,
|
||||
proportion: productItem.proportion ?? parentItem.proportion ?? 0,
|
||||
completed: productItem.completed ?? parentItem.completed ?? 1,
|
||||
route: parentItem.route // 路由沿用 parentItem 的配置
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
cleanPlaceName(place) {
|
||||
return place?.replace('分公司', '') || ''; // 匹配“分公司”并替换为空,兼容null/undefined
|
||||
},
|
||||
// // 唯一的地名提取方法(定义在 methods 中,确保是函数)
|
||||
// extractPlace(item) {
|
||||
// if (!item?.name) return ''; // 处理空值,避免报错
|
||||
// const match = item.name.match(/(.+?)(基地|利润总额|营业收入|成本)/);
|
||||
// return match ? match[1] : item.name;
|
||||
// }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* 样式部分保持不变 */
|
||||
.scroll-container {
|
||||
max-height: 210px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 10px 0;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.proBarInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 27px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.slot {
|
||||
width: 21px;
|
||||
height: 23px;
|
||||
background: rgba(0, 106, 205, 0.22);
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #68B5FF;
|
||||
line-height: 23px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eq-name {
|
||||
margin-left: 8px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eqStatus {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.splitLine {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid #ADADAD;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.yield {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #00FFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.proBarWrapper {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
margin-top: 6px;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.proBarLine {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #ACACAC 100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.proBarLineTop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(53, 223, 247, 0) 0%, rgba(54, 220, 246, 0.92) 92%, #36F6E5 100%, #37ACF5 100%);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 57px;
|
||||
bottom: 42px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #00E8FF;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
273
src/views/home/components/KFAPMiddleBar.vue
Normal file
273
src/views/home/components/KFAPMiddleBar.vue
Normal file
@@ -0,0 +1,273 @@
|
||||
<template>
|
||||
<div class="coreBar" style="height: 100%;">
|
||||
<div class="barTop">
|
||||
<!-- 标题:单独左对齐 -->
|
||||
<!-- <div class="title">销售指标趋势</div> -->
|
||||
<!-- 关键:新增右侧容器,包裹图例和按钮组,实现整体靠右 -->
|
||||
<div class="right-container">
|
||||
<div class="legend">
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon line yield"></span>
|
||||
利润
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square target"></span>
|
||||
营业收入
|
||||
</span>
|
||||
<!-- 给第三个、第四个图例项加 close-item 类 -->
|
||||
<span class="legend-item close-item">
|
||||
<span class="legend-icon square achieved"></span>
|
||||
<!-- 达标 -->
|
||||
</span>
|
||||
<span class="legend-item close-item">
|
||||
<span class="legend-icon square unachieved"></span>
|
||||
成本
|
||||
</span>
|
||||
</div>
|
||||
<!-- <div class="button-group">
|
||||
<div style="letter-spacing: 8px" class="item-button" :class="{ active: activeButton === 0 }"
|
||||
@click="activeButton = 0">
|
||||
销量
|
||||
</div>
|
||||
<div style="letter-spacing: 8px" class="item-button" :class="{ active: activeButton === 1 }"
|
||||
@click="activeButton = 1">
|
||||
毛利率
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||
<coreLineChart :chart-data="{
|
||||
series: chartSeries,
|
||||
allPlaceNames: chartData.allPlaceNames
|
||||
}" style="height: 100%; width: 100%" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import coreLineChart from './operatingLineBar.vue';
|
||||
import * as echarts from 'echarts';
|
||||
export default {
|
||||
name: "Container",
|
||||
components: { coreLineChart },
|
||||
props: ['chartData' ],
|
||||
data() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 原有计算属性(allPlaceNames、profitProportionData 等)保持不变
|
||||
// 新增:定义图表 series 配置
|
||||
chartSeries() {
|
||||
const { allPlaceNames, incomeValueData, profitProportionData, costValueData, costCompletedData } = this.chartData
|
||||
// 处理空数据默认值
|
||||
const incomeData = incomeValueData || Array(allPlaceNames.length).fill(0);
|
||||
const profitData = profitProportionData || Array(allPlaceNames.length).fill(0);
|
||||
const costData = costValueData || Array(allPlaceNames.length).fill(0);
|
||||
|
||||
return [
|
||||
// 1. 利润占比:折线图
|
||||
{
|
||||
name: '利润',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
lineStyle: {
|
||||
color: 'rgba(40, 138, 255, .5)',
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(40, 138, 255, 1)',
|
||||
borderColor: 'rgba(40, 138, 255, 1)',
|
||||
borderWidth: 2,
|
||||
radius: 4
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.2,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||
{ offset: 1, color: 'rgba(255, 132, 0, 0)' }
|
||||
])
|
||||
},
|
||||
data: profitData,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
// 2. 营业收入:柱状图
|
||||
{
|
||||
name: '营业收入',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 18,
|
||||
itemStyle: {
|
||||
color: '#2889FF',
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
},
|
||||
data: incomeData
|
||||
},
|
||||
// 3. 成本:柱状图(动态颜色)
|
||||
{
|
||||
name: '成本',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 18,
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
const completed = costCompletedData?.[params.dataIndex] ?? 0;
|
||||
return completed === 1 ? 'rgba(255, 132, 0, 1)' : 'rgba(40, 203, 151, 1)';
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
},
|
||||
data: costData
|
||||
}
|
||||
];
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coreBar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
|
||||
.barTop {
|
||||
display: flex;
|
||||
justify-content: flex-end; // 标题左、右侧容器右,整体两端对齐
|
||||
align-items: center; // 垂直居中,避免上下错位
|
||||
gap: 16px; // 标题与右侧容器的最小间距,防止拥挤
|
||||
width: 100%; // 确保占满父容器,实现两端对齐
|
||||
|
||||
.title {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
color: #000000;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
// 标题固定在左侧,不挤压右侧空间
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||
.right-container {
|
||||
display: flex;
|
||||
align-items: center; // 图例和按钮组垂直居中
|
||||
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||
}
|
||||
|
||||
// 2. 图例:在右侧容器内横向排列
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 16px; // 图例项之间间距,避免重叠
|
||||
align-items: center;
|
||||
// 移除原margin-left,避免位置偏移
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
white-space: nowrap; // 防止图例文字换行
|
||||
}
|
||||
|
||||
.legend-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.legend-icon.line {
|
||||
width: 12px;
|
||||
height: 2px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: -2px;
|
||||
left: 3px;
|
||||
width: 6px;
|
||||
border-radius: 50%;
|
||||
height: 6px;
|
||||
background-color: rgba(40, 138, 255, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.legend-icon.square {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
// 图例颜色
|
||||
.yield {
|
||||
background: rgba(40, 138, 255, 1);
|
||||
}
|
||||
|
||||
.target {
|
||||
background: #2889FF;
|
||||
}
|
||||
|
||||
.achieved {
|
||||
background: rgba(40, 203, 151, 1);
|
||||
}
|
||||
|
||||
.unachieved {
|
||||
background: rgba(255, 132, 0, 1);
|
||||
}
|
||||
.legend-item.close-item+.legend-item.close-item {
|
||||
margin-left: -8px; // 负margin抵消默认gap,数值可根据需求调整
|
||||
// 若原gap是16px,想让两者间距变为8px,设置 margin-left: -8px 即可
|
||||
}
|
||||
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||
.button-group {
|
||||
display: flex;
|
||||
position: relative;
|
||||
gap: 2px;
|
||||
width: 283px;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
background: #ecf4fe;
|
||||
border-radius: 12px;
|
||||
// 移除原margin-right,由右侧容器统一控制
|
||||
margin: 0;
|
||||
|
||||
.item-button {
|
||||
cursor: pointer;
|
||||
width: 142px;
|
||||
height: 24px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #0b58ff;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
letter-spacing: 8px; // 确保文字间距生效
|
||||
padding-left: 8px; // 抵消letter-spacing导致的文字左偏
|
||||
}
|
||||
|
||||
.item-button.active {
|
||||
width: 142px;
|
||||
height: 24px;
|
||||
background: #3071ff;
|
||||
border-radius: 12px;
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
404
src/views/home/components/PSDO.vue
Normal file
404
src/views/home/components/PSDO.vue
Normal file
@@ -0,0 +1,404 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="库存" icon="cockpitItemIcon" size="psiMiddleBg" topSize="KFAPTopTitle">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex;width: 100%;">
|
||||
<div class="bottom"
|
||||
style="height: 376px;display: flex; width: 100%;background-color: rgba(249, 252, 255, 1);flex-direction: column;">
|
||||
<!-- <top-item /> -->
|
||||
<div class="data" style="width: 100%; display: flex;padding: 26px 0 0 0 ;justify-content: space-evenly;">
|
||||
<div class="data-item" style="display: flex;gap: 21px;">
|
||||
<img style="width: 100px;height: 103px;" src="../../../assets/img/monthNum.png" alt="">
|
||||
<div class="text">
|
||||
<div class="number">
|
||||
{{ stockVO.endMonthValue || 0 }}
|
||||
</div>
|
||||
<div class="title">
|
||||
月末数量
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item" style="display: flex;gap: 21px;">
|
||||
<img style="width: 100px;height: 103px;" src="../../../assets/img/averageNum.png" alt="">
|
||||
<div class="text">
|
||||
<div class="number">
|
||||
{{ stockVO.avgValue || 0 }}
|
||||
</div>
|
||||
<div class="title">
|
||||
平均数量
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item" style="display: flex;gap: 21px;">
|
||||
<img style="width: 100px;height: 103px;" src="../../../assets/img/dayNum.png" alt="">
|
||||
<div class="text">
|
||||
<div class="number">
|
||||
{{ stockVO.totalDays || 0 }}
|
||||
</div>
|
||||
<div class="title">
|
||||
折合天数
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart" style="height: 240px;">
|
||||
<PSDOBar :stockValues="stockValues" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
// import * as echarts from 'echarts'
|
||||
import topItem from './operating-item.vue'
|
||||
import PSDOBar from './PSDOBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, topItem, PSDOBar },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
stockVO: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
stockValues:[],
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
stockVO: {
|
||||
handler(newValue, oldValue) {
|
||||
this.stockValues = newValue.stockValues
|
||||
},
|
||||
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>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||
.scroll-container {
|
||||
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||
max-height: 210px;
|
||||
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||
overflow-y: auto;
|
||||
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||
overflow-x: hidden;
|
||||
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||
padding: 10px 0;
|
||||
|
||||
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||
/* Chrome/Safari */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
scrollbar-width: none;
|
||||
/* IE/Edge */
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||
.proBarInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 27px;
|
||||
/* 调整内边距,优化排版 */
|
||||
margin-bottom: 10px;
|
||||
/* 设备项之间的垂直间距 */
|
||||
}
|
||||
|
||||
/* 原有样式保留,优化细节 */
|
||||
.proBarInfoEqInfo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
/* 垂直居中,避免序号/文字错位 */
|
||||
}
|
||||
|
||||
.slot {
|
||||
width: 21px;
|
||||
height: 23px;
|
||||
background: rgba(0, 106, 205, 0.22);
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #68B5FF;
|
||||
line-height: 23px;
|
||||
/* 垂直居中文字 */
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eq-name {
|
||||
margin-left: 8px;
|
||||
/* 增加与序号的间距 */
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eqStatus {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.splitLine {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid #ADADAD;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #00FFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 序号和设备名垂直居中 */
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 状态/分割线/百分比垂直居中 */
|
||||
}
|
||||
|
||||
.proBarWrapper {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
margin-top: 6px;
|
||||
/* 进度条与上方信息的间距 */
|
||||
border-radius: 5px;
|
||||
/* 进度条圆角,优化视觉 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.proBarLine {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #ACACAC 100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.proBarLineTop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(53, 223, 247, 0) 0%, rgba(54, 220, 246, 0.92) 92%, #36F6E5 100%, #37ACF5 100%);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
/* 进度变化时添加过渡动画,更流畅 */
|
||||
}
|
||||
|
||||
/* 图表相关样式保留 */
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 57px;
|
||||
bottom: 42px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #00E8FF;
|
||||
}
|
||||
|
||||
.data-item {
|
||||
align-items: center;
|
||||
.number {
|
||||
font-family: "YouSheBiaoTiHei", sans-serif;
|
||||
width: 130px;
|
||||
height: 38px;
|
||||
font-family: YouSheBiaoTiHei;
|
||||
font-size: 38px;
|
||||
color: #000000;
|
||||
line-height: 38px;
|
||||
letter-spacing: 2px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
.title{
|
||||
width: 76px;
|
||||
height: 25px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
color: #868687;
|
||||
line-height: 25px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
178
src/views/home/components/PSDOBar.vue
Normal file
178
src/views/home/components/PSDOBar.vue
Normal file
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<div class="coreBar" style="height: 100%;">
|
||||
<div class="barTop">
|
||||
<!-- 标题:单独左对齐 -->
|
||||
<!-- <div class="title">销售指标趋势</div> -->
|
||||
<!-- 关键:新增右侧容器,包裹图例和按钮组,实现整体靠右 -->
|
||||
<div class="right-container">
|
||||
<div class="legend">
|
||||
<span class="legend-item close-item">
|
||||
<span class="legend-icon square unachieved"></span>
|
||||
库存
|
||||
</span>
|
||||
</div>
|
||||
<!-- <div class="button-group">
|
||||
<div style="letter-spacing: 8px" class="item-button" :class="{ active: activeButton === 0 }"
|
||||
@click="activeButton = 0">
|
||||
销量
|
||||
</div>
|
||||
<div style="letter-spacing: 8px" class="item-button" :class="{ active: activeButton === 1 }"
|
||||
@click="activeButton = 1">
|
||||
毛利率
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||
<psrChartBar :stockValues="stockValues" style="height: 100%; width: 100%" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import psrChartBar from './psrChartBar.vue';
|
||||
export default {
|
||||
name: "Container",
|
||||
components: { psrChartBar },
|
||||
props: [ "stockValues"],
|
||||
data() {
|
||||
return {
|
||||
activeButton: 0,
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coreBar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
|
||||
.barTop {
|
||||
display: flex;
|
||||
justify-content: flex-end; // 标题左、右侧容器右,整体两端对齐
|
||||
align-items: center; // 垂直居中,避免上下错位
|
||||
gap: 16px; // 标题与右侧容器的最小间距,防止拥挤
|
||||
width: 100%; // 确保占满父容器,实现两端对齐
|
||||
|
||||
.title {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
color: #000000;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
// 标题固定在左侧,不挤压右侧空间
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||
.right-container {
|
||||
display: flex;
|
||||
align-items: center; // 图例和按钮组垂直居中
|
||||
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||
}
|
||||
|
||||
// 2. 图例:在右侧容器内横向排列
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 16px; // 图例项之间间距,避免重叠
|
||||
align-items: center;
|
||||
// 移除原margin-left,避免位置偏移
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
white-space: nowrap; // 防止图例文字换行
|
||||
}
|
||||
|
||||
.legend-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.legend-icon.line {
|
||||
width: 12px;
|
||||
height: 2px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: -2px;
|
||||
left: 3px;
|
||||
width: 6px;
|
||||
border-radius: 50%;
|
||||
height: 6px;
|
||||
background-color: rgba(40, 138, 255, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.legend-icon.square {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.unachieved {
|
||||
background: rgba(40, 137, 255, 1);
|
||||
}
|
||||
.legend-item.close-item+.legend-item.close-item {
|
||||
margin-left: -8px; // 负margin抵消默认gap,数值可根据需求调整
|
||||
// 若原gap是16px,想让两者间距变为8px,设置 margin-left: -8px 即可
|
||||
}
|
||||
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||
.button-group {
|
||||
display: flex;
|
||||
position: relative;
|
||||
gap: 2px;
|
||||
width: 283px;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
background: #ecf4fe;
|
||||
border-radius: 12px;
|
||||
// 移除原margin-right,由右侧容器统一控制
|
||||
margin: 0;
|
||||
|
||||
.item-button {
|
||||
cursor: pointer;
|
||||
width: 142px;
|
||||
height: 24px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #0b58ff;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
letter-spacing: 8px; // 确保文字间距生效
|
||||
padding-left: 8px; // 抵消letter-spacing导致的文字左偏
|
||||
}
|
||||
|
||||
.item-button.active {
|
||||
width: 142px;
|
||||
height: 24px;
|
||||
background: #3071ff;
|
||||
border-radius: 12px;
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
253
src/views/home/components/baseTable.vue
Normal file
253
src/views/home/components/baseTable.vue
Normal file
@@ -0,0 +1,253 @@
|
||||
<!--
|
||||
* @Date: 2020-12-14 09:07:03
|
||||
* @LastEditors: zhp
|
||||
* @LastEditTime: 2024-09-05 09:50:14
|
||||
* @FilePath: \mt-bus-fe\src\views\OperationalOverview\components\baseTable.vue
|
||||
* @Description:
|
||||
-->
|
||||
<template>
|
||||
<div class="visual-base-table-container">
|
||||
<el-table :max-height="maxHeight" ref="scroll_Table" @mouseenter.native="autoScroll(true)"
|
||||
@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"
|
||||
v-bind="item">
|
||||
<template slot-scope="scope">
|
||||
|
||||
<component :is="item.subcomponent" v-if="item.subcomponent" :inject-data="{...scope.row, ...item}"
|
||||
@emitData="emitData" />
|
||||
<span v-else>{{ scope.row[item.prop] | commonFilter(item.filter) }}</span>
|
||||
|
||||
</template>
|
||||
</el-table-column>
|
||||
<slot name="content" />
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { isObject, isString } from 'lodash'
|
||||
export default {
|
||||
name: 'BaseTable',
|
||||
filters: {
|
||||
commonFilter: (source, filterType = a => a) => {
|
||||
return filterType(source)
|
||||
}
|
||||
},
|
||||
props: {
|
||||
maxHeight: {
|
||||
type: [Number, String], // 支持数字(如300)或字符串(如'300px')
|
||||
required: false,
|
||||
default: 200 // 原固定值,作为默认 fallback
|
||||
},
|
||||
tableData: {
|
||||
type: Array,
|
||||
required: true,
|
||||
validator: val => val.filter(item => !isObject(item)).length === 0
|
||||
},
|
||||
tableConfig: {
|
||||
type: Array,
|
||||
required: true,
|
||||
validator: val => val.filter(item => !isString(item.prop) || !isString(item.label)).length === 0
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
},
|
||||
page: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 1
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 5
|
||||
},
|
||||
beilv: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
showOverflow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showIndex: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableConfigBak: [],
|
||||
selectedBox: new Array(100).fill(true)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
renderData() {
|
||||
if (this.tableData.length && !this.tableData[0]._pageIndex) {
|
||||
this.tableData.forEach((item, index) => {
|
||||
item._pageIndex = (this.page - 1) * this.limit + index + 1
|
||||
})
|
||||
}
|
||||
return this.tableData.slice((this.page - 1) * this.limit, this.page * this.limit)
|
||||
},
|
||||
renderTableHeadList() {
|
||||
return this.tableConfig.filter((item, index) => {
|
||||
return this.selectedBox[index]
|
||||
})
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
this.selectedBox = new Array(100).fill(true)
|
||||
},
|
||||
mounted() {
|
||||
this.autoScroll()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.autoScroll(true)
|
||||
},
|
||||
methods: {
|
||||
autoScroll(stop) {
|
||||
const table = this.$refs.scroll_Table
|
||||
if (!table) return; // 防止table未加载时出错
|
||||
|
||||
const divData = table.$refs.bodyWrapper
|
||||
if (stop) {
|
||||
window.clearInterval(this.scrolltimer)
|
||||
} else {
|
||||
// 先清除已有计时器,避免重复创建
|
||||
if (this.scrolltimer) {
|
||||
window.clearInterval(this.scrolltimer)
|
||||
}
|
||||
|
||||
this.scrolltimer = window.setInterval(() => {
|
||||
// 每次滚动1像素
|
||||
divData.scrollTop += 1
|
||||
|
||||
// 关键修改:使用>=判断,允许微小像素偏差
|
||||
if (divData.scrollTop + divData.clientHeight >= divData.scrollHeight - 1) {
|
||||
// 滚动到底部后,重置到顶部(延迟一点更自然)
|
||||
// setTimeout(() => {
|
||||
divData.scrollTop = 0
|
||||
// }, 2000); // 停顿500ms后再从头滚动
|
||||
}
|
||||
}, 200) // 滚动速度(数值越小越快)
|
||||
}
|
||||
},
|
||||
emitData(val) {
|
||||
this.$emit('emitFun', val)
|
||||
},
|
||||
setRowStyle(v) {
|
||||
if (v.rowIndex % 2 === 0) {
|
||||
return {
|
||||
background: '#F9FCFF',
|
||||
color: 'rgba(87, 87, 87, 1)',
|
||||
height: 35 + 'px',
|
||||
lineHeight: 26 + 'px',
|
||||
padding: 0,
|
||||
fontSize: 12 + 'px'
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
background: 'rgba(239, 243, 248, 1)',
|
||||
color: 'rgba(87, 87, 87, 1)',
|
||||
height: 35 + 'px',
|
||||
lineHeight: 26 + 'px',
|
||||
padding: 0,
|
||||
fontSize: 12 + 'px'
|
||||
}
|
||||
}
|
||||
},
|
||||
setCellStyle() {
|
||||
return {
|
||||
// lineHeight: 23 + 'px'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
// @import "./styles/index.scss";
|
||||
.visual-base-table-container {
|
||||
.el-table {
|
||||
border: 0;
|
||||
// .el-table__body-wrapper::-webkit-scrollbar-thumb {
|
||||
// background-color: blue;
|
||||
// border-radius: 3px;
|
||||
// }
|
||||
// 关键修改:隐藏滚动条但保留滚动功能
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
background: transparent;
|
||||
}
|
||||
// 隐藏表头的gutter
|
||||
.el-table__header .el-table__cell.gutter {
|
||||
display: none !important;
|
||||
}
|
||||
// 表格主体内容区滚动条处理
|
||||
.el-table__body-wrapper {
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
overflow-y: auto !important; // 确保垂直滚动可用
|
||||
overflow-x: auto !important; // 确保水平滚动可用
|
||||
}
|
||||
|
||||
// 固定列滚动条处理
|
||||
.el-table__fixed,
|
||||
.el-table__fixed-right {
|
||||
.el-table__fixed-body-wrapper {
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
overflow-y: auto !important;
|
||||
overflow-x: auto !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-table::before,.el-table--border::after {
|
||||
background-color: transparent;
|
||||
}
|
||||
.el-table th,td{
|
||||
border-color: rgba(221, 221, 221, 1) !important;
|
||||
padding: 0;
|
||||
}
|
||||
.el-table tr {
|
||||
background: transparent;
|
||||
}
|
||||
.el-table__row:hover > td {
|
||||
background-color: rgba(79,114,136,0.29) !important;
|
||||
}
|
||||
|
||||
.el-table__row--striped:hover > td {
|
||||
background-color: rgba(79,114,136,0.29) !important;
|
||||
}
|
||||
}
|
||||
// .setting {
|
||||
// text-align: right;
|
||||
// padding: 15px;
|
||||
// .setting-box {
|
||||
// width: 100px;
|
||||
// }
|
||||
// i {
|
||||
// color: #aaa;
|
||||
// @extend .pointer;
|
||||
// }
|
||||
// }
|
||||
|
||||
</style>
|
||||
287
src/views/home/components/bottomLeftContainer.vue
Normal file
287
src/views/home/components/bottomLeftContainer.vue
Normal file
@@ -0,0 +1,287 @@
|
||||
<template>
|
||||
<div class="cockpitContainer" :class="['cockpitContainer__' + size]">
|
||||
<div class="container-top" style="display: flex;gap: 9px;">
|
||||
<div class="title-wrapper content-top-left">
|
||||
<svg-icon class="title-icon" :icon-class="icon" />
|
||||
<span class="title-text">
|
||||
{{ name }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- <div class="content-top-left">
|
||||
<svg-icon style="font-size: 32px; margin-left: 16px" :icon-class="icon" />
|
||||
<span style="
|
||||
width: 412px;
|
||||
height: 32px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 24px;
|
||||
color: #000000;
|
||||
line-height: 60px;
|
||||
letter-spacing: 3px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
">
|
||||
{{ name }}
|
||||
</span>
|
||||
</div> -->
|
||||
<div class="title-wrapper content-top-right">
|
||||
<svg-icon class="title-icon" :icon-class="icon" />
|
||||
<span class="title-text">
|
||||
{{ nameTwo }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- <div class="content-top-right">
|
||||
<svg-icon style="font-size: 32px; margin-left: 16px" :icon-class="icon" />
|
||||
<span style="
|
||||
width: 412px;
|
||||
height: 32px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 24px;
|
||||
color: #000000;
|
||||
line-height: 60px;
|
||||
letter-spacing: 3px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
">
|
||||
{{ nameTwo }}
|
||||
</span>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="container-body">
|
||||
<slot>
|
||||
<div class="test-body">something test....</div>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Container',
|
||||
components: {},
|
||||
// eslint-disable-next-line vue/require-prop-types
|
||||
props: ['name', 'size', 'icon','nameTwo'],
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.cockpitContainer {
|
||||
display: inline-block;
|
||||
// width: 100%;
|
||||
// height: 00px;
|
||||
padding: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
.title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// margin-left: 10px;
|
||||
/* 垂直居中关键属性 */
|
||||
height: 100%;
|
||||
/* 继承父容器高度,确保垂直居中范围 */
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
font-size: 30px;
|
||||
margin-right: 12px;
|
||||
margin-top: 4px;
|
||||
margin-left: 10px;
|
||||
/* 图标和文字之间的间距 */
|
||||
flex-shrink: 0;
|
||||
/* 防止图标被压缩 */
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 24px;
|
||||
color: #000000;
|
||||
letter-spacing: 3px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
// 移除固定行高,避免影响垂直对齐
|
||||
// line-height: 60px;
|
||||
}
|
||||
.content-top-left {
|
||||
width: 270px;
|
||||
height: 60px;
|
||||
background: linear-gradient(90deg, #FFFFFF 0%, rgba(253, 255, 255, 0) 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 左上角折现边框(主边框) */
|
||||
.content-top-left::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid;
|
||||
border-image: linear-gradient(277deg, rgba(255, 255, 255, 0), rgba(92, 140, 255, 1)) 1 1;
|
||||
clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%, 0 20px);
|
||||
/* 切割左上角形成折现 */
|
||||
}
|
||||
|
||||
/* 左上角折现细节(仅保留边框线条,去掉蓝色填充) */
|
||||
.content-top-left::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
/* 调整为纯边框线条,去掉背景填充 */
|
||||
background: #EFF3F8;
|
||||
border-top: 1px solid rgba(92, 140, 255, 1);
|
||||
border-left: 1px solid rgba(92, 140, 255, 1);
|
||||
transform: rotate(135deg) translate(-50%, -50%);
|
||||
transform-origin: top left;
|
||||
}
|
||||
|
||||
.content-top-right {
|
||||
width: 270px;
|
||||
height: 60px;
|
||||
background: linear-gradient(90deg, #FFFFFF 0%, rgba(253, 255, 255, 0) 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 左上角折现边框(主边框) */
|
||||
.content-top-right::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid;
|
||||
border-image: linear-gradient(277deg, rgba(255, 255, 255, 0), rgba(92, 140, 255, 1)) 1 1;
|
||||
clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%, 0 20px);
|
||||
/* 切割左上角形成折现 */
|
||||
}
|
||||
|
||||
/* 左上角折现细节(仅保留边框线条,去掉蓝色填充) */
|
||||
.content-top-right::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
/* 调整为纯边框线条,去掉背景填充 */
|
||||
background: #EFF3F8;
|
||||
border-top: 1px solid rgba(92, 140, 255, 1);
|
||||
border-left: 1px solid rgba(92, 140, 255, 1);
|
||||
transform: rotate(135deg) translate(-50%, -50%);
|
||||
transform-origin: top left;
|
||||
}
|
||||
&__topBasic {
|
||||
background: url(../../../assets/img/top-basic.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
&__bottomBasic {
|
||||
background: url(../../../assets/img/bottom-basic.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
// &__left {
|
||||
// background: url(../../../../../../../assets/img/left.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__energyConsumption {
|
||||
// background: url(../../../../../../../assets/img/energyConsumption.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__left2 {
|
||||
// background: url(../../assets/left_2.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__left3 {
|
||||
// background: url(../../assets/left_3.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__mid2 {
|
||||
// background: url(../../assets/mid_2.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__mid3 {
|
||||
// background: url(../../assets/mid_3.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__right1 {
|
||||
// background: url(../../assets/right_1.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__right2 {
|
||||
// background: url(../../assets/right_2.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__right3 {
|
||||
// background: url(../../assets/right_3.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__weekRight2 {
|
||||
// background: url(../../assets/week_right_2.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__weekMidTop {
|
||||
// background: url(../../assets/week-mid-top.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__weekMidMid {
|
||||
// background: url(../../assets/week-mid-mid.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &::after {
|
||||
// content: ' ';
|
||||
// display: block;
|
||||
// position: absolute;
|
||||
// left: 0;
|
||||
// top: 0;
|
||||
// right: 0;
|
||||
// bottom: 0;
|
||||
// // background: inherit;
|
||||
// /* 设置模糊,不用 filter */
|
||||
// backdrop-filter: blur(5px);
|
||||
// z-index: -1;
|
||||
// }
|
||||
}
|
||||
|
||||
.container-body {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
212
src/views/home/components/bottomMiddleContainer.vue
Normal file
212
src/views/home/components/bottomMiddleContainer.vue
Normal file
@@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<div class="cockpitContainer" :class="['cockpitContainer__' + size]">
|
||||
<div class="container-top">
|
||||
<div class="title-wrapper">
|
||||
<svg-icon class="title-icon" :icon-class="icon" />
|
||||
<span class="title-text">
|
||||
{{ name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-body">
|
||||
<slot>
|
||||
<div class="test-body">something test....</div>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Container',
|
||||
components: {},
|
||||
// eslint-disable-next-line vue/require-prop-types
|
||||
props: ['name', 'size', 'icon', 'nameTwo'],
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.cockpitContainer {
|
||||
display: inline-block;
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
padding: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
.container-top {
|
||||
width: 746px;
|
||||
height: 60px;
|
||||
background: linear-gradient(90deg, #FFFFFF 50%, rgba(253, 255, 255, 0) 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
.title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 10px;
|
||||
/* 垂直居中关键属性 */
|
||||
height: 100%;
|
||||
/* 继承父容器高度,确保垂直居中范围 */
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
font-size: 30px;
|
||||
margin-right: 12px;
|
||||
margin-top: 4px;
|
||||
/* 图标和文字之间的间距 */
|
||||
flex-shrink: 0;
|
||||
/* 防止图标被压缩 */
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 24px;
|
||||
color: #000000;
|
||||
letter-spacing: 3px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
// 移除固定行高,避免影响垂直对齐
|
||||
// line-height: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 左上角折现边框(主边框) */
|
||||
.container-top::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid;
|
||||
border-image: linear-gradient(277deg, rgba(255, 255, 255, 0), rgba(92, 140, 255, 1)) 1 1;
|
||||
clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%, 0 20px);
|
||||
/* 切割左上角形成折现 */
|
||||
}
|
||||
|
||||
/* 左上角折现细节(仅保留边框线条,去掉蓝色填充) */
|
||||
.container-top::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
/* 调整为纯边框线条,去掉背景填充 */
|
||||
background: #EFF3F8;
|
||||
border-top: 1px solid rgba(92, 140, 255, 1);
|
||||
border-left: 1px solid rgba(92, 140, 255, 1);
|
||||
transform: rotate(135deg) translate(-50%, -50%);
|
||||
transform-origin: top left;
|
||||
}
|
||||
|
||||
&__topBasic {
|
||||
background: url(../../../assets/img/top-basic.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__bottomBasic {
|
||||
background: url(../../../assets/img/bottom-basic.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
// &__left {
|
||||
// background: url(../../../../../../../assets/img/left.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__energyConsumption {
|
||||
// background: url(../../../../../../../assets/img/energyConsumption.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__left2 {
|
||||
// background: url(../../assets/left_2.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__left3 {
|
||||
// background: url(../../assets/left_3.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__mid2 {
|
||||
// background: url(../../assets/mid_2.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__mid3 {
|
||||
// background: url(../../assets/mid_3.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__right1 {
|
||||
// background: url(../../assets/right_1.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__right2 {
|
||||
// background: url(../../assets/right_2.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__right3 {
|
||||
// background: url(../../assets/right_3.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__weekRight2 {
|
||||
// background: url(../../assets/week_right_2.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__weekMidTop {
|
||||
// background: url(../../assets/week-mid-top.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__weekMidMid {
|
||||
// background: url(../../assets/week-mid-mid.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
&::after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
// background: inherit;
|
||||
/* 设置模糊,不用 filter */
|
||||
backdrop-filter: blur(5px);
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
.container-body {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
265
src/views/home/components/container.vue
Normal file
265
src/views/home/components/container.vue
Normal file
@@ -0,0 +1,265 @@
|
||||
<template>
|
||||
<div class="cockpitContainer" :class="['cockpitContainer__' + size]">
|
||||
<div class="content-top" :class="['content-top__' + topSize]">
|
||||
<!-- 使用 flex 容器包裹图标和文字,实现垂直居中 -->
|
||||
<div class="title-wrapper">
|
||||
<svg-icon class="title-icon" :icon-class="icon" />
|
||||
<span class="title-text">
|
||||
{{ name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cockpitContainer-body">
|
||||
<slot>
|
||||
<div class="test-body">something test....</div>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Container',
|
||||
components: {},
|
||||
// eslint-disable-next-line vue/require-prop-types
|
||||
props: ['name', 'size', 'icon', 'topSize'],
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.cockpitContainer {
|
||||
display: inline-block;
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
padding: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
.content-top {
|
||||
height: 60px;
|
||||
.title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 10px;
|
||||
/* 垂直居中关键属性 */
|
||||
height: 100%;
|
||||
/* 继承父容器高度,确保垂直居中范围 */
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
font-size: 30px;
|
||||
margin-right: 12px;
|
||||
margin-top: 4px;
|
||||
/* 图标和文字之间的间距 */
|
||||
flex-shrink: 0;
|
||||
/* 防止图标被压缩 */
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 24px;
|
||||
color: #000000;
|
||||
letter-spacing: 3px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
// 移除固定行高,避免影响垂直对齐
|
||||
// line-height: 60px;
|
||||
}
|
||||
// width: 547px;
|
||||
// background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
&__basic {
|
||||
// width: 547px;
|
||||
background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__middle {
|
||||
background: url(../../../assets/img/topTileMiddle.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__large {
|
||||
background: url(../../../assets/img/topTitleLargeBg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__KFAPTopTitle {
|
||||
background: url(../../../assets/img/KFAPTopTitle.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__psiTopTitleBasic {
|
||||
background: url(../../../assets/img/psiTopTitleBasic.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
&__rawTopTitleLarge {
|
||||
background: url(../../../assets/img/rawTopTitleLarge.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&__topBasic {
|
||||
background: url(../../../assets/img/top-basic.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__bottomBasic {
|
||||
background: url(../../../assets/img/bottom-basic.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__operatingBasic {
|
||||
background: url(../../../assets/img/operating-basic.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__operatingLarge {
|
||||
background: url(../../../assets/img/operating-large.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__profitTopBasic {
|
||||
background: url(../../../assets/img/profitTopBasic.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__profitMiddleBasic {
|
||||
background: url(../../../assets/img/profitMiddleBasic.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__psiBasicBg {
|
||||
background: url(../../../assets/img/psiBasicBg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__psiMiddleBg {
|
||||
background: url(../../../assets/img/psiMiddleBg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__costBasicBg {
|
||||
background: url(../../../assets/img/costBasicBg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
&__rawTopBg {
|
||||
background: url(../../../assets/img/rawTopBg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
// &__left {
|
||||
// background: url(../../../../../../../assets/img/left.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__energyConsumption {
|
||||
// background: url(../../../../../../../assets/img/energyConsumption.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__left2 {
|
||||
// background: url(../../assets/left_2.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__left3 {
|
||||
// background: url(../../assets/left_3.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__mid2 {
|
||||
// background: url(../../assets/mid_2.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__mid3 {
|
||||
// background: url(../../assets/mid_3.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__right1 {
|
||||
// background: url(../../assets/right_1.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__right2 {
|
||||
// background: url(../../assets/right_2.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__right3 {
|
||||
// background: url(../../assets/right_3.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__weekRight2 {
|
||||
// background: url(../../assets/week_right_2.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__weekMidTop {
|
||||
// background: url(../../assets/week-mid-top.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
// &__weekMidMid {
|
||||
// background: url(../../assets/week-mid-mid.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
// background-position: 0 0;
|
||||
// }
|
||||
|
||||
&::after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
// background: inherit;
|
||||
/* 设置模糊,不用 filter */
|
||||
backdrop-filter: blur(5px);
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
.container-body {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
199
src/views/home/components/core-bottom-leftItem.vue
Normal file
199
src/views/home/components/core-bottom-leftItem.vue
Normal file
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<div class="coreItem">
|
||||
<div class="item" 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="number">{{ item.targetValue }}</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="right">
|
||||
<div class="number" :style="{ color: getTargetColor(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 + '%',
|
||||
background: getTargetColor(item.currentValue, item.targetValue)
|
||||
}"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="yield" style="display: flex;justify-content: space-between;">
|
||||
<div class="progress-percent" :style="{ color: getTargetColor(item.currentValue, item.targetValue) }">完成率
|
||||
</div>
|
||||
<div class="progress-percent" :style="{ color: getTargetColor(item.currentValue, item.targetValue) }">
|
||||
{{ item.progress }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Container",
|
||||
components: {},
|
||||
props: ["name", "size", "icon"],
|
||||
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
|
||||
// }
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
getTargetColor(currentValue, targetValue) {
|
||||
return currentValue >= targetValue
|
||||
? "rgba(98, 213, 180, 1)"
|
||||
: "rgba(249, 164, 74, 1)";
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coreItem {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 252px;
|
||||
height: 122px;
|
||||
background: #f9fcff;
|
||||
padding: 12px 12px 0 12px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.unit {
|
||||
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;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: calc(100% - 26px);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 1px;
|
||||
height: 46px;
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(255, 0, 0, 0),
|
||||
rgba(40, 203, 151, 1));
|
||||
}
|
||||
|
||||
.left,
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
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: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.title {
|
||||
height: 14px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #868687;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.progress-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 230px;
|
||||
height: 10px;
|
||||
background: #ECEFF7;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.progress-percent {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #868687;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.yield {
|
||||
margin-top: 3px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
370
src/views/home/components/coreBottomBar.vue
Normal file
370
src/views/home/components/coreBottomBar.vue
Normal file
@@ -0,0 +1,370 @@
|
||||
<template>
|
||||
<div class="coreBar">
|
||||
<div class="barTop">
|
||||
<div class="title">销售指标趋势</div>
|
||||
<div class="legend">
|
||||
<span class="legend-item target">目标</span>
|
||||
<span class="legend-item real">实际</span>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<div style="letter-spacing: 4px" class="item-button" :class="{ active: activeButton === 0 }"
|
||||
@click="activeButton = 0">
|
||||
单价
|
||||
</div>
|
||||
<div class="button-line lineOne" v-if="activeButton !== 0 && activeButton !== 1"></div>
|
||||
<div style="letter-spacing: 4px" class="item-button" :class="{ active: activeButton === 1 }"
|
||||
@click="activeButton = 1">
|
||||
净价
|
||||
</div>
|
||||
<div class="button-line lineTwo" v-if="activeButton !== 1 && activeButton !== 2"></div>
|
||||
<div style="letter-spacing: 4px" class="item-button" :class="{ active: activeButton === 2 }"
|
||||
@click="activeButton = 2">
|
||||
销量
|
||||
</div>
|
||||
<div class="button-line lineThree" v-if="activeButton !== 2 && activeButton !== 3"></div>
|
||||
<div class="item-button" :class="{ active: activeButton === 3 }" @click="activeButton = 3">
|
||||
双镀产品
|
||||
</div>
|
||||
</div>
|
||||
</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)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import coreLineChart from './coresBar.vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: "Container",
|
||||
components: { coreLineChart },
|
||||
props: ["name", "size", "icon"],
|
||||
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: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
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: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
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)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
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)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
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)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
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)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
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)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
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)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
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]
|
||||
}
|
||||
]
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 根据激活按钮动态返回对应 series 数据
|
||||
currentSeries() {
|
||||
return this.seriesMap[this.activeButton] || [];
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 原有样式保持不变 */
|
||||
.coreBar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 12px;
|
||||
|
||||
.barTop {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
color: #000000;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
margin-left: 10px;
|
||||
|
||||
.legend-item {
|
||||
position: relative;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -13px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -18px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.target::before,
|
||||
.target::after {
|
||||
background-color: rgba(91, 230, 190, 1);
|
||||
}
|
||||
|
||||
.real::before,
|
||||
.real::after {
|
||||
background-color: rgba(255, 132, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
position: relative;
|
||||
gap: 2px;
|
||||
margin-left: 30px;
|
||||
width: 230px;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
background: #ecf4fe;
|
||||
border-radius: 12px;
|
||||
|
||||
.button-line {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid rgba(11, 88, 255, 0.25);
|
||||
}
|
||||
|
||||
.lineOne {
|
||||
top: 5px;
|
||||
left: 54px;
|
||||
}
|
||||
|
||||
.lineTwo {
|
||||
top: 5px;
|
||||
left: 108px;
|
||||
}
|
||||
|
||||
.lineThree {
|
||||
top: 5px;
|
||||
left: 162px;
|
||||
}
|
||||
|
||||
.item-button {
|
||||
cursor: pointer;
|
||||
width: 54px;
|
||||
height: 24px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #0b58ff;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.item-button.active {
|
||||
background: #3071ff;
|
||||
border-radius: 12px;
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
363
src/views/home/components/coreBottomLeft.vue
Normal file
363
src/views/home/components/coreBottomLeft.vue
Normal file
@@ -0,0 +1,363 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<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>
|
||||
<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" />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './bottomLeftContainer.vue'
|
||||
// import * as echarts from 'echarts'
|
||||
import coreBottomLeftItem from './core-bottom-leftItem.vue'
|
||||
import baseTable from './baseTable.vue'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, coreBottomLeftItem, baseTable },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
leftEqInfoData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
|
||||
type: Object,
|
||||
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: 2, eqName: '螺杆挤出', taskName: '例行维护', },
|
||||
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
|
||||
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
|
||||
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
|
||||
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
|
||||
|
||||
],
|
||||
tableProps: [
|
||||
// { prop: 'id', label: '序号', width: 50, align: 'center' },
|
||||
{ prop: 'eqName', label: '物料', align: 'left' },
|
||||
{ prop: 'taskName', label: '库存/吨', align: 'left' },
|
||||
]
|
||||
}
|
||||
},
|
||||
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>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||
.scroll-container {
|
||||
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||
max-height: 210px;
|
||||
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||
overflow-y: auto;
|
||||
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||
overflow-x: hidden;
|
||||
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||
padding: 10px 0;
|
||||
|
||||
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||
/* Chrome/Safari */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
scrollbar-width: none;
|
||||
/* IE/Edge */
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||
.proBarInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 27px;
|
||||
/* 调整内边距,优化排版 */
|
||||
margin-bottom: 10px;
|
||||
/* 设备项之间的垂直间距 */
|
||||
}
|
||||
|
||||
/* 原有样式保留,优化细节 */
|
||||
.proBarInfoEqInfo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
/* 垂直居中,避免序号/文字错位 */
|
||||
}
|
||||
|
||||
.slot {
|
||||
width: 21px;
|
||||
height: 23px;
|
||||
background: rgba(0, 106, 205, 0.22);
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #68B5FF;
|
||||
line-height: 23px;
|
||||
/* 垂直居中文字 */
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eq-name {
|
||||
margin-left: 8px;
|
||||
/* 增加与序号的间距 */
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eqStatus {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.splitLine {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid #ADADAD;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #00FFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 序号和设备名垂直居中 */
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 状态/分割线/百分比垂直居中 */
|
||||
}
|
||||
|
||||
.proBarWrapper {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
margin-top: 6px;
|
||||
/* 进度条与上方信息的间距 */
|
||||
border-radius: 5px;
|
||||
/* 进度条圆角,优化视觉 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.proBarLine {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #ACACAC 100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.proBarLineTop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(53, 223, 247, 0) 0%, rgba(54, 220, 246, 0.92) 92%, #36F6E5 100%, #37ACF5 100%);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
/* 进度变化时添加过渡动画,更流畅 */
|
||||
}
|
||||
|
||||
/* 图表相关样式保留 */
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 57px;
|
||||
bottom: 42px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #00E8FF;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
347
src/views/home/components/coreSalesKPIs.vue
Normal file
347
src/views/home/components/coreSalesKPIs.vue
Normal file
@@ -0,0 +1,347 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="销售重点指标" icon="cockpitItemIcon" size="topBasic" topSize="basic">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<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 />
|
||||
</div>
|
||||
<div class="bottom"
|
||||
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
||||
<!-- <top-item /> -->
|
||||
<coreBottomBar />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
// import * as echarts from 'echarts'
|
||||
import topItem from './top-item.vue'
|
||||
import coreBottomBar from './coreBottomBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, topItem, coreBottomBar },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
leftEqInfoData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
}
|
||||
},
|
||||
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>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||
.scroll-container {
|
||||
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||
max-height: 210px;
|
||||
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||
overflow-y: auto;
|
||||
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||
overflow-x: hidden;
|
||||
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||
padding: 10px 0;
|
||||
|
||||
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||
/* Chrome/Safari */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
scrollbar-width: none;
|
||||
/* IE/Edge */
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||
.proBarInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 27px;
|
||||
/* 调整内边距,优化排版 */
|
||||
margin-bottom: 10px;
|
||||
/* 设备项之间的垂直间距 */
|
||||
}
|
||||
|
||||
/* 原有样式保留,优化细节 */
|
||||
.proBarInfoEqInfo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
/* 垂直居中,避免序号/文字错位 */
|
||||
}
|
||||
|
||||
.slot {
|
||||
width: 21px;
|
||||
height: 23px;
|
||||
background: rgba(0, 106, 205, 0.22);
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #68B5FF;
|
||||
line-height: 23px;
|
||||
/* 垂直居中文字 */
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eq-name {
|
||||
margin-left: 8px;
|
||||
/* 增加与序号的间距 */
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eqStatus {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.splitLine {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid #ADADAD;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #00FFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 序号和设备名垂直居中 */
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 状态/分割线/百分比垂直居中 */
|
||||
}
|
||||
|
||||
.proBarWrapper {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
margin-top: 6px;
|
||||
/* 进度条与上方信息的间距 */
|
||||
border-radius: 5px;
|
||||
/* 进度条圆角,优化视觉 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.proBarLine {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #ACACAC 100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.proBarLineTop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(53, 223, 247, 0) 0%, rgba(54, 220, 246, 0.92) 92%, #36F6E5 100%, #37ACF5 100%);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
/* 进度变化时添加过渡动画,更流畅 */
|
||||
}
|
||||
|
||||
/* 图表相关样式保留 */
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 57px;
|
||||
bottom: 42px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #00E8FF;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
112
src/views/home/components/coresBar.vue
Normal file
112
src/views/home/components/coresBar.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="height: 219px; width: 100%;"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'Container',
|
||||
components: {},
|
||||
// 接收父组件传递的 series 数据
|
||||
props: {
|
||||
chartSeries: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 存储图表实例,方便后续操作
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initData();
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
// 监听 series 数据变化,实时更新图表
|
||||
chartSeries: {
|
||||
handler() {
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true // 深度监听数组内元素变化
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
this.myChart = echarts.init(chartDom);
|
||||
this.updateChart(); // 初始化时渲染图表
|
||||
|
||||
// 监听窗口缩放
|
||||
window.addEventListener('resize', () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
|
||||
// 组件销毁时清理
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
this.myChart && this.myChart.dispose();
|
||||
});
|
||||
},
|
||||
// 单独提取更新图表的方法,方便复用
|
||||
updateChart() {
|
||||
if (!this.myChart) return;
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: { backgroundColor: '#6a7985' }
|
||||
}
|
||||
},
|
||||
grid: { top: 10, bottom: 20, right: 25, left: 30 },
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
onZero: false,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
width: 38,
|
||||
overflow: 'break'
|
||||
},
|
||||
data: ['6月', '7月', '8月', '9月', '10月', '11月']
|
||||
}
|
||||
],
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'left' },
|
||||
min: () => 0,
|
||||
max: (value) => Math.ceil(value.max),
|
||||
scale: true,
|
||||
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 数据
|
||||
};
|
||||
|
||||
this.myChart.setOption(option, true); // 第二个参数 true 表示替换现有配置
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
191
src/views/home/components/cost-Item.vue
Normal file
191
src/views/home/components/cost-Item.vue
Normal file
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div class="coreItem">
|
||||
<!-- 动态生成每个 item -->
|
||||
<div class="item" 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="number">{{ item.targetValue }}</div>
|
||||
<div class="title">上月</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="right">
|
||||
<!-- 本月数值:动态绑定颜色 -->
|
||||
<div class="number" :style="{ color: getColor(item.currentValue, item.targetValue) }">
|
||||
{{ item.currentValue }}
|
||||
</div>
|
||||
<div class="title">本月</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
|
||||
<div class="right">
|
||||
<!-- 环比额:计算差值并动态绑定颜色 -->
|
||||
<div class="number" :style="{ color: getColor(item.currentValue, item.targetValue) }">
|
||||
{{ get环比额(item.currentValue, item.targetValue) }}
|
||||
</div>
|
||||
<div class="title">环比额</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Container",
|
||||
components: {},
|
||||
props: ["name", "size", "icon"],
|
||||
data() {
|
||||
return {
|
||||
itemList: [
|
||||
{
|
||||
unit: "管理费用·万元",
|
||||
targetValue: 16, // 上月值
|
||||
currentValue: 14.5, // 本月值(小于上月,应显示橙色)
|
||||
},
|
||||
{
|
||||
unit: "销售费用·万元",
|
||||
targetValue: 16,
|
||||
currentValue: 17, // 大于上月,应显示绿色
|
||||
},
|
||||
{
|
||||
unit: "财务费用·万元",
|
||||
targetValue: 16,
|
||||
currentValue: 16, // 等于上月,应显示绿色
|
||||
},
|
||||
]
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 颜色判断:本月 >= 上月 绿色,否则 橙色
|
||||
getColor(current, target) {
|
||||
return current >= target
|
||||
? "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);
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coreItem {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 232px;
|
||||
height: 228px;
|
||||
background: #f9fcff;
|
||||
padding: 12px 0px 17px 12px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.unit {
|
||||
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;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: calc(100% - 26px);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 149px;
|
||||
height: 1px;
|
||||
background: linear-gradient(to left,
|
||||
rgba(255, 0, 0, 0),
|
||||
#cbcbcb);
|
||||
}
|
||||
|
||||
.left,
|
||||
.right {
|
||||
margin-top: 11px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.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;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.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: 15px;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 138px;
|
||||
height: 10px;
|
||||
background: #ECEFF7;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: #28CB97;
|
||||
border-radius: 8px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.progress-percent {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #868687;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.yield {
|
||||
width: 138px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
237
src/views/home/components/costOverview.vue
Normal file
237
src/views/home/components/costOverview.vue
Normal file
@@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="产销率总览·万m²" icon="cockpitItemIcon" size="psiBasicBg" topSize="psiTopTitleBasic">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex;flex-direction: column; width: 100%;height: 404px;">
|
||||
<!-- 2. .top 保持 flex,无需固定高度,自动跟随子元素拉伸 -->
|
||||
<div class="top" style="display: flex; width: 100%;">
|
||||
<psr-item :itemList="targetItemList" />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
// import * as echarts from 'echarts'
|
||||
import psrItem from './psr-item.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, psrItem },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
productSaleData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
parentItemList: [
|
||||
{ name: "销量", targetValue: 0, value: 0, proportion: 0 },
|
||||
{ name: "产量", targetValue: 0, value: 0, proportion: 0 },
|
||||
{ name: "累计完成产销率", targetValue: 0, value: 0, proportion: 0 },
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
targetItemList() {
|
||||
// 基于 parentItemList 的名称,匹配 productAndSaleData 中对应名称的数据
|
||||
return this.parentItemList.map(parentItem => {
|
||||
// 按名称精准匹配(name 完全一致才关联)
|
||||
const productItem = this.productSaleData.find(
|
||||
item => item.name === parentItem.name
|
||||
) || {};
|
||||
|
||||
// 优先使用匹配到的 productItem 数据,缺失则用 parentItem 的默认值(0)
|
||||
return {
|
||||
name: parentItem.name, // 保留原名称(销量、产量等)
|
||||
targetValue: productItem.targetValue ?? parentItem.targetValue ?? 0,
|
||||
value: productItem.value ?? parentItem.value ?? 0,
|
||||
proportion: productItem.proportion ?? parentItem.proportion ?? 0,
|
||||
completed: productItem.completed ?? parentItem.completed ?? 1,
|
||||
route: parentItem.route // 路由沿用 parentItem 的配置
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||
.scroll-container {
|
||||
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||
max-height: 210px;
|
||||
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||
overflow-y: auto;
|
||||
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||
overflow-x: hidden;
|
||||
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||
padding: 10px 0;
|
||||
|
||||
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||
/* Chrome/Safari */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
scrollbar-width: none;
|
||||
/* IE/Edge */
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||
.proBarInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 27px;
|
||||
/* 调整内边距,优化排版 */
|
||||
margin-bottom: 10px;
|
||||
/* 设备项之间的垂直间距 */
|
||||
}
|
||||
|
||||
/* 原有样式保留,优化细节 */
|
||||
.proBarInfoEqInfo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
/* 垂直居中,避免序号/文字错位 */
|
||||
}
|
||||
|
||||
.slot {
|
||||
width: 21px;
|
||||
height: 23px;
|
||||
background: rgba(0, 106, 205, 0.22);
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #68B5FF;
|
||||
line-height: 23px;
|
||||
/* 垂直居中文字 */
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eq-name {
|
||||
margin-left: 8px;
|
||||
/* 增加与序号的间距 */
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eqStatus {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.splitLine {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid #ADADAD;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #00FFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 序号和设备名垂直居中 */
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 状态/分割线/百分比垂直居中 */
|
||||
}
|
||||
|
||||
.proBarWrapper {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
margin-top: 6px;
|
||||
/* 进度条与上方信息的间距 */
|
||||
border-radius: 5px;
|
||||
/* 进度条圆角,优化视觉 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.proBarLine {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #ACACAC 100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.proBarLineTop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(53, 223, 247, 0) 0%, rgba(54, 220, 246, 0.92) 92%, #36F6E5 100%, #37ACF5 100%);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
/* 进度变化时添加过渡动画,更流畅 */
|
||||
}
|
||||
|
||||
/* 图表相关样式保留 */
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 57px;
|
||||
bottom: 42px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #00E8FF;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
365
src/views/home/components/financeCosts.vue
Normal file
365
src/views/home/components/financeCosts.vue
Normal file
@@ -0,0 +1,365 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<!-- 传入点击切换的状态到Container组件 -->
|
||||
<Container name="财务重点指标" nameTwo="费用重点指标" icon="cockpitItemIcon" size="topBasic" @switchTab="handleTabSwitch">
|
||||
<div class="bottom-left-content" style="display: flex;gap: 9px;padding: 14px 16px;flex-direction: column;">
|
||||
<!-- 根据activeTab状态,切换显示采购/存货内容 -->
|
||||
<template v-if="activeTab === 'purchase'">
|
||||
<!-- 采购重点指标对应的内容 -->
|
||||
<coreBottomLeftItem></coreBottomLeftItem>
|
||||
<div class="bottom"
|
||||
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
||||
<coreBottomBar></coreBottomBar>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="activeTab === 'inventory'">
|
||||
<!-- 存货重点指标对应的内容 -->
|
||||
<costItem></costItem>
|
||||
<div class="bottom"
|
||||
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
||||
<CostsBottomBar>
|
||||
</CostsBottomBar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 图表容器:根据状态控制显示(若需展示图表) -->
|
||||
<!-- <div id="productionStatusChart" style="height: 200px; margin-top: 12px;" v-if="showChart"></div> -->
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Container from './financeCostsContainer.vue'
|
||||
import * as echarts from 'echarts'
|
||||
import coreBottomLeftItem from './purchase-Item.vue'
|
||||
import coreBottomBar from './financeCostsBottomBar.vue'
|
||||
import costItem from './cost-Item.vue'
|
||||
import CostsBottomBar from './CostsBottomBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, coreBottomLeftItem, coreBottomBar, costItem, CostsBottomBar },
|
||||
props: {
|
||||
leftEqInfoData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
productionOverviewVo: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'purchase', // 激活的标签:purchase=采购,inventory=存货
|
||||
showChart: true, // 控制图表是否显示
|
||||
chart: null // 图表实例
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 切换标签时更新图表
|
||||
activeTab(newVal) {
|
||||
this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
productionOverviewVo: {
|
||||
handler() {
|
||||
this.updateChart()
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化图表
|
||||
this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 销毁图表,避免内存泄漏
|
||||
if (this.chart) {
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理标题点击切换标签
|
||||
handleTabSwitch(tabType) {
|
||||
this.activeTab = tabType // tabType由Container组件传递:'purchase'或'inventory'
|
||||
this.showChart = true // 切换时默认显示图表(可根据需求调整)
|
||||
},
|
||||
// 更新图表内容
|
||||
updateChart() {
|
||||
const chartDom = document.getElementById('productionStatusChart')
|
||||
if (!chartDom) return
|
||||
|
||||
// 销毁已有图表实例
|
||||
if (this.chart) this.chart.dispose()
|
||||
this.chart = echarts.init(chartDom)
|
||||
|
||||
// 根据当前激活的标签,设置对应图表数据(示例:可根据实际需求修改)
|
||||
const data = this.activeTab === 'purchase'
|
||||
? [
|
||||
this.productionOverviewVo.purchaseInput || 0,
|
||||
this.productionOverviewVo.purchaseOutput || 0,
|
||||
this.productionOverviewVo.purchaseNg || 0,
|
||||
0, 0, 0, 0 // 补充默认值,避免图表报错
|
||||
]
|
||||
: [
|
||||
this.productionOverviewVo.inventoryInput || 0,
|
||||
this.productionOverviewVo.inventoryOutput || 0,
|
||||
this.productionOverviewVo.inventoryNg || 0,
|
||||
0, 0, 0, 0
|
||||
]
|
||||
|
||||
const option = {
|
||||
type: 'bar',
|
||||
grid: { left: 51, right: 40, top: 50, bottom: 45 },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
className: 'production-status-chart-tooltip'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
offset: 8,
|
||||
data: this.activeTab === 'purchase'
|
||||
? ['采购投入', '采购产出', '采购待判', '低价值', '报废', '在制', '实验片']
|
||||
: ['存货投入', '存货产出', '存货待判', '低价值', '报废', '在制', '实验片'],
|
||||
axisTick: { show: false },
|
||||
axisLine: { show: true, onZero: false, lineStyle: { color: '#00E8FF' } },
|
||||
axisLabel: {
|
||||
color: 'rgba(255,255,255,0.7)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
width: 38,
|
||||
overflow: 'break'
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '单位/片',
|
||||
nameTextStyle: { color: 'rgba(255,255,255,0.7)', fontSize: 14, align: 'left' },
|
||||
min: 0,
|
||||
max: (value) => Math.ceil(value.max),
|
||||
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)
|
||||
// 监听窗口 resize,自适应图表
|
||||
window.addEventListener('resize', this.chart.resize)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* 原有样式保留,新增内容容器样式 */
|
||||
.bottom-left-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 其他原有样式... */
|
||||
.scroll-container {
|
||||
max-height: 210px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 10px 0;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.proBarInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 27px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.slot {
|
||||
width: 21px;
|
||||
height: 23px;
|
||||
background: rgba(0, 106, 205, 0.22);
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #68B5FF;
|
||||
line-height: 23px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eq-name {
|
||||
margin-left: 8px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eqStatus {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.splitLine {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid #ADADAD;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.yield {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #00FFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.proBarWrapper {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
margin-top: 6px;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.proBarLine {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #ACACAC 100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.proBarLineTop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(53, 223, 247, 0) 0%, rgba(54, 220, 246, 0.92) 92%, #36F6E5 100%, #37ACF5 100%);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 57px;
|
||||
bottom: 42px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #00E8FF;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
366
src/views/home/components/financeCostsBottomBar.vue
Normal file
366
src/views/home/components/financeCostsBottomBar.vue
Normal file
@@ -0,0 +1,366 @@
|
||||
<template>
|
||||
<div class="coreBar">
|
||||
<div class="barTop">
|
||||
<div class="barTop-left" style="display: flex;">
|
||||
<div class="title">财务指标趋势</div>
|
||||
<div class="legend">
|
||||
<span class="legend-item target">目标</span>
|
||||
<span class="legend-item real">实际</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<div style="width: 59px;" class="item-button" :class="{ active: activeButton === 0 }" @click="activeButton = 0">
|
||||
营业收入
|
||||
</div>
|
||||
<div class="button-line lineOne" v-if="activeButton !== 0 && activeButton !== 1"></div>
|
||||
|
||||
<div style="width: 75px;" class="item-button" :class="{ active: activeButton === 1 }" @click="activeButton = 1">
|
||||
经营性利润
|
||||
</div>
|
||||
<div class="button-line lineTwo" v-if="activeButton !== 1 && activeButton !== 2"></div>
|
||||
|
||||
<div class="item-button" :class="{ active: activeButton === 2 }" @click="activeButton = 2">
|
||||
利润总额
|
||||
</div>
|
||||
<div class="button-line lineThree" v-if="activeButton !== 2 && activeButton !== 3"></div>
|
||||
|
||||
<div class="item-button" :class="{ active: activeButton === 3 }" @click="activeButton = 3">
|
||||
毛利率
|
||||
</div>
|
||||
</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)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import coreLineChart from './coresBar.vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: "Container",
|
||||
components: { coreLineChart },
|
||||
props: ["name", "size", "icon"],
|
||||
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',
|
||||
lineStyle: { color: 'rgba(91, 230, 190, 1)' },
|
||||
itemStyle: {
|
||||
color: 'rgba(91, 230, 190, 1)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
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',
|
||||
lineStyle: { color: 'rgba(255, 132, 0, 1)' },
|
||||
itemStyle: {
|
||||
color: 'rgba(255, 132, 0, 1)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
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',
|
||||
lineStyle: { color: 'rgba(91, 230, 190, 1)' },
|
||||
itemStyle: {
|
||||
color: 'rgba(91, 230, 190, 1)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
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',
|
||||
lineStyle: { color: 'rgba(255, 132, 0, 1)' },
|
||||
itemStyle: {
|
||||
color: 'rgba(255, 132, 0, 1)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
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',
|
||||
lineStyle: { color: 'rgba(91, 230, 190, 1)' },
|
||||
itemStyle: {
|
||||
color: 'rgba(91, 230, 190, 1)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
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',
|
||||
lineStyle: { color: 'rgba(255, 132, 0, 1)' },
|
||||
itemStyle: {
|
||||
color: 'rgba(255, 132, 0, 1)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
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',
|
||||
lineStyle: { color: 'rgba(91, 230, 190, 1)' },
|
||||
itemStyle: {
|
||||
color: 'rgba(91, 230, 190, 1)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
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',
|
||||
lineStyle: { color: 'rgba(255, 132, 0, 1)' },
|
||||
itemStyle: {
|
||||
color: 'rgba(255, 132, 0, 1)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
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]
|
||||
}
|
||||
]
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 根据当前激活的按钮,返回对应的 series 数据
|
||||
currentSeries() {
|
||||
return this.seriesMap[this.activeButton] || [];
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 原有样式保持不变 */
|
||||
.coreBar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 12px;
|
||||
|
||||
.barTop {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: space-between;
|
||||
|
||||
.title {
|
||||
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;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
margin-left: 30px;
|
||||
|
||||
.legend-item {
|
||||
position: relative;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -13px;
|
||||
top: 30%;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -16px;
|
||||
top: 38%;
|
||||
width: 12px;
|
||||
height: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.target::before,
|
||||
.target::after {
|
||||
background-color: rgba(91, 230, 190, 1);
|
||||
}
|
||||
|
||||
.real::before,
|
||||
.real::after {
|
||||
background-color: rgba(255, 132, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
position: relative;
|
||||
gap: 2px;
|
||||
width: 252px;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
background: #ecf4fe;
|
||||
border-radius: 12px;
|
||||
|
||||
.button-line {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid rgba(11, 88, 255, 0.25);
|
||||
}
|
||||
|
||||
.lineOne {
|
||||
top: 5px;
|
||||
left: 59px;
|
||||
}
|
||||
|
||||
.lineTwo {
|
||||
top: 5px;
|
||||
left: 134px;
|
||||
}
|
||||
|
||||
.lineThree {
|
||||
top: 5px;
|
||||
left: 193px;
|
||||
}
|
||||
|
||||
.item-button {
|
||||
cursor: pointer;
|
||||
width: 54px;
|
||||
height: 24px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #0b58ff;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.item-button.active {
|
||||
width: 54px;
|
||||
height: 24px;
|
||||
background: #3071ff;
|
||||
border-radius: 12px;
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
261
src/views/home/components/financeCostsContainer.vue
Normal file
261
src/views/home/components/financeCostsContainer.vue
Normal file
@@ -0,0 +1,261 @@
|
||||
<template>
|
||||
<div class="cockpitContainer" :class="['cockpitContainer__' + size]">
|
||||
<div class="container-top">
|
||||
<!-- 左侧标题:点击切换到采购标签,并更新透明度 -->
|
||||
<div class="content-top-left title-wrapper" @click="handleLeftClick"
|
||||
:style="{ opacity: isLeftTransparent ? 1 : 0.3 }">
|
||||
<svg-icon class="title-icon" style="font-size: 32px; margin-left: 16px" :icon-class="icon" />
|
||||
<span class="title-text">{{ name }}</span>
|
||||
<!-- <span v-if="!isLeftTransparent" class="change-text">点击切换</span> -->
|
||||
|
||||
</div>
|
||||
<!-- 右侧标题:点击切换到存货标签,并更新透明度 -->
|
||||
<div class="content-top-right title-wrapper" v-if="nameTwo" @click="handleRightClick"
|
||||
:style="{ opacity: isRightTransparent ? 1 : 0.3 }">
|
||||
<svg-icon class="title-icon" style="font-size: 32px; margin-left: 16px" :icon-class="iconTwo || icon" />
|
||||
<span class="title-text">{{ nameTwo }}</span>
|
||||
|
||||
</div>
|
||||
<span class="change-text" :class="{ 'change-text-right': isLeftTransparent }">点击切换</span>
|
||||
|
||||
</div>
|
||||
<div class="container-body">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Container',
|
||||
components: {},
|
||||
props: {
|
||||
name: { type: String, required: true },
|
||||
nameTwo: { type: String, required: false },
|
||||
size: { type: String, default: 'default' },
|
||||
icon: { type: String, default: '' },
|
||||
iconTwo: { type: String, default: '' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 初始状态:左侧不透明(1),右侧透明(0.3)
|
||||
isLeftTransparent: true, // 左侧透明度状态(true=1,false=0.3)
|
||||
isRightTransparent: false // 右侧透明度状态(true=1,false=0.3)
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 点击左侧标题:左侧保持不透明,右侧变透明,并派发采购标签事件
|
||||
handleLeftClick() {
|
||||
this.isLeftTransparent = true; // 左侧不透明
|
||||
this.isRightTransparent = false; // 右侧透明
|
||||
this.$emit('switchTab', 'purchase'); // 通知父组件切换到采购内容
|
||||
},
|
||||
// 点击右侧标题:右侧保持不透明,左侧变透明,并派发存货标签事件
|
||||
handleRightClick() {
|
||||
this.isLeftTransparent = false; // 左侧透明
|
||||
this.isRightTransparent = true; // 右侧不透明
|
||||
this.$emit('switchTab', 'inventory'); // 通知父组件切换到存货内容
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// 样式保持不变,确保透明度过渡生效
|
||||
.cockpitContainer {
|
||||
display: inline-block;
|
||||
padding: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
.container-top {
|
||||
position: relative;
|
||||
height: 60px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content-top-left {
|
||||
width: 746px;
|
||||
height: 60px;
|
||||
background: linear-gradient(90deg, #FFFFFF 0%, rgba(253, 255, 255, 0) 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
transition: opacity 0.3s ease; // 透明度过渡动画
|
||||
}
|
||||
.title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// margin-left: 10px;
|
||||
/* 垂直居中关键属性 */
|
||||
height: 100%;
|
||||
/* 继承父容器高度,确保垂直居中范围 */
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
font-size: 30px;
|
||||
margin-right: 12px;
|
||||
margin-top: 4px;
|
||||
margin-left: 10px;
|
||||
/* 图标和文字之间的间距 */
|
||||
flex-shrink: 0;
|
||||
/* 防止图标被压缩 */
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 24px;
|
||||
color: #000000;
|
||||
letter-spacing: 3px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
// 移除固定行高,避免影响垂直对齐
|
||||
// line-height: 60px;
|
||||
}
|
||||
/* 左侧标题 - 左上角折现边框 */
|
||||
.content-top-left::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid;
|
||||
border-image: linear-gradient(277deg, rgba(255, 255, 255, 0), rgba(92, 140, 255, 1)) 1 1;
|
||||
clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%, 0 20px);
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
/* 左侧标题 - 左上角折现细节 */
|
||||
.content-top-left::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: #E1f0fd;
|
||||
border-top: 1px solid rgba(92, 140, 255, 1);
|
||||
border-left: 1px solid rgba(92, 140, 255, 1);
|
||||
transform: rotate(135deg) translate(-50%, -50%);
|
||||
transform-origin: top left;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.content-top-right {
|
||||
width: 368px;
|
||||
height: 60px;
|
||||
background: linear-gradient(90deg, #FFFFFF 0%, rgba(253, 255, 255, 0) 100%);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.3s ease; // 透明度过渡动画
|
||||
}
|
||||
|
||||
/* 右侧标题 - 左上角折现边框 */
|
||||
.content-top-right::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid;
|
||||
border-image: linear-gradient(277deg, rgba(255, 255, 255, 0), rgba(92, 140, 255, 1)) 1 1;
|
||||
clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%, 0 20px);
|
||||
z-index: 12;
|
||||
}
|
||||
|
||||
/* 右侧标题 - 左上角折现细节 */
|
||||
.content-top-right::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: #E1f0fd;
|
||||
border-top: 1px solid rgba(92, 140, 255, 1);
|
||||
border-left: 1px solid rgba(92, 140, 255, 1);
|
||||
transform: rotate(135deg) translate(-50%, -50%);
|
||||
transform-origin: top left;
|
||||
z-index: 12;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
margin-left: 6px;
|
||||
height: 32px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 24px;
|
||||
color: #000000;
|
||||
// line-height: 60px;
|
||||
letter-spacing: 3px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&__topBasic {
|
||||
background: url(../../../assets/img/top-basic.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__bottomBasic {
|
||||
background: url(../../../assets/img/bottom-basic.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.container-body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.test-body {
|
||||
padding: 20px;
|
||||
color: #666;
|
||||
}
|
||||
.change-text{
|
||||
position: absolute;
|
||||
top: 26px;
|
||||
left: 300px;
|
||||
z-index: 999;
|
||||
// width: 48px;
|
||||
// height: 17px;
|
||||
// margin-left: 80px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #0B58FF;
|
||||
line-height: 17px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
.change-text-right {
|
||||
// position: absolute;
|
||||
// top: 26px;
|
||||
left: auto; // 清除左侧定位
|
||||
right: 30px;
|
||||
// z-index: 999;
|
||||
// // width: 48px;
|
||||
// // height: 17px;
|
||||
// // margin-left: 80px;
|
||||
// font-family: PingFangSC, PingFang SC;
|
||||
// font-weight: 400;
|
||||
// font-size: 12px;
|
||||
// color: #0B58FF;
|
||||
// line-height: 17px;
|
||||
// text-align: left;
|
||||
// font-style: normal;
|
||||
}
|
||||
</style>
|
||||
44
src/views/home/components/finishDiv.vue
Normal file
44
src/views/home/components/finishDiv.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<!-- 显示累计值,并绑定颜色类 -->
|
||||
<div class="accumulated-value" :class="injectData.status">
|
||||
{{ injectData.accumulated }} <!-- 显示累计数据 -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'finishDiv', // 补充组件名,便于识别
|
||||
props: {
|
||||
injectData: { // 接收父组件传递的当前行数据
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// list: this.injectData // 将接收的行数据赋值给list,便于模板使用
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 若无需输入功能,可删除changeInput方法;若需保留,可按需调整
|
||||
// changeInput() {
|
||||
// console.log(this.list)
|
||||
// this.$emit('emitData', this.list)
|
||||
// }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 累计值基础样式 */
|
||||
|
||||
/* 已完成:绿色 #4CAF50 */
|
||||
.accumulated-value.done {
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
/* 未完成:橙色 #FF9800 */
|
||||
.accumulated-value.pending {
|
||||
color: #FF9800;
|
||||
}
|
||||
</style>
|
||||
230
src/views/home/components/keyProductionIndicators.vue
Normal file
230
src/views/home/components/keyProductionIndicators.vue
Normal file
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="生产重点指标" icon="cockpitItemIcon" size="topBasic" topSize="basic">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<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 />
|
||||
</div>
|
||||
<div class="bottom" style="display: flex;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
||||
<!-- <top-item /> -->
|
||||
<coreBottomBar />
|
||||
|
||||
</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'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, topItem, coreBottomBar },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
leftEqInfoData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
productionOverviewVo: {
|
||||
handler(newValue, oldValue) {
|
||||
// this.updateChart()
|
||||
},
|
||||
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||
.scroll-container {
|
||||
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||
max-height: 210px;
|
||||
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||
overflow-y: auto;
|
||||
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||
overflow-x: hidden;
|
||||
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||
padding: 10px 0;
|
||||
|
||||
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||
/* Chrome/Safari */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
scrollbar-width: none;
|
||||
/* IE/Edge */
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||
.proBarInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 27px;
|
||||
/* 调整内边距,优化排版 */
|
||||
margin-bottom: 10px;
|
||||
/* 设备项之间的垂直间距 */
|
||||
}
|
||||
|
||||
/* 原有样式保留,优化细节 */
|
||||
.proBarInfoEqInfo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
/* 垂直居中,避免序号/文字错位 */
|
||||
}
|
||||
|
||||
.slot {
|
||||
width: 21px;
|
||||
height: 23px;
|
||||
background: rgba(0, 106, 205, 0.22);
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #68B5FF;
|
||||
line-height: 23px;
|
||||
/* 垂直居中文字 */
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eq-name {
|
||||
margin-left: 8px;
|
||||
/* 增加与序号的间距 */
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eqStatus {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.splitLine {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid #ADADAD;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #00FFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 序号和设备名垂直居中 */
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 状态/分割线/百分比垂直居中 */
|
||||
}
|
||||
|
||||
.proBarWrapper {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
margin-top: 6px;
|
||||
/* 进度条与上方信息的间距 */
|
||||
border-radius: 5px;
|
||||
/* 进度条圆角,优化视觉 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.proBarLine {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #ACACAC 100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.proBarLineTop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(53, 223, 247, 0) 0%, rgba(54, 220, 246, 0.92) 92%, #36F6E5 100%, #37ACF5 100%);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
/* 进度变化时添加过渡动画,更流畅 */
|
||||
}
|
||||
|
||||
/* 图表相关样式保留 */
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 57px;
|
||||
bottom: 42px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #00E8FF;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
135
src/views/home/components/keyWork.vue
Normal file
135
src/views/home/components/keyWork.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<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"
|
||||
style="display: flex;padding: 14px 16px;flex-direction: column; background-color: rgba(249, 252, 255, 1);">
|
||||
<div class="legend-group" style="justify-content: end;">
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot done"></span>
|
||||
<span class="legend-text">已完成</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot pending"></span>
|
||||
<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" />
|
||||
|
||||
</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],
|
||||
props: {
|
||||
leftEqInfoData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
maintenanceTasks: [
|
||||
{ id: 1, eqName: '研发经费入强度/%', taskName: '例行维护', monthlyActual: '85%', accumulated: '78%', status: 'done' }, // 已完成-绿色
|
||||
{ id: 2, eqName: '存货/亿元', taskName: '例行维护', monthlyActual: '60%', accumulated: '65%', status: 'pending' }, // 未完成-橙色
|
||||
{ id: 3, eqName: '三年以上应收款/亿元', taskName: '故障排查', monthlyActual: '100%', accumulated: '92%', status: 'done' },
|
||||
{ id: 4, eqName: '非经营性资产处置到账金额/万元', taskName: '部件更换', monthlyActual: '45%', accumulated: '50%', status: 'pending' },
|
||||
{ id: 4, eqName: '研发经费投入/万元', taskName: '部件更换', monthlyActual: '45%', accumulated: '50%', status: 'pending' },
|
||||
{ id: 4, eqName: '经营性现金流/万元', taskName: '部件更换', monthlyActual: '45%', accumulated: '50%', status: 'pending' },
|
||||
|
||||
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
|
||||
|
||||
],
|
||||
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 },
|
||||
|
||||
]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
productionOverviewVo: {
|
||||
handler(newValue, oldValue) {
|
||||
this.updateChart()
|
||||
},
|
||||
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 销毁图表,避免内存泄漏
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.legend-group {
|
||||
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>
|
||||
371
src/views/home/components/noRouterHeader.vue
Normal file
371
src/views/home/components/noRouterHeader.vue
Normal file
@@ -0,0 +1,371 @@
|
||||
<template>
|
||||
<header class="report-header" :class="['report-header__' + size]">
|
||||
<!-- 左侧区域:标题 -->
|
||||
<div class="left-content">
|
||||
<div class="top-title">{{ topTitle }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧区域:全屏按钮 -->
|
||||
<div class="right-content">
|
||||
<el-button type="text" class="return-btn" :title="'返回'" @click="handleReturn">
|
||||
<svg-icon style="color: #0B58FF;" icon-class="returnIcon" />
|
||||
</el-button>
|
||||
<el-button type="text" class="screen-btn" :title="isFullScreen ? '退出全屏' : '全屏'" @click="changeFullScreen">
|
||||
<svg-icon style="color: #0B58FF;" v-if="isFullScreen" icon-class="unFullScreenView" />
|
||||
<svg-icon style="color: #0B58FF;" v-else icon-class="fullScreenView" />
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 时间选择区域:日/月/年按钮 + 日期选择器 -->
|
||||
<div class="timeType">
|
||||
<div class="item" v-for="(item, index) in timeTypes" :key="index" @click="activeTime = index"
|
||||
:class="{ 'no-skew': activeTime === index }">
|
||||
<span class="item-text">{{ item.text }}</span>
|
||||
</div>
|
||||
<div class="dateP">
|
||||
<div class="label">
|
||||
<span class="label-text">日期选择</span>
|
||||
</div>
|
||||
<el-date-picker v-model="date" :type="getPickerType" :placeholder="getPickerPlaceholder"
|
||||
class="custom-date-picker" value-format="yyyy-MM-dd" :clearable="false" style="width: 132px;height: 29px;"
|
||||
@change="emitTimeRange" />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'; // 引入moment
|
||||
|
||||
export default {
|
||||
name: 'Header',
|
||||
props: {
|
||||
isFullScreen: { type: Boolean, default: false },
|
||||
topTitle: { type: String, default: '' },
|
||||
size: { type: String, default: 'basic' },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentTime: '',
|
||||
timeTimer: null,
|
||||
date: undefined, // 存储选择的日期(字符串格式:yyyy-MM-dd/yyyy-MM/yyyy)
|
||||
activeTime: 1, // 默认月维度(0=日,1=月,2=年)
|
||||
timeTypes: [
|
||||
{ text: '日', pickerType: 'date', placeholder: '选择日期' },
|
||||
{ text: '月', pickerType: 'month', placeholder: '选择月份' },
|
||||
{ text: '年', pickerType: 'year', placeholder: '选择年份' }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getPickerType() {
|
||||
return this.timeTypes[this.activeTime].pickerType;
|
||||
},
|
||||
getPickerPlaceholder() {
|
||||
return this.timeTypes[this.activeTime].placeholder;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
activeTime(newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
this.date = undefined;
|
||||
// this.emitTimeRange();
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化默认日期(当前月,格式:yyyy-MM)
|
||||
console.log(this.$router);
|
||||
this.date = moment().format('YYYY-MM');
|
||||
this.$nextTick(() => this.emitTimeRange());
|
||||
},
|
||||
methods: {
|
||||
changeFullScreen() {
|
||||
this.$emit('screenfullChange');
|
||||
},
|
||||
handleReturn() {
|
||||
console.log(this.$router);
|
||||
|
||||
this.$router.go(-1);
|
||||
},
|
||||
exportPDF() {
|
||||
this.$emit('exportPDF');
|
||||
},
|
||||
/**
|
||||
* 核心方法:用moment计算时间范围(时间戳格式)
|
||||
* 日:选择日00:00:00 → 次日00:00:00
|
||||
* 月:当月1日00:00:00 → 次月1日00:00:00
|
||||
* 年:当年1月1日00:00:00 → 次年1月1日00:00:00
|
||||
*/
|
||||
calculateTimeRange() {
|
||||
let startTime = 0;
|
||||
let endTime = 0;
|
||||
const mode = this.activeTime + 1; // 1=日,2=月,3=年
|
||||
const defaultMoment = moment(); // 默认当前时间
|
||||
|
||||
// 处理选择的日期:转为moment对象(兼容不同选择器格式)
|
||||
console.log('this.date', this.date);
|
||||
|
||||
const targetMoment = this.date
|
||||
? moment(this.date, this.getPickerType === 'date' ? 'YYYY-MM-DD' : (this.getPickerType === 'month' ? 'YYYY-MM' : 'YYYY'))
|
||||
: defaultMoment;
|
||||
|
||||
// 验证日期有效性
|
||||
if (!targetMoment.isValid()) {
|
||||
console.error('无效日期:', this.date);
|
||||
return { startTime, endTime, mode };
|
||||
}
|
||||
|
||||
// 1. 日维度:当天0点 → 次日0点
|
||||
if (this.activeTime === 0) {
|
||||
startTime = targetMoment.startOf('day').valueOf(); // 当天00:00:00 时间戳
|
||||
endTime = targetMoment.add(1, 'day').startOf('day').valueOf(); // 次日00:00:00 时间戳
|
||||
}
|
||||
|
||||
// 2. 月维度:当月1日0点 → 次月1日0点
|
||||
else if (this.activeTime === 1) {
|
||||
startTime = targetMoment.startOf('month').valueOf(); // 当月1日00:00:00 时间戳
|
||||
endTime = targetMoment.add(1, 'month').startOf('month').valueOf(); // 次月1日00:00:00 时间戳
|
||||
}
|
||||
|
||||
// 3. 年维度:当年1月1日0点 → 次年1月1日0点
|
||||
else if (this.activeTime === 2) {
|
||||
startTime = targetMoment.startOf('year').valueOf(); // 当年1月1日00:00:00 时间戳
|
||||
endTime = targetMoment.add(1, 'year').startOf('year').valueOf(); // 次年1月1日00:00:00 时间戳
|
||||
}
|
||||
|
||||
// 调试输出(格式化显示,便于验证)
|
||||
console.log('时间范围计算结果:', {
|
||||
mode,
|
||||
startTime: moment(startTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
endTime: moment(endTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
startTimeStamp: startTime,
|
||||
endTimeStamp: endTime
|
||||
});
|
||||
|
||||
return { startTime, endTime, mode };
|
||||
},
|
||||
|
||||
// 传递时间范围给父组件
|
||||
emitTimeRange() {
|
||||
const timeRange = this.calculateTimeRange();
|
||||
this.$emit('timeRangeChange', timeRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 字体引入 */
|
||||
@font-face {
|
||||
font-family: "YouSheBiaoTiHei";
|
||||
src: url('../../../assets/fonts/YouSheBiaoTiHe.ttf') format('truetype');
|
||||
}
|
||||
|
||||
/* 头部容器基础样式 */
|
||||
.report-header {
|
||||
height: 117px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
|
||||
&__basic {
|
||||
background: url(../../../assets/img/topBg.png) no-repeat;
|
||||
background-size: cover;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__psi {
|
||||
background: url(../../../assets/img/psiTopTitle.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
/* 左侧标题区域 */
|
||||
.left-content {
|
||||
margin-top: 11px;
|
||||
margin-left: 350px;
|
||||
height: 55px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.top-title {
|
||||
height: 55px;
|
||||
font-family: "YouSheBiaoTiHei", sans-serif;
|
||||
font-size: 42px;
|
||||
color: #1E1651;
|
||||
line-height: 55px;
|
||||
letter-spacing: 6px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* 时间选择区域 */
|
||||
.timeType {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
top: 42px;
|
||||
right: 0px;
|
||||
margin-top: 18px;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.timeType .item {
|
||||
width: 50px;
|
||||
height: 28px;
|
||||
background: rgba(236, 244, 254, 1);
|
||||
transform: skew(-25deg);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: rgba(11, 88, 255, 1);
|
||||
line-height: 28px;
|
||||
letter-spacing: 2px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.timeType .item .item-text {
|
||||
display: inline-block;
|
||||
transform: skew(25deg);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.timeType .item.no-skew {
|
||||
background: rgba(11, 88, 255, 1);
|
||||
color: rgba(249, 252, 255, 1);
|
||||
transform: skew(-25deg) !important;
|
||||
box-shadow: 0 2px 8px rgba(11, 88, 255, 0.3);
|
||||
}
|
||||
|
||||
.timeType .item.no-skew .item-text {
|
||||
transform: skew(25deg) !important;
|
||||
}
|
||||
|
||||
.dateP {
|
||||
position: relative;
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.dateP .label {
|
||||
width: 70px;
|
||||
height: 28px;
|
||||
background: rgba(236, 244, 254, 1);
|
||||
transform: skew(-25deg);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #0B58FF;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dateP .label-text {
|
||||
display: inline-block;
|
||||
transform: skew(25deg);
|
||||
}
|
||||
|
||||
/* 右侧全屏按钮区域 */
|
||||
.right-content {
|
||||
display: flex;
|
||||
// flex-direction: column;
|
||||
margin-top: 12px;
|
||||
margin-right: 10px;
|
||||
gap: 21px;
|
||||
}
|
||||
|
||||
// .current-time {
|
||||
// color: #FFFFFF;
|
||||
// font-family: PingFangSC, PingFang SC;
|
||||
// font-weight: 500;
|
||||
// font-size: 22px;
|
||||
// line-height: 24px;
|
||||
// letter-spacing: 1px;
|
||||
// }
|
||||
|
||||
.screen-btn {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
color: #00fff0;
|
||||
font-size: 26px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.home-btn {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
// margin-left: 300px;
|
||||
color: #00fff0;
|
||||
font-size: 26px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.return-btn {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
// margin-left: 300px;
|
||||
color: #00fff0;
|
||||
font-size: 26px;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 日期选择器自定义样式 */
|
||||
::v-deep .custom-date-picker {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
width: 132px !important;
|
||||
height: 28px !important;
|
||||
position: relative;
|
||||
margin: 0 !important;
|
||||
|
||||
.el-input__inner {
|
||||
height: 28px !important;
|
||||
width: 132px !important;
|
||||
text-align: center;
|
||||
padding-left: 15px !important;
|
||||
padding-right: 32px !important;
|
||||
font-size: 14px !important;
|
||||
line-height: 28px !important;
|
||||
color: rgba(237, 245, 253, 1) !important;
|
||||
vertical-align: middle !important;
|
||||
clip-path: polygon(18px 0, 100% 0, 100% 100%, 0 100%);
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: rgba(11, 88, 255, 1) !important;
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.el-input__prefix {
|
||||
left: auto !important;
|
||||
right: 8px !important;
|
||||
top: 50% !important;
|
||||
transform: translateY(-50%) !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
height: 28px !important;
|
||||
}
|
||||
|
||||
.el-input__icon {
|
||||
color: #ffffff !important;
|
||||
font-size: 16px !important;
|
||||
line-height: 28px !important;
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
.el-icon-date::before {
|
||||
color: #ffffff !important;
|
||||
font-size: 16px !important;
|
||||
line-height: inherit !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
203
src/views/home/components/operating-item.vue
Normal file
203
src/views/home/components/operating-item.vue
Normal file
@@ -0,0 +1,203 @@
|
||||
<template>
|
||||
<div class="coreItem" :style="{ 'height': height + 'px' }">
|
||||
<!-- 用 v-for 动态生成每个 item -->
|
||||
<div class="item" @click="handleRoute(item.route)" v-for="(item, index) in itemList" :key="index">
|
||||
<div class="unit">{{ item.name }}</div>
|
||||
<div class="item-content">
|
||||
<!-- 左右内容容器 -->
|
||||
<div class="content-wrapper">
|
||||
<div class="left">
|
||||
<div class="number">{{ item.targetValue || 0 }}</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="right">
|
||||
<!-- 实际值颜色动态绑定 -->
|
||||
<div class="number" :style="{ 'color': item.completed === 0 ? '#FF8400' : 'rgba(54, 181, 138, .7)' }">
|
||||
{{ item.value ||0 }}
|
||||
</div>
|
||||
<div class="title">实际值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="right">
|
||||
<!-- 完成率颜色动态绑定 -->
|
||||
<div class="number" :style="{ 'color': item.completed === 0 ? '#FF8400' : 'rgba(54, 181, 138, .7)' }">
|
||||
{{ item.proportion !== null && item.proportion !== undefined ? (item.proportion * 100) + '%' : '0%' }}
|
||||
</div>
|
||||
<div class="title">完成率</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Container",
|
||||
components: {},
|
||||
props: ['itemList', 'height'],
|
||||
data() {
|
||||
return {
|
||||
progress: 90, // 进度值,方便统一控制
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
itemList: {
|
||||
handler(newList) {
|
||||
// 当 itemList 变化时执行的逻辑(如重新计算、更新视图等)
|
||||
console.log("子组件接收的 itemList 已更新:", newList);
|
||||
},
|
||||
deep: true // 深度监听:若 itemList 内部对象属性变化(如 currentValue 改变),也能触发监听
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
handleRoute(route) {
|
||||
this.$router.push({
|
||||
path:route
|
||||
})
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coreItem {
|
||||
display: flex;
|
||||
// height: 382px;
|
||||
overflow: hidden;
|
||||
flex-wrap: wrap;
|
||||
// grid-template-columns: 1fr 1fr;
|
||||
// grid-template-rows: 1fr 1fr;
|
||||
justify-content: flex-start;
|
||||
align-content: flex-start;
|
||||
gap: 8px;
|
||||
overflow-y: auto;
|
||||
|
||||
// padding-right: 4px;
|
||||
/* 2. 隐藏 Chrome/Safari 滚动条 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
/* 滚动条宽度设为0 */
|
||||
height: 0;
|
||||
/* 横向滚动条(如需)也隐藏 */
|
||||
}
|
||||
|
||||
/* 3. 隐藏 Firefox 滚动条 */
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 376px;
|
||||
height: 95px;
|
||||
background: #f9fcff;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
&:hover {
|
||||
box-shadow: 0px 4px 12px 2px #B5CDE5;
|
||||
}
|
||||
// &:hover {
|
||||
// box-shadow: 0px 4px 12px 2px #B5CDE5;
|
||||
// }
|
||||
.unit {
|
||||
// width: 124px;
|
||||
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: 8px;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: calc(100% - 26px);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 1px;
|
||||
height: 46px;
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(255, 0, 0, 0),
|
||||
rgba(40, 203, 151, 1));
|
||||
}
|
||||
|
||||
.left,
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.number {
|
||||
height: 22px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 600;
|
||||
font-size: 26px;
|
||||
color: rgba(103, 103, 103, 0.79);
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.title {
|
||||
height: 14px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #868687;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* 进度条和百分比的外层容器 */
|
||||
.progress-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
/* 进度条和百分比的间距 */
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 190px;
|
||||
height: 10px;
|
||||
background: #ECEFF7;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: #28CB97;
|
||||
border-radius: 8px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* 百分比文本样式 */
|
||||
.progress-percent {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #868687;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
206
src/views/home/components/operatingBar.vue
Normal file
206
src/views/home/components/operatingBar.vue
Normal file
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<div class="coreBar">
|
||||
<div class="barTop">
|
||||
<!-- 标题:单独左对齐 -->
|
||||
<!-- <div class="title">销售指标趋势</div> -->
|
||||
<!-- 关键:新增右侧容器,包裹图例和按钮组,实现整体靠右 -->
|
||||
<div class="right-container">
|
||||
<div class="legend">
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon line yield"></span>
|
||||
完成率
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square target"></span>
|
||||
目标
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square achieved"></span>
|
||||
达标
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square unachieved"></span>
|
||||
未达标
|
||||
</span>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<div style="letter-spacing: 8px" class="item-button" :class="{ active: activeButton === 0 }"
|
||||
@click="activeButton = 0">
|
||||
销量
|
||||
</div>
|
||||
<div style="letter-spacing: 8px" class="item-button" :class="{ active: activeButton === 1 }"
|
||||
@click="activeButton = 1">
|
||||
毛利率
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||
<operatingLineBar style="height: 99%; width: 100%" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import operatingLineBar from './operatingLineBar.vue';
|
||||
export default {
|
||||
name: "Container",
|
||||
components: { operatingLineBar },
|
||||
props: ["name", "size", "icon"],
|
||||
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 },
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coreBar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
|
||||
.barTop {
|
||||
display: flex;
|
||||
justify-content: flex-end; // 标题左、右侧容器右,整体两端对齐
|
||||
align-items: center; // 垂直居中,避免上下错位
|
||||
gap: 16px; // 标题与右侧容器的最小间距,防止拥挤
|
||||
width: 100%; // 确保占满父容器,实现两端对齐
|
||||
|
||||
.title {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
color: #000000;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
// 标题固定在左侧,不挤压右侧空间
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||
.right-container {
|
||||
display: flex;
|
||||
align-items: center; // 图例和按钮组垂直居中
|
||||
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||
}
|
||||
|
||||
// 2. 图例:在右侧容器内横向排列
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 16px; // 图例项之间间距,避免重叠
|
||||
align-items: center;
|
||||
// 移除原margin-left,避免位置偏移
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
white-space: nowrap; // 防止图例文字换行
|
||||
}
|
||||
|
||||
.legend-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.legend-icon.line {
|
||||
width: 12px;
|
||||
height: 2px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: -2px;
|
||||
left: 3px;
|
||||
width: 6px;
|
||||
border-radius: 50%;
|
||||
height: 6px;
|
||||
background-color: rgba(40, 138, 255, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.legend-icon.square {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
// 图例颜色
|
||||
.yield {
|
||||
background: rgba(40, 138, 255, 1);
|
||||
}
|
||||
|
||||
.target {
|
||||
background: #2889FF;
|
||||
}
|
||||
|
||||
.achieved {
|
||||
background: rgba(40, 203, 151, 1);
|
||||
}
|
||||
|
||||
.unachieved {
|
||||
background: rgba(255, 132, 0, 1);
|
||||
}
|
||||
|
||||
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||
.button-group {
|
||||
display: flex;
|
||||
position: relative;
|
||||
gap: 2px;
|
||||
width: 283px;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
background: #ecf4fe;
|
||||
border-radius: 12px;
|
||||
// 移除原margin-right,由右侧容器统一控制
|
||||
margin: 0;
|
||||
|
||||
.item-button {
|
||||
cursor: pointer;
|
||||
width: 142px;
|
||||
height: 24px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #0b58ff;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
letter-spacing: 8px; // 确保文字间距生效
|
||||
padding-left: 8px; // 抵消letter-spacing导致的文字左偏
|
||||
}
|
||||
|
||||
.item-button.active {
|
||||
width: 142px;
|
||||
height: 24px;
|
||||
background: #3071ff;
|
||||
border-radius: 12px;
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
173
src/views/home/components/operatingBottomLineBar.vue
Normal file
173
src/views/home/components/operatingBottomLineBar.vue
Normal file
@@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<div ref="cockpitEffChipBottom" id="cockpitEffChipBottom" style="width: 100%; height: 400px;"></div>
|
||||
</template>
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 存储图表实例,避免重复创建
|
||||
};
|
||||
},
|
||||
props: {
|
||||
// 明确接收的props结构,增强可读性
|
||||
chartData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
series: [],
|
||||
allPlaceNames: []
|
||||
}),
|
||||
// 校验数据格式
|
||||
validator: (value) => {
|
||||
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
},
|
||||
|
||||
// 新增:监听 chartData 变化
|
||||
watch: {
|
||||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||
chartData: {
|
||||
handler() {
|
||||
console.log(this.chartData,'chartData');
|
||||
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
immediate: true // 初始化时立即执行
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateChart() {
|
||||
const chartDom = this.$refs.cockpitEffChipBottom;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
}
|
||||
|
||||
this.myChart = echarts.init(chartDom);
|
||||
const { allPlaceNames, series } = this.chartData || {};
|
||||
|
||||
// 处理空数据
|
||||
const xData = allPlaceNames || [];
|
||||
const chartSeries = series || []; // 父组件传递的 series
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
},
|
||||
formatter: (params) => {
|
||||
let html = `${params[0].axisValue}<br/>`;
|
||||
params.forEach(item => {
|
||||
const unit = item.seriesName === '完成率' ? '%' : (
|
||||
['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||
);
|
||||
html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||
});
|
||||
return html;
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 30,
|
||||
bottom: 30,
|
||||
right: 70,
|
||||
left: 40,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: true,
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0]
|
||||
},
|
||||
data: xData
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
// 左侧Y轴:营业收入、成本(单位万元)
|
||||
{
|
||||
type: 'value',
|
||||
name: '万元',
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'right'
|
||||
},
|
||||
min: 0,
|
||||
max: (value) => Math.ceil((value.max || 0) * 1.1),
|
||||
scale: false,
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
formatter: '{value}'
|
||||
},
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
splitNumber: 4
|
||||
},
|
||||
// 右侧Y轴:利润占比(百分比)
|
||||
{
|
||||
type: 'value',
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'left'
|
||||
},
|
||||
min: 0,
|
||||
max: 100,
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
formatter: '{value}%'
|
||||
},
|
||||
splitLine: { show: false },
|
||||
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
splitNumber: 4
|
||||
}
|
||||
],
|
||||
series: chartSeries // 直接使用父组件传递的 series
|
||||
};
|
||||
|
||||
option && this.myChart.setOption(option);
|
||||
|
||||
// 窗口缩放适配和销毁逻辑保持不变
|
||||
window.addEventListener('resize', () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
this.myChart && this.myChart.dispose();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
173
src/views/home/components/operatingLineBar.vue
Normal file
173
src/views/home/components/operatingLineBar.vue
Normal file
@@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||
</template>
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 存储图表实例,避免重复创建
|
||||
};
|
||||
},
|
||||
props: {
|
||||
// 明确接收的props结构,增强可读性
|
||||
chartData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
series: [],
|
||||
allPlaceNames: []
|
||||
}),
|
||||
// 校验数据格式
|
||||
validator: (value) => {
|
||||
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
},
|
||||
|
||||
// 新增:监听 chartData 变化
|
||||
watch: {
|
||||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||
chartData: {
|
||||
handler() {
|
||||
console.log(this.chartData,'chartData');
|
||||
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
immediate: true // 初始化时立即执行
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateChart() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
}
|
||||
|
||||
this.myChart = echarts.init(chartDom);
|
||||
const { allPlaceNames, series } = this.chartData || {};
|
||||
|
||||
// 处理空数据
|
||||
const xData = allPlaceNames || [];
|
||||
const chartSeries = series || []; // 父组件传递的 series
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
},
|
||||
// formatter: (params) => {
|
||||
// let html = `${params[0].axisValue}<br/>`;
|
||||
// params.forEach(item => {
|
||||
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||
// );
|
||||
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||
// });
|
||||
// return html;
|
||||
// }
|
||||
},
|
||||
grid: {
|
||||
top: 30,
|
||||
bottom: 30,
|
||||
right: 70,
|
||||
left: 40,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: true,
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0]
|
||||
},
|
||||
data: xData
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
// 左侧Y轴:营业收入、成本(单位万元)
|
||||
{
|
||||
type: 'value',
|
||||
name: '万元',
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'right'
|
||||
},
|
||||
min: 0,
|
||||
max: (value) => Math.ceil((value.max || 0) * 1.1),
|
||||
scale: false,
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
formatter: '{value}'
|
||||
},
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
splitNumber: 4
|
||||
},
|
||||
// 右侧Y轴:利润占比(百分比)
|
||||
{
|
||||
type: 'value',
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'left'
|
||||
},
|
||||
min: 0,
|
||||
max: 100,
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
formatter: '{value}%'
|
||||
},
|
||||
splitLine: { show: false },
|
||||
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
splitNumber: 4
|
||||
}
|
||||
],
|
||||
series: chartSeries // 直接使用父组件传递的 series
|
||||
};
|
||||
|
||||
option && this.myChart.setOption(option);
|
||||
|
||||
// 窗口缩放适配和销毁逻辑保持不变
|
||||
window.addEventListener('resize', () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
this.myChart && this.myChart.dispose();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
342
src/views/home/components/operatingLineChart.vue
Normal file
342
src/views/home/components/operatingLineChart.vue
Normal file
@@ -0,0 +1,342 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="趋势图" icon="cockpitItemIcon" size="operatingLarge" topSize="large">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex;width: 100%;">
|
||||
<div class="bottom"
|
||||
style="height: 380px; display: flex; width: 100%;background-color: rgba(249, 252, 255, 1);">
|
||||
<!-- <top-item /> -->
|
||||
<coreBottomBar />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
// import * as echarts from 'echarts'
|
||||
import coreBottomBar from './operatingBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, coreBottomBar },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
leftEqInfoData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
}
|
||||
},
|
||||
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>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||
.scroll-container {
|
||||
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||
max-height: 210px;
|
||||
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||
overflow-y: auto;
|
||||
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||
overflow-x: hidden;
|
||||
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||
padding: 10px 0;
|
||||
|
||||
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||
/* Chrome/Safari */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
scrollbar-width: none;
|
||||
/* IE/Edge */
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||
.proBarInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 27px;
|
||||
/* 调整内边距,优化排版 */
|
||||
margin-bottom: 10px;
|
||||
/* 设备项之间的垂直间距 */
|
||||
}
|
||||
|
||||
/* 原有样式保留,优化细节 */
|
||||
.proBarInfoEqInfo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
/* 垂直居中,避免序号/文字错位 */
|
||||
}
|
||||
|
||||
.slot {
|
||||
width: 21px;
|
||||
height: 23px;
|
||||
background: rgba(0, 106, 205, 0.22);
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #68B5FF;
|
||||
line-height: 23px;
|
||||
/* 垂直居中文字 */
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eq-name {
|
||||
margin-left: 8px;
|
||||
/* 增加与序号的间距 */
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eqStatus {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.splitLine {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid #ADADAD;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #00FFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 序号和设备名垂直居中 */
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 状态/分割线/百分比垂直居中 */
|
||||
}
|
||||
|
||||
.proBarWrapper {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
margin-top: 6px;
|
||||
/* 进度条与上方信息的间距 */
|
||||
border-radius: 5px;
|
||||
/* 进度条圆角,优化视觉 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.proBarLine {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #ACACAC 100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.proBarLineTop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(53, 223, 247, 0) 0%, rgba(54, 220, 246, 0.92) 92%, #36F6E5 100%, #37ACF5 100%);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
/* 进度变化时添加过渡动画,更流畅 */
|
||||
}
|
||||
|
||||
/* 图表相关样式保留 */
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 57px;
|
||||
bottom: 42px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #00E8FF;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
358
src/views/home/components/operatingSalesRevenue.vue
Normal file
358
src/views/home/components/operatingSalesRevenue.vue
Normal file
@@ -0,0 +1,358 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="销售重点指标" icon="cockpitItemIcon" size="operatingBasic" topSize="middle">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 14px; display: flex;flex-direction: column; width: 100%;">
|
||||
<!-- 2. .top 保持 flex,无需固定高度,自动跟随子元素拉伸 -->
|
||||
<div class="top" style="display: flex; width: 100%;">
|
||||
<top-item :height="367" :itemList="parentItemList" />
|
||||
</div>
|
||||
<div class="bottom"
|
||||
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
||||
<!-- <top-item /> -->
|
||||
<!-- <coreBottomBar /> -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
// import * as echarts from 'echarts'
|
||||
import topItem from './operating-item.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, topItem },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
leftEqInfoData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
parentItemList: [
|
||||
{
|
||||
name: "利润总额", targetValue: 0, value: 0, proportion: 0,
|
||||
route:'profitAnalysis'
|
||||
},
|
||||
{ name: "毛利率", targetValue: 0, value: 0, proportion: 0, route: 'profitAnalysis' },
|
||||
{ name: "单价", targetValue: 0, value: 0, proportion: 0, route: 'cost/cost' },
|
||||
{ name: "净价", targetValue: 0, value: 0, proportion: 0, route: 'cost/cost' },
|
||||
{ name: "销量", targetValue: 0, value: 0, proportion: 0, route: 'profitAnalysis' },
|
||||
{ name: "双镀面板", targetValue: 0, value: 0, proportion: 0, route: 'profitAnalysis' },
|
||||
{ name: "溢价产品销量", targetValue: 0, value: 0, proportion: 0, route: 'profitAnalysis' }
|
||||
]
|
||||
}
|
||||
},
|
||||
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>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||
.scroll-container {
|
||||
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||
max-height: 210px;
|
||||
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||
overflow-y: auto;
|
||||
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||
overflow-x: hidden;
|
||||
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||
padding: 10px 0;
|
||||
|
||||
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||
/* Chrome/Safari */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
scrollbar-width: none;
|
||||
/* IE/Edge */
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||
.proBarInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 27px;
|
||||
/* 调整内边距,优化排版 */
|
||||
margin-bottom: 10px;
|
||||
/* 设备项之间的垂直间距 */
|
||||
}
|
||||
|
||||
/* 原有样式保留,优化细节 */
|
||||
.proBarInfoEqInfo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
/* 垂直居中,避免序号/文字错位 */
|
||||
}
|
||||
|
||||
.slot {
|
||||
width: 21px;
|
||||
height: 23px;
|
||||
background: rgba(0, 106, 205, 0.22);
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #68B5FF;
|
||||
line-height: 23px;
|
||||
/* 垂直居中文字 */
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eq-name {
|
||||
margin-left: 8px;
|
||||
/* 增加与序号的间距 */
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eqStatus {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.splitLine {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid #ADADAD;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #00FFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 序号和设备名垂直居中 */
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 状态/分割线/百分比垂直居中 */
|
||||
}
|
||||
|
||||
.proBarWrapper {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
margin-top: 6px;
|
||||
/* 进度条与上方信息的间距 */
|
||||
border-radius: 5px;
|
||||
/* 进度条圆角,优化视觉 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.proBarLine {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #ACACAC 100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.proBarLineTop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(53, 223, 247, 0) 0%, rgba(54, 220, 246, 0.92) 92%, #36F6E5 100%, #37ACF5 100%);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
/* 进度变化时添加过渡动画,更流畅 */
|
||||
}
|
||||
|
||||
/* 图表相关样式保留 */
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 57px;
|
||||
bottom: 42px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #00E8FF;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
499
src/views/home/components/order-bottom-leftItem.vue
Normal file
499
src/views/home/components/order-bottom-leftItem.vue
Normal file
@@ -0,0 +1,499 @@
|
||||
<template>
|
||||
<div class="coreItem">
|
||||
<!-- 单独渲染第一个item -->
|
||||
<div class="item" v-if="itemList.length > 0">
|
||||
<div class="unit">{{ itemList[0].unit }}</div>
|
||||
<div class="item-content">
|
||||
<div class="content-wrapper">
|
||||
<div class="left">
|
||||
<div class="number">{{ itemList[0].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>
|
||||
<div class="title">当前值</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress-group">
|
||||
<div class="progress-container">
|
||||
<!-- 进度条颜色和宽度动态绑定 -->
|
||||
<div class="progress-bar" :style="{
|
||||
width: itemList[0].progress + '%',
|
||||
background: getColor(itemList[0].currentValue, itemList[0].targetValue)
|
||||
}"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="yield" style="display: flex;justify-content: space-between;">
|
||||
<div class="progress-percent">完成率</div>
|
||||
<!-- 百分比颜色动态绑定 -->
|
||||
<div class="progress-percent">
|
||||
{{ 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>
|
||||
|
||||
<!-- 第一个城市进度 -->
|
||||
<div class="right-city">
|
||||
<div class="city">桐城</div>
|
||||
<div class="city-progress-group">
|
||||
<div class="city-progress-container">
|
||||
<!-- 城市进度条颜色动态绑定 -->
|
||||
<div class="city-progress-bar" :style="{
|
||||
width: item.progress + '%',
|
||||
background: getColor(item.currentValue, item.targetValue)
|
||||
}"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="city-progress-yield" style="display: flex;justify-content: space-between;">
|
||||
<!-- numerator颜色动态绑定 -->
|
||||
<div class="numerator" :style="{ color: getColor(item.currentValue, item.targetValue) }">
|
||||
12/13
|
||||
</div>
|
||||
<!-- 城市完成率颜色动态绑定 -->
|
||||
<div class="city-yield" :style="{ color: getColor(item.currentValue, item.targetValue) }">
|
||||
{{ item.progress }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第二个城市进度 -->
|
||||
<div class="right-city" style="margin-top: 2px;">
|
||||
<div class="city">桐城</div>
|
||||
<div class="city-progress-group">
|
||||
<div class="city-progress-container">
|
||||
<!-- 城市进度条颜色动态绑定 -->
|
||||
<div class="city-progress-bar" :style="{
|
||||
width: item.progress + '%',
|
||||
background: getColor(item.currentValue, item.targetValue)
|
||||
}"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="city-progress-yield" style="display: flex;justify-content: space-between;">
|
||||
<!-- numerator颜色动态绑定 -->
|
||||
<div class="numerator" :style="{ color: getColor(item.currentValue, item.targetValue) }">
|
||||
12/13
|
||||
</div>
|
||||
<!-- 城市完成率颜色动态绑定 -->
|
||||
<div class="city-yield" :style="{ color: getColor(item.currentValue, item.targetValue) }">
|
||||
{{ item.progress }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Container",
|
||||
components: {},
|
||||
props: ["name", "size", "icon"],
|
||||
data() {
|
||||
return {
|
||||
progress: 90, // 进度值基础参数
|
||||
itemList: [
|
||||
{
|
||||
unit: "总进度",
|
||||
targetValue: 16,
|
||||
currentValue: 14.5, // 小于目标值,应显示橙色
|
||||
progress: 90
|
||||
},
|
||||
{
|
||||
unit: "一组",
|
||||
targetValue: 16,
|
||||
currentValue: 17, // 大于目标值,应显示绿色
|
||||
progress: 106
|
||||
},
|
||||
{
|
||||
unit: "二组",
|
||||
targetValue: 16,
|
||||
currentValue: 16, // 等于目标值,应显示绿色
|
||||
progress: 100
|
||||
},
|
||||
{
|
||||
unit: "三组",
|
||||
targetValue: 16,
|
||||
currentValue: 15.2, // 小于目标值,应显示橙色
|
||||
progress: 85
|
||||
},
|
||||
{
|
||||
unit: "四组",
|
||||
targetValue: 16,
|
||||
currentValue: 18, // 大于目标值,应显示绿色
|
||||
progress: 112
|
||||
},
|
||||
{
|
||||
unit: "五组",
|
||||
targetValue: 16,
|
||||
currentValue: 14, // 小于目标值,应显示橙色
|
||||
progress: 80
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 颜色判断核心方法:实际值≥目标值返回绿色,否则返回橙色
|
||||
getColor(currentValue, targetValue) {
|
||||
return currentValue >= targetValue
|
||||
? "rgba(98, 213, 180, 1)"
|
||||
: "rgba(249, 164, 74, 1)";
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 第一个item单独样式(可选) */
|
||||
.item:first-of-type {
|
||||
// background: #f0f7ff; /* 示例背景色 */
|
||||
}
|
||||
|
||||
.coreItem {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 220px;
|
||||
height: 122px;
|
||||
background: #f9fcff;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.unit {
|
||||
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;
|
||||
}
|
||||
|
||||
.groupName {
|
||||
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;
|
||||
}
|
||||
|
||||
.left-target {
|
||||
.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: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.title {
|
||||
height: 14px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #868687;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.left-real {
|
||||
.number {
|
||||
height: 22px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
/* 颜色由动态绑定控制,此处不设置固定值 */
|
||||
}
|
||||
|
||||
.title {
|
||||
height: 14px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #868687;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 100%;
|
||||
.bg-default {
|
||||
background-image: url("../../../assets/img/order-item-bg.png");
|
||||
}
|
||||
|
||||
// 实际值 >= 目标值:绿色背景图
|
||||
.bg-green {
|
||||
background-image: url("../../../assets/img/order-item-greenbg.png");
|
||||
}
|
||||
.groupName {
|
||||
width: 129px;
|
||||
height: 32px;
|
||||
// background: url(../../../assets/img/order-item-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
color: #000000;
|
||||
text-align: center;
|
||||
|
||||
.title {
|
||||
margin-top: 10px;
|
||||
height: 12px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
margin-right: 8px;
|
||||
color: #000000;
|
||||
line-height: 12px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.unit {
|
||||
margin-top: 10px;
|
||||
margin-right: 14px;
|
||||
height: 12px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #000000;
|
||||
line-height: 12px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.right-city {
|
||||
.city {
|
||||
height: 14px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #575757;
|
||||
line-height: 14px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.title {
|
||||
height: 14px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #868687;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: calc(100% - 26px);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 1px;
|
||||
height: 46px;
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(255, 0, 0, 0),
|
||||
rgba(40, 203, 151, 1));
|
||||
}
|
||||
|
||||
.cityLine {
|
||||
width: 1px;
|
||||
margin-top: 40px;
|
||||
height: 76px;
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(255, 0, 0, 0),
|
||||
#cbcbcb);
|
||||
}
|
||||
|
||||
.left,
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
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: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.title {
|
||||
height: 14px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #868687;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.progress-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 230px;
|
||||
height: 10px;
|
||||
background: #ECEFF7;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
/* 背景色由动态绑定控制,移除固定值 */
|
||||
}
|
||||
|
||||
.city-progress-group {
|
||||
margin-top: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.city-progress-container {
|
||||
width: 112px;
|
||||
height: 10px;
|
||||
background: #ECEFF7;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.city-progress-bar {
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
opacity: 1;
|
||||
/* 背景色由动态绑定控制,移除固定值 */
|
||||
}
|
||||
|
||||
.progress-percent {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
/* 颜色由动态绑定控制,此处不设置固定值 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.numerator {
|
||||
/* 颜色由动态绑定控制,此处不设置固定值 */
|
||||
}
|
||||
|
||||
.city-yield {
|
||||
/* 颜色由动态绑定控制,此处不设置固定值 */
|
||||
}
|
||||
}
|
||||
|
||||
.groupData {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 右上角折现边框(主边框) */
|
||||
.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::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #EFF3F8;
|
||||
transform: rotate(-45deg) translate(50%, -50%);
|
||||
transform-origin: top right;
|
||||
}
|
||||
</style>
|
||||
115
src/views/home/components/orderProgress.vue
Normal file
115
src/views/home/components/orderProgress.vue
Normal file
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<bottomMiddleContainer name="订单进度跟踪·万m²" icon="cockpitItemIcon" size="bottomBasic">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div style="display: flex;gap: 9px;padding: 14px 16px;">
|
||||
<orderItem />
|
||||
</div>
|
||||
</bottomMiddleContainer>
|
||||
</div>
|
||||
</template>
|
||||
<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'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { bottomMiddleContainer, topItem, coreBottomBar, orderItem },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
leftEqInfoData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
maintenanceTasks: [
|
||||
{ id: 1, eqName: '连续化', taskName: '例行维护', },
|
||||
{ id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
|
||||
{ id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
|
||||
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
|
||||
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
|
||||
|
||||
],
|
||||
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' },
|
||||
|
||||
]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
productionOverviewVo: {
|
||||
handler(newValue, oldValue) {
|
||||
this.updateChart()
|
||||
},
|
||||
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 销毁图表,避免内存泄漏
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.legend-group {
|
||||
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>
|
||||
266
src/views/home/components/pieChart.vue
Normal file
266
src/views/home/components/pieChart.vue
Normal file
@@ -0,0 +1,266 @@
|
||||
<template>
|
||||
<!-- 动态绑定 ref:使用 props 中的 chartRef,而非硬编码 -->
|
||||
<div :ref="chartRef" id="coreLineChart" style="height: 100%; width: 100%;"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'Container',
|
||||
props: {
|
||||
// 1. 重命名 props:ref → chartRef,避免关键字冲突,同时定义类型和默认值
|
||||
chartRef: {
|
||||
type: String,
|
||||
required: true, // 强制父组件传值,避免获取不到 DOM
|
||||
// validator: (value) => {
|
||||
// // 验证:ref 名不能为空,确保有效
|
||||
// return value.trim() !== '';
|
||||
// }
|
||||
}
|
||||
},
|
||||
components: {},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initData();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
// 2. 动态获取 DOM:通过 props 中的 chartRef 拿到对应的 ref 元素
|
||||
const chartDom = this.$refs[this.chartRef];
|
||||
if (!chartDom) {
|
||||
console.error(`图表容器未找到!请确认父组件传递的 chartRef 为 "${this.chartRef}"`);
|
||||
return;
|
||||
}
|
||||
const myChart = echarts.init(chartDom);
|
||||
|
||||
// 自定义颜色数组(与系列一一对应)
|
||||
const customColors = [
|
||||
'rgba(113, 100, 255, 1)',
|
||||
'rgba(40, 138, 255, 1)',
|
||||
'rgba(118, 218, 190, 1)',
|
||||
'rgba(255, 206, 106, 1)',
|
||||
];
|
||||
|
||||
const option = {
|
||||
// 标题配置(主标题+副标题)
|
||||
title: [
|
||||
{
|
||||
text: '月度',
|
||||
left: 'center',
|
||||
top: '35%',
|
||||
textStyle: {
|
||||
fontSize: 24,
|
||||
letterSpacing: 5,
|
||||
color: 'rgba(0, 0, 0, 0.85)',
|
||||
fontFamily: 'PingFangSC, PingFang SC'
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '单位:万m²',
|
||||
left: 'center',
|
||||
top: '50%',
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
color: 'rgba(0, 0, 0, 0.55)',
|
||||
fontFamily: 'PingFangSC, PingFang SC'
|
||||
}
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '销量',
|
||||
type: 'pie',
|
||||
radius: ['60%', '80%'],
|
||||
center: ['50%', '50%'],
|
||||
avoidLabelOverlap: false,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'outside',
|
||||
distance: 10,
|
||||
formatter: '{c}万m²\n{b}',
|
||||
textStyle: {
|
||||
fontSize: 14,
|
||||
color: 'rgba(0, 0, 0, 0.7)',
|
||||
fontFamily: 'PingFangSC, PingFang SC',
|
||||
lineHeight: 1.5
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: true,
|
||||
length: 0,
|
||||
length2: 30,
|
||||
lineStyle: {
|
||||
color: (params) => customColors[params.dataIndex]
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
color: (params) => customColors[params.dataIndex]
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: 1048, name: '单镀面板',
|
||||
label: {
|
||||
normal: {
|
||||
align: 'left',
|
||||
distanceToLabelLine: 2,
|
||||
formatter: (params) => [
|
||||
`{b|${params.value.toLocaleString()}}`,
|
||||
`{hr|■}{c|${params.name}}`
|
||||
].join('\n'),
|
||||
rich: {
|
||||
hr: {
|
||||
color: 'rgba(39, 96, 255, 1)',
|
||||
fontSize: 20,
|
||||
padding: [26, 8, 0, 0]
|
||||
},
|
||||
b: {
|
||||
color: 'rgba(0, 0, 0, 0.75)',
|
||||
fontSize: 18,
|
||||
padding: [0, 0, 0, 0]
|
||||
},
|
||||
c: {
|
||||
color: 'rgba(64, 64, 64, 1)',
|
||||
fontSize: 14,
|
||||
padding: [30, 0, 0, -5]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
lineStyle: { color: 'rgba(39, 96, 255, 1)' }
|
||||
},
|
||||
itemStyle: { color: 'rgba(39, 96, 255, 1)' }
|
||||
},
|
||||
{
|
||||
value: 735, name: '双镀面板',
|
||||
label: {
|
||||
normal: {
|
||||
align: 'left',
|
||||
distanceToLabelLine: 2,
|
||||
formatter: (params) => [
|
||||
`{b|${params.value.toLocaleString()}}`,
|
||||
`{hr|■}{c|${params.name}}`
|
||||
].join('\n'),
|
||||
rich: {
|
||||
hr: {
|
||||
color: 'rgba(40, 138, 255, 1)',
|
||||
fontSize: 20,
|
||||
padding: [6, 8, 0, 10]
|
||||
},
|
||||
b: {
|
||||
color: 'rgba(0, 0, 0, 0.75)',
|
||||
fontSize: 18,
|
||||
padding: [-30, 0, 0, 30]
|
||||
},
|
||||
c: {
|
||||
color: 'rgba(64, 64, 64, 1)',
|
||||
fontSize: 14,
|
||||
padding: [10, 0, 0, -5]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
length: 0,
|
||||
length2: 50,
|
||||
lineStyle: { color: 'rgba(40, 138, 255, 1)' }
|
||||
},
|
||||
itemStyle: { color: 'rgba(40, 138, 255, 1)' }
|
||||
},
|
||||
{
|
||||
value: 580, name: '丝印打孔',
|
||||
label: {
|
||||
normal: {
|
||||
align: 'left',
|
||||
distanceToLabelLine: 2,
|
||||
formatter: (params) => [
|
||||
`{b|${params.value.toLocaleString()}}`,
|
||||
`{hr|■}{c|${params.name}}`
|
||||
].join('\n'),
|
||||
rich: {
|
||||
hr: {
|
||||
color: 'rgba(118, 218, 190, 1)',
|
||||
fontSize: 20,
|
||||
padding: [36, 5, 0, 0]
|
||||
},
|
||||
b: {
|
||||
color: 'rgba(0, 0, 0, 0.75)',
|
||||
fontSize: 18,
|
||||
padding: [0, 10, 0, 0]
|
||||
},
|
||||
c: {
|
||||
color: 'rgba(64, 64, 64, 1)',
|
||||
fontSize: 14,
|
||||
padding: [40, -5, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
lineStyle: { color: 'rgba(118, 218, 190, 1)' }
|
||||
},
|
||||
itemStyle: { color: 'rgba(118, 218, 190, 1)' }
|
||||
},
|
||||
{
|
||||
value: 484, name: '无印打孔',
|
||||
label: {
|
||||
normal: {
|
||||
align: 'left',
|
||||
distanceToLabelLine: 2,
|
||||
formatter: (params) => [
|
||||
`{b|${params.value.toLocaleString()}}`,
|
||||
`{hr|■}{c|${params.name}}`
|
||||
].join('\n'),
|
||||
rich: {
|
||||
hr: {
|
||||
color: 'rgba(255, 206, 106, 1)',
|
||||
fontSize: 20,
|
||||
padding: [36, 5, 0, 0]
|
||||
},
|
||||
b: {
|
||||
color: 'rgba(0, 0, 0, 0.75)',
|
||||
fontSize: 18,
|
||||
padding: [0, 20, 0, 0]
|
||||
},
|
||||
c: {
|
||||
color: 'rgba(64, 64, 64, 1)',
|
||||
fontSize: 14,
|
||||
padding: [40, 15, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
lineStyle: { color: 'rgba(255, 206, 106, 1)' }
|
||||
},
|
||||
itemStyle: { color: 'rgba(255, 206, 106, 1)' }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
option && myChart.setOption(option);
|
||||
|
||||
// 窗口缩放监听
|
||||
window.addEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
|
||||
// 组件销毁清理
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
myChart.dispose();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
398
src/views/home/components/premProdStatus.vue
Normal file
398
src/views/home/components/premProdStatus.vue
Normal file
@@ -0,0 +1,398 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="溢价产品完成情况" icon="cockpitItemIcon" size="operatingBasic" topSize="middle">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px 8px 16px; display: flex;flex-direction: column; width: 100%;">
|
||||
<!-- 2. .top 保持 flex,无需固定高度,自动跟随子元素拉伸 -->
|
||||
<div class="top" style="display: flex; width: 100%;">
|
||||
<top-item :itemList="parentItemList" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom-content" style="display: flex;flex-direction: column; width: 100%;">
|
||||
<div class="content-top">
|
||||
<svg-icon style="font-size: 32px; margin-left: 16px" icon-class="cockpitItemIcon" />
|
||||
<span style="
|
||||
width: 412px;
|
||||
height: 32px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 24px;
|
||||
color: #000000;
|
||||
line-height: 60px;
|
||||
letter-spacing: 3px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
">
|
||||
各产品类型销量占比
|
||||
</span>
|
||||
</div>
|
||||
<div class="pie" style="display: flex;gap: 8px;margin-top: 68px;padding: 0 16px;">
|
||||
<div class="month-pie" style="height: 212px;width: 382px;background: #F9FCFF;">
|
||||
<pieChart :chartRef=" 'monthChart' " />
|
||||
</div>
|
||||
<div class="-pie" style="height: 212px;width: 382px;background: #F9FCFF;">
|
||||
<pieChartTwo :chartRef="'yearChart'" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
import pieChart from './pieChart.vue'
|
||||
import pieChartTwo from './pieChart.vue'
|
||||
|
||||
|
||||
// import * as echarts from 'echarts'
|
||||
import topItem from './operating-item.vue'
|
||||
import coreBottomBar from './coreBottomBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, topItem, coreBottomBar, pieChart, pieChartTwo },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
leftEqInfoData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
parentItemList: [
|
||||
{ name: "月度", targetValue: 0, value: 0, proportion: 0, route:'profitAnalysis' },
|
||||
{ name: "年度", targetValue: 0, value: 0, proportion: 0, route: 'profitAnalysis' },
|
||||
// { unit: "单价", targetValue: 20, currentValue: 16, progress: 80 },
|
||||
// { unit: "净价", targetValue: 20, currentValue: 16, progress: 80 },
|
||||
// { unit: "销量", targetValue: 20, currentValue: 16, progress: 80 },
|
||||
// { unit: "双镀面板", targetValue: 15, currentValue: 13.8, progress: 92 },
|
||||
// { unit: "溢价产品销量", targetValue: 15, currentValue: 13.8, progress: 92 }
|
||||
]
|
||||
}
|
||||
},
|
||||
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>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||
.scroll-container {
|
||||
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||
max-height: 210px;
|
||||
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||
overflow-y: auto;
|
||||
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||
overflow-x: hidden;
|
||||
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||
padding: 10px 0;
|
||||
|
||||
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||
/* Chrome/Safari */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
scrollbar-width: none;
|
||||
/* IE/Edge */
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||
.proBarInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 27px;
|
||||
/* 调整内边距,优化排版 */
|
||||
margin-bottom: 10px;
|
||||
/* 设备项之间的垂直间距 */
|
||||
}
|
||||
|
||||
/* 原有样式保留,优化细节 */
|
||||
.proBarInfoEqInfo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
/* 垂直居中,避免序号/文字错位 */
|
||||
}
|
||||
|
||||
.slot {
|
||||
width: 21px;
|
||||
height: 23px;
|
||||
background: rgba(0, 106, 205, 0.22);
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #68B5FF;
|
||||
line-height: 23px;
|
||||
/* 垂直居中文字 */
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eq-name {
|
||||
margin-left: 8px;
|
||||
/* 增加与序号的间距 */
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eqStatus {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.splitLine {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid #ADADAD;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #00FFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 序号和设备名垂直居中 */
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 状态/分割线/百分比垂直居中 */
|
||||
}
|
||||
|
||||
.proBarWrapper {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
margin-top: 6px;
|
||||
/* 进度条与上方信息的间距 */
|
||||
border-radius: 5px;
|
||||
/* 进度条圆角,优化视觉 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.proBarLine {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #ACACAC 100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.proBarLineTop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(53, 223, 247, 0) 0%, rgba(54, 220, 246, 0.92) 92%, #36F6E5 100%, #37ACF5 100%);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
/* 进度变化时添加过渡动画,更流畅 */
|
||||
}
|
||||
|
||||
/* 图表相关样式保留 */
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 57px;
|
||||
bottom: 42px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #00E8FF;
|
||||
}
|
||||
.bottom-content{
|
||||
position: relative;
|
||||
// height: 280px;
|
||||
padding: 0 0 6px 0;
|
||||
.content-top {
|
||||
top: 0;
|
||||
left: -5px;
|
||||
position: absolute;
|
||||
height: 60px;
|
||||
width: 804px;
|
||||
background: url(../../../assets/img/topTileMiddle.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
273
src/views/home/components/productBar.vue
Normal file
273
src/views/home/components/productBar.vue
Normal file
@@ -0,0 +1,273 @@
|
||||
<template>
|
||||
<div style="position: relative;">
|
||||
<div class="legend">
|
||||
<span class="legend-item-line">
|
||||
<span class="line target"></span>
|
||||
目标
|
||||
</span>
|
||||
<span class="legend-item-line">
|
||||
<span class="line real"></span>
|
||||
实际
|
||||
</span>
|
||||
</div>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="height: 219px; width: 100%;"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'Container',
|
||||
components: {},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initData();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
// 优先使用 ref 获取 DOM,避免 id 冲突
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
const myChart = echarts.init(chartDom);
|
||||
const option = {
|
||||
// color: ['#80FFA5', '#00DDFF', '#37A2FF', '#FF0087', '#FFBF00'],
|
||||
// title: {
|
||||
// text: 'Gradient Stacked Area Chart'
|
||||
// },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
}
|
||||
},
|
||||
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
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
onZero: false,
|
||||
lineStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.15)'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
width: 38,
|
||||
overflow: 'break'
|
||||
},
|
||||
data: ['6月', '7月', '8月', '9月', '10月', '11月']
|
||||
}
|
||||
],
|
||||
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)
|
||||
},
|
||||
scale: true,
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.15)',
|
||||
// type: 'dashed'
|
||||
}
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.15)'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Line 1',
|
||||
type: 'line',
|
||||
stack: 'Total',
|
||||
symbol: 'circle', // 点的形状(circle为圆形)
|
||||
lineStyle: {
|
||||
color: 'rgba(255, 132, 0, .5)',
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(255, 132, 0, .5)',
|
||||
borderColor: 'rgba(255, 132, 0, .5)', // 数据点边框色(白色)
|
||||
borderWidth: 2, // 数据点边框宽度
|
||||
},
|
||||
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)',
|
||||
},
|
||||
]),
|
||||
},
|
||||
// emphasis: { focus: 'series' },
|
||||
data: [140, 232, 101, 264, 90, 340, 250]
|
||||
},
|
||||
{
|
||||
name: 'Line 2',
|
||||
type: 'line',
|
||||
stack: 'Total',
|
||||
symbol: 'circle', // 点的形状(circle为圆形)
|
||||
lineStyle: {
|
||||
color: 'rgba(98, 213, 180, .5)',
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(98, 213, 180, .5)',
|
||||
borderColor: 'rgba(98, 213, 180, .5)', // 数据点边框色(白色)
|
||||
borderWidth: 2, // 数据点边框宽度
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.5,
|
||||
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)',
|
||||
},
|
||||
]),
|
||||
},
|
||||
// emphasis: { focus: 'series' },
|
||||
data: [120, 282, 111, 234, 220, 340, 310]
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
option && myChart.setOption(option);
|
||||
|
||||
// 监听窗口缩放
|
||||
window.addEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
|
||||
// 组件销毁时清理
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
myChart.dispose();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.legend {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: -5px;
|
||||
}
|
||||
|
||||
.legend-item-line {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
margin-right: 20px;
|
||||
/* 增加两个图例项之间的间距 */
|
||||
position: relative;
|
||||
padding-left: 8px;
|
||||
/* 给文字左侧增加内边距 */
|
||||
|
||||
.line {
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
/* 调整线条位置 */
|
||||
top: 10px;
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 2px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.target {
|
||||
background: rgba(91, 230, 190, 1);
|
||||
}
|
||||
|
||||
.real {
|
||||
background: rgba(255, 132, 0, 1);
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
/* 关键:增加图例圆点和文字之间的间距 */
|
||||
margin-bottom: 2px;
|
||||
background-color: rgba(255, 132, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
186
src/views/home/components/productBottomBar.vue
Normal file
186
src/views/home/components/productBottomBar.vue
Normal file
@@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<div class="coreBar" style="width: 100%;">
|
||||
<!-- 循环渲染item,data中配置的每一项对应一个卡片 -->
|
||||
<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="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="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="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 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 class="lineBottom" style="height: 219px; width: 100%">
|
||||
<coreLineChart style="height: 219px; width: 500px" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import coreLineChart from './productBar.vue';
|
||||
export default {
|
||||
name: "Container",
|
||||
components: { coreLineChart },
|
||||
props: ["name", "size", "icon"],
|
||||
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: {
|
||||
},
|
||||
};
|
||||
</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;
|
||||
font-size: 18px;
|
||||
color: #000000;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
.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;
|
||||
// }
|
||||
.item-button {
|
||||
cursor: pointer;
|
||||
width: 59px;
|
||||
height: 24px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #0b58ff;
|
||||
line-height: 24px;
|
||||
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; // 文字加粗,突出激活状态
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
208
src/views/home/components/profit-item-middle.vue
Normal file
208
src/views/home/components/profit-item-middle.vue
Normal file
@@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<div class="coreItem" :style="{ 'height': height + 'px' }">
|
||||
<!-- 用 v-for 动态生成每个 item -->
|
||||
<div class="item" v-for="(item, index) in itemList" :key="index" :class="{ 'has-route': item.route }" @click="handleRoute(item.route)">
|
||||
<div class="unit">{{ item.name }}</div>
|
||||
<div class="item-content">
|
||||
<!-- 左右内容容器 -->
|
||||
<div class="content-wrapper">
|
||||
<div class="left">
|
||||
<div class="number">{{ item.targetValue || 0 }}</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="right">
|
||||
<!-- 实际值颜色动态绑定 -->
|
||||
<div class="number" :style="{ 'color': item.completed === 0 ? '#FF8400' : 'rgba(54, 181, 138, .7)' }">
|
||||
{{ item.value || 0 }}
|
||||
</div>
|
||||
<div class="title">实际值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="right">
|
||||
<!-- 完成率颜色动态绑定 -->
|
||||
<div class="number" :style="{ 'color': item.completed === 0 ? '#FF8400' : 'rgba(54, 181, 138, .7)' }">
|
||||
{{ item.proportion !== null && item.proportion !== undefined ? (item.proportion * 100) + '%' : '0%' }}
|
||||
</div>
|
||||
<div class="title">完成率</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Container",
|
||||
components: {},
|
||||
props: ['itemList', 'height'],
|
||||
data() {
|
||||
return {
|
||||
progress: 90, // 进度值,方便统一控制
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
itemList: {
|
||||
handler(newList) {
|
||||
// 当 itemList 变化时执行的逻辑(如重新计算、更新视图等)
|
||||
console.log("子组件接收的 itemList 已更新:", newList);
|
||||
},
|
||||
deep: true // 深度监听:若 itemList 内部对象属性变化(如 currentValue 改变),也能触发监听
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
handleRoute(route) {
|
||||
this.$router.push({
|
||||
path:route
|
||||
})
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coreItem {
|
||||
display: flex;
|
||||
// height: 382px;
|
||||
// overflow: hidden;
|
||||
flex-wrap: wrap;
|
||||
// grid-template-columns: 1fr 1fr;
|
||||
// grid-template-rows: 1fr 1fr;
|
||||
justify-content: flex-start;
|
||||
align-content: flex-start;
|
||||
gap: 8px;
|
||||
|
||||
// overflow-y: auto;
|
||||
// padding: 8px;
|
||||
/* 2. 隐藏 Chrome/Safari 滚动条 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
/* 滚动条宽度设为0 */
|
||||
height: 0;
|
||||
/* 横向滚动条(如需)也隐藏 */
|
||||
}
|
||||
|
||||
/* 3. 隐藏 Firefox 滚动条 */
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 376px;
|
||||
height: 110px;
|
||||
background: #f9fcff;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.has-route {
|
||||
cursor: pointer; // 有路由时显示指针,提示可点击
|
||||
box-shadow: 0 0 0 transparent; // 默认无阴影
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 4px 12px 2px #B5CDE5; // hover 阴影
|
||||
transform: translateY(-2px); // 轻微上浮,增强交互感
|
||||
}
|
||||
}
|
||||
|
||||
.unit {
|
||||
// width: 124px;
|
||||
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: 8px;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: calc(100% - 26px);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 1px;
|
||||
height: 46px;
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(255, 0, 0, 0),
|
||||
rgba(40, 203, 151, 1));
|
||||
}
|
||||
|
||||
.left,
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.number {
|
||||
height: 22px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 600;
|
||||
font-size: 26px;
|
||||
color: rgba(103, 103, 103, 0.79);
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.title {
|
||||
height: 14px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #868687;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* 进度条和百分比的外层容器 */
|
||||
.progress-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
/* 进度条和百分比的间距 */
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 190px;
|
||||
height: 10px;
|
||||
background: #ECEFF7;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: #28CB97;
|
||||
border-radius: 8px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* 百分比文本样式 */
|
||||
.progress-percent {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #868687;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
206
src/views/home/components/profit-item.vue
Normal file
206
src/views/home/components/profit-item.vue
Normal file
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<div class="coreItem" :style="{'height':height+ 'px'} ">
|
||||
<!-- 用 v-for 动态生成每个 item -->
|
||||
<div class="item" v-for="(item, index) in itemList" :key="index" :class="{ 'has-route': item.route }"
|
||||
@click="handleRoute(item.route)">
|
||||
<div class="unit">{{ item.name }}</div>
|
||||
<div class="item-content">
|
||||
<!-- 左右内容容器 -->
|
||||
<div class="content-wrapper">
|
||||
<div class="left">
|
||||
<div class="number">{{ item.targetValue ||0 }}</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="right">
|
||||
<!-- 实际值颜色动态绑定 -->
|
||||
<div class="number" :style="{ 'color': item.completed === 0 ? '#FF8400' : 'rgba(54, 181, 138, .7)' }">
|
||||
{{ item.value || 0 }}
|
||||
</div>
|
||||
<div class="title">实际值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="right">
|
||||
<!-- 完成率颜色动态绑定 -->
|
||||
<div class="number" :style="{ 'color': item.completed === 0 ? '#FF8400' : 'rgba(54, 181, 138, .7)' }">
|
||||
{{ item.proportion !== null && item.proportion !== undefined ? (item.proportion * 100) + '%' : '0%' }}
|
||||
</div>
|
||||
<div class="title">完成率</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Container",
|
||||
components: {},
|
||||
props: ['itemList','height'],
|
||||
data() {
|
||||
return {
|
||||
progress: 90, // 进度值,方便统一控制
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
itemList: {
|
||||
handler(newList) {
|
||||
// 当 itemList 变化时执行的逻辑(如重新计算、更新视图等)
|
||||
console.log("子组件接收的 itemList 已更新:", newList);
|
||||
},
|
||||
deep: true // 深度监听:若 itemList 内部对象属性变化(如 currentValue 改变),也能触发监听
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
handleRoute(route) {
|
||||
this.$router.push({
|
||||
path: route
|
||||
})
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coreItem {
|
||||
display: flex;
|
||||
// height: 382px;
|
||||
// overflow: hidden;
|
||||
flex-wrap: wrap;
|
||||
// grid-template-columns: 1fr 1fr;
|
||||
// grid-template-rows: 1fr 1fr;
|
||||
justify-content: flex-start;
|
||||
align-content: flex-start;
|
||||
gap: 8px;
|
||||
// overflow-y: auto;
|
||||
// padding: 8px;
|
||||
/* 2. 隐藏 Chrome/Safari 滚动条 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
/* 滚动条宽度设为0 */
|
||||
height: 0;
|
||||
/* 横向滚动条(如需)也隐藏 */
|
||||
}
|
||||
|
||||
/* 3. 隐藏 Firefox 滚动条 */
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 376px;
|
||||
height: 95px;
|
||||
background: #f9fcff;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
&.has-route {
|
||||
cursor: pointer; // 有路由时显示指针,提示可点击
|
||||
box-shadow: 0 0 0 transparent; // 默认无阴影
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 4px 12px 2px #B5CDE5; // hover 阴影
|
||||
transform: translateY(-2px); // 轻微上浮,增强交互感
|
||||
}
|
||||
}
|
||||
.unit {
|
||||
// width: 124px;
|
||||
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: 8px;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: calc(100% - 26px);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 1px;
|
||||
height: 46px;
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(255, 0, 0, 0),
|
||||
rgba(40, 203, 151, 1));
|
||||
}
|
||||
|
||||
.left,
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.number {
|
||||
height: 22px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 600;
|
||||
font-size: 26px;
|
||||
color: rgba(103, 103, 103, 0.79);
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.title {
|
||||
height: 14px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #868687;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* 进度条和百分比的外层容器 */
|
||||
.progress-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
/* 进度条和百分比的间距 */
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 190px;
|
||||
height: 10px;
|
||||
background: #ECEFF7;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: #28CB97;
|
||||
border-radius: 8px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* 百分比文本样式 */
|
||||
.progress-percent {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #868687;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
379
src/views/home/components/profitBar.vue
Normal file
379
src/views/home/components/profitBar.vue
Normal file
@@ -0,0 +1,379 @@
|
||||
<template>
|
||||
<div class="coreBar">
|
||||
<div class="barTop">
|
||||
<div class="right-container">
|
||||
<div class="legend">
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon line yield"></span>
|
||||
完成率
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square target"></span>
|
||||
目标
|
||||
</span>
|
||||
<span class="legend-item close-item">
|
||||
<span class="legend-icon square achieved"></span>
|
||||
</span>
|
||||
<span class="legend-item close-item">
|
||||
<span class="legend-icon square unachieved"></span>
|
||||
实际
|
||||
</span>
|
||||
</div>
|
||||
<!-- 按钮组 -->
|
||||
<div class="button-group">
|
||||
<div class="item-button category-btn">
|
||||
<span class="item-text" style="width: 88px;">类目选择</span>
|
||||
</div>
|
||||
<div class="dropdown-container">
|
||||
<div class="item-button profit-btn active"
|
||||
@click.stop="isDropdownShow = !isDropdownShow">
|
||||
<span class="item-text profit-text">{{ selectedProfit || '请选择' }}</span>
|
||||
<span class="dropdown-arrow" :class="{ 'rotate': isDropdownShow }"></span>
|
||||
</div>
|
||||
<div class="dropdown-options" v-if="isDropdownShow">
|
||||
<div class="dropdown-option" v-for="(item, index) in profitOptions" :key="index"
|
||||
@click.stop="selectProfit(item)">
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||
<!-- 调用方法传递数据,通过 :key 触发组件更新 -->
|
||||
<operatingLineBar :chart-data="dataChart" :key="selectedProfit + JSON.stringify(formattedData)"
|
||||
style="height: 99%; width: 100%" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import operatingLineBar from './operatingBottomLineBar.vue';
|
||||
export default {
|
||||
name: "Container",
|
||||
components: { operatingLineBar },
|
||||
props: [
|
||||
"formattedData" // 接收父组件处理后的数据
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
activeButton: 0,
|
||||
isDropdownShow: false,
|
||||
dataChart:{},
|
||||
selectedProfit: '', // 初始为空字符串
|
||||
profitOptions: ['利润总额', '毛利率', '产量', '销量', '成本', '营业收入'],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 核心:将原计算属性改为方法,主动处理数据
|
||||
getChartData() {
|
||||
const currentKey = this.selectedProfit
|
||||
console.log(this.formattedData,'22222');
|
||||
|
||||
// 严格判断数据有效性
|
||||
if (
|
||||
!this.formattedData ||
|
||||
typeof this.formattedData !== 'object' ||
|
||||
!this.formattedData[currentKey]
|
||||
) {
|
||||
return { series: [], allPlaceNames: [] };
|
||||
}
|
||||
|
||||
const targetData = this.formattedData[currentKey];
|
||||
const allPlaceNames = this.formattedData.allPlaceNames || [];
|
||||
|
||||
// 生成图表系列数据
|
||||
const series = [
|
||||
{
|
||||
name: '完成率',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
lineStyle: { color: 'rgba(40, 138, 255, .5)', width: 2 },
|
||||
itemStyle: {
|
||||
color: 'rgba(40, 138, 255, 1)',
|
||||
borderColor: 'rgba(40, 138, 255, 1)',
|
||||
borderWidth: 2,
|
||||
radius: 4
|
||||
},
|
||||
data: targetData.map(item => item.proportion),
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
{
|
||||
name: '目标',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 18,
|
||||
itemStyle: {
|
||||
color: '#2889FF',
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
},
|
||||
data: targetData.map(item => item.targetValue)
|
||||
},
|
||||
{
|
||||
name: '实际',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 18,
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
const completed = targetData[params.dataIndex].completed;
|
||||
return completed === 1 ? 'rgba(255, 132, 0, 1)' : 'rgba(40, 203, 151, 1)';
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
},
|
||||
data: targetData.map(item => item.value)
|
||||
}
|
||||
];
|
||||
|
||||
// return { series, allPlaceNames };
|
||||
this.dataChart = { series, allPlaceNames };
|
||||
console.log(this.dataChart,'dataChart');
|
||||
|
||||
},
|
||||
|
||||
selectProfit(item) {
|
||||
this.selectedProfit = item;
|
||||
this.activeButton = 1;
|
||||
this.isDropdownShow = false;
|
||||
this.getChartData()
|
||||
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化时如果有数据,默认选中第一个选项
|
||||
// this.selectedProfit = '总额'
|
||||
// this.getChartData()
|
||||
|
||||
// 点击外部关闭下拉菜单
|
||||
const handleOutsideClick = (e) => {
|
||||
if (!this.$el.contains(e.target)) {
|
||||
this.isDropdownShow = false;
|
||||
}
|
||||
};
|
||||
document.addEventListener('click', handleOutsideClick);
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
document.removeEventListener('click', handleOutsideClick);
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
// 深度监听formattedData变化,确保异步加载的数据能被捕获
|
||||
formattedData: {
|
||||
handler(newVal) {
|
||||
if (newVal && Object.keys(newVal).length) {
|
||||
// 数据加载完成后,默认选中第一个选项
|
||||
this.selectedProfit = this.profitOptions[0];
|
||||
this.getChartData()
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
immediate: true // 初始化时立即执行一次
|
||||
},
|
||||
// selectedProfit: {
|
||||
// handler(newVal) {
|
||||
// this.getChartData()
|
||||
|
||||
// },
|
||||
// deep: true,
|
||||
// immediate: true // 初始化时立即执行一次
|
||||
// },
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 样式保持不变 */
|
||||
.coreBar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
|
||||
.barTop {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
|
||||
.right-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
margin-right: 46px;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.legend-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.legend-icon.line {
|
||||
width: 12px;
|
||||
height: 2px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: -2px;
|
||||
left: 3px;
|
||||
width: 6px;
|
||||
border-radius: 50%;
|
||||
height: 6px;
|
||||
background-color: rgba(40, 138, 255, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.legend-icon.square {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.yield {
|
||||
background: rgba(40, 138, 255, 1);
|
||||
}
|
||||
|
||||
.target {
|
||||
background: #2889FF;
|
||||
}
|
||||
|
||||
.achieved {
|
||||
background: rgba(40, 203, 151, 1);
|
||||
}
|
||||
|
||||
.unachieved {
|
||||
background: rgba(255, 132, 0, 1);
|
||||
}
|
||||
|
||||
.legend-item.close-item+.legend-item.close-item {
|
||||
margin-left: -8px;
|
||||
}
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
position: relative;
|
||||
gap: 2px;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
background: #ecf4fe;
|
||||
margin: 0;
|
||||
|
||||
.dropdown-container {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.item-button {
|
||||
cursor: pointer;
|
||||
height: 24px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
font-style: normal;
|
||||
letter-spacing: 2px;
|
||||
padding: 0 24px 0 12px;
|
||||
overflow: hidden;
|
||||
|
||||
.item-text {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.category-btn {
|
||||
width: 88px;
|
||||
border-top-left-radius: 12px;
|
||||
border-bottom-left-radius: 12px;
|
||||
background: #ffffff;
|
||||
color: #0b58ff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.profit-btn {
|
||||
width: 118px;
|
||||
border-top-right-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
position: relative;
|
||||
background: #ffffff;
|
||||
color: #0b58ff;
|
||||
text-align: left;
|
||||
|
||||
&.active {
|
||||
background: #3071ff;
|
||||
color: rgba(249, 252, 255, .8);
|
||||
}
|
||||
|
||||
.profit-text {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-arrow {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 6px solid currentColor;
|
||||
border-top: 4px solid transparent;
|
||||
border-bottom: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&.rotate {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-options {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
margin-top: 2px;
|
||||
width: 118px;
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
|
||||
.dropdown-option {
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
letter-spacing: 1px;
|
||||
|
||||
&:hover {
|
||||
background: #f5f7fa;
|
||||
color: #3071ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
288
src/views/home/components/profitLineChart.vue
Normal file
288
src/views/home/components/profitLineChart.vue
Normal file
@@ -0,0 +1,288 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="趋势图" icon="cockpitItemIcon" size="operatingLarge" topSize="large">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex;width: 100%;">
|
||||
<div class="bottom" style="height: 330px; display: flex; width: 100%;background-color: rgba(249, 252, 255, 1);">
|
||||
<!-- <top-item /> -->
|
||||
<profitBar :formattedData="formatData" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
// import * as echarts from 'echarts'
|
||||
import profitBar from './profitBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, profitBar },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
bottomChartData: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
formatData:{},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听原始数据变化,自动更新图表(不直接调用initChart,而是通过数据变化触发)
|
||||
bottomChartData: {
|
||||
handler() {
|
||||
this.formatData = this.formatAllData();
|
||||
console.log(this.formatData,'this.formattedData');
|
||||
|
||||
// this.updateChart(); // 用updateChart替代initChart,语义更清晰
|
||||
},
|
||||
deep: true,
|
||||
immediate: true // 初始化时立即执行一次
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
|
||||
methods: {
|
||||
cleanPlaceName(place) {
|
||||
return place?.replace('分公司', '') || ''; // 匹配“分公司”并替换为空,兼容null/undefined
|
||||
},
|
||||
formatAllData() {
|
||||
const dataSource = this.bottomChartData;
|
||||
if (!Array.isArray(dataSource) || dataSource.length === 0) return { allPlaceNames: [], ...this.getDefaultEmptyData() };
|
||||
|
||||
// 提取所有分公司名(x轴数据)
|
||||
const allPlaceNames = dataSource.map(item => this.cleanPlaceName(item.name)).filter(Boolean)
|
||||
const PlaceNames = dataSource.map(item => item.name).filter(Boolean);
|
||||
// 定义指标与 productFactors 字段的映射关系
|
||||
const indicatorMap = {
|
||||
'利润总额': 'profit',
|
||||
'毛利率': 'profitRate',
|
||||
'产量': 'products',
|
||||
'销量': 'sales',
|
||||
'成本': 'cost',
|
||||
'营业收入': 'income'
|
||||
};
|
||||
|
||||
// 格式化单个指标数据
|
||||
const formatSingleType = (indicatorKey) => {
|
||||
const field = indicatorMap[indicatorKey];
|
||||
return PlaceNames.map(place => {
|
||||
const targetItem = dataSource.find(item => item.name === place);
|
||||
return {
|
||||
targetValue: targetItem?.[`${field}Target`] || 0, // 对应指标的目标值字段(如 profitTarget)
|
||||
value: targetItem?.[field] || 0, // 对应指标的实际值
|
||||
proportion: (targetItem?.[`${field}Proportion`] || 0) * 100, // 对应指标的占比(转百分比)
|
||||
completed: targetItem?.[`${field}Completed`] ?? 0 // 状态字段(复用分公司的 completed)
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// 生成最终格式化数据
|
||||
return {
|
||||
allPlaceNames,
|
||||
'利润总额': formatSingleType('利润总额'),
|
||||
'毛利率': formatSingleType('毛利率'),
|
||||
'产量': formatSingleType('产量'),
|
||||
'销量': formatSingleType('销量'),
|
||||
'成本': formatSingleType('成本'),
|
||||
'营业收入': formatSingleType('营业收入')
|
||||
};
|
||||
},
|
||||
|
||||
// 生成默认空数据(避免无数据时图表报错)
|
||||
getDefaultEmptyData() {
|
||||
return {
|
||||
'利润总额': [],
|
||||
'毛利率': [],
|
||||
'产量': [],
|
||||
'销量': [],
|
||||
'成本': [],
|
||||
'营业收入': []
|
||||
};
|
||||
},
|
||||
|
||||
getValueColor(completed) { // 保留原颜色方法
|
||||
return completed === 1 ? 'rgba(255, 132, 0, 1)' : 'rgba(40, 203, 151, 1)';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||
.scroll-container {
|
||||
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||
max-height: 210px;
|
||||
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||
overflow-y: auto;
|
||||
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||
overflow-x: hidden;
|
||||
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||
padding: 10px 0;
|
||||
|
||||
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||
/* Chrome/Safari */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
scrollbar-width: none;
|
||||
/* IE/Edge */
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||
.proBarInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 27px;
|
||||
/* 调整内边距,优化排版 */
|
||||
margin-bottom: 10px;
|
||||
/* 设备项之间的垂直间距 */
|
||||
}
|
||||
|
||||
/* 原有样式保留,优化细节 */
|
||||
.proBarInfoEqInfo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
/* 垂直居中,避免序号/文字错位 */
|
||||
}
|
||||
|
||||
.slot {
|
||||
width: 21px;
|
||||
height: 23px;
|
||||
background: rgba(0, 106, 205, 0.22);
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #68B5FF;
|
||||
line-height: 23px;
|
||||
/* 垂直居中文字 */
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eq-name {
|
||||
margin-left: 8px;
|
||||
/* 增加与序号的间距 */
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eqStatus {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.splitLine {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid #ADADAD;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #00FFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 序号和设备名垂直居中 */
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 状态/分割线/百分比垂直居中 */
|
||||
}
|
||||
|
||||
.proBarWrapper {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
margin-top: 6px;
|
||||
/* 进度条与上方信息的间距 */
|
||||
border-radius: 5px;
|
||||
/* 进度条圆角,优化视觉 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.proBarLine {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #ACACAC 100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.proBarLineTop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(53, 223, 247, 0) 0%, rgba(54, 220, 246, 0.92) 92%, #36F6E5 100%, #37ACF5 100%);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
/* 进度变化时添加过渡动画,更流畅 */
|
||||
}
|
||||
|
||||
/* 图表相关样式保留 */
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 57px;
|
||||
bottom: 42px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #00E8FF;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
235
src/views/home/components/profitOverview.vue
Normal file
235
src/views/home/components/profitOverview.vue
Normal file
@@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="利润数据总览" icon="cockpitItemIcon" size="profitTopBasic" topSize="middle">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<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 :itemList="targetItemList" />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
// import * as echarts from 'echarts'
|
||||
import topItem from './profit-item.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, topItem },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
profitTotalData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
parentItemList: [
|
||||
{ name: "利润总额", targetValue: 0, value: 0, proportion: 0, completed: 1, },
|
||||
{ name: "毛利率", targetValue: 0, value: 0, proportion: 0, completed:1, },
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
targetItemList() {
|
||||
// 基于 parentItemList 的名称,匹配 productAndSaleData 中对应名称的数据
|
||||
return this.parentItemList.map(parentItem => {
|
||||
// 按名称精准匹配(name 完全一致才关联)
|
||||
const productItem = this.profitTotalData.find(
|
||||
item => item.name === parentItem.name
|
||||
) || {};
|
||||
|
||||
// 优先使用匹配到的 productItem 数据,缺失则用 parentItem 的默认值(0)
|
||||
return {
|
||||
name: parentItem.name, // 保留原名称(销量、产量等)
|
||||
targetValue: productItem.targetValue ?? parentItem.targetValue ?? 0,
|
||||
value: productItem.value ?? parentItem.value ?? 0,
|
||||
proportion: productItem.proportion ?? parentItem.proportion ?? 0,
|
||||
completed: productItem.completed ?? parentItem.completed ?? 1,
|
||||
route: parentItem.route // 路由沿用 parentItem 的配置
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||
.scroll-container {
|
||||
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||
max-height: 210px;
|
||||
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||
overflow-y: auto;
|
||||
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||
overflow-x: hidden;
|
||||
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||
padding: 10px 0;
|
||||
|
||||
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||
/* Chrome/Safari */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
scrollbar-width: none;
|
||||
/* IE/Edge */
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||
.proBarInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 27px;
|
||||
/* 调整内边距,优化排版 */
|
||||
margin-bottom: 10px;
|
||||
/* 设备项之间的垂直间距 */
|
||||
}
|
||||
|
||||
/* 原有样式保留,优化细节 */
|
||||
.proBarInfoEqInfo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
/* 垂直居中,避免序号/文字错位 */
|
||||
}
|
||||
|
||||
.slot {
|
||||
width: 21px;
|
||||
height: 23px;
|
||||
background: rgba(0, 106, 205, 0.22);
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #68B5FF;
|
||||
line-height: 23px;
|
||||
/* 垂直居中文字 */
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eq-name {
|
||||
margin-left: 8px;
|
||||
/* 增加与序号的间距 */
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eqStatus {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.splitLine {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid #ADADAD;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #00FFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 序号和设备名垂直居中 */
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 状态/分割线/百分比垂直居中 */
|
||||
}
|
||||
|
||||
.proBarWrapper {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
margin-top: 6px;
|
||||
/* 进度条与上方信息的间距 */
|
||||
border-radius: 5px;
|
||||
/* 进度条圆角,优化视觉 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.proBarLine {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #ACACAC 100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.proBarLineTop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(53, 223, 247, 0) 0%, rgba(54, 220, 246, 0.92) 92%, #36F6E5 100%, #37ACF5 100%);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
/* 进度变化时添加过渡动画,更流畅 */
|
||||
}
|
||||
|
||||
/* 图表相关样式保留 */
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 57px;
|
||||
bottom: 42px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #00E8FF;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
221
src/views/home/components/psiBar.vue
Normal file
221
src/views/home/components/psiBar.vue
Normal file
@@ -0,0 +1,221 @@
|
||||
<template>
|
||||
<div class="coreBar">
|
||||
<div class="barTop">
|
||||
<!-- 标题:单独左对齐 -->
|
||||
<!-- <div class="title">销售指标趋势</div> -->
|
||||
<!-- 关键:新增右侧容器,包裹图例和按钮组,实现整体靠右 -->
|
||||
<div class="right-container">
|
||||
<div class="legend">
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon line yield"></span>
|
||||
产销率
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square target"></span>
|
||||
销量
|
||||
</span>
|
||||
<span class="legend-item close-item">
|
||||
<span class="legend-icon square achieved"></span>
|
||||
产量
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||
<psiLineBar :saleAndProductData="saleAndProductData" style="height: 99%; width: 100%" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import psiLineBar from './psiLineBar.vue';
|
||||
export default {
|
||||
name: "Container",
|
||||
components: { psiLineBar },
|
||||
props: [ "saleAndProductData"],
|
||||
data() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coreBar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
|
||||
.barTop {
|
||||
display: flex;
|
||||
justify-content: flex-end; // 标题左、右侧容器右,整体两端对齐
|
||||
align-items: center; // 垂直居中,避免上下错位
|
||||
gap: 16px; // 标题与右侧容器的最小间距,防止拥挤
|
||||
width: 100%; // 确保占满父容器,实现两端对齐
|
||||
|
||||
.title {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
color: #000000;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
// 标题固定在左侧,不挤压右侧空间
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||
.right-container {
|
||||
display: flex;
|
||||
align-items: center; // 图例和按钮组垂直居中
|
||||
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||
}
|
||||
|
||||
// 2. 图例:在右侧容器内横向排列
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 16px; // 图例项之间间距,避免重叠
|
||||
align-items: center;
|
||||
// 移除原margin-left,避免位置偏移
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
white-space: nowrap; // 防止图例文字换行
|
||||
}
|
||||
|
||||
.legend-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.legend-icon.line {
|
||||
width: 12px;
|
||||
height: 2px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: -2px;
|
||||
left: 3px;
|
||||
width: 6px;
|
||||
border-radius: 50%;
|
||||
height: 6px;
|
||||
background-color: rgba(40, 138, 255, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.legend-icon.square {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
// 图例颜色
|
||||
.yield {
|
||||
background: rgba(40, 138, 255, 1);
|
||||
}
|
||||
|
||||
.target {
|
||||
background: #2889FF;
|
||||
}
|
||||
|
||||
.achieved {
|
||||
background: rgba(40, 203, 151, 1);
|
||||
}
|
||||
|
||||
.unachieved {
|
||||
background: rgba(255, 132, 0, 1);
|
||||
}
|
||||
|
||||
.legend-item.close-item+.legend-item.close-item {
|
||||
margin-left: -8px; // 负margin抵消默认gap,数值可根据需求调整
|
||||
// 若原gap是16px,想让两者间距变为8px,设置 margin-left: -8px 即可
|
||||
}
|
||||
|
||||
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||
.button-group {
|
||||
display: flex;
|
||||
position: relative;
|
||||
gap: 2px;
|
||||
width: 198px;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
background: #ecf4fe;
|
||||
// border-radius: 12px;
|
||||
// 移除原margin-right,由右侧容器统一控制
|
||||
margin: 0;
|
||||
|
||||
.item-button {
|
||||
cursor: pointer;
|
||||
// width: 142px;
|
||||
height: 24px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #0b58ff;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
letter-spacing: 8px; // 确保文字间距生效
|
||||
padding-left: 8px; // 抵消letter-spacing导致的文字左偏
|
||||
|
||||
&:nth-child(1) {
|
||||
// 第一个按钮(类目选择):左圆角 + 适配文字的宽度
|
||||
border-top-left-radius: 12px;
|
||||
border-bottom-left-radius: 12px;
|
||||
width: 75px; // 与模板中按钮的 width 保持一致
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
// 第二个按钮(原料):右圆角 + 适配文字的宽度
|
||||
border-top-right-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
width: 88px; // 与模板中按钮的 width 保持一致
|
||||
}
|
||||
|
||||
&:nth-child(2)::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 16px; // 三角形距离右侧的距离
|
||||
transform: translateY(-50%);
|
||||
// 三角形绘制(蓝色,朝右)
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 4px solid transparent;
|
||||
border-bottom: 4px solid transparent;
|
||||
border-left: 6px solid #0b58ff; // 颜色与文字一致
|
||||
}
|
||||
}
|
||||
|
||||
.item-button.active {
|
||||
// width: 142px;
|
||||
height: 24px;
|
||||
background: #3071ff;
|
||||
// border-radius: 12px;
|
||||
color: rgba(249, 252, 255, .8);
|
||||
font-weight: 500;
|
||||
&:nth-child(2)::after {
|
||||
border-left-color: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
267
src/views/home/components/psiLineBar.vue
Normal file
267
src/views/home/components/psiLineBar.vue
Normal file
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null,
|
||||
resizeHandler: null
|
||||
};
|
||||
},
|
||||
props: ["saleAndProductData"],
|
||||
watch: {
|
||||
// 监听数据变化,动态更新图表
|
||||
saleAndProductData: {
|
||||
handler() {
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initChart();
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 清理资源
|
||||
this.myChart && this.myChart.dispose();
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
methods: {
|
||||
// 初始化图表实例
|
||||
initChart() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
this.myChart = echarts.init(chartDom);
|
||||
this.resizeHandler = () => this.myChart?.resize();
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
|
||||
// 解析数据:提取地名和对应数值(统一去掉“基地XX”后缀)
|
||||
parseData() {
|
||||
// const { saleValues = [], productValues = [], productAndSaleRates = [] } = this.saleAndProductData || {};
|
||||
const xData = []; // 统一X轴地名
|
||||
const saleData = []; // 销量数据
|
||||
const productData = []; // 产量数据
|
||||
const rateData = []; // 产销率数据(转为百分比)
|
||||
|
||||
// 第一步:提取所有地名(以销量数据为准,其他数据按地名匹配)
|
||||
if (Array.isArray(this.saleAndProductData) && this.saleAndProductData.length) {
|
||||
this.saleAndProductData.forEach(item => {
|
||||
// const key = Object.keys(item)[0];
|
||||
const place = item.name.replace('分公司', ''); // 提取地名(如“宜兴基地销量”→“宜兴”)
|
||||
xData.push(place);
|
||||
saleData.push(item.saleValue); // 销量值(保留两位小数)
|
||||
productData.push(item.productValue);
|
||||
rateData.push(item.saleAndProductProportion *100 ); // 转为百分比后保留两位小数
|
||||
});
|
||||
}
|
||||
|
||||
// // 第二步:匹配产量数据(按X轴地名顺序)
|
||||
// if (Array.isArray(productValues) && productValues.length) {
|
||||
// xData.forEach(place => {
|
||||
// const targetItem = productValues.find(item => {
|
||||
// const key = Object.keys(item)[0];
|
||||
// return key.includes(place); // 匹配包含该地名的产量键
|
||||
// });
|
||||
// productData.push(targetItem ? (Number(targetItem[Object.keys(targetItem)[0]]).toFixed(2) * 1) : 0);
|
||||
// });
|
||||
// }
|
||||
|
||||
// // 第三步:匹配产销率数据(转为百分比,保留两位小数)
|
||||
// if (Array.isArray(productAndSaleRates) && productAndSaleRates.length) {
|
||||
// xData.forEach(place => {
|
||||
// const targetItem = productAndSaleRates.find(item => {
|
||||
// const key = Object.keys(item)[0];
|
||||
// return key.includes(place); // 匹配包含该地名的产销率键
|
||||
// });
|
||||
// const rate = targetItem ? (Number(targetItem[Object.keys(targetItem)[0]]).toFixed(4) * 100) : 0;
|
||||
// rateData.push(rate.toFixed(2) * 1); // 转为百分比后保留两位小数
|
||||
// });
|
||||
// }
|
||||
|
||||
return { xData, saleData, productData, rateData };
|
||||
},
|
||||
|
||||
// 动态更新图表
|
||||
updateChart() {
|
||||
if (!this.myChart) return;
|
||||
|
||||
// 解析数据
|
||||
const { xData, saleData, productData, rateData } = this.parseData();
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
},
|
||||
formatter: (params) => {
|
||||
let html = `${params[0].axisValue}<br/>`;
|
||||
params.forEach(item => {
|
||||
const unit = item.seriesName === '产销率' ? '%' : '万㎡';
|
||||
html += `${item.marker} ${item.seriesName}: ${item.value ? item.value :0}${unit}<br/>`;
|
||||
});
|
||||
return html;
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 30,
|
||||
bottom: 30,
|
||||
right: 70,
|
||||
left: 40,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: true,
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0]
|
||||
},
|
||||
data: xData // 动态X轴(统一地名)
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '万㎡',
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'right'
|
||||
},
|
||||
min: 0,
|
||||
max: (value) => value.max > 0 ? Math.ceil(value.max * 1.1) : 10,
|
||||
scale: false,
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
formatter: '{value}'
|
||||
},
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
splitNumber: 4
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'left'
|
||||
},
|
||||
min: 0,
|
||||
max: 100,
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
formatter: '{value}%'
|
||||
},
|
||||
splitLine: { show: false },
|
||||
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
splitNumber: 4
|
||||
}
|
||||
],
|
||||
series: [
|
||||
// 1. 产销率(折线图,右侧Y轴)
|
||||
{
|
||||
name: '产销率',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
lineStyle: {
|
||||
color: 'rgba(40, 138, 255, .5)',
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(40, 138, 255, 1)',
|
||||
borderColor: 'rgba(40, 138, 255, 1)',
|
||||
borderWidth: 2,
|
||||
radius: 4
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.2,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||
{ offset: 1, color: 'rgba(255, 132, 0, 0)' }
|
||||
])
|
||||
},
|
||||
data: rateData, // 动态产销率数据
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
// 2. 销量(柱状图,左侧Y轴)
|
||||
{
|
||||
name: '销量',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 24,
|
||||
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: saleData // 动态销量数据
|
||||
},
|
||||
// 3. 产量(柱状图,左侧Y轴)
|
||||
{
|
||||
name: '产量',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 24,
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: productData // 动态产量数据
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.myChart.setOption(option);
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
217
src/views/home/components/psiLineChart.vue
Normal file
217
src/views/home/components/psiLineChart.vue
Normal file
@@ -0,0 +1,217 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="趋势图" icon="cockpitItemIcon" size="operatingLarge" topSize="large">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex;width: 100%;">
|
||||
<div class="bottom" style="height: 378px; display: flex; width: 100%;background-color: rgba(249, 252, 255, 1);">
|
||||
<!-- <top-item /> -->
|
||||
<psiBar :saleAndProductData="saleAndProductData" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
// import * as echarts from 'echarts'
|
||||
import psiBar from './psiBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, psiBar },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
saleAndProductData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
saleAndProductData: {
|
||||
handler(newValue, oldValue) {
|
||||
},
|
||||
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||
.scroll-container {
|
||||
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||
max-height: 210px;
|
||||
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||
overflow-y: auto;
|
||||
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||
overflow-x: hidden;
|
||||
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||
padding: 10px 0;
|
||||
|
||||
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||
/* Chrome/Safari */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
scrollbar-width: none;
|
||||
/* IE/Edge */
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||
.proBarInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 27px;
|
||||
/* 调整内边距,优化排版 */
|
||||
margin-bottom: 10px;
|
||||
/* 设备项之间的垂直间距 */
|
||||
}
|
||||
|
||||
/* 原有样式保留,优化细节 */
|
||||
.proBarInfoEqInfo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
/* 垂直居中,避免序号/文字错位 */
|
||||
}
|
||||
|
||||
.slot {
|
||||
width: 21px;
|
||||
height: 23px;
|
||||
background: rgba(0, 106, 205, 0.22);
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #68B5FF;
|
||||
line-height: 23px;
|
||||
/* 垂直居中文字 */
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eq-name {
|
||||
margin-left: 8px;
|
||||
/* 增加与序号的间距 */
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eqStatus {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.splitLine {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid #ADADAD;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #00FFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 序号和设备名垂直居中 */
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 状态/分割线/百分比垂直居中 */
|
||||
}
|
||||
|
||||
.proBarWrapper {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
margin-top: 6px;
|
||||
/* 进度条与上方信息的间距 */
|
||||
border-radius: 5px;
|
||||
/* 进度条圆角,优化视觉 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.proBarLine {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #ACACAC 100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.proBarLineTop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(53, 223, 247, 0) 0%, rgba(54, 220, 246, 0.92) 92%, #36F6E5 100%, #37ACF5 100%);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
/* 进度变化时添加过渡动画,更流畅 */
|
||||
}
|
||||
|
||||
/* 图表相关样式保留 */
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 57px;
|
||||
bottom: 42px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #00E8FF;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
193
src/views/home/components/psr-item.vue
Normal file
193
src/views/home/components/psr-item.vue
Normal file
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<div class="coreItem" :style="{ 'height': height + 'px' }">
|
||||
<!-- 用 v-for 动态生成每个 item -->
|
||||
<div class="item" v-for="(item, index) in itemList" :key="index">
|
||||
<div class="unit">{{ item.name }}</div>
|
||||
<div class="item-content">
|
||||
<!-- 左右内容容器 -->
|
||||
<div class="content-wrapper">
|
||||
<div class="left">
|
||||
<div class="number">{{ item.targetValue || 0 }}</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="right">
|
||||
<!-- 实际值颜色动态绑定 -->
|
||||
<div class="number" :style="{ 'color': item.completed === 0 ? '#FF8400' : 'rgba(54, 181, 138, .7)' }">
|
||||
{{ item.value || 0 }}
|
||||
</div>
|
||||
<div class="title">实际值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="right">
|
||||
<!-- 完成率颜色动态绑定 -->
|
||||
<div class="number" :style="{ 'color': item.completed === 0 ? '#FF8400' : 'rgba(54, 181, 138, .7)' }">
|
||||
{{ item.proportion !== null && item.proportion !== undefined ? (item.proportion * 100) + '%' : '0%' }}
|
||||
</div>
|
||||
<div class="title">完成率</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Container",
|
||||
components: {},
|
||||
props: ['itemList','height'],
|
||||
data() {
|
||||
return {
|
||||
progress: 90, // 进度值,方便统一控制
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
itemList: {
|
||||
handler(newList) {
|
||||
// 当 itemList 变化时执行的逻辑(如重新计算、更新视图等)
|
||||
console.log("子组件接收的 itemList 已更新:", newList);
|
||||
},
|
||||
deep: true // 深度监听:若 itemList 内部对象属性变化(如 currentValue 改变),也能触发监听
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coreItem {
|
||||
display: flex;
|
||||
// height: 382px;
|
||||
overflow: hidden;
|
||||
flex-wrap: wrap;
|
||||
// grid-template-columns: 1fr 1fr;
|
||||
// grid-template-rows: 1fr 1fr;
|
||||
justify-content: flex-start;
|
||||
align-content: flex-start;
|
||||
gap: 8px;
|
||||
overflow-y: auto;
|
||||
// padding-right: 4px;
|
||||
/* 2. 隐藏 Chrome/Safari 滚动条 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
/* 滚动条宽度设为0 */
|
||||
height: 0;
|
||||
/* 横向滚动条(如需)也隐藏 */
|
||||
}
|
||||
|
||||
/* 3. 隐藏 Firefox 滚动条 */
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 376px;
|
||||
height: 120px;
|
||||
background: #f9fcff;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
// &:hover {
|
||||
// box-shadow: 0px 4px 12px 2px #B5CDE5;
|
||||
// }
|
||||
.unit {
|
||||
// width: 124px;
|
||||
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: 8px;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: calc(100% - 26px);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 1px;
|
||||
height: 46px;
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(255, 0, 0, 0),
|
||||
rgba(40, 203, 151, 1));
|
||||
}
|
||||
|
||||
.left,
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.number {
|
||||
height: 22px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 600;
|
||||
font-size: 26px;
|
||||
color: rgba(103, 103, 103, 0.79);
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.title {
|
||||
height: 14px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #868687;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* 进度条和百分比的外层容器 */
|
||||
.progress-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
/* 进度条和百分比的间距 */
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 190px;
|
||||
height: 10px;
|
||||
background: #ECEFF7;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: #28CB97;
|
||||
border-radius: 8px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* 百分比文本样式 */
|
||||
.progress-percent {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #868687;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
186
src/views/home/components/psrChartBar.vue
Normal file
186
src/views/home/components/psrChartBar.vue
Normal file
@@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 存储图表实例,避免重复创建
|
||||
};
|
||||
},
|
||||
props: ["stockValues"],
|
||||
watch: {
|
||||
// 监听 stockValues 变化,动态更新图表
|
||||
stockValues: {
|
||||
handler() {
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initChart();
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 清理图表实例和事件监听
|
||||
this.myChart && this.myChart.dispose();
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
methods: {
|
||||
// 初始化图表实例
|
||||
initChart() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
this.myChart = echarts.init(chartDom);
|
||||
// 绑定窗口缩放事件
|
||||
this.resizeHandler = () => this.myChart?.resize();
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
|
||||
// 解析 stockValues:提取地名和对应库存值
|
||||
parseStockData() {
|
||||
const xData = []; // X轴地名
|
||||
const data = []; // 对应库存值
|
||||
|
||||
if (Array.isArray(this.stockValues) && this.stockValues.length) {
|
||||
this.stockValues.forEach(item => {
|
||||
// 获取对象的唯一键(如“宜兴基地库存”)
|
||||
// const key = Object.keys(item)[0];
|
||||
// 提取地名:去掉“基地库存”后缀(如“宜兴基地库存”→“宜兴”)
|
||||
const place = item.name.replace('分公司', '');
|
||||
// 获取库存值(保留两位小数,避免精度问题)
|
||||
const value = item.stockValue
|
||||
|
||||
xData.push(place);
|
||||
data.push(value);
|
||||
});
|
||||
}
|
||||
|
||||
return { xData, data };
|
||||
},
|
||||
|
||||
// 更新图表(核心:动态渲染数据)
|
||||
updateChart() {
|
||||
if (!this.myChart) return;
|
||||
|
||||
// 解析数据
|
||||
const { xData, data } = this.parseStockData();
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
},
|
||||
formatter: (params) => {
|
||||
let html = `${params[0].axisValue}<br/>`;
|
||||
params.forEach(item => {
|
||||
html += `${item.marker} 库存: ${item.value} 万㎡<br/>`;
|
||||
});
|
||||
return html;
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 30,
|
||||
bottom: 30,
|
||||
right: 70,
|
||||
left: 40,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: true,
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0]
|
||||
},
|
||||
data: xData // 动态 X 轴数据(解析后的地名)
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '万㎡',
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'right'
|
||||
},
|
||||
min: 0,
|
||||
max: (value) => value.max > 0 ? Math.ceil(value.max * 1.1) : 10, // 无数据时默认最大10,避免图表异常
|
||||
scale: false,
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
formatter: '{value}'
|
||||
},
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
splitNumber: 4
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '库存',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 24,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
textStyle: {
|
||||
color: 'rgba(11, 88, 255, 1)',
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data // 动态库存数据(解析后的值)
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.myChart.setOption(option);
|
||||
},
|
||||
|
||||
// 窗口缩放处理函数
|
||||
resizeHandler() {
|
||||
this.myChart && this.myChart.resize();
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
252
src/views/home/components/purchase-Item.vue
Normal file
252
src/views/home/components/purchase-Item.vue
Normal file
@@ -0,0 +1,252 @@
|
||||
<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="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">目标值</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">实际值</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
|
||||
<!-- 进度条:同步绑定类名 -->
|
||||
<div class="progress-group">
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" :style="{ width: item.progress + '%' }" :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.progress }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Container",
|
||||
components: {},
|
||||
props: ["name", "size", "icon"],
|
||||
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'
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleRoute(route) {
|
||||
if (route) {
|
||||
this.$router.push({ path: route });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coreItem {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
// padding: 8px; // 避免边缘item hover阴影被截断
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 170px;
|
||||
height: 228px;
|
||||
background: #f9fcff;
|
||||
padding: 12px 0px 17px 12px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 4px 12px 2px #B5CDE5;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.unit {
|
||||
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;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: calc(100% - 26px);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 149px;
|
||||
height: 1px;
|
||||
background: linear-gradient(to left, rgba(255, 0, 0, 0), #cbcbcb);
|
||||
}
|
||||
|
||||
.left,
|
||||
.right {
|
||||
margin-top: 11px;
|
||||
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: 15px;
|
||||
}
|
||||
|
||||
.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: #28CB97 !important;
|
||||
}
|
||||
|
||||
/* 百分比 - 实际值<目标值(黄色) */
|
||||
.percent-below {
|
||||
color: rgba(249, 164, 74, 1) !important;
|
||||
}
|
||||
|
||||
.yield {
|
||||
width: 138px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
242
src/views/home/components/salesDataOverview.vue
Normal file
242
src/views/home/components/salesDataOverview.vue
Normal file
@@ -0,0 +1,242 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="产销数据总览" icon="cockpitItemIcon" size="profitTopBasic" topSize="middle">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<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 :itemList="targetItemList" />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
// import * as echarts from 'echarts'
|
||||
import topItem from './profit-item.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, topItem },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
productAndSaleData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
},
|
||||
// computed: {
|
||||
// targetItemList() {
|
||||
// // 当 productAndSaleData 为空数组时,使用 parentItemList
|
||||
// return this.productAndSaleData.length === 0 ? this.parentItemList : this.productAndSaleData;
|
||||
// }
|
||||
// },
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
parentItemList: [
|
||||
{ name: "销量", targetValue: 0, value: 0, proportion: 0, route:'PSIAnal' },
|
||||
{ name: "产量", targetValue: 0, value: 0, proportion: 0 },
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
targetItemList() {
|
||||
// 基于 parentItemList 的名称,匹配 productAndSaleData 中对应名称的数据
|
||||
return this.parentItemList.map(parentItem => {
|
||||
// 按名称精准匹配(name 完全一致才关联)
|
||||
const productItem = this.productAndSaleData.find(
|
||||
item => item.name === parentItem.name
|
||||
) || {};
|
||||
|
||||
// 优先使用匹配到的 productItem 数据,缺失则用 parentItem 的默认值(0)
|
||||
return {
|
||||
name: parentItem.name, // 保留原名称(销量、产量等)
|
||||
targetValue: productItem.targetValue ?? parentItem.targetValue ?? 0,
|
||||
value: productItem.value ?? parentItem.value ?? 0,
|
||||
proportion: productItem.proportion ?? parentItem.proportion ?? 0,
|
||||
completed: productItem.completed ?? parentItem.completed ?? 1,
|
||||
route: parentItem.route // 路由沿用 parentItem 的配置
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||
.scroll-container {
|
||||
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||
max-height: 210px;
|
||||
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||
overflow-y: auto;
|
||||
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||
overflow-x: hidden;
|
||||
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||
padding: 10px 0;
|
||||
|
||||
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||
/* Chrome/Safari */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
scrollbar-width: none;
|
||||
/* IE/Edge */
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||
.proBarInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 27px;
|
||||
/* 调整内边距,优化排版 */
|
||||
margin-bottom: 10px;
|
||||
/* 设备项之间的垂直间距 */
|
||||
}
|
||||
|
||||
/* 原有样式保留,优化细节 */
|
||||
.proBarInfoEqInfo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
/* 垂直居中,避免序号/文字错位 */
|
||||
}
|
||||
|
||||
.slot {
|
||||
width: 21px;
|
||||
height: 23px;
|
||||
background: rgba(0, 106, 205, 0.22);
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #68B5FF;
|
||||
line-height: 23px;
|
||||
/* 垂直居中文字 */
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eq-name {
|
||||
margin-left: 8px;
|
||||
/* 增加与序号的间距 */
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eqStatus {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.splitLine {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid #ADADAD;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #00FFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 序号和设备名垂直居中 */
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 状态/分割线/百分比垂直居中 */
|
||||
}
|
||||
|
||||
.proBarWrapper {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
margin-top: 6px;
|
||||
/* 进度条与上方信息的间距 */
|
||||
border-radius: 5px;
|
||||
/* 进度条圆角,优化视觉 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.proBarLine {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #ACACAC 100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.proBarLineTop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(53, 223, 247, 0) 0%, rgba(54, 220, 246, 0.92) 92%, #36F6E5 100%, #37ACF5 100%);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
/* 进度变化时添加过渡动画,更流畅 */
|
||||
}
|
||||
|
||||
/* 图表相关样式保留 */
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 57px;
|
||||
bottom: 42px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #00E8FF;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
267
src/views/home/components/top-item.vue
Normal file
267
src/views/home/components/top-item.vue
Normal file
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<div class="coreItem">
|
||||
<div class="item" v-for="(item, index) in itemList" :key="index" @click="handleRouter(item)">
|
||||
<div class="unit">{{ item.unit }}</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.progress }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Container",
|
||||
components: {},
|
||||
props: ["name", "size", "icon"],
|
||||
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"
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 处理路由跳转
|
||||
handleRouter(obj) {
|
||||
if (obj.path) {
|
||||
this.$router.push({ path: obj.path });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coreItem {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
gap: 8px;
|
||||
// padding: 8px; // 避免边缘item hover时阴影被截断
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 252px;
|
||||
height: 110px;
|
||||
background: #f9fcff;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer; // 提示可点击
|
||||
transition: all 0.3s ease; // 动画过渡
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 4px 12px 2px #B5CDE5;
|
||||
transform: translateY(-2px); // 轻微上浮增强交互感
|
||||
}
|
||||
|
||||
.unit {
|
||||
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: 8px;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: calc(100% - 26px);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 1px;
|
||||
height: 46px;
|
||||
background: linear-gradient(to bottom, rgba(255, 0, 0, 0), rgba(40, 203, 151, 1));
|
||||
}
|
||||
|
||||
.left,
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
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: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.title {
|
||||
height: 14px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #868687;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.progress-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 190px;
|
||||
height: 10px;
|
||||
background: #ECEFF7;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: rgba(98, 213, 180, 1);
|
||||
/* 默认进度条颜色(等于目标值时) */
|
||||
border-radius: 8px;
|
||||
opacity: 0.7;
|
||||
transition: width 0.5s ease; // 进度条动画
|
||||
}
|
||||
|
||||
.progress-percent {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #868687;
|
||||
/* 默认百分比颜色(等于目标值时) */
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 实际值 > 目标值:绿色样式 */
|
||||
.exceed-target {
|
||||
color: rgba(98, 213, 180, 1) !important;
|
||||
/* 文字绿色 */
|
||||
// background: rgba(98, 213, 180, 1) !important;
|
||||
/* 进度条绿色 */
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* 实际值 < 目标值:黄色样式 */
|
||||
.below-target {
|
||||
color: rgba(249, 164, 74, 1) !important;
|
||||
/* 文字黄色 */
|
||||
// background: rgba(249, 164, 74, 1) !important;
|
||||
/* 进度条黄色 */
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.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>
|
||||
258
src/views/home/components/top-product-item.vue
Normal file
258
src/views/home/components/top-product-item.vue
Normal file
@@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<div class="coreItem">
|
||||
<div class="item" :class="`item${index + 1}`" @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="number">{{ item.target }}</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="right">
|
||||
<!-- 实际值颜色动态绑定 -->
|
||||
<div class="number" :style="{ color: getColor(index) }">
|
||||
{{ item.actual }}
|
||||
</div>
|
||||
<div class="title">实际值</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-group">
|
||||
<div class="progress-container">
|
||||
<!-- 进度条样式动态绑定 -->
|
||||
<div class="progress-bar" :style="{
|
||||
width: `${item.progress}%`,
|
||||
background: getColor(index)
|
||||
}"></div>
|
||||
</div>
|
||||
<!-- 百分比颜色动态绑定 -->
|
||||
<div class="progress-percent" :style="{ color: getColor(index) }">
|
||||
{{ item.progress }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Container",
|
||||
components: {},
|
||||
props: ["name", "size", "icon"],
|
||||
data() {
|
||||
return {
|
||||
itemList: [
|
||||
{
|
||||
unit: "总成本·元/㎡",
|
||||
route: "cost/cost",
|
||||
target: 16,
|
||||
actual: 16,
|
||||
progress: 100
|
||||
},
|
||||
{
|
||||
unit: "原片成本·元/㎡",
|
||||
route: "cost/cost",
|
||||
target: 16,
|
||||
actual: 18,
|
||||
progress: 110
|
||||
},
|
||||
{
|
||||
unit: "加工成本·元/㎡",
|
||||
route: "cost/cost",
|
||||
target: 16,
|
||||
actual: 14,
|
||||
progress: 85
|
||||
},
|
||||
{
|
||||
unit: "原片成品率·%",
|
||||
target: 95,
|
||||
actual: 92,
|
||||
progress: 97
|
||||
},
|
||||
{
|
||||
unit: "投入产出率·%",
|
||||
target: 88,
|
||||
actual: 90,
|
||||
progress: 102
|
||||
}
|
||||
],
|
||||
activeIndex: -1
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
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 });
|
||||
}
|
||||
},
|
||||
// 判断颜色的方法
|
||||
getColor(index) {
|
||||
const { actual, target } = this.itemList[index];
|
||||
return actual >= target
|
||||
? "rgba(98, 213, 180, 1)"
|
||||
: "rgba(249, 164, 74, 1)";
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coreItem {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.coreItem> :nth-child(1) {
|
||||
grid-area: item1;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 4px 12px 2px #B5CDE5;
|
||||
}
|
||||
}
|
||||
|
||||
.coreItem> :nth-child(2) {
|
||||
grid-area: item2;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 4px 12px 2px #B5CDE5;
|
||||
}
|
||||
}
|
||||
|
||||
.coreItem> :nth-child(3) {
|
||||
grid-area: item3;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 4px 12px 2px #B5CDE5;
|
||||
}
|
||||
}
|
||||
|
||||
.coreItem> :nth-child(4) {
|
||||
grid-area: item4;
|
||||
}
|
||||
|
||||
.coreItem> :nth-child(5) {
|
||||
grid-area: item5;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 252px;
|
||||
height: 110px;
|
||||
background: #f9fcff;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.unit {
|
||||
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: 8px;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: calc(100% - 26px);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 1px;
|
||||
height: 46px;
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(255, 0, 0, 0),
|
||||
rgba(40, 203, 151, 1));
|
||||
}
|
||||
|
||||
.left,
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
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: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.title {
|
||||
height: 14px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #868687;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.progress-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 190px;
|
||||
height: 10px;
|
||||
background: #ECEFF7;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-percent {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.item1,
|
||||
.item2,
|
||||
.item3 {
|
||||
width: 166px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user