修改
This commit is contained in:
344
src/views/home/costComponents/Header.vue
Normal file
344
src/views/home/costComponents/Header.vue
Normal file
@@ -0,0 +1,344 @@
|
||||
<template>
|
||||
<header class="report-header">
|
||||
<!-- 左侧区域:logo + 标题 -->
|
||||
<div class="left-content">
|
||||
<img style="height: 36px;" src="../../../assets/img/cnbm.png" alt="benmaLogo" class="logo">
|
||||
<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">
|
||||
<!-- 日/月/年按钮:点击更新activeTime,关联选择器类型 -->
|
||||
<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> <!-- 补充span抵消倾斜 -->
|
||||
</div>
|
||||
<!-- 日期选择器:type和placeholder通过activeTime动态切换 -->
|
||||
<el-date-picker v-model="date" :type="getPickerType" :placeholder="getPickerPlaceholder"
|
||||
class="custom-date-picker" style="width: 132px;height: 29px;"></el-date-picker>
|
||||
</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: 0, // 0=日,1=月,2=年(默认选中“日”)
|
||||
pageRoutes: [
|
||||
{ text: '营业收入', path: '/operatingRevenue' },
|
||||
{ text: '利润分析', path: '/profitAnalysis' },
|
||||
{ text: '产销率库存分析', path: '/PSIAnal' },
|
||||
{ text: '成本分析', path: '/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') },
|
||||
exportPDF() { this.$emit('exportPDF') },
|
||||
updateTime() {
|
||||
const now = new Date()
|
||||
const hours = this.padZero(now.getHours())
|
||||
const minutes = this.padZero(now.getMinutes())
|
||||
const year = now.getFullYear()
|
||||
const month = this.padZero(now.getMonth() + 1)
|
||||
const day = this.padZero(now.getDate())
|
||||
const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
|
||||
this.currentTime = `${hours}:${minutes} | ${year}.${month}.${day} | ${weekdays[now.getDay()]}`
|
||||
},
|
||||
padZero(num) { return num < 10 ? '0' + num : num }
|
||||
},
|
||||
mounted() {
|
||||
this.updateTime()
|
||||
this.timeTimer = setInterval(this.updateTime, 1000)
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.timeTimer) clearInterval(this.timeTimer)
|
||||
}
|
||||
}
|
||||
</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: 100px;
|
||||
|
||||
.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: 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;
|
||||
/* 统一倾斜角度,修复原30deg的错位 */
|
||||
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: 62px;
|
||||
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: 40px;
|
||||
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: ;
|
||||
width: 132px !important;
|
||||
height: 28px !important;
|
||||
position: relative;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
::v-deep .custom-date-picker .el-input--medium .el-input__inner,
|
||||
::v-deep .custom-date-picker .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: #fff !important;
|
||||
clip-path: polygon(20px 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);
|
||||
}
|
||||
|
||||
::v-deep .custom-date-picker .el-input__prefix {
|
||||
left: auto !important;
|
||||
right: 8px !important;
|
||||
top: 40% !important;
|
||||
transform: translateY(-50%) !important;
|
||||
}
|
||||
|
||||
::v-deep .custom-date-picker .el-input__prefix .el-icon {
|
||||
color: #fff !important;
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
::v-deep .custom-date-picker .el-input__inner:hover,
|
||||
::v-deep .custom-date-picker .el-input__inner:focus {
|
||||
background-color: rgba(11, 88, 255, 0.9) !important;
|
||||
clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%) !important;
|
||||
}
|
||||
</style>
|
||||
250
src/views/home/costComponents/baseTable.vue
Normal file
250
src/views/home/costComponents/baseTable.vue
Normal file
@@ -0,0 +1,250 @@
|
||||
<!--
|
||||
* @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: 230 // 原固定值,作为默认 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;
|
||||
|
||||
// 关键修改:隐藏滚动条但保留滚动功能
|
||||
&::-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>
|
||||
246
src/views/home/costComponents/bottomLeftContainer.vue
Normal file
246
src/views/home/costComponents/bottomLeftContainer.vue
Normal file
@@ -0,0 +1,246 @@
|
||||
<template>
|
||||
<div class="container" :class="['container__' + size]">
|
||||
<div class="container-top" style="display: flex;gap: 9px;">
|
||||
<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="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">
|
||||
.container {
|
||||
display: inline-block;
|
||||
// width: 100%;
|
||||
// height: 00px;
|
||||
padding: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
.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>
|
||||
192
src/views/home/costComponents/bottomMiddleContainer.vue
Normal file
192
src/views/home/costComponents/bottomMiddleContainer.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<div class="container" :class="['container__' + size]">
|
||||
<div class="container-top">
|
||||
<svg-icon style="font-size: 32px; margin-left: 16px" :icon-class="icon" />
|
||||
<span style="
|
||||
width: 746px;
|
||||
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="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">
|
||||
.container {
|
||||
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;
|
||||
}
|
||||
|
||||
/* 左上角折现边框(主边框) */
|
||||
.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>
|
||||
71
src/views/home/costComponents/changeBase.vue
Normal file
71
src/views/home/costComponents/changeBase.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div class="changeBase">
|
||||
<div class="base-item" :class="{ isChecked: activeButton === index, noChecked: activeButton !== index }"
|
||||
@click="handleClick(index)"
|
||||
v-for="(item, index) in buttonList"
|
||||
:key="index"
|
||||
>
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Container",
|
||||
data() {
|
||||
return {
|
||||
activeButton: 0, // 默认选中“总览”
|
||||
buttonList: ['总览', '宜兴', '漳州', '自贡', '桐城', '洛阳', '合肥']
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleClick(index) {
|
||||
this.activeButton = index; // 更新选中状态
|
||||
// 触发自定义事件,将当前选中的值(如“总览”“宜兴”)传递给父组件
|
||||
this.$emit('selectChange', index+1);
|
||||
}
|
||||
},
|
||||
// 初始化时就将默认选中的值传递给父组件
|
||||
mounted() {
|
||||
this.$emit('selectChange', this.activeButton +1);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 样式不变,省略 */
|
||||
.changeBase {
|
||||
display: flex;
|
||||
width: fit-content; // 宽度由子项总宽度决定
|
||||
// justify-content: space-around;
|
||||
// gap: 50px;
|
||||
// overflow-x: auto;
|
||||
// padding: 10px 0;
|
||||
|
||||
.base-item {
|
||||
width: 264px;
|
||||
height: 82px;
|
||||
font-family: YouSheBiaoTiHei;
|
||||
font-size: 38px;
|
||||
line-height: 54px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
margin-right: -35px;
|
||||
}
|
||||
|
||||
.isChecked {
|
||||
color: #F9FBFE;
|
||||
background: url(../../../assets//img/baseChecked.png) no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.noChecked {
|
||||
color: rgba(30, 22, 81, 1);
|
||||
background: url(../../../assets//img/baseNoChecked.png) no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
219
src/views/home/costComponents/container.vue
Normal file
219
src/views/home/costComponents/container.vue
Normal file
@@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<div class="cockpitContainer" :class="['cockpitContainer__' + size]">
|
||||
<div class="content-top" :class="['content-top__' + topSize]">
|
||||
<div class="title-wrapper">
|
||||
<svg-icon class="title-icon" :icon-class="icon" />
|
||||
<span class="title-text">
|
||||
{{ name }}
|
||||
</span>
|
||||
<!-- <el-button type="text">利润影响额</el-button> -->
|
||||
</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', '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;
|
||||
}
|
||||
|
||||
&__costMiddleBg {
|
||||
background: url(../../../assets/img/costMiddleBg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__costSmallBg {
|
||||
background: url(../../../assets/img/costSmallBg.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;
|
||||
}
|
||||
|
||||
&__productBasicBg {
|
||||
background: url(../../../assets/img/productBasicBg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__productMiddleBg {
|
||||
background: url(../../../assets/img/productMiddleBg.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>
|
||||
189
src/views/home/costComponents/cost-item.vue
Normal file
189
src/views/home/costComponents/cost-item.vue
Normal file
@@ -0,0 +1,189 @@
|
||||
<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>
|
||||
<!-- 实际值:根据 flag 动态绑定颜色 -->
|
||||
<div class="right">
|
||||
<div class="number" :style="{ 'color': flagColor }"> {{ item.value || 0 }}</div>
|
||||
<div class="title">实际值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<!-- 完成率:根据 flag 动态绑定颜色 -->
|
||||
<div class="right">
|
||||
<div class="number" :style="{ 'color': flagColor }"> {{ item.proportion !== null && item.proportion !== undefined ? (item.proportion) + '%' : '0%' }}
|
||||
</div>
|
||||
<div class="title">完成率</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Container",
|
||||
components: {},
|
||||
props: [
|
||||
'itemList',
|
||||
'height',
|
||||
'flag' // 新增 flag 属性,用于控制颜色
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
progress: 90,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 根据 flag 计算实际值和完成率的颜色
|
||||
flagColor() {
|
||||
// flag 为 0 时使用灰色,为 1 时使用橙色
|
||||
return this.flag === 1
|
||||
? 'rgba(255, 132, 0, 1)'
|
||||
: 'rgba(103, 103, 103, 0.79)';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
itemList: {
|
||||
handler(newList) {
|
||||
console.log("子组件接收的 itemList 已更新:", newList);
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 原有样式保持不变 */
|
||||
.coreItem {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-content: flex-start;
|
||||
gap: 8px;
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 498px;
|
||||
height: 120px;
|
||||
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;
|
||||
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(203, 203, 203, 1.00));
|
||||
}
|
||||
|
||||
.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;
|
||||
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>
|
||||
296
src/views/home/costComponents/costBar.vue
Normal file
296
src/views/home/costComponents/costBar.vue
Normal file
@@ -0,0 +1,296 @@
|
||||
<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 square target"></span>
|
||||
目标
|
||||
</span>
|
||||
<!-- 给第三个、第四个图例项加 close-item 类 -->
|
||||
<span class="legend-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: 1590px">
|
||||
<costBaseBarChart style="height: 99%; width: 1590px" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import costBaseBarChart from './costBaseBarChart.vue';
|
||||
export default {
|
||||
name: "Container",
|
||||
components: { costBaseBarChart },
|
||||
props: ["name", "size", "icon"],
|
||||
data() {
|
||||
return {
|
||||
isDropdownShow: false,
|
||||
selectedProfit: '原料', // 默认选中"原料"
|
||||
profitOptions: ['原料', '其他选项1', '其他选项2'], // 可根据实际需求修改选项
|
||||
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: {
|
||||
selectProfit(item) {
|
||||
this.selectedProfit = item;
|
||||
this.isDropdownShow = false;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 点击外部关闭下拉菜单
|
||||
const handleOutsideClick = (e) => {
|
||||
if (!this.$el.contains(e.target)) {
|
||||
this.isDropdownShow = false;
|
||||
}
|
||||
};
|
||||
document.addEventListener('click', handleOutsideClick);
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
document.removeEventListener('click', handleOutsideClick);
|
||||
});
|
||||
}
|
||||
};
|
||||
</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;
|
||||
}
|
||||
|
||||
.right-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.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: 102px; // 保持原组件要求的宽度,其余样式与第二个组件一致
|
||||
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: 102px; // 与按钮宽度一致
|
||||
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>
|
||||
236
src/views/home/costComponents/costBaseBarChart.vue
Normal file
236
src/views/home/costComponents/costBaseBarChart.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 500px;"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initData();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
const myChart = echarts.init(chartDom);
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
},
|
||||
// 优化tooltip内容,区分各系列含义
|
||||
formatter: (params) => {
|
||||
let html = `${params[0].axisValue}<br/>`;
|
||||
params.forEach(item => {
|
||||
// 直接使用系列名,无需映射,仅保留单位判断
|
||||
html += `${item.marker} ${item.seriesName}: ${item.value}${item.seriesName === '完成率' ? '%' : '片'}<br/>`;
|
||||
});
|
||||
return html;
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 30,
|
||||
bottom: 30, // 增大底部间距,避免柱子与X轴标签重叠
|
||||
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: ['漳州', '桐城', '合肥', '宜兴', '自贡', '洛阳']
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
// 左侧Y轴:目标/达标/未达标(数量,单位“片”)
|
||||
{
|
||||
type: 'value',
|
||||
name: '元',
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'right'
|
||||
},
|
||||
min: 0, // 最小值设0,确保柱子从X轴底部开始,不超过X轴
|
||||
max: (value) => Math.ceil(value.max * 1.1), // 最大值留10%余量,避免柱子顶满
|
||||
scale: false, // 关闭缩放,强制从0开始
|
||||
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',
|
||||
// // name: '%',
|
||||
// nameTextStyle: {
|
||||
// color: 'rgba(0, 0, 0, 0.45)',
|
||||
// fontSize: 12,
|
||||
// align: 'left'
|
||||
// },
|
||||
// min: 0,
|
||||
// max: 100, // 完成率最大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, // 绑定右侧Y轴
|
||||
// lineStyle: {
|
||||
// color: 'rgba(40, 138, 255, .5)',
|
||||
// width: 2 // 线条加粗,突出重点
|
||||
// },
|
||||
// itemStyle: {
|
||||
// color: 'rgba(40, 138, 255, 1)',
|
||||
// borderColor: 'rgba(40, 138, 255, 1)', // 数据点白色边框,增强辨识度
|
||||
// borderWidth: 2,
|
||||
// radius: 4 // 数据点圆角,更圆润
|
||||
// },
|
||||
// areaStyle: {
|
||||
// opacity: 0.2, // 降低面积透明度,不抢柱状图视觉焦点
|
||||
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
// { offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||
// { offset: 1, color: 'rgba(255, 132, 0, 0)' }
|
||||
// ])
|
||||
// },
|
||||
// data: [65, 78, 52, 85, 60, 95, 72], // 完成率数据(0-100)
|
||||
// symbol: 'circle', // 数据点为圆形
|
||||
// symbolSize: 6 // 数据点大小
|
||||
// },
|
||||
// 2. 目标:柱状图(绑定左侧数量Y轴)
|
||||
{
|
||||
name: '销量',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 24,
|
||||
// 关键修复:label 直接放在 series 下,而非 itemStyle 内
|
||||
itemStyle: {
|
||||
// 移除多余的 normal 层级,直接配置 color 渐变
|
||||
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: [200, 280, 180, 300, 220, 350]
|
||||
},
|
||||
// 3. 达标:柱状图(绑定左侧数量Y轴)
|
||||
{
|
||||
name: '产量',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 24,
|
||||
// 关键修复:label 直接放在 series 下,而非 itemStyle 内
|
||||
itemStyle: {
|
||||
// 移除多余的 normal 层级,直接配置 color 渐变
|
||||
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: [130, 220, 95, 255, 132, 332] // 达标数据(小于目标)
|
||||
},
|
||||
// 4. 未达标:柱状图(绑定左侧数量Y轴)
|
||||
// {
|
||||
// name: '未达标',
|
||||
// type: 'bar',
|
||||
// yAxisIndex: 0,
|
||||
// barWidth: 18,
|
||||
// itemStyle: {
|
||||
// color: 'rgba(249, 164, 74, 1)',
|
||||
// borderRadius: [4, 4, 0, 0],
|
||||
// borderWidth: 0
|
||||
// },
|
||||
// data: [70, 60, 85, 45, 88, 18, 78] // 未达标数据(目标-达标)
|
||||
// }
|
||||
],
|
||||
// 图例:区分各系列,点击可控制显示隐藏
|
||||
// legend: {
|
||||
// top: 0,
|
||||
// left: 'center',
|
||||
// itemWidth: 12,
|
||||
// itemHeight: 8,
|
||||
// textStyle: {
|
||||
// color: 'rgba(0, 0, 0, 0.45)',
|
||||
// fontSize: 12
|
||||
// },
|
||||
// data: ['完成率', '目标', '达标', '未达标']
|
||||
// }
|
||||
};
|
||||
|
||||
option && myChart.setOption(option);
|
||||
|
||||
// 窗口缩放适配
|
||||
window.addEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
|
||||
// 组件销毁清理
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
myChart.dispose();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
127
src/views/home/costComponents/costItemOverview.vue
Normal file
127
src/views/home/costComponents/costItemOverview.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="成本单项总览·元/m²" icon="cockpitItemIcon" size="costMiddleBg" topSize="middle">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||
<div class="leftTitle" style="display: flex;flex-direction: column;gap: 16px;">
|
||||
<div class="item">
|
||||
原片成本
|
||||
</div>
|
||||
<div class="item">
|
||||
加工成本
|
||||
</div>
|
||||
</div>
|
||||
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||
<div class="topItem-container" style="display: flex; flex-direction: column;gap: 16px;overflow: hidden;">
|
||||
<rawSheet :piecesCostViews="piecesCostViews" />
|
||||
<processingCost :processCostViews="processCostViews" />
|
||||
|
||||
</div>
|
||||
<!-- 2. .top 保持 flex,无需固定高度,自动跟随子元素拉伸 -->
|
||||
<!-- <div class="top" style="display: flex; width: 100%;">
|
||||
<top-item :itemList="parentItemList" />
|
||||
</div> -->
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
// import * as echarts from 'echarts'
|
||||
import processingCost from './processingCost-Item.vue'
|
||||
import rawSheet from './rawSheet-Item.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, processingCost, rawSheet },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
piecesCostViews: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
processCostViews: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
// parentItemList: [
|
||||
// { unit: "销量", targetValue: 16, currentValue: 14.5, progress: 90 },
|
||||
// { unit: "产量", targetValue: 16, currentValue: 15.2, progress: 85 },
|
||||
// ]
|
||||
}
|
||||
},
|
||||
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;
|
||||
}
|
||||
|
||||
.leftTitle {
|
||||
.item {
|
||||
width: 67px;
|
||||
height: 180px;
|
||||
padding: 37px 23px;
|
||||
background: #F9FCFF;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
color: #000000;
|
||||
line-height: 25px;
|
||||
letter-spacing: 1px;
|
||||
// text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
115
src/views/home/costComponents/costItemOverviewItem.vue
Normal file
115
src/views/home/costComponents/costItemOverviewItem.vue
Normal file
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container :name="title" icon="cockpitItemIcon" size="rawTopBg" topSize="rawTopTitleLarge">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||
<div class="topItem-container" style="display: flex; flex-direction: column;gap: 16px;overflow: hidden;">
|
||||
<!-- <topItem :itemList="parentItemList" /> -->
|
||||
<rawItem :itemList="itemData" />
|
||||
|
||||
</div>
|
||||
<!-- 2. .top 保持 flex,无需固定高度,自动跟随子元素拉伸 -->
|
||||
<!-- <div class="top" style="display: flex; width: 100%;">
|
||||
<top-item :itemList="parentItemList" />
|
||||
</div> -->
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
// import * as echarts from 'echarts'
|
||||
import rawItem from './raw-Item.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, rawItem,},
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
itemData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
title: { // 接收父组件传递的设备数据数组
|
||||
type: String,
|
||||
default: () => '原料成本概述·元/ m²' // 默认空数组,避免报错
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
itemData: {
|
||||
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;
|
||||
}
|
||||
|
||||
.leftTitle {
|
||||
.item {
|
||||
width: 67px;
|
||||
height: 180px;
|
||||
padding: 37px 23px;
|
||||
background: #F9FCFF;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
color: #000000;
|
||||
line-height: 25px;
|
||||
letter-spacing: 1px;
|
||||
// text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
216
src/views/home/costComponents/costOverview.vue
Normal file
216
src/views/home/costComponents/costOverview.vue
Normal file
@@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="成本概览·元/m²" icon="cockpitItemIcon" size="costBasicBg" topSize="psiTopTitleBasic">
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex;flex-direction: column; width: 100%;">
|
||||
<div class="top" style="display: flex; width: 100%;">
|
||||
<cost-item :height="376" :itemList="finalItemList" />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
import costItem from './cost-item.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, costItem },
|
||||
props: {
|
||||
costOverviews: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
// 固定3条默认数据,作为展示模板
|
||||
parentItemList: [
|
||||
{ name: "总制造成本", targetValue: 0, value: 0, proportion: 0, flag: 0 },
|
||||
{ name: "原片成本", targetValue: 0, value: 0, proportion: 0, flag: 0 },
|
||||
{ name: "加工成本", targetValue: 0, value: 0, proportion: 0, flag: 0 },
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
finalItemList() {
|
||||
// 核心逻辑:以 parentItemList 为模板,用 costOverviews 数据覆盖,保持3条长度
|
||||
return this.parentItemList.map((defaultItem, index) => {
|
||||
// 找到 costOverviews 中同名称的条目(优先按名称匹配,无匹配则按索引匹配)
|
||||
const matchedItem = this.costOverviews.find(item => item.name === defaultItem.name)
|
||||
|| this.parentItemList[index];
|
||||
|
||||
// 用匹配到的数据覆盖默认值,无匹配则保留默认值
|
||||
return {
|
||||
name: defaultItem.name, // 名称固定为默认模板的名称,不随传入数据改变
|
||||
flag: matchedItem?.flag ?? defaultItem.flag,
|
||||
proportion: matchedItem?.proportion ?? defaultItem.proportion,
|
||||
targetValue: matchedItem?.target ?? defaultItem.targetValue,
|
||||
value: matchedItem?.value ?? defaultItem.value
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
costOverviews: {
|
||||
handler(newValue) {
|
||||
console.log('costOverviews 数据更新:', newValue);
|
||||
console.log('最终展示数据:', this.finalItemList); // 调试用,可删除
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log('初始展示数据:', this.finalItemList); // 调试用,可删除
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</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>
|
||||
337
src/views/home/costComponents/noRouterHeader.vue
Normal file
337
src/views/home/costComponents/noRouterHeader.vue
Normal file
@@ -0,0 +1,337 @@
|
||||
<template>
|
||||
<header class="report-header" :class="['report-header__' + size]">
|
||||
<!-- 左侧区域:logo + 标题 -->
|
||||
<div class="left-content">
|
||||
<!-- <img style="height: 36px;" src="../../../assets/img/cnbm.png" alt="benmaLogo" class="logo"> -->
|
||||
<div class="top-title">{{ topTitle }}</div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="center-content">
|
||||
<div class="item" v-for="(text, index) in ['营业收入', '利润分析', '产销率库存分析', '成本分析', '驾驶舱报表']" :key="index"
|
||||
@click="activeIndex = index" :class="{ 'no-skew': activeIndex === index }">
|
||||
<span class="item-text">{{ text }}</span>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- 右侧区域:全屏按钮 -->
|
||||
<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">
|
||||
<!-- 日/月/年按钮:点击更新activeTime,关联选择器类型 -->
|
||||
<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> <!-- 补充span抵消倾斜 -->
|
||||
</div>
|
||||
<!-- 日期选择器:type和placeholder通过activeTime动态切换 -->
|
||||
<el-date-picker v-model="date" :type="getPickerType" :placeholder="getPickerPlaceholder"
|
||||
class="custom-date-picker" style="width: 132px;height: 29px;"></el-date-picker>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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,
|
||||
activeIndex: -1,
|
||||
activeTime: 0, // 0=日,1=月,2=年(默认选中“日”)
|
||||
// 定义时间类型配置: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: {
|
||||
changeFullScreen() { this.$emit('screenfullChange') },
|
||||
exportPDF() { this.$emit('exportPDF') },
|
||||
updateTime() {
|
||||
const now = new Date()
|
||||
const hours = this.padZero(now.getHours())
|
||||
const minutes = this.padZero(now.getMinutes())
|
||||
const year = now.getFullYear()
|
||||
const month = this.padZero(now.getMonth() + 1)
|
||||
const day = this.padZero(now.getDate())
|
||||
const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
|
||||
this.currentTime = `${hours}:${minutes} | ${year}.${month}.${day} | ${weekdays[now.getDay()]}`
|
||||
},
|
||||
padZero(num) { return num < 10 ? '0' + num : num }
|
||||
},
|
||||
mounted() {
|
||||
this.updateTime()
|
||||
this.timeTimer = setInterval(this.updateTime, 1000)
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.timeTimer) clearInterval(this.timeTimer)
|
||||
}
|
||||
}
|
||||
</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-between;
|
||||
|
||||
&__basic {
|
||||
// width: 547px;
|
||||
background: url(../../../assets/img/topBg.png) no-repeat;
|
||||
background-size: cover;
|
||||
background-position: 0 0;
|
||||
}
|
||||
&__psi {
|
||||
// width: 547px;
|
||||
background: url(../../../assets/img/psiTopTitle.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
/* 确保timeType绝对定位生效 */
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.center-content {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 18px;
|
||||
margin-left: 30px;
|
||||
|
||||
.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: 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;
|
||||
/* 统一倾斜角度,修复原30deg的错位 */
|
||||
box-shadow: 0 2px 8px rgba(11, 88, 255, 0.3);
|
||||
}
|
||||
|
||||
.timeType .item.no-skew .item-text {
|
||||
transform: skew(25deg) !important;
|
||||
/* 同步统一文字倾斜角度 */
|
||||
}
|
||||
|
||||
.dateP {
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.dateP .label {
|
||||
width: 62px;
|
||||
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: 40px;
|
||||
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 {
|
||||
width: 132px !important;
|
||||
height: 28px !important;
|
||||
position: relative;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
::v-deep .custom-date-picker .el-input--medium .el-input__inner,
|
||||
::v-deep .custom-date-picker .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: #fff !important;
|
||||
clip-path: polygon(20px 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);
|
||||
}
|
||||
|
||||
::v-deep .custom-date-picker .el-input__prefix {
|
||||
left: auto !important;
|
||||
right: 8px !important;
|
||||
top: 40% !important;
|
||||
transform: translateY(-50%) !important;
|
||||
}
|
||||
|
||||
::v-deep .custom-date-picker .el-input__prefix .el-icon {
|
||||
color: #fff !important;
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
::v-deep .custom-date-picker .el-input__inner:hover,
|
||||
::v-deep .custom-date-picker .el-input__inner:focus {
|
||||
background-color: rgba(11, 88, 255, 0.9) !important;
|
||||
clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%) !important;
|
||||
}
|
||||
</style>
|
||||
236
src/views/home/costComponents/operatingLineBar.vue
Normal file
236
src/views/home/costComponents/operatingLineBar.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'operatingLineBar',
|
||||
props: {
|
||||
echartData: {
|
||||
type: Object,
|
||||
// 确保默认值中 flag 是数组,避免 undefined
|
||||
default: () => ({
|
||||
locations: [],
|
||||
target: [],
|
||||
value: [],
|
||||
proportion: [],
|
||||
flag: [] // 强制初始化为空数组
|
||||
})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
myChart: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
echartData: {
|
||||
handler() {
|
||||
this.initData();
|
||||
},
|
||||
deep: true,
|
||||
immediate: true // 首次绑定就执行 handler
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initData();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.myChart) {
|
||||
this.myChart = echarts.init(chartDom);
|
||||
}
|
||||
|
||||
// 解构时给 flag 加默认值,防止 undefined
|
||||
const {
|
||||
locations = [],
|
||||
target = [],
|
||||
value = [],
|
||||
proportion = [],
|
||||
flag = []
|
||||
} = this.echartData;
|
||||
console.log('this.echartData', this.echartData);
|
||||
|
||||
// 确保 flag 数组长度与 value 一致(补全缺失的 flag 值为 0)
|
||||
const safeFlag = [...flag];
|
||||
while (safeFlag.length < value.length) {
|
||||
safeFlag.push(0); // 缺失的 flag 默认为 0(达标)
|
||||
}
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
},
|
||||
formatter: (params) => {
|
||||
if (!params.length) return '';
|
||||
const currentIndex = params[0].dataIndex;
|
||||
// 使用处理后的 safeFlag,避免越界
|
||||
const currentFlag = safeFlag[currentIndex] || 0;
|
||||
const statusText = currentFlag === 0 ? '达标' : '不达标';
|
||||
let html = `${params[0].axisValue}(${statusText})<br/>`;
|
||||
params.forEach(item => {
|
||||
const unit = item.seriesName === '完成率' ? '%' : '万元';
|
||||
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: locations
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '万元',
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'right'
|
||||
},
|
||||
min: 0,
|
||||
max: (val) => val.max > 0 ? Math.ceil(val.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: [
|
||||
{
|
||||
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: proportion,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
{
|
||||
name: '目标',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: 'rgba(40, 137, 255, 1)',
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: target
|
||||
},
|
||||
{
|
||||
name: '实际',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
// 使用处理后的 safeFlag 避免 undefined
|
||||
color: (params) => {
|
||||
const dataIndex = params.dataIndex;
|
||||
const currentFlag = safeFlag[dataIndex] || 0; // 双重保险,防止越界
|
||||
return currentFlag === 0
|
||||
? 'rgba(118, 218, 190, 1)'
|
||||
: 'rgba(249, 164, 74, 1)';
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: value
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
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>
|
||||
147
src/views/home/costComponents/overviewTrendChart.vue
Normal file
147
src/views/home/costComponents/overviewTrendChart.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
·<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="概览趋势图" icon="cockpitItemIcon" size="costSmallBg" topSize="psiTopTitleBasic">
|
||||
<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);">
|
||||
<profitBar :cost-group-data="costGroupData" :cost-types="costTypes"
|
||||
:locations="locations"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
import profitBar from './profitBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, profitBar },
|
||||
props: {
|
||||
trendViews: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 1. 地名数组(去重,按出现顺序)
|
||||
locations() {
|
||||
const locationSet = new Set();
|
||||
this.trendViews.forEach(item => {
|
||||
const location = this.extractLocation(item.name);
|
||||
if (location) locationSet.add(location);
|
||||
});
|
||||
return Array.from(locationSet);
|
||||
},
|
||||
// 2. 核心成本类型数组(仅保留“原片”“加工”等,去掉“成本”二字)
|
||||
costTypes() {
|
||||
const typeSet = new Set();
|
||||
this.trendViews.forEach(item => {
|
||||
const coreType = this.extractCoreCostType(item.name);
|
||||
if (coreType) typeSet.add(coreType);
|
||||
});
|
||||
return Array.from(typeSet);
|
||||
},
|
||||
// 3. 按「核心成本类型+地名」分组的数据
|
||||
costGroupData() {
|
||||
return this.trendViews.reduce((group, item) => {
|
||||
const coreType = this.extractCoreCostType(item.name) || '其他';
|
||||
const location = this.extractLocation(item.name) || '未知地区';
|
||||
|
||||
// 初始化层级分组
|
||||
if (!group[coreType]) group[coreType] = {};
|
||||
if (!group[coreType][location]) group[coreType][location] = [];
|
||||
|
||||
// 添加数据
|
||||
group[coreType][location].push({
|
||||
name: item.name,
|
||||
target: item.target ?? 0,
|
||||
value: item.value ?? 0,
|
||||
proportion: item.proportion ?? 0,
|
||||
flag: item.flag ?? 0,
|
||||
time: item.time
|
||||
});
|
||||
|
||||
return group;
|
||||
}, {});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 提取核心成本类型(仅保留“原片”“加工”等,去掉“成本”二字)
|
||||
extractCoreCostType(name) {
|
||||
// 匹配“前缀(含分公司/工厂/区域等)+ 核心词 + 任意后缀”,提取中间核心词
|
||||
// 兼容后缀:成本、费用、支出、损耗等,前缀:XX分公司、XX工厂、XX区域等
|
||||
const match = name.match(/(分公司)\s*([^,。;!?]+?)\s*(成本)/);
|
||||
if (match) {
|
||||
return match[2].trim(); // 提取中间核心词,去除前后空格
|
||||
}
|
||||
|
||||
// 通用匹配:无明确前缀标识时,提取“最后一个连续汉字后缀”前的内容
|
||||
const generalMatch = name.match(/(.+?)(?=[\u4e00-\u9fa5]{2,}$)/);
|
||||
return generalMatch ? generalMatch[1].trim() : name.trim();
|
||||
},
|
||||
// 提取地名(成本类型前的核心地区名)
|
||||
extractLocation(name) {
|
||||
// 匹配“分公司”前面的连续汉字(即地名)
|
||||
const match = name.match(/(.+?)分公司/);
|
||||
|
||||
if (match) {
|
||||
return match[1].trim() || '未知地区';
|
||||
}
|
||||
|
||||
// 兜底(处理不含“分公司”的情况)
|
||||
return '未知地区';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
trendViews: {
|
||||
handler(newValue) {
|
||||
console.log('核心成本类型:', this.costTypes); // 示例输出:["原片", "加工"]
|
||||
console.log('地名数组:', this.locations); // 示例输出:["桐城", "合肥"]
|
||||
console.log('分组数据:', this.costGroupData);
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log('初始核心成本类型:', this.costTypes);
|
||||
}
|
||||
};
|
||||
</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;
|
||||
}
|
||||
|
||||
/* 其他样式省略 */
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
329
src/views/home/costComponents/processingCost-Item.vue
Normal file
329
src/views/home/costComponents/processingCost-Item.vue
Normal file
@@ -0,0 +1,329 @@
|
||||
<template>
|
||||
<!-- 外层滚动容器:添加鼠标事件监听 -->
|
||||
<div class="coreItem-container" @mousedown="handleMouseDown" @mousemove="handleMouseMove" @mouseup="handleMouseUp"
|
||||
@mouseleave="handleMouseUp" :style="{ cursor: isDragging ? 'grabbing' : 'grab' }">
|
||||
<div class="coreItem">
|
||||
<div class="item" @click="handleRoute(item.route,item.name)" v-for="(item, index) in finalItemList" :key="index">
|
||||
<div class="item-header">
|
||||
<div class="unit">{{ item.name }}</div>
|
||||
</div>
|
||||
<div class="item-content">
|
||||
<div class="content-wrapper">
|
||||
<!-- 目标值 + 分割线 + 实际值 -->
|
||||
<div class="value-group">
|
||||
<div class="value-item">
|
||||
<div class="number">{{ item.targetValue || 0 }}</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
<div class="middle-line"></div>
|
||||
<div class="value-item">
|
||||
<!-- 实际值:根据 flag 动态绑定颜色 -->
|
||||
<div class="number" :style="{ color: getColorByFlag(item.flag) }">
|
||||
{{ item.value || 0 }}
|
||||
</div>
|
||||
<div class="title">当前值</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 进度条 + 完成率 -->
|
||||
<div class="progress-yield-group">
|
||||
<div class="progress-group">
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" :style="{
|
||||
width: getProportionPercent(item.proportion) + '%',
|
||||
background: getColorByFlag(item.flag, true)
|
||||
}"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="yield">
|
||||
<span class="progress-percent">完成率</span>
|
||||
<!-- 完成率:动态颜色 + 百分比格式 -->
|
||||
<span class="progress-percent progress-value" :style="{ color: getColorByFlag(item.flag) }">
|
||||
{{ getProportionPercent(item.proportion) }}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Container",
|
||||
components: {},
|
||||
props: ["name", "size", "icon", 'processCostViews'],
|
||||
data() {
|
||||
return {
|
||||
itemList: [
|
||||
{
|
||||
name: "人工", targetValue: 0, value: 0, proportion: 0, flag: 0,
|
||||
// route:'cost/rawMaterialCostAnalysis/rawMaterialCostAnalysis'
|
||||
},
|
||||
{ name: "燃动力", targetValue: 0, value: 0, proportion: 0, flag: 0, route:'fuelPowerCostAnalysis/fuelPowerCostAnalysis' },
|
||||
{
|
||||
name: "包装物", targetValue: 0, value: 0, proportion: 0, flag: 0,
|
||||
route:'rawMaterialCostAnalysis/rawMaterialCostAnalysis'
|
||||
|
||||
},
|
||||
{
|
||||
name: "辅料", targetValue: 0, value: 0, proportion: 0, flag: 0,
|
||||
route: 'rawMaterialCostAnalysis/rawMaterialCostAnalysis'
|
||||
|
||||
},
|
||||
{
|
||||
name: "制造费用", targetValue: 0, value: 0, proportion: 0, flag: 0,
|
||||
route:'productionCostsAnalysis/productionCostsAnalysis'
|
||||
},
|
||||
],
|
||||
isDragging: false,
|
||||
startX: 0,
|
||||
scrollLeft: 0
|
||||
};
|
||||
},
|
||||
// computed: {
|
||||
// finalItemList() {
|
||||
// if (Array.isArray(this.piecesCostViews) && this.piecesCostViews.length > 0) {
|
||||
// return this.piecesCostViews.map((item) => ({
|
||||
// flag: item.flag || 0, // 默认 flag 为 0
|
||||
// proportion: item.proportion || 0, // 默认完成率为 0
|
||||
// targetValue: item.target ?? 0, // 兼容 null 值,显示为 0
|
||||
// value: item.value || 0,
|
||||
// name: item.name
|
||||
// }));
|
||||
// } else {
|
||||
// return this.itemList;
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
computed: {
|
||||
finalItemList() {
|
||||
return this.itemList.map((defaultItem, index) => {
|
||||
// 核心修复:1. 恢复模板名称处理;2. 统一大小写和空格处理
|
||||
const matchedItem = this.processCostViews.find(item => {
|
||||
const processedIncomingName = (item.name || '').replace(/成本/g, '').trim().toLowerCase();
|
||||
// 恢复模板名称处理:去除“成本”(如果有的话)、去空格、小写
|
||||
const processedDefaultName = defaultItem.name.replace(/成本/g, '').trim().toLowerCase();
|
||||
return processedIncomingName === processedDefaultName;
|
||||
});
|
||||
// 移除索引 fallback:只匹配名称,不按索引赋值,避免误匹配
|
||||
// 无匹配项则完全保留默认值
|
||||
const targetItem = matchedItem || defaultItem;
|
||||
|
||||
return {
|
||||
name: defaultItem.name, // 固定模板名称
|
||||
flag: targetItem.flag ?? defaultItem.flag,
|
||||
proportion: targetItem.proportion ?? defaultItem.proportion,
|
||||
targetValue: targetItem.target ?? defaultItem.targetValue,
|
||||
value: targetItem.value ?? defaultItem.value,
|
||||
route: defaultItem.route
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleRoute(route, name) {
|
||||
if (route) {
|
||||
this.$router.push({
|
||||
path: route,
|
||||
query: {
|
||||
name: name + '成本'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
// 根据 flag 获取文字/进度条颜色
|
||||
getColorByFlag(flag, isProgressBar = false) {
|
||||
const colorMap = {
|
||||
0: {
|
||||
text: 'rgba(103, 103, 103, 0.79)',
|
||||
progress: 'rgba(103, 103, 103, 0.5)' // 进度条颜色稍浅
|
||||
},
|
||||
1: {
|
||||
text: 'rgba(255, 132, 0, 1)',
|
||||
progress: 'rgba(255, 132, 0, 0.7)' // 进度条颜色稍浅
|
||||
}
|
||||
};
|
||||
const currentFlag = flag === 1 ? 1 : 0;
|
||||
return isProgressBar ? colorMap[currentFlag].progress : colorMap[currentFlag].text;
|
||||
},
|
||||
// 处理 proportion:乘以 100 并保留 0 位小数
|
||||
getProportionPercent(proportion) {
|
||||
const num = Number(proportion) || 0;
|
||||
return num; // 四舍五入取整,如需保留小数可改为 toFixed(2)
|
||||
},
|
||||
// 拖拽相关方法
|
||||
handleMouseDown(e) {
|
||||
this.isDragging = true;
|
||||
this.startX = e.pageX - this.$el.offsetLeft;
|
||||
this.scrollLeft = this.$el.scrollLeft;
|
||||
this.$el.style.userSelect = "none";
|
||||
},
|
||||
handleMouseMove(e) {
|
||||
if (!this.isDragging) return;
|
||||
e.preventDefault();
|
||||
const x = e.pageX - this.$el.offsetLeft;
|
||||
const walk = (x - this.startX) * 1.2;
|
||||
this.$el.scrollLeft = this.scrollLeft - walk;
|
||||
},
|
||||
handleMouseUp() {
|
||||
this.isDragging = false;
|
||||
this.$el.style.userSelect = "";
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coreItem-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
cursor: grab;
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
|
||||
.coreItem {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 0 8px;
|
||||
width: fit-content;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 202px;
|
||||
height: 180px;
|
||||
background: #f9fcff;
|
||||
padding: 10px 20px 0 27px;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.3s ease;
|
||||
&:not(:nth-child(1)):hover {
|
||||
box-shadow: 0px 4px 12px 2px #B5CDE5;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.item-header {
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
|
||||
.unit {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
color: #333333;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100% - 30px);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.value-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.value-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.middle-line {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, rgba(40, 203, 151, 0.3), rgba(40, 203, 151, 0.8), rgba(40, 203, 151, 0.3));
|
||||
}
|
||||
|
||||
.number {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
/* 颜色由动态样式控制,移除固定颜色 */
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.progress-yield-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.progress-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: #F5F7FA;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
transition: width 0.5s ease;
|
||||
/* 背景色由动态样式控制,移除固定颜色 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.progress-percent {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.progress-value {
|
||||
font-weight: 500;
|
||||
/* 颜色由动态样式控制,移除固定颜色 */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
145
src/views/home/costComponents/processingCostTrendChart.vue
Normal file
145
src/views/home/costComponents/processingCostTrendChart.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="加工成本趋势图" icon="cockpitItemIcon" size="costSmallBg" topSize="psiTopTitleBasic">
|
||||
<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);">
|
||||
<profitBar :cost-group-data="costGroupData" :cost-types="costTypes" :locations="locations" />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
import profitBar from './profitBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, profitBar },
|
||||
props: {
|
||||
trendViews: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 1. 地名数组(去重,按出现顺序)
|
||||
locations() {
|
||||
const locationSet = new Set();
|
||||
this.trendViews.forEach(item => {
|
||||
const location = this.extractLocation(item.name);
|
||||
if (location) locationSet.add(location);
|
||||
});
|
||||
return Array.from(locationSet);
|
||||
},
|
||||
// 2. 核心成本类型数组(仅保留“原片”“加工”等,去掉“成本”二字)
|
||||
costTypes() {
|
||||
const typeSet = new Set();
|
||||
this.trendViews.forEach(item => {
|
||||
const coreType = this.extractCoreCostType(item.name);
|
||||
if (coreType) typeSet.add(coreType);
|
||||
});
|
||||
return Array.from(typeSet);
|
||||
},
|
||||
// 3. 按「核心成本类型+地名」分组的数据
|
||||
costGroupData() {
|
||||
return this.trendViews.reduce((group, item) => {
|
||||
const coreType = this.extractCoreCostType(item.name) || '其他';
|
||||
const location = this.extractLocation(item.name) || '未知地区';
|
||||
|
||||
// 初始化层级分组
|
||||
if (!group[coreType]) group[coreType] = {};
|
||||
if (!group[coreType][location]) group[coreType][location] = [];
|
||||
|
||||
// 添加数据
|
||||
group[coreType][location].push({
|
||||
name: item.name,
|
||||
target: item.target ?? 0,
|
||||
value: item.value ?? 0,
|
||||
proportion: item.proportion ?? 0,
|
||||
flag: item.flag ?? 0,
|
||||
time: item.time
|
||||
});
|
||||
|
||||
return group;
|
||||
}, {});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 提取核心成本类型(仅保留“原片”“加工”等,去掉“成本”二字)
|
||||
extractCoreCostType(name) {
|
||||
// 匹配“前缀(含分公司/工厂/区域等)+ 核心词 + 任意后缀”,提取中间核心词
|
||||
// 兼容后缀:成本、费用、支出、损耗等,前缀:XX分公司、XX工厂、XX区域等
|
||||
const match = name.match(/(分公司)\s*([^,。;!?]+?)\s*(成本)/);
|
||||
if (match) {
|
||||
return match[2].trim(); // 提取中间核心词,去除前后空格
|
||||
}
|
||||
|
||||
// 通用匹配:无明确前缀标识时,提取“最后一个连续汉字后缀”前的内容
|
||||
const generalMatch = name.match(/(.+?)(?=[\u4e00-\u9fa5]{2,}$)/);
|
||||
return generalMatch ? generalMatch[1].trim() : name.trim();
|
||||
},
|
||||
// 提取地名(成本类型前的核心地区名)
|
||||
extractLocation(name) {
|
||||
// 匹配“分公司”前面的连续汉字(即地名)
|
||||
const match = name.match(/(.+?)分公司/);
|
||||
|
||||
if (match) {
|
||||
return match[1].trim() || '未知地区';
|
||||
}
|
||||
|
||||
// 兜底(处理不含“分公司”的情况)
|
||||
return '未知地区';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
trendViews: {
|
||||
handler(newValue) {
|
||||
console.log('核心成本类型:', this.costTypes); // 示例输出:["原片", "加工"]
|
||||
console.log('地名数组:', this.locations); // 示例输出:["桐城", "合肥"]
|
||||
console.log('分组数据:', this.costGroupData);
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log('初始核心成本类型:', this.costTypes);
|
||||
}
|
||||
};
|
||||
</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;
|
||||
}
|
||||
|
||||
/* 其他样式省略 */
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
223
src/views/home/costComponents/product-Item.vue
Normal file
223
src/views/home/costComponents/product-Item.vue
Normal file
@@ -0,0 +1,223 @@
|
||||
<template>
|
||||
<!-- 外层滚动容器:添加鼠标事件监听 -->
|
||||
<div class="coreItem-container" @mousedown="handleMouseDown" @mousemove="handleMouseMove" @mouseup="handleMouseUp"
|
||||
@mouseleave="handleMouseUp" :style="{ cursor: isDragging ? 'grabbing' : 'grab' }">
|
||||
<div class="coreItem">
|
||||
<div class="item" v-for="(item, index) in itemList" :key="index">
|
||||
<div class="item-header">
|
||||
<div class="unit">{{ item.name }}</div>
|
||||
</div>
|
||||
<div class="item-content">
|
||||
<div class="content-wrapper">
|
||||
<!-- 目标值 + 分割线 + 实际值 -->
|
||||
<div class="value-group">
|
||||
<div class="value-item">
|
||||
<div class="number">{{ item.value }}</div>
|
||||
<!-- <div class="title">目标值</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Container",
|
||||
components: {},
|
||||
props: ["name", "size", "icon",'itemList'],
|
||||
data() {
|
||||
return {
|
||||
itemList: [
|
||||
// { unit: "营业收入·万元", targetValue: 16, currentValue: 14.5, progress: 90 },
|
||||
// { unit: "经营性利润·万元", targetValue: 16, currentValue: 15.2, progress: 85 },
|
||||
// { unit: "利润总额·万元", targetValue: 16, currentValue: 15.2, progress: 85 },
|
||||
// { unit: "毛利率·%", targetValue: 16, currentValue: 15.2, progress: 85 },
|
||||
// { unit: "销量·万㎡", targetValue: 20, currentValue: 16, progress: 80 },
|
||||
// { unit: "双镀面板·万㎡", targetValue: 15, currentValue: 13.8, progress: 92 }
|
||||
],
|
||||
// 拖拽相关状态
|
||||
isDragging: false, // 是否正在拖拽
|
||||
startX: 0, // 拖拽开始时的鼠标X坐标
|
||||
scrollLeft: 0 // 拖拽开始时的滚动条位置
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 鼠标按下:开始拖拽
|
||||
handleMouseDown(e) {
|
||||
this.isDragging = true;
|
||||
this.startX = e.pageX - this.$el.offsetLeft; // 记录初始鼠标X坐标(相对容器)
|
||||
this.scrollLeft = this.$el.scrollLeft; // 记录初始滚动位置
|
||||
this.$el.style.userSelect = "none"; // 防止拖拽时选中文字
|
||||
},
|
||||
// 鼠标移动:处理拖拽滚动
|
||||
handleMouseMove(e) {
|
||||
if (!this.isDragging) return;
|
||||
e.preventDefault(); // 阻止默认行为(如选中文本)
|
||||
const x = e.pageX - this.$el.offsetLeft; // 计算当前鼠标X坐标(相对容器)
|
||||
const walk = (x - this.startX) * 1.2; // 计算移动距离(乘以1.2增加灵敏度)
|
||||
this.$el.scrollLeft = this.scrollLeft - walk; // 更新滚动位置
|
||||
},
|
||||
// 鼠标松开/离开:结束拖拽
|
||||
handleMouseUp() {
|
||||
this.isDragging = false;
|
||||
this.$el.style.userSelect = ""; // 恢复文本选择
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coreItem-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
// 隐藏滚动条(保留功能)
|
||||
&::-webkit-scrollbar {
|
||||
height: 0; // 彻底隐藏横向滚动条
|
||||
width: 0;
|
||||
}
|
||||
|
||||
scrollbar-width: none; // Firefox
|
||||
-ms-overflow-style: none; // IE/Edge
|
||||
|
||||
// 拖拽时的光标样式(增强交互体验)
|
||||
cursor: grab;
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
|
||||
.coreItem {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 0 8px;
|
||||
width: fit-content; // 宽度由子项总宽度决定
|
||||
min-width: 100%; // 子项较少时铺满容器
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 280px;
|
||||
height: 80px;
|
||||
background: #f9fcff;
|
||||
padding: 10px 20px 0 27px;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.item-header {
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
|
||||
.unit {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 500;
|
||||
font-size: 18px;
|
||||
color: #333333;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100% - 30px);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.value-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.value-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.middle-line {
|
||||
width: 207px;
|
||||
height: 3px;
|
||||
border: 1px solid;
|
||||
border-image: linear-gradient(109deg, rgba(203, 203, 203, 1), rgba(255, 255, 255, 0)) 1 1;
|
||||
}
|
||||
|
||||
.number {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
color: #666666;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.progress-yield-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.progress-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: #F5F7FA;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: rgba(98, 213, 180, .7);
|
||||
border-radius: 4px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.yield {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.progress-percent {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.progress-value {
|
||||
color: #28CB97;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
114
src/views/home/costComponents/productItemOverviewItem.vue
Normal file
114
src/views/home/costComponents/productItemOverviewItem.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container :name="title" icon="cockpitItemIcon" size="productBasicBg" topSize="middle">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||
<div class="topItem-container" style="display: flex; flex-direction: column;gap: 16px;overflow: hidden;">
|
||||
<!-- <topItem :itemList="parentItemList" /> -->
|
||||
<productItem :itemList="parentItemList" />
|
||||
|
||||
</div>
|
||||
<!-- 2. .top 保持 flex,无需固定高度,自动跟随子元素拉伸 -->
|
||||
<!-- <div class="top" style="display: flex; width: 100%;">
|
||||
<top-item :itemList="parentItemList" />
|
||||
</div> -->
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
// import * as echarts from 'echarts'
|
||||
import productItem from './product-Item.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, productItem },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
parentItemList: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
title: { // 接收父组件传递的设备数据数组
|
||||
type: String,
|
||||
default: () =>'' // 默认空数组,避免报错
|
||||
},
|
||||
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
// parentItemList: [
|
||||
// { unit: "销量", targetValue: 16, currentValue: 14.5, progress: 90 },
|
||||
// { unit: "产量", targetValue: 16, currentValue: 15.2, progress: 85 },
|
||||
// ]
|
||||
}
|
||||
},
|
||||
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;
|
||||
}
|
||||
|
||||
.leftTitle {
|
||||
.item {
|
||||
width: 67px;
|
||||
height: 180px;
|
||||
padding: 37px 23px;
|
||||
background: #F9FCFF;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
color: #000000;
|
||||
line-height: 25px;
|
||||
letter-spacing: 1px;
|
||||
// text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
252
src/views/home/costComponents/productLeftTable.vue
Normal file
252
src/views/home/costComponents/productLeftTable.vue
Normal file
@@ -0,0 +1,252 @@
|
||||
<template>
|
||||
<div style="width: 100%;">
|
||||
<Container name="领用明细" icon="cockpitItemIcon" size="productMiddleBg" topSize="middle">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex;width: 100%;">
|
||||
<div class="bottom"
|
||||
style="padding: 14px 16px; height: 620px; display: flex; width: 100%;background-color: rgba(249, 252, 255, 1);">
|
||||
<!-- <top-item /> -->
|
||||
<base-table style="width: 100%;" :page="1" :limit="10" :show-index="true" :beilv="1" :tableConfig="tableProps"
|
||||
:table-data="tableData" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
import baseTable from './baseTable.vue'
|
||||
import { parseTime } from "@/utils/ruoyi";
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, baseTable },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
tableData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
baseList: [
|
||||
{ value: 1, name: "总览" },
|
||||
|
||||
{ value: 2, name: "宜兴" },
|
||||
{ value: 3, name: "漳州" },
|
||||
{ value: 4, name: "自贡" },
|
||||
{ value: 5, name: "桐城" },
|
||||
{ value: 6, name: "洛阳" },
|
||||
{ value: 7, name: "合肥" },
|
||||
],
|
||||
tableProps: [
|
||||
// { prop: 'id', label: '序号', width: 50, align: 'center' },
|
||||
// { prop: 'id', label: '序号', width: 50, align: 'center' },
|
||||
{
|
||||
prop: 'levelId', label: '基地', align: 'center',
|
||||
filter: (levelId) => this.getBaseName(levelId)
|
||||
},
|
||||
{ prop: 'type', label: '物料类别', align: 'center' },
|
||||
{ prop: 'name', label: '物料名称', align: 'center' },
|
||||
{ prop: 'code', label: '物料编码', align: 'center' },
|
||||
{
|
||||
prop: 'time', label: '领用日期', align: 'center',
|
||||
filter: parseTime,
|
||||
},
|
||||
{ prop: 'num', label: '数量', align: 'center' },
|
||||
|
||||
|
||||
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
methods: {
|
||||
getBaseName(levelId) {
|
||||
if (!levelId) return '';
|
||||
// 从baseList中匹配对应的基地名称,无匹配则返回原始levelId
|
||||
const baseItem = this.baseList.find(item => item.value === levelId);
|
||||
return baseItem ? baseItem.name : levelId;
|
||||
},
|
||||
}
|
||||
}
|
||||
</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>
|
||||
252
src/views/home/costComponents/productRightTable.vue
Normal file
252
src/views/home/costComponents/productRightTable.vue
Normal file
@@ -0,0 +1,252 @@
|
||||
<template>
|
||||
<div style="width: 100%;">
|
||||
<Container name="入账明细" icon="cockpitItemIcon" size="productMiddleBg" topSize="middle">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex;width: 100%;">
|
||||
<div class="bottom"
|
||||
style="padding: 14px 16px; height: 620px; display: flex; width: 100%;background-color: rgba(249, 252, 255, 1);">
|
||||
<!-- <top-item /> -->
|
||||
<base-table style="width: 100%;" :page="1" :limit="10" :show-index="true" :beilv="1" :tableConfig="tableProps"
|
||||
:table-data="tableData" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
import baseTable from './baseTable.vue'
|
||||
import { parseTime } from "@/utils/ruoyi";
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, baseTable },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
tableData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
baseList: [
|
||||
{ value: 1, name: "总览" },
|
||||
|
||||
{ value: 2, name: "宜兴" },
|
||||
{ value: 3, name: "漳州" },
|
||||
{ value: 4, name: "自贡" },
|
||||
{ value: 5, name: "桐城" },
|
||||
{ value: 6, name: "洛阳" },
|
||||
{ value: 7, name: "合肥" },
|
||||
],
|
||||
tableProps: [
|
||||
// { prop: 'id', label: '序号', width: 50, align: 'center' },
|
||||
// { prop: 'id', label: '序号', width: 50, align: 'center' },
|
||||
{
|
||||
prop: 'levelId', label: '基地', align: 'center',
|
||||
filter: (levelId) => this.getBaseName(levelId)
|
||||
},
|
||||
{ prop: 'type', label: '物料类别', align: 'center' },
|
||||
{ prop: 'name', label: '物料名称', align: 'center' },
|
||||
{ prop: 'code', label: '物料编码', align: 'center' },
|
||||
{
|
||||
prop: 'time', label: '领用日期', align: 'center',
|
||||
filter: parseTime,
|
||||
},
|
||||
{ prop: 'num', label: '数量', align: 'center' },
|
||||
|
||||
|
||||
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
methods: {
|
||||
getBaseName(levelId) {
|
||||
if (!levelId) return '';
|
||||
// 从baseList中匹配对应的基地名称,无匹配则返回原始levelId
|
||||
const baseItem = this.baseList.find(item => item.value === levelId);
|
||||
return baseItem ? baseItem.name : levelId;
|
||||
},
|
||||
}
|
||||
}
|
||||
</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/costComponents/profitBar.vue
Normal file
365
src/views/home/costComponents/profitBar.vue
Normal file
@@ -0,0 +1,365 @@
|
||||
<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: 458px">
|
||||
<operatingLineBar :echartData="echartData" style="height: 99%; width: 100%" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import operatingLineBar from './operatingLineBar.vue';
|
||||
export default {
|
||||
name: "Container",
|
||||
components: { operatingLineBar },
|
||||
props: ["name", "size", "icon", 'costGroupData', 'costTypes', 'locations'],
|
||||
data() {
|
||||
return {
|
||||
isDropdownShow: false,
|
||||
selectedProfit: '', // 默认选中"原料"
|
||||
echartData: {
|
||||
locations: [],
|
||||
target: [],
|
||||
value: [],
|
||||
proportion: [],
|
||||
flag: [] // 接收 flag 数组(0/1)
|
||||
},
|
||||
profitOptions: [], // 可根据实际需求修改选项
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
defaultProfit() {
|
||||
// 若 costTypes 有数据,取第一个;否则默认空
|
||||
return this.profitOptions.length > 0 ? this.profitOptions[0] : '';
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
defaultProfit(newVal) {
|
||||
// 仅在 selectedProfit 为空时赋值(避免用户手动选择后被覆盖)
|
||||
if (!this.selectedProfit && newVal) {
|
||||
this.selectedProfit = newVal;
|
||||
this.getEchartData();
|
||||
}
|
||||
},
|
||||
costTypes(newVal) {
|
||||
if (newVal && newVal.length > 0) {
|
||||
this.profitOptions = newVal;
|
||||
// 关键:costTypes 变化时,若未选中值则自动选第一个
|
||||
if (!this.selectedProfit) {
|
||||
this.selectedProfit = newVal[0];
|
||||
this.getEchartData();
|
||||
}
|
||||
}
|
||||
},
|
||||
// 监听 locations 变化,重新加载数据(避免数据依赖未更新)
|
||||
locations() {
|
||||
if (this.selectedProfit) {
|
||||
this.getEchartData();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectProfit(item) {
|
||||
this.selectedProfit = item;
|
||||
this.isDropdownShow = false;
|
||||
this.getEchartData();
|
||||
},
|
||||
getEchartData() {
|
||||
// 若 selectedProfit 无值,返回空数据
|
||||
if (!this.selectedProfit || !this.profitOptions.length) return;
|
||||
|
||||
// 从 costGroupData 中获取当前选中类型的数据(如“原片”“加工”)
|
||||
const selectedTypeData = this.costGroupData[this.selectedProfit] || {};
|
||||
|
||||
// 整理数据格式:按地名分组,提取 target/value/proportion
|
||||
const locations = [];
|
||||
const target = [];
|
||||
const value = [];
|
||||
const proportion = [];
|
||||
const flag = [];
|
||||
|
||||
// 遍历所有地名,对应提取数据
|
||||
this.locations.forEach(location => {
|
||||
const locationData = selectedTypeData[location] || [];
|
||||
const firstItem = locationData[0] || {}; // 每组取第一条数据(可根据需求调整)
|
||||
|
||||
locations.push(location);
|
||||
target.push(firstItem.target || 0);
|
||||
value.push(firstItem.value || 0);
|
||||
proportion.push(firstItem.proportion || 0);
|
||||
flag.push(firstItem.flag || 0);
|
||||
});
|
||||
|
||||
// 更新 ECharts 数据(包含 flag 字段)
|
||||
this.echartData = {
|
||||
locations, // x轴:地名列表
|
||||
target, // 目标值数组
|
||||
value, // 实际值数组
|
||||
proportion,
|
||||
flag
|
||||
};
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 点击外部关闭下拉菜单
|
||||
const handleOutsideClick = (e) => {
|
||||
if (!this.$el.contains(e.target)) {
|
||||
this.isDropdownShow = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 关键:页面加载时强制初始化
|
||||
setTimeout(() => {
|
||||
// 优先使用 costTypes 数据初始化
|
||||
if (this.costTypes && this.costTypes.length > 0) {
|
||||
this.profitOptions = this.costTypes;
|
||||
this.selectedProfit = this.costTypes[0];
|
||||
this.getEchartData();
|
||||
}
|
||||
// 若 costTypes 未及时加载,用 profitOptions 初始化
|
||||
else if (this.profitOptions.length > 0) {
|
||||
this.selectedProfit = this.profitOptions[0];
|
||||
this.getEchartData();
|
||||
}
|
||||
}, 100); // 轻微延迟,确保 props 数据已传递
|
||||
|
||||
document.addEventListener('click', handleOutsideClick);
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
document.removeEventListener('click', handleOutsideClick);
|
||||
});
|
||||
}
|
||||
};
|
||||
</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;
|
||||
margin-left: 0px;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
|
||||
.right-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.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: 102px;
|
||||
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: 102px;
|
||||
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>
|
||||
336
src/views/home/costComponents/profitLineChart.vue
Normal file
336
src/views/home/costComponents/profitLineChart.vue
Normal file
@@ -0,0 +1,336 @@
|
||||
<template>
|
||||
<div style="width: 100%;">
|
||||
<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: 420px; display: flex; width: 100%;background-color: rgba(249, 252, 255, 1);">
|
||||
<!-- <top-item /> -->
|
||||
<costBar />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
import costBar from './costBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, costBar },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
trendData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
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>
|
||||
238
src/views/home/costComponents/purchase-Item.vue
Normal file
238
src/views/home/costComponents/purchase-Item.vue
Normal file
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<!-- 外层滚动容器:添加鼠标事件监听 -->
|
||||
<div class="coreItem-container" @mousedown="handleMouseDown" @mousemove="handleMouseMove" @mouseup="handleMouseUp"
|
||||
@mouseleave="handleMouseUp" :style="{ cursor: isDragging ? 'grabbing' : 'grab' }">
|
||||
<div class="coreItem">
|
||||
<div class="item" v-for="(item, index) in itemList" :key="index">
|
||||
<div class="item-header">
|
||||
<div class="unit">{{ item.unit }}</div>
|
||||
</div>
|
||||
<div class="item-content">
|
||||
<div class="content-wrapper">
|
||||
<!-- 目标值 + 分割线 + 实际值 -->
|
||||
<div class="value-group">
|
||||
<div class="value-item">
|
||||
<div class="number">{{ item.targetValue }}</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
<div class="middle-line"></div>
|
||||
<div class="value-item">
|
||||
<div class="number">{{ item.currentValue }}</div>
|
||||
<div class="title">当前值</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 进度条 + 完成率 -->
|
||||
<div class="progress-yield-group">
|
||||
<div class="progress-group">
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" :style="{ width: item.progress + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="yield">
|
||||
<span class="progress-percent">完成率</span>
|
||||
<span class="progress-percent progress-value">{{ item.progress }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</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 },
|
||||
{ unit: "经营性利润·万元", targetValue: 16, currentValue: 15.2, progress: 85 },
|
||||
{ unit: "利润总额·万元", targetValue: 16, currentValue: 15.2, progress: 85 },
|
||||
{ unit: "毛利率·%", targetValue: 16, currentValue: 15.2, progress: 85 },
|
||||
{ unit: "销量·万㎡", targetValue: 20, currentValue: 16, progress: 80 },
|
||||
{ unit: "双镀面板·万㎡", targetValue: 15, currentValue: 13.8, progress: 92 }
|
||||
],
|
||||
// 拖拽相关状态
|
||||
isDragging: false, // 是否正在拖拽
|
||||
startX: 0, // 拖拽开始时的鼠标X坐标
|
||||
scrollLeft: 0 // 拖拽开始时的滚动条位置
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 鼠标按下:开始拖拽
|
||||
handleMouseDown(e) {
|
||||
this.isDragging = true;
|
||||
this.startX = e.pageX - this.$el.offsetLeft; // 记录初始鼠标X坐标(相对容器)
|
||||
this.scrollLeft = this.$el.scrollLeft; // 记录初始滚动位置
|
||||
this.$el.style.userSelect = "none"; // 防止拖拽时选中文字
|
||||
},
|
||||
// 鼠标移动:处理拖拽滚动
|
||||
handleMouseMove(e) {
|
||||
if (!this.isDragging) return;
|
||||
e.preventDefault(); // 阻止默认行为(如选中文本)
|
||||
const x = e.pageX - this.$el.offsetLeft; // 计算当前鼠标X坐标(相对容器)
|
||||
const walk = (x - this.startX) * 1.2; // 计算移动距离(乘以1.2增加灵敏度)
|
||||
this.$el.scrollLeft = this.scrollLeft - walk; // 更新滚动位置
|
||||
},
|
||||
// 鼠标松开/离开:结束拖拽
|
||||
handleMouseUp() {
|
||||
this.isDragging = false;
|
||||
this.$el.style.userSelect = ""; // 恢复文本选择
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coreItem-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
// 隐藏滚动条(保留功能)
|
||||
&::-webkit-scrollbar {
|
||||
height: 0; // 彻底隐藏横向滚动条
|
||||
width: 0;
|
||||
}
|
||||
|
||||
scrollbar-width: none; // Firefox
|
||||
-ms-overflow-style: none; // IE/Edge
|
||||
|
||||
// 拖拽时的光标样式(增强交互体验)
|
||||
cursor: grab;
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
|
||||
.coreItem {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 0 8px;
|
||||
width: fit-content; // 宽度由子项总宽度决定
|
||||
min-width: 100%; // 子项较少时铺满容器
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 202px;
|
||||
height: 180px;
|
||||
background: #f9fcff;
|
||||
padding: 10px 20px 0 27px;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.item-header {
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
|
||||
.unit {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
color: #333333;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100% - 30px);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.value-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.value-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.middle-line {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, rgba(40, 203, 151, 0.3), rgba(40, 203, 151, 0.8), rgba(40, 203, 151, 0.3));
|
||||
}
|
||||
|
||||
.number {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
color: #666666;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.progress-yield-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.progress-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: #F5F7FA;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: rgba(98, 213, 180, .7);
|
||||
border-radius: 4px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.yield {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.progress-percent {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.progress-value {
|
||||
color: #28CB97;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
251
src/views/home/costComponents/raw-Item.vue
Normal file
251
src/views/home/costComponents/raw-Item.vue
Normal file
@@ -0,0 +1,251 @@
|
||||
<template>
|
||||
<div class="coreItem-container" @mousedown="handleMouseDown" @mousemove="handleMouseMove" @mouseup="handleMouseUp"
|
||||
@mouseleave="handleMouseUp" :style="{ cursor: isDragging ? 'grabbing' : 'grab' }">
|
||||
<div class="coreItem">
|
||||
<div class="item" @click="handleRoute(item)" v-for="(item, index) in itemList" :key="index">
|
||||
<div class="item-header">
|
||||
<div class="unit">{{ item.name }}</div>
|
||||
</div>
|
||||
<div class="item-content">
|
||||
<div class="content-wrapper">
|
||||
<div class="value-group">
|
||||
<div class="value-item">
|
||||
<div class="number" style="color: rgba(103, 103, 103, 0.79);">{{ item.target }}</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
<div class="middle-line"></div>
|
||||
<!-- 当前值数字:根据 flag 变色 -->
|
||||
<div class="value-item">
|
||||
<div class="number" :style="{ color: item.flag === 1 ? '#36B58A' : '#F9A44A' }">
|
||||
{{ item.value }}
|
||||
</div>
|
||||
<div class="title">当前值</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-yield-group">
|
||||
<div class="progress-group">
|
||||
<div class="progress-container">
|
||||
<!-- 进度条:根据 flag 变色 -->
|
||||
<div class="progress-bar" :style="{
|
||||
width: (item.proportion ? item.proportion : 0) + '%',
|
||||
background: item.flag === 1 ? 'rgba(54, 181, 138, 1)' : 'rgba(249, 164, 74, 1)'
|
||||
}"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="yield">
|
||||
<span class="progress-percent" :style="{ color: item.flag === 1 ? '#36B58A' : '#F9A44A' }">完成率</span>
|
||||
<!-- 完成率百分比:根据 flag 变色 -->
|
||||
<span class="progress-percent progress-value"
|
||||
:style="{ color: item.flag === 1 ? '#36B58A' : '#F9A44A' }">
|
||||
{{ (item.proportion ? item.proportion : 0 ) }}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Container",
|
||||
components: {},
|
||||
props: ["name", "size", "icon", "itemList",'route'],
|
||||
data() {
|
||||
return {
|
||||
// parentItemList: [
|
||||
// { name: "燃料成本", target: 0, value: 0, proportion: 0, flag: 0 },
|
||||
// { name: "天然气", target: 0, value: 0, proportion: 0, flag: 0 }
|
||||
// ],
|
||||
isDragging: false,
|
||||
startX: 0,
|
||||
scrollLeft: 0
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleRoute(item) {
|
||||
this.$router.push({
|
||||
name: item.route,
|
||||
query: {
|
||||
name: item.name,
|
||||
}
|
||||
})
|
||||
},
|
||||
handleMouseDown(e) {
|
||||
this.isDragging = true;
|
||||
this.startX = e.pageX - this.$el.offsetLeft;
|
||||
this.scrollLeft = this.$el.scrollLeft;
|
||||
this.$el.style.userSelect = "none";
|
||||
},
|
||||
handleMouseMove(e) {
|
||||
if (!this.isDragging) return;
|
||||
e.preventDefault();
|
||||
const x = e.pageX - this.$el.offsetLeft;
|
||||
const walk = (x - this.startX) * 1.2;
|
||||
this.$el.scrollLeft = this.scrollLeft - walk;
|
||||
},
|
||||
handleMouseUp() {
|
||||
this.isDragging = false;
|
||||
this.$el.style.userSelect = "";
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 样式保持不变,仅删除 progress-bar 和 progress-value 的固定颜色 */
|
||||
.coreItem-container {
|
||||
width: 100%;
|
||||
height: 270px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
cursor: grab;
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
|
||||
.coreItem {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 0 8px;
|
||||
width: fit-content;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 280px;
|
||||
height: 265px;
|
||||
background: #f9fcff;
|
||||
padding: 10px 20px 0 27px;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.3s ease;
|
||||
&:hover {
|
||||
box-shadow: 0px 4px 12px 2px #B5CDE5;
|
||||
}
|
||||
.item-header {
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
|
||||
.unit {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 500;
|
||||
font-size: 18px;
|
||||
color: #333333;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100% - 30px);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.value-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.value-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.middle-line {
|
||||
width: 207px;
|
||||
height: 1px;
|
||||
// border: 1px solid;
|
||||
background: linear-gradient(to left, rgba(255, 0, 0, 0), #cbcbcb);
|
||||
}
|
||||
|
||||
.number {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
/* 移除固定颜色,由模板动态绑定 */
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.progress-yield-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.progress-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: #F5F7FA;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
transition: width 0.5s ease;
|
||||
/* 移除固定背景色,由模板动态绑定 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.progress-percent {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.progress-value {
|
||||
font-weight: 500;
|
||||
/* 移除固定颜色,由模板动态绑定 */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
145
src/views/home/costComponents/rawMaterialCost.vue
Normal file
145
src/views/home/costComponents/rawMaterialCost.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="原片成本趋势图" icon="cockpitItemIcon" size="costSmallBg" topSize="psiTopTitleBasic">
|
||||
<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);">
|
||||
<profitBar :cost-group-data="costGroupData" :cost-types="costTypes" :locations="locations" />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
import profitBar from './profitBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, profitBar },
|
||||
props: {
|
||||
trendViews: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 1. 地名数组(去重,按出现顺序)
|
||||
locations() {
|
||||
const locationSet = new Set();
|
||||
this.trendViews.forEach(item => {
|
||||
const location = this.extractLocation(item.name);
|
||||
if (location) locationSet.add(location);
|
||||
});
|
||||
return Array.from(locationSet);
|
||||
},
|
||||
// 2. 核心成本类型数组(仅保留“原片”“加工”等,去掉“成本”二字)
|
||||
costTypes() {
|
||||
const typeSet = new Set();
|
||||
this.trendViews.forEach(item => {
|
||||
const coreType = this.extractCoreCostType(item.name);
|
||||
if (coreType) typeSet.add(coreType);
|
||||
});
|
||||
return Array.from(typeSet);
|
||||
},
|
||||
// 3. 按「核心成本类型+地名」分组的数据
|
||||
costGroupData() {
|
||||
return this.trendViews.reduce((group, item) => {
|
||||
const coreType = this.extractCoreCostType(item.name) || '其他';
|
||||
const location = this.extractLocation(item.name) || '未知地区';
|
||||
|
||||
// 初始化层级分组
|
||||
if (!group[coreType]) group[coreType] = {};
|
||||
if (!group[coreType][location]) group[coreType][location] = [];
|
||||
|
||||
// 添加数据
|
||||
group[coreType][location].push({
|
||||
name: item.name,
|
||||
target: item.target ?? 0,
|
||||
value: item.value ?? 0,
|
||||
proportion: item.proportion ?? 0,
|
||||
flag: item.flag ?? 0,
|
||||
time: item.time
|
||||
});
|
||||
|
||||
return group;
|
||||
}, {});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 提取核心成本类型(仅保留“原片”“加工”等,去掉“成本”二字)
|
||||
extractCoreCostType(name) {
|
||||
// 匹配“前缀(含分公司/工厂/区域等)+ 核心词 + 任意后缀”,提取中间核心词
|
||||
// 兼容后缀:成本、费用、支出、损耗等,前缀:XX分公司、XX工厂、XX区域等
|
||||
const match = name.match(/(分公司)\s*([^,。;!?]+?)\s*(成本)/);
|
||||
if (match) {
|
||||
return match[2].trim(); // 提取中间核心词,去除前后空格
|
||||
}
|
||||
|
||||
// 通用匹配:无明确前缀标识时,提取“最后一个连续汉字后缀”前的内容
|
||||
const generalMatch = name.match(/(.+?)(?=[\u4e00-\u9fa5]{2,}$)/);
|
||||
return generalMatch ? generalMatch[1].trim() : name.trim();
|
||||
},
|
||||
// 提取地名(成本类型前的核心地区名)
|
||||
extractLocation(name) {
|
||||
// 匹配“分公司”前面的连续汉字(即地名)
|
||||
const match = name.match(/(.+?)分公司/);
|
||||
|
||||
if (match) {
|
||||
return match[1].trim() || '未知地区';
|
||||
}
|
||||
|
||||
// 兜底(处理不含“分公司”的情况)
|
||||
return '未知地区';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
trendViews: {
|
||||
handler(newValue) {
|
||||
console.log('核心成本类型:', this.costTypes); // 示例输出:["原片", "加工"]
|
||||
console.log('地名数组:', this.locations); // 示例输出:["桐城", "合肥"]
|
||||
console.log('分组数据:', this.costGroupData);
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log('初始核心成本类型:', this.costTypes);
|
||||
}
|
||||
};
|
||||
</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;
|
||||
}
|
||||
|
||||
/* 其他样式省略 */
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
325
src/views/home/costComponents/rawSheet-Item.vue
Normal file
325
src/views/home/costComponents/rawSheet-Item.vue
Normal file
@@ -0,0 +1,325 @@
|
||||
<template>
|
||||
<!-- 外层滚动容器:添加鼠标事件监听 -->
|
||||
<div class="coreItem-container" @mousedown="handleMouseDown" @mousemove="handleMouseMove" @mouseup="handleMouseUp"
|
||||
@mouseleave="handleMouseUp" :style="{ cursor: isDragging ? 'grabbing' : 'grab' }">
|
||||
<div class="coreItem">
|
||||
<div class="item" @click="handleRoute(item.route, item.name)" v-for="(item, index) in finalItemList" :key="index">
|
||||
<div class="item-header">
|
||||
<div class="unit">{{ item.name }}</div>
|
||||
</div>
|
||||
<div class="item-content">
|
||||
<div class="content-wrapper">
|
||||
<!-- 目标值 + 分割线 + 实际值 -->
|
||||
<div class="value-group">
|
||||
<div class="value-item">
|
||||
<div class="number">{{ item.targetValue || 0 }}</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
<div class="middle-line"></div>
|
||||
<div class="value-item">
|
||||
<!-- 实际值:根据 flag 动态绑定颜色 -->
|
||||
<div class="number" :style="{ color: getColorByFlag(item.flag) }">
|
||||
{{ item.value || 0 }}
|
||||
</div>
|
||||
<div class="title">当前值</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 进度条 + 完成率 -->
|
||||
<div class="progress-yield-group">
|
||||
<div class="progress-group">
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" :style="{
|
||||
width: getProportionPercent(item.proportion) + '%',
|
||||
background: getColorByFlag(item.flag, true)
|
||||
}"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="yield">
|
||||
<span class="progress-percent">完成率</span>
|
||||
<!-- 完成率:动态颜色 + 百分比格式 -->
|
||||
<span class="progress-percent progress-value" :style="{ color: getColorByFlag(item.flag) }">
|
||||
{{ getProportionPercent(item.proportion) }}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Container",
|
||||
components: {},
|
||||
props: ["name", "size", "icon", 'piecesCostViews'],
|
||||
data() {
|
||||
return {
|
||||
itemList: [
|
||||
{ name: "原料", targetValue: 0, value: 0, proportion: 0, flag: 1, route: 'rawMaterialCostAnalysis/rawMaterialCostAnalysis' },
|
||||
{ name: "人工", targetValue: 0, value: 0, proportion: 0, flag: 1 },
|
||||
{
|
||||
name: "燃料", targetValue: 0, value: 0, proportion: 0, flag: 1,
|
||||
route: 'fuelCostAnalysis/fuelCostAnalysis'
|
||||
},
|
||||
{
|
||||
name: "电", targetValue: 0, value: 0, proportion: 0, flag: 1,
|
||||
route: 'fuelPowerCostAnalysis/fuelPowerCostAnalysis'
|
||||
},
|
||||
{
|
||||
name: "制造费用", targetValue: 0, value: 0, proportion: 0, flag: 1,
|
||||
route: 'productionCostsAnalysis/productionCostsAnalysis'
|
||||
},
|
||||
],
|
||||
isDragging: false,
|
||||
startX: 0,
|
||||
scrollLeft: 0
|
||||
};
|
||||
},
|
||||
// computed: {
|
||||
// finalItemList() {
|
||||
// if (Array.isArray(this.piecesCostViews) && this.piecesCostViews.length > 0) {
|
||||
// return this.piecesCostViews.map((item) => ({
|
||||
// flag: item.flag || 0, // 默认 flag 为 0
|
||||
// proportion: item.proportion || 0, // 默认完成率为 0
|
||||
// targetValue: item.target ?? 0, // 兼容 null 值,显示为 0
|
||||
// value: item.value || 0,
|
||||
// name: item.name
|
||||
// }));
|
||||
// } else {
|
||||
// return this.itemList;
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
computed: {
|
||||
finalItemList() {
|
||||
return this.itemList.map((defaultItem, index) => {
|
||||
// 核心修复:1. 恢复模板名称处理;2. 统一大小写和空格处理
|
||||
const matchedItem = this.piecesCostViews.find(item => {
|
||||
const processedIncomingName = (item.name || '').replace(/成本/g, '').trim().toLowerCase();
|
||||
// 恢复模板名称处理:去除“成本”(如果有的话)、去空格、小写
|
||||
const processedDefaultName = defaultItem.name.replace(/成本/g, '').trim().toLowerCase();
|
||||
return processedIncomingName === processedDefaultName;
|
||||
});
|
||||
// 移除索引 fallback:只匹配名称,不按索引赋值,避免误匹配
|
||||
// 无匹配项则完全保留默认值
|
||||
const targetItem = matchedItem || defaultItem;
|
||||
|
||||
return {
|
||||
name: defaultItem.name, // 固定模板名称
|
||||
flag: targetItem.flag ?? defaultItem.flag,
|
||||
proportion: targetItem.proportion ?? defaultItem.proportion,
|
||||
targetValue: targetItem.target ?? defaultItem.targetValue,
|
||||
value: targetItem.value ?? defaultItem.value,
|
||||
route: defaultItem.route
|
||||
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleRoute(route, name) {
|
||||
if (route) {
|
||||
this.$router.push({
|
||||
path: route,
|
||||
query: {
|
||||
name: name + '成本'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
// 根据 flag 获取文字/进度条颜色
|
||||
getColorByFlag(flag, isProgressBar = false) {
|
||||
const colorMap = {
|
||||
1: {
|
||||
text: 'rgba(103, 103, 103, 0.79)',
|
||||
progress: 'rgba(103, 103, 103, 0.5)' // 进度条颜色稍浅
|
||||
},
|
||||
0: {
|
||||
text: 'rgba(255, 132, 0, 1)',
|
||||
progress: 'rgba(255, 132, 0, 0.7)' // 进度条颜色稍浅
|
||||
}
|
||||
};
|
||||
const currentFlag = flag === 1 ? 1 : 0;
|
||||
return isProgressBar ? colorMap[currentFlag].progress : colorMap[currentFlag].text;
|
||||
},
|
||||
// 处理 proportion:乘以 100 并保留 0 位小数
|
||||
getProportionPercent(proportion) {
|
||||
const num = Number(proportion) || 0;
|
||||
return num; // 四舍五入取整,如需保留小数可改为 toFixed(2)
|
||||
},
|
||||
// 拖拽相关方法
|
||||
handleMouseDown(e) {
|
||||
this.isDragging = true;
|
||||
this.startX = e.pageX - this.$el.offsetLeft;
|
||||
this.scrollLeft = this.$el.scrollLeft;
|
||||
this.$el.style.userSelect = "none";
|
||||
},
|
||||
handleMouseMove(e) {
|
||||
if (!this.isDragging) return;
|
||||
e.preventDefault();
|
||||
const x = e.pageX - this.$el.offsetLeft;
|
||||
const walk = (x - this.startX) * 1.2;
|
||||
this.$el.scrollLeft = this.scrollLeft - walk;
|
||||
},
|
||||
handleMouseUp() {
|
||||
this.isDragging = false;
|
||||
this.$el.style.userSelect = "";
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coreItem-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
cursor: grab;
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
|
||||
.coreItem {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 0 8px;
|
||||
width: fit-content;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 202px;
|
||||
height: 180px;
|
||||
background: #f9fcff;
|
||||
padding: 10px 20px 0 27px;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.3s ease;
|
||||
&:not(:nth-child(2)):hover {
|
||||
box-shadow: 0px 4px 12px 2px #B5CDE5;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.item-header {
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
|
||||
.unit {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
color: #333333;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100% - 30px);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.value-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.value-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.middle-line {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, rgba(40, 203, 151, 0.3), rgba(40, 203, 151, 0.8), rgba(40, 203, 151, 0.3));
|
||||
}
|
||||
|
||||
.number {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
/* 颜色由动态样式控制,移除固定颜色 */
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.progress-yield-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.progress-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: #F5F7FA;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
transition: width 0.5s ease;
|
||||
/* 背景色由动态样式控制,移除固定颜色 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.progress-percent {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.progress-value {
|
||||
font-weight: 500;
|
||||
/* 颜色由动态样式控制,移除固定颜色 */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
229
src/views/home/costComponents/single-container.vue
Normal file
229
src/views/home/costComponents/single-container.vue
Normal file
@@ -0,0 +1,229 @@
|
||||
<template>
|
||||
<div class="cockpitContainer" :class="['cockpitContainer__' + size]">
|
||||
<div class="content-top" :class="['content-top__' + topSize]">
|
||||
<div class="title-wrapper">
|
||||
<svg-icon class="title-icon" :icon-class="icon" />
|
||||
<span class="title-text">
|
||||
{{ name }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- 按钮单独放在右侧容器 -->
|
||||
<div class="button-wrapper">
|
||||
<el-button @click="handleRoute" type="text">利润影响额</el-button>
|
||||
</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', 'topSize'],
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
handleRoute() {
|
||||
this.$router.push({
|
||||
name:'profitImpactAnalysis'
|
||||
})
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.cockpitContainer {
|
||||
display: inline-block;
|
||||
padding: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
.content-top {
|
||||
height: 60px;
|
||||
display: flex;
|
||||
/* 开启flex布局 */
|
||||
justify-content: space-between;
|
||||
/* 标题居左,按钮居右 */
|
||||
align-items: center;
|
||||
/* 垂直居中 */
|
||||
padding: 0 10px;
|
||||
/* 左右内边距,避免贴边 */
|
||||
|
||||
.title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
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;
|
||||
}
|
||||
|
||||
// 按钮容器样式
|
||||
.button-wrapper {
|
||||
margin-left: auto;
|
||||
margin-top: 10px;
|
||||
/* 强制靠右(兼容flex布局) */
|
||||
}
|
||||
|
||||
&__basic {
|
||||
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;
|
||||
}
|
||||
|
||||
&__costMiddleBg {
|
||||
background: url(../../../assets/img/costMiddleBg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__costSmallBg {
|
||||
background: url(../../../assets/img/costSmallBg.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;
|
||||
}
|
||||
|
||||
&__productBasicBg {
|
||||
background: url(../../../assets/img/productBasicBg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__productMiddleBg {
|
||||
background: url(../../../assets/img/productMiddleBg.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;
|
||||
backdrop-filter: blur(5px);
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
.container-body {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
353
src/views/home/costComponents/single/bottomLeftChart.vue
Normal file
353
src/views/home/costComponents/single/bottomLeftChart.vue
Normal file
@@ -0,0 +1,353 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container :name="name" icon="cockpitItemIcon" size="operatingBasic" topSize="middle">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex;flex-direction: column; width: 100%;">
|
||||
<!-- 2. .top 保持 flex,无需固定高度,自动跟随子元素拉伸 -->
|
||||
<div style="display: flex; width: 100%; background-color: rgba(249, 252, 255, 1);">
|
||||
<costBaseBarChart />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from '../container.vue'
|
||||
// import * as echarts from 'echarts'
|
||||
import costBaseBarChart from './costBaseBarChart.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, costBaseBarChart },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
leftEqInfoData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
name: { // 接收父组件传递的设备数据数组
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
},
|
||||
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
parentItemList: [
|
||||
{ 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: 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;
|
||||
}
|
||||
</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>
|
||||
353
src/views/home/costComponents/single/bottomRightChart.vue
Normal file
353
src/views/home/costComponents/single/bottomRightChart.vue
Normal file
@@ -0,0 +1,353 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container :name="name" icon="cockpitItemIcon" size="operatingBasic" topSize="middle">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex;flex-direction: column; width: 100%;">
|
||||
<!-- 2. .top 保持 flex,无需固定高度,自动跟随子元素拉伸 -->
|
||||
<div style="display: flex; width: 100%; background-color: rgba(249, 252, 255, 1);">
|
||||
<costBaseBarChart />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from '../container.vue'
|
||||
// import * as echarts from 'echarts'
|
||||
import costBaseBarChart from './costBaseBarChart.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, costBaseBarChart },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
leftEqInfoData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
name: { // 接收父组件传递的设备数据数组
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
},
|
||||
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
parentItemList: [
|
||||
{ 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: 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;
|
||||
}
|
||||
</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>
|
||||
236
src/views/home/costComponents/single/costBaseBarChart.vue
Normal file
236
src/views/home/costComponents/single/costBaseBarChart.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 345px;"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initData();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
const myChart = echarts.init(chartDom);
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
},
|
||||
// 优化tooltip内容,区分各系列含义
|
||||
formatter: (params) => {
|
||||
let html = `${params[0].axisValue}<br/>`;
|
||||
params.forEach(item => {
|
||||
// 直接使用系列名,无需映射,仅保留单位判断
|
||||
html += `${item.marker} ${item.seriesName}: ${item.value}${item.seriesName === '完成率' ? '%' : '片'}<br/>`;
|
||||
});
|
||||
return html;
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 50,
|
||||
bottom: 30, // 增大底部间距,避免柱子与X轴标签重叠
|
||||
right: 70,
|
||||
left: 50,
|
||||
},
|
||||
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: ['漳州', '桐城', '合肥', '宜兴', '自贡', '洛阳']
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
// 左侧Y轴:目标/达标/未达标(数量,单位“片”)
|
||||
{
|
||||
type: 'value',
|
||||
// name: '元/吨',
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'right'
|
||||
},
|
||||
min: 0, // 最小值设0,确保柱子从X轴底部开始,不超过X轴
|
||||
max: (value) => Math.ceil(value.max * 1.1), // 最大值留10%余量,避免柱子顶满
|
||||
scale: false, // 关闭缩放,强制从0开始
|
||||
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',
|
||||
// // name: '%',
|
||||
// nameTextStyle: {
|
||||
// color: 'rgba(0, 0, 0, 0.45)',
|
||||
// fontSize: 12,
|
||||
// align: 'left'
|
||||
// },
|
||||
// min: 0,
|
||||
// max: 100, // 完成率最大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, // 绑定右侧Y轴
|
||||
// lineStyle: {
|
||||
// color: 'rgba(40, 138, 255, .5)',
|
||||
// width: 2 // 线条加粗,突出重点
|
||||
// },
|
||||
// itemStyle: {
|
||||
// color: 'rgba(40, 138, 255, 1)',
|
||||
// borderColor: 'rgba(40, 138, 255, 1)', // 数据点白色边框,增强辨识度
|
||||
// borderWidth: 2,
|
||||
// radius: 4 // 数据点圆角,更圆润
|
||||
// },
|
||||
// areaStyle: {
|
||||
// opacity: 0.2, // 降低面积透明度,不抢柱状图视觉焦点
|
||||
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
// { offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||
// { offset: 1, color: 'rgba(255, 132, 0, 0)' }
|
||||
// ])
|
||||
// },
|
||||
// data: [65, 78, 52, 85, 60, 95, 72], // 完成率数据(0-100)
|
||||
// symbol: 'circle', // 数据点为圆形
|
||||
// symbolSize: 6 // 数据点大小
|
||||
// },
|
||||
// 2. 目标:柱状图(绑定左侧数量Y轴)
|
||||
{
|
||||
name: '销量',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 24,
|
||||
// 关键修复:label 直接放在 series 下,而非 itemStyle 内
|
||||
itemStyle: {
|
||||
// 移除多余的 normal 层级,直接配置 color 渐变
|
||||
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: [200, 280, 180, 300, 220, 350]
|
||||
},
|
||||
// 3. 达标:柱状图(绑定左侧数量Y轴)
|
||||
// {
|
||||
// name: '产量',
|
||||
// type: 'bar',
|
||||
// yAxisIndex: 0,
|
||||
// barWidth: 24,
|
||||
// // 关键修复:label 直接放在 series 下,而非 itemStyle 内
|
||||
// itemStyle: {
|
||||
// // 移除多余的 normal 层级,直接配置 color 渐变
|
||||
// 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: [130, 220, 95, 255, 132, 332] // 达标数据(小于目标)
|
||||
// },
|
||||
// 4. 未达标:柱状图(绑定左侧数量Y轴)
|
||||
// {
|
||||
// name: '未达标',
|
||||
// type: 'bar',
|
||||
// yAxisIndex: 0,
|
||||
// barWidth: 18,
|
||||
// itemStyle: {
|
||||
// color: 'rgba(249, 164, 74, 1)',
|
||||
// borderRadius: [4, 4, 0, 0],
|
||||
// borderWidth: 0
|
||||
// },
|
||||
// data: [70, 60, 85, 45, 88, 18, 78] // 未达标数据(目标-达标)
|
||||
// }
|
||||
],
|
||||
// 图例:区分各系列,点击可控制显示隐藏
|
||||
// legend: {
|
||||
// top: 0,
|
||||
// left: 'center',
|
||||
// itemWidth: 12,
|
||||
// itemHeight: 8,
|
||||
// textStyle: {
|
||||
// color: 'rgba(0, 0, 0, 0.45)',
|
||||
// fontSize: 12
|
||||
// },
|
||||
// data: ['完成率', '目标', '达标', '未达标']
|
||||
// }
|
||||
};
|
||||
|
||||
option && myChart.setOption(option);
|
||||
|
||||
// 窗口缩放适配
|
||||
window.addEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
|
||||
// 组件销毁清理
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
myChart.dispose();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
496
src/views/home/costComponents/single/noRouterHeader.vue
Normal file
496
src/views/home/costComponents/single/noRouterHeader.vue
Normal file
@@ -0,0 +1,496 @@
|
||||
<template>
|
||||
<header class="report-header" :class="['report-header__' + size]">
|
||||
<!-- 左侧区域:logo + 标题 -->
|
||||
<div class="left-content">
|
||||
<!-- <img style="height: 36px;" src="../../../assets/img/cnbm.png" alt="benmaLogo" class="logo"> -->
|
||||
<div class="top-title">{{ topTitle }}</div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="center-content">
|
||||
<div class="item" v-for="(text, index) in ['营业收入', '利润分析', '产销率库存分析', '成本分析', '驾驶舱报表']" :key="index"
|
||||
@click="activeIndex = index" :class="{ 'no-skew': activeIndex === index }">
|
||||
<span class="item-text">{{ text }}</span>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- 右侧区域:全屏按钮 -->
|
||||
<div class="right-content">
|
||||
<el-button type="text" class="home-btn" :title="'主页'" @click="handleRouteHome">
|
||||
<svg-icon style="color: #0B58FF;" icon-class="homeIcon" />
|
||||
</el-button>
|
||||
<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>
|
||||
|
||||
<!-- 时间选择区域:日/月/年按钮 + label + 日期选择器 -->
|
||||
<div class="timeType">
|
||||
<!-- 日/月/年按钮:点击更新activeTime,关联选择器类型 -->
|
||||
<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> <!-- 补充span抵消倾斜 -->
|
||||
</div>
|
||||
<!-- 日期选择器:type和placeholder通过activeTime动态切换 -->
|
||||
<el-date-picker v-model="date" :type="getPickerType" :placeholder="getPickerPlaceholder"
|
||||
class="custom-date-picker" @change="emitTimeRange" :clearable="false"
|
||||
style="width: 132px;height: 29px;"></el-date-picker>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from "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,
|
||||
// activeIndex: -1,
|
||||
activeTime: 1, // 0=日,1=月,2=年(默认选中“日”)
|
||||
// 定义时间类型配置:text=按钮文字,pickerType=选择器类型,placeholder=占位符
|
||||
timeTypes: [
|
||||
{ text: '日', pickerType: 'date', placeholder: '选择日期' },
|
||||
{ text: '月', pickerType: 'month', placeholder: '选择月份' },
|
||||
{ text: '年', pickerType: 'year', placeholder: '选择年份' }
|
||||
]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 维度切换时:清空选择的日期,并传递当前维度的默认区间
|
||||
activeTime(newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
this.date = undefined;
|
||||
// this.emitTimeRange();
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 动态获取日期选择器类型
|
||||
getPickerType() {
|
||||
return this.timeTypes[this.activeTime].pickerType;
|
||||
},
|
||||
// 动态获取日期选择器占位符
|
||||
getPickerPlaceholder() {
|
||||
return this.timeTypes[this.activeTime].placeholder;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleRouteHome() {
|
||||
this.$router.push({
|
||||
path:'/'
|
||||
})
|
||||
},
|
||||
handleReturn() {
|
||||
this.$router.go(-1);
|
||||
},
|
||||
changeFullScreen() { this.$emit('screenfullChange') },
|
||||
exportPDF() { this.$emit('exportPDF') },
|
||||
padZero(num) { return num < 10 ? '0' + num : num },
|
||||
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);
|
||||
// 调试用:查看首次传递的区间(如{start: "2025-10-01T00:00:00", end: "2025-11-01T00:00:00", dimension: "月"})
|
||||
console.log('当前时间区间:', timeRange);
|
||||
}
|
||||
},
|
||||
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-between;
|
||||
|
||||
&__basic {
|
||||
// width: 547px;
|
||||
background: url(../../../../assets/img/topBg.png) no-repeat;
|
||||
background-size: cover;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__psi {
|
||||
// width: 547px;
|
||||
background: url(../../../../assets/img/psiTopTitle.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
/* 确保timeType绝对定位生效 */
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.center-content {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 18px;
|
||||
margin-left: 30px;
|
||||
|
||||
.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: 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;
|
||||
/* 统一倾斜角度,修复原30deg的错位 */
|
||||
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);
|
||||
/* 与按钮倾斜角度统一(原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: 20px;
|
||||
gap: 21px;
|
||||
}
|
||||
.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;
|
||||
// }
|
||||
|
||||
// ::v-deep .custom-date-picker .el-input--medium .el-input__inner,
|
||||
// ::v-deep .custom-date-picker .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: #fff !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);
|
||||
// }
|
||||
|
||||
// ::v-deep .custom-date-picker .el-input__prefix {
|
||||
// left: auto !important;
|
||||
// right: 8px !important;
|
||||
// top: 40% !important;
|
||||
// transform: translateY(-50%) !important;
|
||||
// }
|
||||
|
||||
// // ::v-deep .custom-date-picker .el-input__prefix .el-icon {
|
||||
// // color: #fff !important;
|
||||
// // font-size: 16px !important;
|
||||
// // }
|
||||
// ::v-deep .el-icon-date::before {
|
||||
// color: #ffffff !important;
|
||||
// // margin-bottom: 20px;
|
||||
// font-size: 16px !important;
|
||||
// }
|
||||
|
||||
// // 额外补充:如果图标有父级容器样式干扰,同步处理父容器
|
||||
// ::v-deep .custom-date-picker .el-input__icon {
|
||||
// color: #ffffff !important; // 清除父容器可能的颜色继承
|
||||
// }
|
||||
// ::v-deep .custom-date-picker .el-input__inner:hover,
|
||||
// ::v-deep .custom-date-picker .el-input__inner:focus {
|
||||
// background-color: rgba(11, 88, 255, 0.9) !important;
|
||||
// clip-path: polygon(18px 0, 100% 0, 100% 100%, 0 100%) !important;
|
||||
// }
|
||||
::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>
|
||||
505
src/views/home/costComponents/single/noRouterHeaderPa.vue
Normal file
505
src/views/home/costComponents/single/noRouterHeaderPa.vue
Normal file
@@ -0,0 +1,505 @@
|
||||
<template>
|
||||
<header class="report-header" :class="['report-header__' + size]">
|
||||
<!-- 左侧区域:logo + 标题 -->
|
||||
<div class="left-content">
|
||||
<!-- <img style="height: 36px;" src="../../../assets/img/cnbm.png" alt="benmaLogo" class="logo"> -->
|
||||
<div class="top-title">{{ topTitle }}</div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="center-content">
|
||||
<div class="item" v-for="(text, index) in ['营业收入', '利润分析', '产销率库存分析', '成本分析', '驾驶舱报表']" :key="index"
|
||||
@click="activeIndex = index" :class="{ 'no-skew': activeIndex === index }">
|
||||
<span class="item-text">{{ text }}</span>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- 右侧区域:全屏按钮 -->
|
||||
<div class="right-content">
|
||||
<!-- <el-button type="text" class="home-btn" :title="'主页'" @click="handleRouteHome">
|
||||
<svg-icon style="color: #0B58FF;" icon-class="homeIcon" />
|
||||
</el-button>
|
||||
<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>
|
||||
|
||||
<!-- 时间选择区域:日/月/年按钮 + label + 日期选择器 -->
|
||||
<div class="timeType">
|
||||
<!-- 日/月/年按钮:点击更新activeTime,关联选择器类型 -->
|
||||
<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> <!-- 补充span抵消倾斜 -->
|
||||
</div>
|
||||
<!-- 日期选择器:type和placeholder通过activeTime动态切换 -->
|
||||
<el-date-picker v-model="date" :type="getPickerType" :placeholder="getPickerPlaceholder"
|
||||
class="custom-date-picker" @change="emitTimeRange" :clearable="false"
|
||||
style="width: 132px;height: 29px;"></el-date-picker>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from "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,
|
||||
activeIndex: -1,
|
||||
activeTime: 0, // 0=日,1=月,2=年(默认选中“日”)
|
||||
// 定义时间类型配置:text=按钮文字,pickerType=选择器类型,placeholder=占位符
|
||||
timeTypes: [
|
||||
{ text: '日', pickerType: 'date', placeholder: '选择日期' },
|
||||
{ text: '月', pickerType: 'month', placeholder: '选择月份' },
|
||||
{ text: '年', pickerType: 'year', placeholder: '选择年份' }
|
||||
]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 维度切换时:清空选择的日期,并传递当前维度的默认区间
|
||||
activeTime(newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
this.date = undefined;
|
||||
this.emitTimeRange();
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 动态获取日期选择器类型
|
||||
getPickerType() {
|
||||
return this.timeTypes[this.activeTime].pickerType;
|
||||
},
|
||||
// 动态获取日期选择器占位符
|
||||
getPickerPlaceholder() {
|
||||
return this.timeTypes[this.activeTime].placeholder;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleRouteHome() {
|
||||
this.$router.push({
|
||||
path: '/'
|
||||
})
|
||||
},
|
||||
handleReturn() {
|
||||
this.$router.go(-1);
|
||||
},
|
||||
changeFullScreen() { this.$emit('screenfullChange') },
|
||||
exportPDF() { this.$emit('exportPDF') },
|
||||
padZero(num) { return num < 10 ? '0' + num : num },
|
||||
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);
|
||||
// 调试用:查看首次传递的区间(如{start: "2025-10-01T00:00:00", end: "2025-11-01T00:00:00", dimension: "月"})
|
||||
console.log('当前时间区间:', timeRange);
|
||||
}
|
||||
},
|
||||
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-between;
|
||||
|
||||
&__basic {
|
||||
// width: 547px;
|
||||
background: url(../../../../assets/img/topBg.png) no-repeat;
|
||||
background-size: cover;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__psi {
|
||||
// width: 547px;
|
||||
background: url(../../../../assets/img/psiTopTitle.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
/* 确保timeType绝对定位生效 */
|
||||
|
||||
.left-content {
|
||||
margin-top: 11px;
|
||||
margin-left: 300px;
|
||||
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: 30px;
|
||||
|
||||
.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: 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;
|
||||
/* 统一倾斜角度,修复原30deg的错位 */
|
||||
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);
|
||||
/* 与按钮倾斜角度统一(原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: 20px;
|
||||
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;
|
||||
// }
|
||||
|
||||
// ::v-deep .custom-date-picker .el-input--medium .el-input__inner,
|
||||
// ::v-deep .custom-date-picker .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: #fff !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);
|
||||
// }
|
||||
|
||||
// ::v-deep .custom-date-picker .el-input__prefix {
|
||||
// left: auto !important;
|
||||
// right: 8px !important;
|
||||
// top: 40% !important;
|
||||
// transform: translateY(-50%) !important;
|
||||
// }
|
||||
|
||||
// // ::v-deep .custom-date-picker .el-input__prefix .el-icon {
|
||||
// // color: #fff !important;
|
||||
// // font-size: 16px !important;
|
||||
// // }
|
||||
// ::v-deep .el-icon-date::before {
|
||||
// color: #ffffff !important;
|
||||
// // margin-bottom: 20px;
|
||||
// font-size: 16px !important;
|
||||
// }
|
||||
|
||||
// // 额外补充:如果图标有父级容器样式干扰,同步处理父容器
|
||||
// ::v-deep .custom-date-picker .el-input__icon {
|
||||
// color: #ffffff !important; // 清除父容器可能的颜色继承
|
||||
// }
|
||||
// ::v-deep .custom-date-picker .el-input__inner:hover,
|
||||
// ::v-deep .custom-date-picker .el-input__inner:focus {
|
||||
// background-color: rgba(11, 88, 255, 0.9) !important;
|
||||
// clip-path: polygon(18px 0, 100% 0, 100% 100%, 0 100%) !important;
|
||||
// }
|
||||
::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>
|
||||
353
src/views/home/costComponents/single/topLeftChart.vue
Normal file
353
src/views/home/costComponents/single/topLeftChart.vue
Normal file
@@ -0,0 +1,353 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container :name="name" icon="cockpitItemIcon" size="operatingBasic" topSize="middle">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex;flex-direction: column; width: 100%;">
|
||||
<!-- 2. .top 保持 flex,无需固定高度,自动跟随子元素拉伸 -->
|
||||
<div style="display: flex; width: 100%; background-color: rgba(249, 252, 255, 1);">
|
||||
<costBaseBarChart />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from '../single-container.vue'
|
||||
// import * as echarts from 'echarts'
|
||||
import costBaseBarChart from './costBaseBarChart.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, costBaseBarChart },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
leftEqInfoData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
name: { // 接收父组件传递的设备数据数组
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
},
|
||||
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
parentItemList: [
|
||||
{ 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: 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;
|
||||
}
|
||||
</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>
|
||||
353
src/views/home/costComponents/single/topRightChart.vue
Normal file
353
src/views/home/costComponents/single/topRightChart.vue
Normal file
@@ -0,0 +1,353 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container :name="name" icon="cockpitItemIcon" size="operatingBasic" topSize="middle">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex;flex-direction: column; width: 100%;">
|
||||
<!-- 2. .top 保持 flex,无需固定高度,自动跟随子元素拉伸 -->
|
||||
<div style="display: flex; width: 100%; background-color: rgba(249, 252, 255, 1);">
|
||||
<costBaseBarChart />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from '../container.vue'
|
||||
// import * as echarts from 'echarts'
|
||||
import costBaseBarChart from './costBaseBarChart.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, costBaseBarChart },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
leftEqInfoData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
name: { // 接收父组件传递的设备数据数组
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
},
|
||||
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
parentItemList: [
|
||||
{ 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: 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;
|
||||
}
|
||||
</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>
|
||||
147
src/views/home/costComponents/singleTopSelect.vue
Normal file
147
src/views/home/costComponents/singleTopSelect.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<div style="">
|
||||
<div class="topSelect">
|
||||
<div class="baseSelect">
|
||||
<div class="label">
|
||||
基地
|
||||
</div>
|
||||
<el-select class="custom-select" style="margin-left: 10px;" v-model="levelId" placeholder="">
|
||||
<el-option v-for="item in baseList" :key="item.value" :label="item.name" :value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="baseSelect" style="margin-left: 24px;">
|
||||
<div class="label">
|
||||
{{ typeName }}
|
||||
</div>
|
||||
<el-select class="custom-select" style="margin-left: 10px;" v-model="type" placeholder="">
|
||||
<el-option v-for="item in TypeList" :key="item.value" :label="item.name" :value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<el-button type="primary" style="margin-left: 24px;">查询</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: {},
|
||||
props: ['typeName','typeList'],
|
||||
data() {
|
||||
return {
|
||||
levelId: '',
|
||||
baseList: [
|
||||
{
|
||||
value: 2,
|
||||
name: "宜兴",
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
name: "漳州",
|
||||
},
|
||||
{
|
||||
value: 4,
|
||||
name: "自贡",
|
||||
|
||||
},
|
||||
{
|
||||
value: 5,
|
||||
name: "桐城",
|
||||
},
|
||||
{
|
||||
value: 6,
|
||||
name: "洛阳",
|
||||
},
|
||||
{
|
||||
value: 7,
|
||||
name: "合肥",
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateChart() { }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.topSelect {
|
||||
width: 1620px;
|
||||
height: 60px;
|
||||
background: url('../../../assets/img/topSelectBg.png') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.baseSelect {
|
||||
margin-left: 54px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.label {
|
||||
height: 32px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 24px;
|
||||
color: #000000;
|
||||
line-height: 32px;
|
||||
letter-spacing: 5px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .custom-select {
|
||||
width: 200px;
|
||||
|
||||
.el-input__inner {
|
||||
border: 1px solid rgba(94, 142, 255, 1) !important;
|
||||
border-radius: 4px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.el-input__inner:focus {
|
||||
border-color: rgba(94, 142, 255, 1) !important;
|
||||
box-shadow: 0 0 0 2px rgba(94, 142, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
.el-select__caret {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
// 默认状态:三角形朝右
|
||||
.el-input__suffix {
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-20%);
|
||||
/* 修正垂直居中 */
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 6px solid transparent;
|
||||
border-bottom: 6px solid transparent;
|
||||
border-left: 6px solid rgba(94, 142, 255, 1);
|
||||
transition: all 0.3s ease;
|
||||
/* 平滑过渡 */
|
||||
}
|
||||
}
|
||||
|
||||
// 展开状态:三角形朝下(核心修改)
|
||||
&.el-select-open .el-input__suffix::after {
|
||||
border-left: 6px solid transparent !important;
|
||||
border-right: 6px solid transparent !important;
|
||||
border-top: 6px solid rgba(94, 142, 255, 1) !important;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-select-dropdown {
|
||||
border: 1px solid rgba(94, 142, 255, 1) !important;
|
||||
}
|
||||
</style>
|
||||
189
src/views/home/costComponents/top-item.vue
Normal file
189
src/views/home/costComponents/top-item.vue
Normal file
@@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<div class="coreItem">
|
||||
<!-- 用 v-for 动态生成每个 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">{{ 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 + '%' }"></div>
|
||||
</div>
|
||||
<div class="progress-percent">{{ 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: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coreItem {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 252px;
|
||||
height: 110px;
|
||||
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: 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: #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>
|
||||
301
src/views/home/costComponents/top-product-item.vue
Normal file
301
src/views/home/costComponents/top-product-item.vue
Normal file
@@ -0,0 +1,301 @@
|
||||
<template>
|
||||
<div class="coreItem">
|
||||
<div class="item item1">
|
||||
<div class="unit">总成本·元/㎡</div>
|
||||
<div class="item-content">
|
||||
<!-- 左右内容容器:包裹 left、line、right -->
|
||||
<div class="content-wrapper">
|
||||
<div class="left">
|
||||
<div class="number">16</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="right">
|
||||
<div class="number">16</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 进度条和百分比容器:横向排列 -->
|
||||
<div class="progress-group">
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" :style="{
|
||||
width: (90) + '%',
|
||||
}"></div>
|
||||
</div>
|
||||
<!-- 百分比文本 -->
|
||||
<div class="progress-percent">90%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item item2">
|
||||
<div class="unit">原片成本·元/㎡</div>
|
||||
<div class="item-content">
|
||||
<!-- 左右内容容器:包裹 left、line、right -->
|
||||
<div class="content-wrapper">
|
||||
<div class="left">
|
||||
<div class="number">16</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="right">
|
||||
<div class="number">16</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 进度条和百分比容器:横向排列 -->
|
||||
<div class="progress-group">
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" :style="{
|
||||
width: (90) + '%',
|
||||
}"></div>
|
||||
</div>
|
||||
<!-- 百分比文本 -->
|
||||
<div class="progress-percent">90%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item item3">
|
||||
<div class="unit">加工成本·元/㎡</div>
|
||||
<div class="item-content">
|
||||
<!-- 左右内容容器:包裹 left、line、right -->
|
||||
<div class="content-wrapper">
|
||||
<div class="left">
|
||||
<div class="number">16</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="right">
|
||||
<div class="number">16</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 进度条和百分比容器:横向排列 -->
|
||||
<div class="progress-group">
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" :style="{
|
||||
width: (90) + '%',
|
||||
}"></div>
|
||||
</div>
|
||||
<!-- 百分比文本 -->
|
||||
<div class="progress-percent">90%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item item4">
|
||||
<div class="unit">原片成品率·%</div>
|
||||
<div class="item-content">
|
||||
<!-- 左右内容容器:包裹 left、line、right -->
|
||||
<div class="content-wrapper">
|
||||
<div class="left">
|
||||
<div class="number">16</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="right">
|
||||
<div class="number">16</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 进度条和百分比容器:横向排列 -->
|
||||
<div class="progress-group">
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" :style="{
|
||||
width: (90) + '%',
|
||||
}"></div>
|
||||
</div>
|
||||
<!-- 百分比文本 -->
|
||||
<div class="progress-percent">90%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item item5">
|
||||
<div class="unit">投入产出率·%</div>
|
||||
<div class="item-content">
|
||||
<!-- 左右内容容器:包裹 left、line、right -->
|
||||
<div class="content-wrapper">
|
||||
<div class="left">
|
||||
<div class="number">16</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="right">
|
||||
<div class="number">16</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 进度条和百分比容器:横向排列 -->
|
||||
<div class="progress-group">
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" :style="{
|
||||
width: (90) + '%',
|
||||
}"></div>
|
||||
</div>
|
||||
<!-- 百分比文本 -->
|
||||
<div class="progress-percent">90%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Container",
|
||||
components: {},
|
||||
props: ["name", "size", "icon"],
|
||||
data() {
|
||||
return {
|
||||
progress: 90, // 进度值,方便统一控制
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coreItem {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
/* 允许元素换行 */
|
||||
// justify-content: center;
|
||||
/* 整体水平居中 */
|
||||
gap: 8px;
|
||||
/* 元素之间的间距(水平+垂直) */
|
||||
// width: 100%;
|
||||
box-sizing: border-box;
|
||||
// padding: 8px;
|
||||
}
|
||||
|
||||
/* 需给子元素指定对应的区域名 */
|
||||
.coreItem> :nth-child(1) {
|
||||
grid-area: item1;
|
||||
}
|
||||
|
||||
.coreItem> :nth-child(2) {
|
||||
grid-area: item2;
|
||||
}
|
||||
|
||||
.coreItem> :nth-child(3) {
|
||||
grid-area: item3;
|
||||
}
|
||||
|
||||
.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;
|
||||
|
||||
.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: 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: #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;
|
||||
}
|
||||
}
|
||||
.item1,.item2,.item3{
|
||||
width: 166px;
|
||||
}
|
||||
</style>
|
||||
236
src/views/home/costComponents/totalProfit.vue
Normal file
236
src/views/home/costComponents/totalProfit.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="总利润指标·元" icon="cockpitItemIcon" size="rawTopBg" topSize="rawTopTitleLarge">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||
<div class=""
|
||||
style="display: flex;width: 100%; background-color: rgba(249, 252, 255, 1);">
|
||||
<!-- <topItem :itemList="parentItemList" /> -->
|
||||
<totalProfitBar />
|
||||
|
||||
</div>
|
||||
<!-- 2. .top 保持 flex,无需固定高度,自动跟随子元素拉伸 -->
|
||||
<!-- <div class="top" style="display: flex; width: 100%;">
|
||||
<top-item :itemList="parentItemList" />
|
||||
</div> -->
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
// import * as echarts from 'echarts'
|
||||
import totalProfitBar from './totalProfitBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container,totalProfitBar },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
leftEqInfoData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
productionOverviewVo: { // 恢复生产概览数据(原代码注释了,需根据实际需求保留)
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
parentItemList: [
|
||||
{ unit: "销量", targetValue: 16, currentValue: 14.5, progress: 90 },
|
||||
{ unit: "产量", targetValue: 16, currentValue: 15.2, progress: 85 },
|
||||
]
|
||||
}
|
||||
},
|
||||
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;
|
||||
}
|
||||
|
||||
.leftTitle {
|
||||
.item {
|
||||
width: 67px;
|
||||
height: 180px;
|
||||
padding: 37px 23px;
|
||||
background: #F9FCFF;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
color: #000000;
|
||||
line-height: 25px;
|
||||
letter-spacing: 1px;
|
||||
// text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
236
src/views/home/costComponents/totalProfitBar.vue
Normal file
236
src/views/home/costComponents/totalProfitBar.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 280px;"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initData();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
const myChart = echarts.init(chartDom);
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
},
|
||||
// 优化tooltip内容,区分各系列含义
|
||||
formatter: (params) => {
|
||||
let html = `${params[0].axisValue}<br/>`;
|
||||
params.forEach(item => {
|
||||
// 直接使用系列名,无需映射,仅保留单位判断
|
||||
html += `${item.marker} ${item.seriesName}: ${item.value}${item.seriesName === '完成率' ? '%' : '片'}<br/>`;
|
||||
});
|
||||
return html;
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 50,
|
||||
bottom: 30, // 增大底部间距,避免柱子与X轴标签重叠
|
||||
right: 70,
|
||||
left: 50,
|
||||
},
|
||||
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: ['漳州', '桐城', '合肥', '宜兴', '自贡', '洛阳']
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
// 左侧Y轴:目标/达标/未达标(数量,单位“片”)
|
||||
{
|
||||
type: 'value',
|
||||
name: '元',
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'right'
|
||||
},
|
||||
min: 0, // 最小值设0,确保柱子从X轴底部开始,不超过X轴
|
||||
max: (value) => Math.ceil(value.max * 1.1), // 最大值留10%余量,避免柱子顶满
|
||||
scale: false, // 关闭缩放,强制从0开始
|
||||
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',
|
||||
// // name: '%',
|
||||
// nameTextStyle: {
|
||||
// color: 'rgba(0, 0, 0, 0.45)',
|
||||
// fontSize: 12,
|
||||
// align: 'left'
|
||||
// },
|
||||
// min: 0,
|
||||
// max: 100, // 完成率最大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, // 绑定右侧Y轴
|
||||
// lineStyle: {
|
||||
// color: 'rgba(40, 138, 255, .5)',
|
||||
// width: 2 // 线条加粗,突出重点
|
||||
// },
|
||||
// itemStyle: {
|
||||
// color: 'rgba(40, 138, 255, 1)',
|
||||
// borderColor: 'rgba(40, 138, 255, 1)', // 数据点白色边框,增强辨识度
|
||||
// borderWidth: 2,
|
||||
// radius: 4 // 数据点圆角,更圆润
|
||||
// },
|
||||
// areaStyle: {
|
||||
// opacity: 0.2, // 降低面积透明度,不抢柱状图视觉焦点
|
||||
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
// { offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||
// { offset: 1, color: 'rgba(255, 132, 0, 0)' }
|
||||
// ])
|
||||
// },
|
||||
// data: [65, 78, 52, 85, 60, 95, 72], // 完成率数据(0-100)
|
||||
// symbol: 'circle', // 数据点为圆形
|
||||
// symbolSize: 6 // 数据点大小
|
||||
// },
|
||||
// 2. 目标:柱状图(绑定左侧数量Y轴)
|
||||
{
|
||||
name: '销量',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 24,
|
||||
// 关键修复:label 直接放在 series 下,而非 itemStyle 内
|
||||
itemStyle: {
|
||||
// 移除多余的 normal 层级,直接配置 color 渐变
|
||||
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: [200, 280, 180, 300, 220, 350]
|
||||
},
|
||||
// 3. 达标:柱状图(绑定左侧数量Y轴)
|
||||
// {
|
||||
// name: '产量',
|
||||
// type: 'bar',
|
||||
// yAxisIndex: 0,
|
||||
// barWidth: 24,
|
||||
// // 关键修复:label 直接放在 series 下,而非 itemStyle 内
|
||||
// itemStyle: {
|
||||
// // 移除多余的 normal 层级,直接配置 color 渐变
|
||||
// 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: [130, 220, 95, 255, 132, 332] // 达标数据(小于目标)
|
||||
// },
|
||||
// 4. 未达标:柱状图(绑定左侧数量Y轴)
|
||||
// {
|
||||
// name: '未达标',
|
||||
// type: 'bar',
|
||||
// yAxisIndex: 0,
|
||||
// barWidth: 18,
|
||||
// itemStyle: {
|
||||
// color: 'rgba(249, 164, 74, 1)',
|
||||
// borderRadius: [4, 4, 0, 0],
|
||||
// borderWidth: 0
|
||||
// },
|
||||
// data: [70, 60, 85, 45, 88, 18, 78] // 未达标数据(目标-达标)
|
||||
// }
|
||||
],
|
||||
// 图例:区分各系列,点击可控制显示隐藏
|
||||
// legend: {
|
||||
// top: 0,
|
||||
// left: 'center',
|
||||
// itemWidth: 12,
|
||||
// itemHeight: 8,
|
||||
// textStyle: {
|
||||
// color: 'rgba(0, 0, 0, 0.45)',
|
||||
// fontSize: 12
|
||||
// },
|
||||
// data: ['完成率', '目标', '达标', '未达标']
|
||||
// }
|
||||
};
|
||||
|
||||
option && myChart.setOption(option);
|
||||
|
||||
// 窗口缩放适配
|
||||
window.addEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
|
||||
// 组件销毁清理
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
myChart.dispose();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user