Compare commits
5 Commits
projects/l
...
05fe91618c
| Author | SHA1 | Date | |
|---|---|---|---|
| 05fe91618c | |||
| b85ceb2542 | |||
| e770dc4fed | |||
| 4f7466bb29 | |||
| bb66f97b95 |
4
.env.dev
4
.env.dev
@@ -10,11 +10,11 @@ VUE_APP_TITLE = 洛玻集团驾驶舱
|
|||||||
# VUE_APP_BASE_API = 'http://172.16.33.83:7070'
|
# VUE_APP_BASE_API = 'http://172.16.33.83:7070'
|
||||||
|
|
||||||
# 杨姗姗
|
# 杨姗姗
|
||||||
# VUE_APP_BASE_API = 'http://172.16.20.218:7070'
|
VUE_APP_BASE_API = 'http://172.16.20.218:7070'
|
||||||
# 小田
|
# 小田
|
||||||
# VUE_APP_BASE_API = 'http://172.16.19.232:7070'
|
# VUE_APP_BASE_API = 'http://172.16.19.232:7070'
|
||||||
# 测试
|
# 测试
|
||||||
VUE_APP_BASE_API = 'http://192.168.0.35:8080'
|
# VUE_APP_BASE_API = 'http://192.168.0.35:8080'
|
||||||
|
|
||||||
|
|
||||||
# 路由懒加载
|
# 路由懒加载
|
||||||
|
|||||||
24
src/assets/icons/svg/turn-data.svg
Normal file
24
src/assets/icons/svg/turn-data.svg
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>切换数据</title>
|
||||||
|
<g id="页面" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="icon和插图" transform="translate(-339.000000, -683.000000)" fill-rule="nonzero">
|
||||||
|
<g id="检测数据" transform="translate(339.000000, 683.000000)">
|
||||||
|
<rect id="矩形" fill="#000000" opacity="0" x="0" y="0" width="32" height="32"></rect>
|
||||||
|
<g id="编组-5" transform="translate(1.358796, 1.358796)">
|
||||||
|
<path d="M14.6412037,0 C6.56329821,0 0,6.56329821 0,14.6412037 C0,22.7191092 6.56329821,29.2824074 14.6412037,29.2824074 C22.7191092,29.2824074 29.2824074,22.7191092 29.2824074,14.6412037 C29.2824074,6.56329821 22.7191092,0 14.6412037,0 Z" id="形状" fill="#ECF5FE"></path>
|
||||||
|
<g id="烧瓶,实验,化学,科学" transform="translate(4.362930, 5.001581)">
|
||||||
|
<rect id="矩形" fill="#000000" opacity="0" x="1.95940783" y="1.320757" width="16.6377315" height="16.6377315"></rect>
|
||||||
|
<g>
|
||||||
|
<rect id="矩形" fill="#000000" opacity="0" x="2.83308197" y="2.32185598" width="14.8903832" height="14.8903832"></rect>
|
||||||
|
<g id="还原画布" fill="#3E6AF7">
|
||||||
|
<rect id="矩形" opacity="0" x="1.23578458" y="0.724558589" width="18.084978" height="18.084978"></rect>
|
||||||
|
<path d="M17.6932207,6.74823569 L13.2296336,6.74823569 C13.0215873,6.74823569 12.8513571,6.54528408 12.8513571,6.29724746 L12.8513571,1.97232623 C12.8513571,1.72428962 13.0215542,1.521338 13.2296171,1.521338 L17.6932207,1.521338 C17.901267,1.521338 18.0714972,1.72428962 18.0714972,1.97232623 L18.0714972,6.29722771 C18.0714972,6.54526433 17.9031719,6.74823569 17.6932207,6.74823569 L17.6932207,6.74823569 Z M6.87068348,17.3028361 L2.40704671,17.3028361 C2.19900041,17.3028361 2.02877016,17.0998845 2.02877016,16.8518479 L2.02877016,12.5269266 C2.02877016,12.27889 2.19900041,12.0759384 2.40704671,12.0759384 L6.87068348,12.0759384 C7.07872978,12.0759384 7.24896003,12.2788703 7.24896003,12.5269266 L7.24896003,16.8518479 C7.24896003,17.1021358 7.07872978,17.3028361 6.87068348,17.3028361 Z M20.4661601,11.5941637 L19.1062728,9.97893922 C19.0135961,9.86923455 18.8717403,9.82573697 18.7412476,9.85222312 C18.680722,9.86356959 18.6239731,9.89194406 18.574794,9.93355332 L16.8858257,11.3558546 C16.7269585,11.4901405 16.7061539,11.7284496 16.8404398,11.8892217 C16.9160885,11.9781052 17.0220165,12.0235076 17.1298163,12.0235076 C17.2149231,12.0235076 17.3019183,11.9951332 17.3738069,11.9346076 L18.4537759,11.0267571 C18.4518711,11.0418803 18.4518711,11.0551316 18.4518711,11.0702547 C18.4518711,13.3493556 17.7350555,15.2804262 16.3789447,16.6535483 C15.0077109,18.0437151 13.0596123,18.7775586 10.7445837,18.7775586 C10.5365374,18.7775586 10.3663072,18.9477723 10.3663072,19.1558352 C10.3663072,19.3638981 10.5365208,19.5340952 10.7445837,19.5340952 C13.2657703,19.5340952 15.3992222,18.7227146 16.9160885,17.1850271 C17.6556136,16.4360439 18.2287016,15.5319866 18.6183247,14.4993084 C18.9890314,13.5157926 19.1857312,12.4301421 19.2065193,11.2707477 L19.8912003,12.0840332 C19.9668656,12.1729167 20.072777,12.2183192 20.1805768,12.2183192 C20.2657002,12.2183192 20.3526954,12.1899447 20.4245674,12.1294191 C20.5815463,11.9932449 20.602351,11.7549358 20.4661767,11.5941637 L20.4661601,11.5941637 Z M9.98803061,0 C8.67730576,0 7.47441384,0.202364785 6.41524944,0.603334282 C5.37500136,0.996733941 4.47470417,1.57928016 3.74086058,2.33204005 C2.34880553,3.76191116 1.58658748,5.7724236 1.52796679,8.16311747 L0.622004647,7.3990111 C0.461232559,7.2647086 0.222923469,7.28551323 0.0886375303,7.444397 C-0.0456484081,7.6032642 -0.0248437777,7.84347818 0.134023428,7.97776411 L1.82300826,9.40006538 C1.88255245,9.4508878 1.95677081,9.48136671 2.03484776,9.48706054 C2.04619423,9.48706054 2.05565239,9.48896542 2.06699887,9.48896542 C2.17481522,9.48896542 2.28261501,9.44357953 2.35637537,9.35466292 L3.71626275,7.73945502 C3.85054869,7.58057125 3.82974406,7.34037384 3.67087685,7.20608791 C3.51200965,7.07180197 3.27179567,7.0926066 3.13750974,7.2514738 L2.28263157,8.26714062 C2.32044763,6.0334422 3.00890532,4.16667386 4.28178097,2.86163053 C4.93808755,2.18640764 5.74570807,1.66627531 6.681933,1.3125966 C7.65597399,0.943778206 8.76809412,0.758424851 9.98803061,0.758424851 C10.1960769,0.758424851 10.3663072,0.588211171 10.3663072,0.380164867 C10.3663072,0.172118563 10.1979652,0 9.98803061,0 Z" id="形状" stroke="#3E6AF7" stroke-width="0.7"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.8 KiB |
@@ -19,6 +19,7 @@ function findFirstLeafPathFromMenus(menus) {
|
|||||||
const currentPath = rawPath.startsWith('/')
|
const currentPath = rawPath.startsWith('/')
|
||||||
? rawPath
|
? rawPath
|
||||||
: `${parentPath}/${rawPath}`.replace(/\/+/g, '/')
|
: `${parentPath}/${rawPath}`.replace(/\/+/g, '/')
|
||||||
|
|
||||||
if (item.children && item.children.length > 0) {
|
if (item.children && item.children.length > 0) {
|
||||||
const found = dfs(item.children, currentPath)
|
const found = dfs(item.children, currentPath)
|
||||||
if (found != null) return found
|
if (found != null) return found
|
||||||
@@ -30,6 +31,7 @@ function findFirstLeafPathFromMenus(menus) {
|
|||||||
}
|
}
|
||||||
return dfs(menus)
|
return dfs(menus)
|
||||||
}
|
}
|
||||||
|
|
||||||
function findFirstLeafPathFromRoutes(routes) {
|
function findFirstLeafPathFromRoutes(routes) {
|
||||||
if (!Array.isArray(routes) || routes.length === 0) return null
|
if (!Array.isArray(routes) || routes.length === 0) return null
|
||||||
const stack = [...routes]
|
const stack = [...routes]
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
<div class="bullshit__info">
|
<div class="bullshit__info">
|
||||||
对不起,您正在寻找的页面不存在。尝试检查URL的错误,然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容。
|
对不起,您正在寻找的页面不存在。尝试检查URL的错误,然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容。
|
||||||
</div>
|
</div>
|
||||||
<router-link to="/" class="bullshit__return-home">
|
<router-link to="/login" class="bullshit__return-home">
|
||||||
返回首页
|
返回登录页
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
246
src/views/home/accountsReceivable/accountsReceivable.vue
Normal file
246
src/views/home/accountsReceivable/accountsReceivable.vue
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
<template>
|
||||||
|
<div id="dayReport" class="dayReport" :style="styles">
|
||||||
|
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||||
|
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||||
|
<ReportHeader :dateData="dateData" top-title="应收账款" :is-full-screen="isFullScreen"
|
||||||
|
@screenfullChange="screenfullChange" @timeRangeChange="handleTimeChange" />
|
||||||
|
<div class="main-body" style="
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
padding: 0px 16px 0 272px;
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div class="top" style="margin-top: -20px; display: flex; gap: 16px">
|
||||||
|
<div class="top-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns:1624px;
|
||||||
|
">
|
||||||
|
<operatingLineChart :dateData="dateData" :monthData="monthData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||||
|
<div class="left-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns: 1624px;
|
||||||
|
">
|
||||||
|
<operatingLineChartCumulative :dateData="dateData" :ytdData="ytdData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import ReportHeader from "../components/noRouterHeader.vue";
|
||||||
|
import { Sidebar } from "../../../layout/components";
|
||||||
|
import screenfull from "screenfull";
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
import operatingLineChart from "../accountsReceivableComponents/operatingLineChart";
|
||||||
|
import operatingLineChartCumulative from "../accountsReceivableComponents/operatingLineChartCumulative.vue";
|
||||||
|
|
||||||
|
import { getSalesRevenueGroupData } from '@/api/cockpit'
|
||||||
|
export default {
|
||||||
|
name: "AccountsReceivable",
|
||||||
|
components: {
|
||||||
|
ReportHeader,
|
||||||
|
operatingLineChartCumulative,
|
||||||
|
operatingLineChart,
|
||||||
|
Sidebar,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
weekArr: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
|
||||||
|
isFullScreen: false,
|
||||||
|
timer: null,
|
||||||
|
beilv: 1,
|
||||||
|
value: 100,
|
||||||
|
sort:1,
|
||||||
|
selectDate:{},
|
||||||
|
monthData: {},
|
||||||
|
ytdData:{},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.init();
|
||||||
|
this.windowWidth(document.documentElement.clientWidth);
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
theme: (state) => state.settings.theme,
|
||||||
|
sideTheme: (state) => state.settings.sideTheme,
|
||||||
|
sidebar: (state) => state.app.sidebar,
|
||||||
|
device: (state) => state.app.device,
|
||||||
|
needTagsView: (state) => state.settings.tagsView,
|
||||||
|
fixedHeader: (state) => state.settings.fixedHeader,
|
||||||
|
}),
|
||||||
|
classObj() {
|
||||||
|
return {
|
||||||
|
hideSidebar: !this.sidebar.opened,
|
||||||
|
openSidebar: this.sidebar.opened,
|
||||||
|
withoutAnimation: this.sidebar.withoutAnimation,
|
||||||
|
mobile: this.device === "mobile",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
variables() {
|
||||||
|
return variables;
|
||||||
|
},
|
||||||
|
// ...mapGetters(['sidebar']),
|
||||||
|
styles() {
|
||||||
|
const v = Math.floor(this.value * this.beilv * 100) / 10000;
|
||||||
|
return {
|
||||||
|
transform: `scale(${v})`,
|
||||||
|
transformOrigin: "left top",
|
||||||
|
// overflow: hidden;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
clientWidth(val) {
|
||||||
|
if (!this.timer) {
|
||||||
|
this.clientWidth = val;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
this.timer = true;
|
||||||
|
let _this = this;
|
||||||
|
setTimeout(function () {
|
||||||
|
_this.timer = false;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
// 这里可以添加修改时的方法
|
||||||
|
this.windowWidth(val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.destroy();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const _this = this;
|
||||||
|
_this.beilv = document.documentElement.clientWidth / 1920;
|
||||||
|
window.onresize = () => {
|
||||||
|
return (() => {
|
||||||
|
_this.clientWidth = `${document.documentElement.clientWidth}`;
|
||||||
|
this.beilv = _this.clientWidth / 1920;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// sortChange(value) {
|
||||||
|
// this.sort = value
|
||||||
|
// this.getData()
|
||||||
|
// },
|
||||||
|
getData() {
|
||||||
|
getSalesRevenueGroupData({
|
||||||
|
startTime: this.dateData.startTime,
|
||||||
|
endTime: this.dateData.endTime,
|
||||||
|
sort: this.sort,
|
||||||
|
index: undefined,
|
||||||
|
factory: undefined
|
||||||
|
// timeDim: obj.mode
|
||||||
|
}).then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
this.monthData= res.data.month
|
||||||
|
this.ytdData = res.data.ytd
|
||||||
|
|
||||||
|
// this.saleData = res.data.SaleData
|
||||||
|
// this.premiumProduct = res.data.premiumProduct
|
||||||
|
// this.salesTrendMap = res.data.salesTrendMap
|
||||||
|
// this.grossMarginTrendMap = res.data.grossMarginTrendMap
|
||||||
|
// this.salesProportion = res.data.salesProportion ? res.data.salesProportion : {}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleTimeChange(obj) {
|
||||||
|
console.log(obj, 'obj');
|
||||||
|
this.dateData= obj
|
||||||
|
this.getData()
|
||||||
|
},
|
||||||
|
handleClickOutside() {
|
||||||
|
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
||||||
|
},
|
||||||
|
windowWidth(value) {
|
||||||
|
this.clientWidth = value;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
},
|
||||||
|
change() {
|
||||||
|
this.isFullScreen = screenfull.isFullscreen;
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.on("change", this.change);
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.off("change", this.change);
|
||||||
|
},
|
||||||
|
// 全屏
|
||||||
|
screenfullChange() {
|
||||||
|
console.log("screenfull.enabled", screenfull.isEnabled);
|
||||||
|
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.toggle(this.$refs.dayReportB);
|
||||||
|
},
|
||||||
|
// 导出
|
||||||
|
// exportPDF() {
|
||||||
|
// this.$message.success('正在导出,请稍等!')
|
||||||
|
// const element = document.getElementById('dayRepDom')
|
||||||
|
// element.style.display = 'block'
|
||||||
|
// const fileName = '株洲碲化镉生产日报' + moment().format('yyMMDD') + '.pdf'
|
||||||
|
// html2canvas(element, {
|
||||||
|
// dpi: 300, // Set to 300 DPI
|
||||||
|
// scale: 3 // Adjusts your resolution
|
||||||
|
// }).then(function(canvas) {
|
||||||
|
// const imgWidth = 595.28
|
||||||
|
// const imgHeight = 841.89
|
||||||
|
// const pageData = canvas.toDataURL('image/jpeg', 1.0)
|
||||||
|
// const PDF = new JsPDF('', 'pt', [imgWidth, imgHeight])
|
||||||
|
// PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
|
||||||
|
// setTimeout(() => {
|
||||||
|
// PDF.save(fileName) // 导出文件名
|
||||||
|
// }, 1000)
|
||||||
|
// })
|
||||||
|
// element.style.display = 'none'
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "~@/assets/styles/mixin.scss";
|
||||||
|
@import "~@/assets/styles/variables.scss";
|
||||||
|
.dayReport {
|
||||||
|
width: 1920px;
|
||||||
|
height: 1080px;
|
||||||
|
background: url("../../../assets/img/backp.png") no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
.hideSidebar .fixed-header {
|
||||||
|
width: calc(100% - 54px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarHide .fixed-header {
|
||||||
|
width: calc(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile .fixed-header {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
419
src/views/home/accountsReceivableComponents/Header.vue
Normal file
419
src/views/home/accountsReceivableComponents/Header.vue
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
<template>
|
||||||
|
<header class="report-header">
|
||||||
|
<!-- 左侧区域:logo + 标题 -->
|
||||||
|
<div class="left-content">
|
||||||
|
<img style="height: 36px;" src="../../../assets/img/cnbm.png" alt="benmaLogo" >
|
||||||
|
<div class="top-title">{{ topTitle }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="center-content">
|
||||||
|
<!-- 循环 pageRoutes,不再硬编码文字 -->
|
||||||
|
<div class="item" v-for="(page, index) in pageRoutes" :key="index" @click="goToPage(page.path, index)">
|
||||||
|
<span class="item-text">{{ page.text }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- :class="{ 'no-skew': activeIndex === index }
|
||||||
|
" -->
|
||||||
|
<!-- 右侧区域:全屏按钮 -->
|
||||||
|
<div class="right-content">
|
||||||
|
<el-button type="text" class="screen-btn" :title="isFullScreen ? '退出全屏' : '全屏'" @click="changeFullScreen">
|
||||||
|
<svg-icon style="color: #0B58FF;" v-if="isFullScreen" icon-class="unFullScreenView" />
|
||||||
|
<svg-icon style="color: #0B58FF;" v-else icon-class="fullScreenView" />
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 时间选择区域:日/月/年按钮 + label + 日期选择器 -->
|
||||||
|
<div class="timeType">
|
||||||
|
<!-- <div class="item" v-for="(item, index) in timeTypes" :key="index" @click="activeTime = index"
|
||||||
|
:class="{ 'no-skew': activeTime === index }">
|
||||||
|
<span class="item-text">{{ item.text }}</span>
|
||||||
|
</div> -->
|
||||||
|
<div class="dateP">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">月份选择</span>
|
||||||
|
</div>
|
||||||
|
<el-date-picker v-model="date" :type="getPickerType" :placeholder="getPickerPlaceholder"
|
||||||
|
class="custom-date-picker" style="width: 132px;height: 29px;" @change="emitTimeRange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment'
|
||||||
|
export default {
|
||||||
|
name: 'Header',
|
||||||
|
props: {
|
||||||
|
isFullScreen: { type: Boolean, default: false },
|
||||||
|
topTitle: { type: String, default: '' }
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentTime: '',
|
||||||
|
timeTimer: null,
|
||||||
|
date: undefined,
|
||||||
|
activeIndex: -1,
|
||||||
|
activeTime: 1, // 0=日,1=月,2=年(默认选中“日”)
|
||||||
|
pageRoutes: [
|
||||||
|
{ text: '营业收入', path: '/operatingRevenue/operatingRevenueIndex' },
|
||||||
|
{ text: '利润分析', path: '/profitAnalysis' },
|
||||||
|
{ text: '产销率库存分析', path: '/PSIAnal' },
|
||||||
|
{ text: '成本分析', path: '/cost/cost' },
|
||||||
|
{ text: '驾驶舱报表', path: '/cockpit' }
|
||||||
|
],
|
||||||
|
// 定义时间类型配置:text=按钮文字,pickerType=选择器类型,placeholder=占位符
|
||||||
|
timeTypes: [
|
||||||
|
{ text: '日', pickerType: 'date', placeholder: '选择日期' },
|
||||||
|
{ text: '月', pickerType: 'month', placeholder: '选择月份' },
|
||||||
|
{ text: '年', pickerType: 'year', placeholder: '选择年份' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 动态获取日期选择器类型
|
||||||
|
getPickerType() {
|
||||||
|
return this.timeTypes[this.activeTime].pickerType;
|
||||||
|
},
|
||||||
|
// 动态获取日期选择器占位符
|
||||||
|
getPickerPlaceholder() {
|
||||||
|
return this.timeTypes[this.activeTime].placeholder;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goToPage(path, index) {
|
||||||
|
// 1. 跳转到对应路由
|
||||||
|
this.$router.push(path);
|
||||||
|
// 2. 更新activeIndex,让当前点击项高亮
|
||||||
|
this.activeIndex = index;
|
||||||
|
},
|
||||||
|
changeFullScreen() { this.$emit('screenfullChange') },
|
||||||
|
padZero(num) { return num < 10 ? '0' + num : num },
|
||||||
|
/**
|
||||||
|
* 核心方法1:根据维度计算时间区间(首次进入时基于赋值的当月日期,计算“当月第一天0点→次月第一天0点”)
|
||||||
|
* @returns {Object} 包含 start(开始时间)、end(结束时间)、dimension(维度)的区间对象
|
||||||
|
*/
|
||||||
|
calculateTimeRange() {
|
||||||
|
let startTime = 0;
|
||||||
|
let endTime = 0;
|
||||||
|
const mode = this.activeTime + 1; // 1=日,2=月,3=年
|
||||||
|
const defaultMoment = moment(); // 默认当前时间
|
||||||
|
|
||||||
|
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. 日维度:00:00:00 → 23:59:59(无毫秒)
|
||||||
|
if (this.activeTime === 0) {
|
||||||
|
startTime = targetMoment.startOf('day').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('day').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 月维度:当月1日00:00:00 → 当月最后一天23:59:59(无毫秒)
|
||||||
|
else if (this.activeTime === 1) {
|
||||||
|
startTime = targetMoment.startOf('month').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('month').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 年维度:当年1月1日00:00:00 → 当年最后一天23:59:59(无毫秒)
|
||||||
|
else if (this.activeTime === 2) {
|
||||||
|
startTime = targetMoment.startOf('year').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('year').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调试输出:验证是否去掉毫秒
|
||||||
|
console.log('时间范围计算结果:', {
|
||||||
|
mode,
|
||||||
|
startTime: moment(startTime * 1000).format('YYYY-MM-DD HH:mm:ss'), // 格式:2025-11-30 00:00:00
|
||||||
|
endTime: moment(endTime * 1000).format('YYYY-MM-DD HH:mm:ss'), // 格式:2025-11-30 23:59:59(无毫秒)
|
||||||
|
startTimeStamp: startTime, // 秒级时间戳(如:1764422400)
|
||||||
|
endTimeStamp: endTime // 秒级时间戳(如:1764508799)
|
||||||
|
});
|
||||||
|
|
||||||
|
return { startTime, endTime, mode };
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 核心方法2:传递时间区间给父组件(首次进入时触发,传递“当月第一天0点→次月第一天0点”)
|
||||||
|
*/
|
||||||
|
emitTimeRange() {
|
||||||
|
const timeRange = this.calculateTimeRange();
|
||||||
|
this.$emit('timeRangeChange', timeRange);
|
||||||
|
// 调试用:查看首次传递的区间(如{start: "2025-10-01T00:00:00", end: "2025-11-01T00:00:00", dimension: "月"})
|
||||||
|
console.log('当前时间区间:', timeRange);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 维度切换时:清空选择的日期,并传递当前维度的默认区间
|
||||||
|
activeTime(newVal, oldVal) {
|
||||||
|
if (newVal !== oldVal) {
|
||||||
|
this.date = undefined;
|
||||||
|
// this.emitTimeRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 核心逻辑:首次进入页面,计算当月默认日期并赋值给选择器,同时传递区间
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = this.padZero(now.getMonth() + 1); // 月份从0开始,+1后补零(如1月→01)
|
||||||
|
// 赋值当月默认日期(格式:YYYY-MM,适配month类型选择器)
|
||||||
|
this.date = `${year}-${month}`;
|
||||||
|
// 确保选择器渲染完成后,传递“当月第一天0点→次月第一天0点”的区间
|
||||||
|
this.$nextTick(() => this.emitTimeRange());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
/* 原有样式不变,仅补充label文字的倾斜抵消样式 */
|
||||||
|
@font-face {
|
||||||
|
font-family: "YouSheBiaoTiHei";
|
||||||
|
src: url('../../../assets/fonts/YouSheBiaoTiHe.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-header {
|
||||||
|
height: 117px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
background: url('../../../assets/img/topTitle.png') no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: 0 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
/* 确保timeType绝对定位生效 */
|
||||||
|
|
||||||
|
.left-content {
|
||||||
|
margin-top: 11px;
|
||||||
|
margin-left: 44px;
|
||||||
|
height: 55px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-title {
|
||||||
|
height: 55px;
|
||||||
|
font-family: "YouSheBiaoTiHei", sans-serif;
|
||||||
|
font-size: 42px;
|
||||||
|
color: #1E1651;
|
||||||
|
line-height: 55px;
|
||||||
|
letter-spacing: 6px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 18px;
|
||||||
|
margin-left: 70px;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
width: 180px;
|
||||||
|
height: 50px;
|
||||||
|
background: #E1EEFC;
|
||||||
|
transform: skew(-20deg);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #1E1651;
|
||||||
|
line-height: 50px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0px 13px 16px 0px rgba(179, 217, 255, 0.43),
|
||||||
|
0px 2px 4px 0px rgba(92, 140, 255, 0.25),
|
||||||
|
inset 0px -43px 13px 0px rgba(255, 255, 255, 0.51);
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(20deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.no-skew {
|
||||||
|
background: none !important;
|
||||||
|
transform: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
color: #1E1651;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 垂直居中,避免元素高低错位 */
|
||||||
|
top: 42px;
|
||||||
|
right:10px;
|
||||||
|
margin-top: 18px;
|
||||||
|
gap: 0;
|
||||||
|
/* 清除间隙,让按钮与选择器紧密连接 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item {
|
||||||
|
width: 40px;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(236, 244, 254, 1);
|
||||||
|
transform: skew(-20deg);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(11, 88, 255, 1);
|
||||||
|
line-height: 28px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
/* 选中按钮与未选中按钮倾斜角度统一,避免切换时跳动 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item .item-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(20deg);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item.no-skew {
|
||||||
|
background: rgba(11, 88, 255, 1);
|
||||||
|
color: rgba(249, 252, 255, 1);
|
||||||
|
transform: skew(-20deg) !important;
|
||||||
|
/* 统一倾斜角度,修复原30deg的错位 */
|
||||||
|
box-shadow: 0 2px 8px rgba(11, 88, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item.no-skew .item-text {
|
||||||
|
transform: skew(20deg) !important;
|
||||||
|
/* 同步统一文字倾斜角度 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateP {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateP .label {
|
||||||
|
width: 165px;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(236, 244, 254, 1);
|
||||||
|
transform: skew(-25deg);
|
||||||
|
/* 与按钮倾斜角度统一(原30deg改为25deg,避免视觉错位) */
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 28px;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 补充:label文字抵消倾斜(原代码遗漏,导致文字倾斜) */
|
||||||
|
.dateP .label-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(25deg);
|
||||||
|
/* 与label倾斜角度相反,确保文字正立 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-right: 4px;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-time {
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 24px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screen-btn {
|
||||||
|
width: 26px;
|
||||||
|
margin-left: 300px;
|
||||||
|
color: #00fff0;
|
||||||
|
font-size: 26px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 日期选择器样式保持不变 */
|
||||||
|
::v-deep .custom-date-picker {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
width: 165px !important;
|
||||||
|
height: 28px !important;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 !important;
|
||||||
|
|
||||||
|
/* 1. 调整输入框文字:确保行高与输入框高度一致,垂直居中 */
|
||||||
|
.el-input__inner {
|
||||||
|
height: 28px !important;
|
||||||
|
width: 165px !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>
|
||||||
282
src/views/home/accountsReceivableComponents/container.vue
Normal file
282
src/views/home/accountsReceivableComponents/container.vue
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
<template>
|
||||||
|
<div class="cockpitContainer" :class="['cockpitContainer__' + size]">
|
||||||
|
<div class="content-top" :class="['content-top__' + topSize]">
|
||||||
|
<!-- 使用 flex 容器包裹图标和文字,实现垂直居中 -->
|
||||||
|
<div class="title-wrapper">
|
||||||
|
<svg-icon class="title-icon" :icon-class="icon" />
|
||||||
|
<span class="title-text">
|
||||||
|
{{ name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cockpitContainer-body">
|
||||||
|
<slot>
|
||||||
|
<div class="test-body">something test....</div>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Container',
|
||||||
|
components: {},
|
||||||
|
// eslint-disable-next-line vue/require-prop-types
|
||||||
|
props: ['name', 'size', 'icon', 'topSize'],
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.cockpitContainer {
|
||||||
|
display: inline-block;
|
||||||
|
// width: 100%;
|
||||||
|
// height: 100%;
|
||||||
|
padding: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.content-top {
|
||||||
|
height: 60px;
|
||||||
|
|
||||||
|
.title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 10px;
|
||||||
|
/* 垂直居中关键属性 */
|
||||||
|
height: 100%;
|
||||||
|
/* 继承父容器高度,确保垂直居中范围 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
font-size: 30px;
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
/* 图标和文字之间的间距 */
|
||||||
|
flex-shrink: 0;
|
||||||
|
/* 防止图标被压缩 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 移除固定行高,避免影响垂直对齐
|
||||||
|
// line-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// width: 547px;
|
||||||
|
// background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
&__basic {
|
||||||
|
// width: 547px;
|
||||||
|
background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__middle {
|
||||||
|
background: url(../../../assets/img/topTileMiddle.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__large {
|
||||||
|
background: url(../../../assets/img/topTitleLargeBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__KFAPTopTitle {
|
||||||
|
background: url(../../../assets/img/KFAPTopTitle.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiTopTitleBasic {
|
||||||
|
background: url(../../../assets/img/psiTopTitleBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__rawTopTitleLarge {
|
||||||
|
background: url(../../../assets/img/rawTopTitleLarge.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__topBasic {
|
||||||
|
background: url(../../../assets/img/top-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bottomBasic {
|
||||||
|
background: url(../../../assets/img/bottom-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingBasic {
|
||||||
|
background: url(../../../assets/img/operating-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingLarge {
|
||||||
|
background: url(../../../assets/img/operating-large.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__profitTopBasic {
|
||||||
|
background: url(../../../assets/img/profitTopBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__profitMiddleBasic {
|
||||||
|
background: url(../../../assets/img/profitMiddleBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiBasicBg {
|
||||||
|
background: url(../../../assets/img/psiBasicBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiMiddleBg {
|
||||||
|
background: url(../../../assets/img/psiMiddleBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingRevenueBg {
|
||||||
|
background: url(../../../assets/img/operatingRevenueBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__costBasicBg {
|
||||||
|
background: url(../../../assets/img/costBasicBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__rawTopBg {
|
||||||
|
background: url(../../../assets/img/rawTopBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__opLargeBg {
|
||||||
|
background: url(../../../assets/img/opLargeBg.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>
|
||||||
298
src/views/home/accountsReceivableComponents/dataTrend.vue
Normal file
298
src/views/home/accountsReceivableComponents/dataTrend.vue
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="数据趋势" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="right" style="
|
||||||
|
height: 191px;
|
||||||
|
display: flex;
|
||||||
|
width: 1595px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<dataTrendBar @changeItem="handleChange" :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import dataTrendBar from "./dataTrendBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, dataTrendBar },
|
||||||
|
props: {
|
||||||
|
trend: {
|
||||||
|
type: Array,
|
||||||
|
// 默认值与实际数据结构一致(12个月)
|
||||||
|
default: () => [
|
||||||
|
// { title: "2025年01月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年02月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年03月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年04月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年05月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年06月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年07月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年08月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年09月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年10月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年11月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年12月", budget: 0, real: 0, rate: 0, diff: 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: {
|
||||||
|
months: [], // 月份数组(2025年01月 - 2025年12月)
|
||||||
|
rates: [], // 每月完成率(百分比)
|
||||||
|
reals: [], // 每月实际值
|
||||||
|
budgets: [],// 每月预算值
|
||||||
|
diffs: [], // 每月差值
|
||||||
|
flags: [] // 每月达标标识(≥100 → 1,<100 → 0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
trend: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.processTrendData(newVal);
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.processTrendData(this.trend);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleChange(value) {
|
||||||
|
this.$emit("handleChange", value);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 处理趋势数据(适配12个月的数组结构)
|
||||||
|
* @param {Array} trendData - 原始趋势数组(12个月)
|
||||||
|
*/
|
||||||
|
processTrendData(trendData) {
|
||||||
|
// 数据兜底:确保是数组且长度为12
|
||||||
|
const validTrend = Array.isArray(trendData)
|
||||||
|
? trendData
|
||||||
|
: []
|
||||||
|
|
||||||
|
// 初始化空数组
|
||||||
|
const months = [];
|
||||||
|
const rates = [];
|
||||||
|
const reals = [];
|
||||||
|
const budgets = [];
|
||||||
|
const diffs = [];
|
||||||
|
const flags = [];
|
||||||
|
|
||||||
|
// 遍历12个月数据
|
||||||
|
validTrend.forEach(item => {
|
||||||
|
// 基础数据提取(兜底处理)
|
||||||
|
const month = item.title ?? '';
|
||||||
|
const budget = Number(item.budget) || 0;
|
||||||
|
const real = Number(item.real) || 0;
|
||||||
|
const rate = Number(item.rate) || 0;
|
||||||
|
const diff = Number(item.diff) || 0;
|
||||||
|
|
||||||
|
// 计算达标标识(≥100 → 1,<100 → 0)
|
||||||
|
const flag = this.getRateFlag(rate, real, budget);
|
||||||
|
|
||||||
|
// 填充数组
|
||||||
|
months.push(month);
|
||||||
|
rates.push(rate); // 转为百分比并取整
|
||||||
|
reals.push(real);
|
||||||
|
budgets.push(budget);
|
||||||
|
diffs.push(diff);
|
||||||
|
flags.push(flag);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新chartData(响应式)
|
||||||
|
this.chartData = {
|
||||||
|
months,
|
||||||
|
rates,
|
||||||
|
reals,
|
||||||
|
budgets,
|
||||||
|
diffs,
|
||||||
|
flags
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('处理后的趋势数据:', this.chartData);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算达标标识
|
||||||
|
* @param {Number} rate - 完成率(原始值,如1.2 → 120%)
|
||||||
|
* @returns {Number} 1: 达标(≥100%),0: 未达标(<100%)
|
||||||
|
*/
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</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>
|
||||||
|
/* 全局 tooltip 样式 */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
477
src/views/home/accountsReceivableComponents/dataTrendBar.vue
Normal file
477
src/views/home/accountsReceivableComponents/dataTrendBar.vue
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<!-- 新增行容器:包裹“各基地情况”和barTop -->
|
||||||
|
<div class="header-row">
|
||||||
|
<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">
|
||||||
|
<span class="legend-icon square achieved"></span>
|
||||||
|
实际·达标
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square unachieved"></span>
|
||||||
|
实际·未达标
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<div class="item-button category-btn">
|
||||||
|
<span class="item-text">类目选择</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>
|
||||||
|
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||||
|
<operatingLineBar :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSale.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
isDropdownShow: false,
|
||||||
|
selectedProfit: '营业收入', // 选中的名称,初始为null
|
||||||
|
profitOptions: [
|
||||||
|
'营业收入',
|
||||||
|
'单价',
|
||||||
|
'销量',
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// profitOptions() {
|
||||||
|
// return this.categoryData.map(item => item.name) || [];
|
||||||
|
// },
|
||||||
|
currentDataSource() {
|
||||||
|
console.log('yyyy', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
console.log('this.chartData', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData.months
|
||||||
|
},
|
||||||
|
// 根据按钮切换生成对应的 chartData
|
||||||
|
chartD() {
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
console.log(this.currentDataSource, 'currentDataSource');
|
||||||
|
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
// 1. 完成率(折线图)
|
||||||
|
{
|
||||||
|
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(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: data.rates, // 完成率(%)
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 2. 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||||
|
barWidth: 14,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.budgets // 目标销量(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图,含达标状态)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 14,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
// 固定label尺寸:68px×20px
|
||||||
|
width: 68,
|
||||||
|
height: 20,
|
||||||
|
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||||
|
formatter: (params) => {
|
||||||
|
const diff = data.diffs || [];
|
||||||
|
const flags = data.flags || [];
|
||||||
|
const currentDiff = diff[params.dataIndex] || 0;
|
||||||
|
const currentFlag = flags[params.dataIndex] || 0;
|
||||||
|
|
||||||
|
const prefix = currentFlag === 1 ? '+' : '-';
|
||||||
|
|
||||||
|
// 根据标志位选择不同的样式类
|
||||||
|
if (currentFlag === 1) {
|
||||||
|
// 达标 - 使用 rate-achieved 样式
|
||||||
|
return `{achieved|${currentDiff}}{text|差值}`;
|
||||||
|
} else {
|
||||||
|
// 未达标 - 使用 rate-unachieved 样式
|
||||||
|
return `{unachieved|${currentDiff}}{text|差值}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||||
|
{ offset: 0.2, color: '#ffffff' },
|
||||||
|
{ offset: 1, color: '#ffffff' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
shadowColor: 'rgba(191,203,215,0.5)',
|
||||||
|
shadowBlur: 2,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowOffsetY: 2,
|
||||||
|
borderRadius: 4,
|
||||||
|
borderColor: '#BFCBD577',
|
||||||
|
borderWidth: 0,
|
||||||
|
lineHeight: 20,
|
||||||
|
rich: {
|
||||||
|
text: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 10, 5, 0],
|
||||||
|
align: 'center',
|
||||||
|
color: '#464646',
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
},
|
||||||
|
achieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#76DABE', // 与达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
},
|
||||||
|
// 未达标样式
|
||||||
|
unachieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||||
|
const safeFlag = data.flags;
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
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)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals // 实际销量(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 根据按钮状态返回对应数据
|
||||||
|
return salesData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectProfit(item) {
|
||||||
|
this.selectedProfit = item;
|
||||||
|
this.isDropdownShow = false;
|
||||||
|
this.$emit("changeItem", item);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
// 新增:头部行容器,实现一行排列
|
||||||
|
.header-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end; // 左右两端对齐
|
||||||
|
align-items: center; // 垂直居中
|
||||||
|
// width: 100%;
|
||||||
|
margin-bottom: 8px; // 与下方图表区保留间距(可根据需求调整)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 各基地情况标题样式
|
||||||
|
.base-title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0 0 0 16px; // 保留原有内边距
|
||||||
|
white-space: nowrap; // 防止文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
// 移除原有flex和justify-content,由header-row控制
|
||||||
|
width: auto; // 自适应宽度
|
||||||
|
// 保留原有align-items,确保内部元素垂直居中
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; // 图例和按钮组垂直居中
|
||||||
|
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||||
|
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 图例:在右侧容器内横向排列
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||||
|
.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;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-btn {
|
||||||
|
width: 75px;
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-bottom-left-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-btn {
|
||||||
|
width: 123px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 18px 0 8px;
|
||||||
|
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: 123px;
|
||||||
|
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>
|
||||||
226
src/views/home/accountsReceivableComponents/electricityGauge.vue
Normal file
226
src/views/home/accountsReceivableComponents/electricityGauge.vue
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="chartBox" style="width: 100%; height: 108px; position: relative;">
|
||||||
|
<div :id="id" style="width: 100%; height:100%;"></div>
|
||||||
|
<div class="bottomTip">
|
||||||
|
<div class="precent">
|
||||||
|
<span class="precentNum">{{ detailData.rate || 0 }}% </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'EnergyConsumption',
|
||||||
|
// components: { Container },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
detailData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
// electricComu: 0,
|
||||||
|
// steamComu: 20, // 调整为符合max范围的数值(0-8)
|
||||||
|
// // electricity: [120, 150, 130, 180, 160, 200, 190],
|
||||||
|
// // steam: [80, 95, 85, 110, 100, 120, 115],
|
||||||
|
// // dates: ['1日', '2日', '3日', '4日', '5日', '6日', '7日']
|
||||||
|
})
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
default: () => ('monthG')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// electricityChart: null,
|
||||||
|
// steamChart: null,
|
||||||
|
// specialTicks: [2, 4, 6, 8], // 统一的刻度显示
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
detailData: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true, // 初始化时立即执行
|
||||||
|
handler() {
|
||||||
|
this.updateGauges()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initGauges()
|
||||||
|
// window.addEventListener('resize', this.handleResize)
|
||||||
|
this.observeContainerResize()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
observeContainerResize() {
|
||||||
|
// 修复:获取正确的容器(组件内的.gauge-container)
|
||||||
|
const container = this.$el.querySelector('.gauge-container')
|
||||||
|
if (container && window.ResizeObserver) {
|
||||||
|
this.resizeObserver = new ResizeObserver(entries => {
|
||||||
|
if (this.electricityChart) {
|
||||||
|
this.electricityChart.resize() // 直接触发resize,无需防抖
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.resizeObserver.observe(container)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initGauges() {
|
||||||
|
// console.log('this.id',this.id);
|
||||||
|
|
||||||
|
// 初始化电气图表实例
|
||||||
|
const electricityDom = document.getElementById(this.id)
|
||||||
|
if (electricityDom) {
|
||||||
|
// 修复:正确创建并存储图表实例
|
||||||
|
this.electricityChart = echarts.init(electricityDom)
|
||||||
|
// 首次更新数据
|
||||||
|
this.updateGauges()
|
||||||
|
}
|
||||||
|
// 蒸汽图表若未使用,可注释/删除
|
||||||
|
// const steamDom = document.getElementById('steamGauge')
|
||||||
|
// if (steamDom) {
|
||||||
|
// this.steamChart = echarts.init(steamDom)
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
updateGauges() {
|
||||||
|
// 修复:先判断实例是否存在,再更新配置
|
||||||
|
if (!this.electricityChart) return
|
||||||
|
|
||||||
|
// 修复:兜底获取rate值,确保数值有效
|
||||||
|
const rate = Number(this.detailData?.rate) || 0
|
||||||
|
console.log('当前rate值:', rate); // 调试:确认rate值正确
|
||||||
|
|
||||||
|
// 关键:第二个参数传true,清空原有配置,强制更新
|
||||||
|
this.electricityChart.setOption(this.getElectricityGaugeOption(rate), true)
|
||||||
|
},
|
||||||
|
// 用电量仪表盘配置(保留原有样式,优化数值范围)
|
||||||
|
getElectricityGaugeOption(value) {
|
||||||
|
const electricityGradient = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: '#0B58FF' },
|
||||||
|
{ offset: 1, color: '#32FFCD' }
|
||||||
|
])
|
||||||
|
|
||||||
|
return {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '月度',
|
||||||
|
type: 'gauge',
|
||||||
|
radius: '95', // 修复:添加%,避免数值错误
|
||||||
|
center: ['50%', '90%'],
|
||||||
|
startAngle: 180,
|
||||||
|
endAngle: 0,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
splitNumber: 4,
|
||||||
|
label: { show: false },
|
||||||
|
progress: {
|
||||||
|
show: true,
|
||||||
|
overlap: false,
|
||||||
|
roundCap: true,
|
||||||
|
clip: false,
|
||||||
|
width: 14,
|
||||||
|
itemStyle: { color: electricityGradient }
|
||||||
|
},
|
||||||
|
pointer: {
|
||||||
|
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
|
||||||
|
length: '75%',
|
||||||
|
width: 16,
|
||||||
|
itemStyle: { color: '#288AFF' },
|
||||||
|
offsetCenter: [0, '10%']
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
roundCap: true,
|
||||||
|
lineStyle: { width: 12, color: [[1, '#E6EBF7']] }
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
length: 10,
|
||||||
|
lineStyle: { width: 5, color: '#D6DAE5' },
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
splitNumber: 2,
|
||||||
|
length: 6,
|
||||||
|
lineStyle: { width: 2, color: '#D6DAE5' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
detail: { show: false },
|
||||||
|
data: [{ value: value, unit: '' }] // 确保数值正确传入
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 未使用的蒸汽仪表盘可注释/删除
|
||||||
|
// getSteamGaugeOption(value) { ... }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
.chartBox {
|
||||||
|
.bottomTip {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.precent {
|
||||||
|
line-height: 3px;
|
||||||
|
|
||||||
|
&::after,
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 54px;
|
||||||
|
height: 5px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
margin-right: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用电量线条颜色
|
||||||
|
.precent::before {
|
||||||
|
|
||||||
|
background: linear-gradient(90deg, rgba(40, 138, 255, 0) 0%, rgba(12, 125, 254, 0.4) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ::after:从 透明 到 rgba(12, 125, 254, 0.4)(90度渐变,左到右) */
|
||||||
|
.precent::after {
|
||||||
|
background: linear-gradient(90deg, rgba(12, 125, 254, 0.4) 0%, rgba(40, 138, 255, 0) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蒸汽线条颜色
|
||||||
|
// .steam-precent::after,
|
||||||
|
// .steam-precent::before {
|
||||||
|
// background: linear-gradient(90deg, rgba(11, 168, 255, 0.26) 0%, rgba(54, 239, 230, 1) 100%);
|
||||||
|
// }
|
||||||
|
|
||||||
|
.precentNum {
|
||||||
|
display: inline-block;
|
||||||
|
// width: 52px;
|
||||||
|
height: 22px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蒸汽数字颜色
|
||||||
|
.steam-num {
|
||||||
|
color: rgba(54, 239, 230, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
204
src/views/home/accountsReceivableComponents/monthlyOverview.vue
Normal file
204
src/views/home/accountsReceivableComponents/monthlyOverview.vue
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" 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; gap: 8px;">
|
||||||
|
<div class="dashboard">
|
||||||
|
<div class="title">
|
||||||
|
{{ month }}月完成率
|
||||||
|
</div>
|
||||||
|
<div class="number">
|
||||||
|
<div class="yield">
|
||||||
|
{{ monthData?.rate || 0 }}%
|
||||||
|
</div>
|
||||||
|
<div class="mom">
|
||||||
|
环比{{ monthData?.momRate }}%
|
||||||
|
<img v-if="monthData?.momRate >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="">
|
||||||
|
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="electricityGauge">
|
||||||
|
<electricityGauge :detailData="monthData" id="month"></electricityGauge>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
<div class="line" style="padding: 0px;">
|
||||||
|
<verticalBarChart :detailData="monthData">
|
||||||
|
</verticalBarChart>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import electricityGauge from './electricityGauge.vue'
|
||||||
|
import verticalBarChart from './verticalBarChart.vue'
|
||||||
|
|
||||||
|
// import * as echarts from 'echarts'
|
||||||
|
// import rawItem from './raw-Item.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, electricityGauge, verticalBarChart },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
monthData: { // 接收父组件传递的设备数据数组
|
||||||
|
type: Object,
|
||||||
|
default: () => {} // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
title: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
month: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// itemData: {
|
||||||
|
// handler(newValue, oldValue) {
|
||||||
|
// // this.updateChart()
|
||||||
|
// },
|
||||||
|
// deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
// computed: {
|
||||||
|
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||||
|
// sortedItemData() {
|
||||||
|
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||||
|
// const totalCostItems = this.itemData.filter(item =>
|
||||||
|
// item.name && item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 过滤出不包含“总成本”的项
|
||||||
|
// const otherItems = this.itemData.filter(item =>
|
||||||
|
// !item.name || !item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 合并:总成本项在前,其他项在后
|
||||||
|
// return [...totalCostItems, ...otherItems];
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 264px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
// width: 190px;
|
||||||
|
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;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 46px;
|
||||||
|
color: #0B58FF;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
width: 500px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .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> -->
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px; width: 100%;">
|
||||||
|
<!-- 销量模块(直接传递整合了flag的salesData) -->
|
||||||
|
<div class="dashboard left" @click="handleDashboardClick('/salesVolumeAnalysis/salesVolumeAnalysisBase')">
|
||||||
|
<div style='position: relative;'>
|
||||||
|
<div class="title">
|
||||||
|
销量·万㎡
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{monthAnalysis[0].rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:monthAnalysis[0].flags>0?'#30B590':'#FF9423'}" >{{monthAnalysis[0].diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-wrap">
|
||||||
|
<operatingSingleBar :detailData="salesData"></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 单价模块(直接传递整合了flag的unitPriceData) -->
|
||||||
|
<div class="dashboard right" @click="handleDashboardClick('/unitPriceAnalysis/unitPriceAnalysisBase')">
|
||||||
|
<div style='position: relative;'>
|
||||||
|
<div class="title">
|
||||||
|
单价·元/㎡
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{monthAnalysis[1].rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:monthAnalysis[1].flags>0?'#30B590':'#FF9423'}" >{{monthAnalysis[1].diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-wrap">
|
||||||
|
<operatingSingleBar :detailData="unitPriceData"></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import operatingSingleBar from './operatingSingleBar.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, operatingSingleBar },
|
||||||
|
props: {
|
||||||
|
monthAnalysis: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [
|
||||||
|
{ title: "销量", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
{ title: "单价", budget: 0, real: 0, rate: 0, diff: 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
factory: {
|
||||||
|
type: [String,Number],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
month: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
// 初始化数据包含flag字段
|
||||||
|
salesData: { title: "销量", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 },
|
||||||
|
unitPriceData: { title: "单价", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
monthAnalysis: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.updateChart(newVal)
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateChart(this.monthAnalysis)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleDashboardClick(path) {
|
||||||
|
this.$router.push({
|
||||||
|
path: path,
|
||||||
|
query: {
|
||||||
|
factory: this.$route.query.factory ? this.$route.query.factory : this.factory,
|
||||||
|
dateData: this.dateData
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 判断flag的核心方法
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateChart(data) {
|
||||||
|
// 数据兜底
|
||||||
|
const salesItem = Array.isArray(data) && data[0] ? data[0] : { title: "销量", budget: 0, real: 0, rate: 0, diff: 0 };
|
||||||
|
const unitPriceItem = Array.isArray(data) && data[1] ? data[1] : { title: "单价", budget: 0, real: 0, rate: 0, diff: 0 };
|
||||||
|
|
||||||
|
// 核心修改:将flag整合到数据对象中,无需单独定义salesFlag/unitPriceFlag
|
||||||
|
this.salesData = {
|
||||||
|
...salesItem, // 合并原有字段
|
||||||
|
flag: this.getRateFlag(salesItem.rate, salesItem.real, salesItem.budget) // 新增flag字段
|
||||||
|
};
|
||||||
|
|
||||||
|
this.unitPriceData = {
|
||||||
|
...unitPriceItem, // 合并原有字段
|
||||||
|
flag: this.getRateFlag(unitPriceItem.rate, unitPriceItem.real, unitPriceItem.budget) // 新增flag字段
|
||||||
|
};
|
||||||
|
|
||||||
|
// 调试:确认整合后的数据
|
||||||
|
console.log('整合flag后的销量数据:', this.salesData);
|
||||||
|
console.log('整合flag后的单价数据:', this.unitPriceData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topItem-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 300px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
margin: 0 4px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-wrap {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 30px;
|
||||||
|
height: 32px;
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
width: 97px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard.left {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard.right {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
503
src/views/home/accountsReceivableComponents/operatingBar.vue
Normal file
503
src/views/home/accountsReceivableComponents/operatingBar.vue
Normal file
@@ -0,0 +1,503 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<div class="header-row">
|
||||||
|
<div class="base-title">各基地情况</div>
|
||||||
|
<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">
|
||||||
|
<span class="legend-icon square achieved"></span>实际·达标
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square unachieved"></span>实际·未达标
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<div class="item-button category-btn">
|
||||||
|
<span class="item-text">展示顺序</span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-container">
|
||||||
|
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||||
|
<span class="item-text profit-text">{{ selectedSort || '请选择' }}</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.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||||
|
<operatingLineBar :dateData="dateData" :chartData="chartD" style="height: 100%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSale.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData", 'dateData'],
|
||||||
|
emits: ['sort-change'], // 声明事件(Vue3 推荐)
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
isDropdownShow: false,
|
||||||
|
selectedSort: '实际值:高~低', // 选中的label
|
||||||
|
selectedSortValue: 1, // 选中的value,用于排序逻辑
|
||||||
|
profitOptions: [
|
||||||
|
{ label: '实际值:高~低', value: 1 },
|
||||||
|
{ label: '实际值:低~高', value: 2 },
|
||||||
|
{ label: '完成率:高~低', value: 3 },
|
||||||
|
{ label: '完成率:低~高', value: 4 },
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 排序后的数据源(核心:根据selectedSortValue重新排序)
|
||||||
|
currentDataSource() {
|
||||||
|
if (!this.chartData?.factory) return {};
|
||||||
|
|
||||||
|
// 深拷贝原始数据,避免修改原数据
|
||||||
|
const factory = JSON.parse(JSON.stringify(this.chartData.factory));
|
||||||
|
if (!factory.locations.length || !this.selectedSortValue) return factory;
|
||||||
|
|
||||||
|
// 构建带索引的数组,方便同步所有字段排序
|
||||||
|
const dataWithIndex = factory.locations.map((name, index) => ({
|
||||||
|
index,
|
||||||
|
name,
|
||||||
|
real: factory.reals[index],
|
||||||
|
target: factory.targets[index],
|
||||||
|
rate: factory.rates[index],
|
||||||
|
diff: factory.diff[index],
|
||||||
|
flag: factory.flags[index]
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 根据选中的排序规则排序
|
||||||
|
switch (this.selectedSortValue) {
|
||||||
|
case 1: // 实际值:高~低
|
||||||
|
dataWithIndex.sort((a, b) => b.real - a.real);
|
||||||
|
break;
|
||||||
|
case 2: // 实际值:低~高
|
||||||
|
dataWithIndex.sort((a, b) => a.real - b.real);
|
||||||
|
break;
|
||||||
|
case 3: // 预算值:高~低
|
||||||
|
dataWithIndex.sort((a, b) => b.rate - a.rate);
|
||||||
|
break;
|
||||||
|
case 4: // 预算值:低~高
|
||||||
|
dataWithIndex.sort((a, b) => a.rate - b.rate);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步更新所有数组
|
||||||
|
factory.locations = dataWithIndex.map(item => item.name);
|
||||||
|
factory.reals = dataWithIndex.map(item => item.real);
|
||||||
|
factory.targets = dataWithIndex.map(item => item.target);
|
||||||
|
factory.rates = dataWithIndex.map(item => item.rate);
|
||||||
|
factory.diff = dataWithIndex.map(item => item.diff);
|
||||||
|
factory.flags = dataWithIndex.map(item => item.flag);
|
||||||
|
|
||||||
|
return factory;
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
return this.currentDataSource.locations || [];
|
||||||
|
},
|
||||||
|
// 最终传递给图表的排序后数据
|
||||||
|
chartD() {
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
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(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: data.rates || [],
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 40,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.targets || []
|
||||||
|
},
|
||||||
|
// 实际(柱状图)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 40,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [32, 0],
|
||||||
|
width: 100,
|
||||||
|
height: 22,
|
||||||
|
formatter: (params) => {
|
||||||
|
const diff = data.diff || [];
|
||||||
|
const flags = data.flags || [];
|
||||||
|
const currentDiff = diff[params.dataIndex] || 0;
|
||||||
|
const currentFlag = flags[params.dataIndex] || 0;
|
||||||
|
|
||||||
|
const prefix = currentFlag === 1 ? '+' : '-';
|
||||||
|
|
||||||
|
// 根据标志位选择不同的样式类
|
||||||
|
if (currentFlag === 1) {
|
||||||
|
// 达标 - 使用 rate-achieved 样式
|
||||||
|
return `{achieved|${currentDiff}}{text|差值}`;
|
||||||
|
} else {
|
||||||
|
// 未达标 - 使用 rate-unachieved 样式
|
||||||
|
return `{unachieved|${currentDiff}}{text|差值}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||||
|
{ offset: 0.2, color: '#ffffff' },
|
||||||
|
{ offset: 1, color: '#ffffff' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
shadowColor: 'rgba(191,203,215,0.5)',
|
||||||
|
shadowBlur: 2,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowOffsetY: 2,
|
||||||
|
borderRadius: 4,
|
||||||
|
borderColor: '#BFCBD577',
|
||||||
|
borderWidth: 0,
|
||||||
|
lineHeight: 26,
|
||||||
|
rich: {
|
||||||
|
text: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 10, 5, 0],
|
||||||
|
align: 'center',
|
||||||
|
color: '#464646',
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
achieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#76DABE', // 与达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
// 未达标样式
|
||||||
|
unachieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 14,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const safeFlag = data.flags || [];
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
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)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals || []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
return salesData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectProfit(item) {
|
||||||
|
// 更新选中的label和value
|
||||||
|
this.selectedSort = item.label;
|
||||||
|
this.selectedSortValue = item.value;
|
||||||
|
this.isDropdownShow = false;
|
||||||
|
// 向父组件传递排序事件(可选,保持原有逻辑)
|
||||||
|
this.$emit('sort-change', item.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 监听父组件传入的chartData变化,重置选中状态(可选)
|
||||||
|
watch: {
|
||||||
|
'chartData.factory': {
|
||||||
|
handler() {
|
||||||
|
// 若需要切换数据源后重置排序,可取消注释
|
||||||
|
// this.selectedSort = null;
|
||||||
|
// this.selectedSortValue = null;
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.header-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0 0 0 16px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
width: auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
margin-right: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
fontSize: 12px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-btn {
|
||||||
|
width: 75px;
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-bottom-left-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-btn {
|
||||||
|
width: 123px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 18px 0 8px;
|
||||||
|
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: 123px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.dropdown-option {
|
||||||
|
padding: 6px 12px;
|
||||||
|
fontSize: 12px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
color: #3071ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChipBottom" id="cockpitEffChipBottom" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
series: [],
|
||||||
|
allPlaceNames: []
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
validator: (value) => {
|
||||||
|
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChipBottom;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatter: (params) => {
|
||||||
|
let html = `${params[0].axisValue}<br/>`;
|
||||||
|
params.forEach(item => {
|
||||||
|
const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
);
|
||||||
|
html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 60,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
splitNumber: 4,
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: (value) => Math.ceil((value.max || 0) * 1.1),
|
||||||
|
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
scale:true,
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
172
src/views/home/accountsReceivableComponents/operatingLineBar.vue
Normal file
172
src/views/home/accountsReceivableComponents/operatingLineBar.vue
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
series: [],
|
||||||
|
allPlaceNames: []
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
validator: (value) => {
|
||||||
|
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 60,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
// {
|
||||||
|
// type: 'value',
|
||||||
|
// nameTextStyle: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// align: 'left'
|
||||||
|
// },
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
// axisTick: { show: false },
|
||||||
|
// axisLabel: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// formatter: '{value}%'
|
||||||
|
// },
|
||||||
|
// splitLine: { show: false },
|
||||||
|
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
// splitNumber: 4
|
||||||
|
// }
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null, // 存储图表实例
|
||||||
|
resizeHandler: null, // 存储resize事件处理函数
|
||||||
|
// 核心:基地名称与序号的映射表(固定顺序)
|
||||||
|
baseNameToIndexMap: {
|
||||||
|
'宜兴': 7,
|
||||||
|
'漳州': 8,
|
||||||
|
'自贡': 3,
|
||||||
|
'桐城': 2,
|
||||||
|
'洛阳': 9,
|
||||||
|
'合肥': 5,
|
||||||
|
'宿迁': 6,
|
||||||
|
'秦皇岛': 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.initChart(); // 初始化图表(只执行一次)
|
||||||
|
this.updateChart(); // 更新图表数据
|
||||||
|
});
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
this.updateChart(); // 仅更新数据,不重新创建实例
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 组件销毁时清理资源
|
||||||
|
this.destroyChart();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 初始化图表(只在mounted中执行一次)
|
||||||
|
initChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只创建一次图表实例
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
// 绑定点击事件(只绑定一次,永久生效)
|
||||||
|
this.myChart.getZr().on('click', (params) => {
|
||||||
|
console.log('params', params);
|
||||||
|
|
||||||
|
// 提取点击的基地名称
|
||||||
|
// const itemName = params.name;
|
||||||
|
let itemName = undefined
|
||||||
|
// 根据映射表获取对应的序号(未匹配到则返回0或其他默认值)
|
||||||
|
let pointInPixel = [params.offsetX, params.offsetY];
|
||||||
|
if (this.myChart.containPixel('grid', pointInPixel)) {
|
||||||
|
let pointInGrid = this.myChart.convertFromPixel({
|
||||||
|
seriesIndex: 0
|
||||||
|
}, pointInPixel);
|
||||||
|
let xIndex = pointInGrid[0]; //索引
|
||||||
|
let handleIndex = Number(xIndex);
|
||||||
|
let seriesObj = this.myChart.getOption(); //图表object对象
|
||||||
|
var op = this.myChart.getOption();
|
||||||
|
//获得图表中点击的列
|
||||||
|
itemName = op.xAxis[0].data[handleIndex]; //获取点击的列名
|
||||||
|
console.log(itemName, 'monthmonthmonth');
|
||||||
|
console.log(handleIndex, seriesObj);
|
||||||
|
};
|
||||||
|
const baseIndex = this.baseNameToIndexMap[itemName] || 0;
|
||||||
|
|
||||||
|
console.log(`你点击了【${itemName}】(序号:${baseIndex})`);
|
||||||
|
if (itemName === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 路由跳转时携带序号(或名称+序号)
|
||||||
|
this.$router.push({
|
||||||
|
path: 'operatingRevenueBase',
|
||||||
|
query: { // 使用query传递参数(推荐),也可使用params
|
||||||
|
// baseName: itemName,
|
||||||
|
factory: baseIndex,
|
||||||
|
dateData: this.dateData
|
||||||
|
}
|
||||||
|
// 若仍需用base作为参数:
|
||||||
|
// base: itemName,
|
||||||
|
// params: { baseIndex: baseIndex }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定义resize处理函数(命名函数,方便移除)
|
||||||
|
this.resizeHandler = () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
};
|
||||||
|
// 绑定resize事件(只绑定一次)
|
||||||
|
window.addEventListener('resize', this.resizeHandler);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新图表数据(数据变化时执行)
|
||||||
|
updateChart() {
|
||||||
|
if (!this.myChart) {
|
||||||
|
return; // 实例未初始化则返回
|
||||||
|
}
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || [];
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 5,
|
||||||
|
right: 20,
|
||||||
|
left: 25,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
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],
|
||||||
|
// 可选:X轴标签显示“序号+名称”(如“1 宜兴”)
|
||||||
|
// formatter: (value) => {
|
||||||
|
// const index = this.baseNameToIndexMap[value] || '';
|
||||||
|
// return index ? `${index} ${value}` : value;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries
|
||||||
|
};
|
||||||
|
|
||||||
|
// 只更新配置,不重新创建实例
|
||||||
|
this.myChart.setOption(option, true); // 第二个参数true表示清空原有配置,避免数据残留
|
||||||
|
},
|
||||||
|
|
||||||
|
// 销毁图表资源
|
||||||
|
destroyChart() {
|
||||||
|
// 移除resize事件
|
||||||
|
if (this.resizeHandler) {
|
||||||
|
window.removeEventListener('resize', this.resizeHandler);
|
||||||
|
}
|
||||||
|
// 销毁图表实例
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
this.myChart = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 380px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
console.log('chartData', this.chartData);
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
console.log('xData', xData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom:20,
|
||||||
|
right: 10,
|
||||||
|
left: 25,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
//
|
||||||
|
splitNumber: 4,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 200px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
refName: {
|
||||||
|
type: String,
|
||||||
|
default: () => 'cockpitEffChip',
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs[this.refName];
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
console.log('chartData', this.chartData);
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
console.log('xData', xData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
// axisPointer: {
|
||||||
|
// type: 'cross',
|
||||||
|
// label: {
|
||||||
|
// backgroundColor: '#6a7985'
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 20,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 5,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
// name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true, // 显示Y轴轴线(关键)
|
||||||
|
lineStyle: {
|
||||||
|
color: '#E5E6EB', // 轴线颜色(浅灰色,可自定义)
|
||||||
|
width: 1, // 轴线宽度
|
||||||
|
type: 'solid' // 实线(可选:dashed虚线、dotted点线)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 2,
|
||||||
|
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)' } },
|
||||||
|
},
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,349 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="当月数据对比" icon="cockpitItemIcon" size="operatingLarge" topSize="large">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="left" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 348px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
">
|
||||||
|
集团情况
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{chartData.group.rate[0]}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.group.flags>0?'#30B590':'#FF9423'}" >{{chartData.group.diff[0]}}</span></span>
|
||||||
|
</div>
|
||||||
|
<operatingTopBar :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
<div class="right" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 1220px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<!-- <top-item /> -->
|
||||||
|
<operatingBar :dateData="dateData" :chartData="chartData" @sort-change="sortChange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import operatingBar from "./operatingBar.vue";
|
||||||
|
import operatingTopBar from "./operatingTopBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, operatingBar, operatingTopBar },
|
||||||
|
props: {
|
||||||
|
monthData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
group: {
|
||||||
|
rate: 0,
|
||||||
|
diff: 0,
|
||||||
|
real: 0,
|
||||||
|
target: 0
|
||||||
|
},
|
||||||
|
factory: []
|
||||||
|
}),
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: null, // 初始化 chartData 为 null
|
||||||
|
groupData: {}, // 集团数据
|
||||||
|
factoryData: [] // 工厂数据
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
monthData: {
|
||||||
|
handler() {
|
||||||
|
this.processChartData();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
sortChange(value) {
|
||||||
|
this.$emit('sort-change', value);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 判断rate对应的flag值(<1为0,>1为1)
|
||||||
|
* @param {number} rate 处理后的rate值(已*100)
|
||||||
|
* @returns {0|1} flag值
|
||||||
|
*/
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 核心处理函数:在所有数据都准备好后,才组装 chartData
|
||||||
|
*/
|
||||||
|
processChartData() {
|
||||||
|
// 1. 处理集团数据 - 提取各字段到对应数组
|
||||||
|
this.groupData = this.monthData.group || { rate: 0, diff: 0, real: 0, target: 0 };
|
||||||
|
|
||||||
|
// 集团各维度数据数组(单条数据,对应凯盛新能)
|
||||||
|
const groupTarget = [this.groupData.target]; // 预算值数组
|
||||||
|
const groupDiff = [this.groupData.diff]; // 差值数组
|
||||||
|
const groupReal = [this.groupData.real]; // 实际值数组
|
||||||
|
const groupRate = [this.groupData.rate]; // 完成率数组
|
||||||
|
// 新增:集团rate对应的flag
|
||||||
|
const groupFlag = [this.getRateFlag(groupRate[0], groupReal[0], groupTarget[0])];
|
||||||
|
|
||||||
|
console.log('集团数据数组:', {
|
||||||
|
groupTarget,
|
||||||
|
groupDiff,
|
||||||
|
groupReal,
|
||||||
|
groupRate,
|
||||||
|
groupFlag,
|
||||||
|
rawGroupData: this.groupData
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 处理工厂数据 - 提取每个工厂的对应字段到数组
|
||||||
|
this.factoryData = this.monthData.factory || [];
|
||||||
|
// 提取工厂名称数组
|
||||||
|
const factoryNames = this.factoryData.map(item => item.title || '');
|
||||||
|
// 提取工厂各维度数据数组
|
||||||
|
const factoryBudget = this.factoryData.map(item => item.budget || 0);
|
||||||
|
const factoryReal = this.factoryData.map(item => item.real || 0);
|
||||||
|
const factoryRate = this.factoryData.map(item => item.rate || 0);
|
||||||
|
const factoryDiff = this.factoryData.map(item => item.diff || 0);
|
||||||
|
// 新增:每个工厂rate对应的flag数组
|
||||||
|
const factoryFlags = this.factoryData.map(item => this.getRateFlag(item.rate, item.real, item.budget));
|
||||||
|
|
||||||
|
// 3. 组装最终的chartData(供子组件使用)
|
||||||
|
this.chartData = {
|
||||||
|
// 集团数据(对应凯盛新能)
|
||||||
|
group: {
|
||||||
|
locations: ['凯盛新能'], // 集团名称
|
||||||
|
targets: groupTarget, // 集团预算值数组
|
||||||
|
diff: groupDiff, // 集团差值数组
|
||||||
|
reals: groupReal, // 集团实际值数组
|
||||||
|
rate: groupRate, // 集团完成率数组
|
||||||
|
flags: groupFlag // 新增:集团rate对应的flag
|
||||||
|
},
|
||||||
|
// 工厂数据
|
||||||
|
factory: {
|
||||||
|
locations: factoryNames, // 工厂名称数组
|
||||||
|
targets: factoryBudget, // 工厂预算数组
|
||||||
|
reals: factoryReal, // 工厂实际值数组
|
||||||
|
rates: factoryRate, // 工厂完成率数组
|
||||||
|
diff: factoryDiff, // 工厂差值数组
|
||||||
|
flags: factoryFlags // 新增:工厂rate对应的flags数组
|
||||||
|
},
|
||||||
|
// 原始数据备份(方便后续使用)
|
||||||
|
rawData: {
|
||||||
|
group: this.groupData,
|
||||||
|
factory: this.factoryData
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('最终处理后的图表数据:', this.chartData);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</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>
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="累计数据对比" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="left" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 348px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
">
|
||||||
|
集团情况
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{chartData.group.rate[0]}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.group.flags>0?'#30B590':'#FF9423'}" >{{chartData.group.diff[0]}}</span></span>
|
||||||
|
</div>
|
||||||
|
<operatingTopBar :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
<div class="right" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 1220px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<!-- <top-item /> -->
|
||||||
|
<operatingBar :dateData="dateData" @sort-change="sortChange" :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import operatingBar from "./operatingBar.vue";
|
||||||
|
import operatingTopBar from "./operatingTopBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, operatingBar, operatingTopBar },
|
||||||
|
props: {
|
||||||
|
salesTrendMap: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
ytdData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: null, // 初始化 chartData 为 null
|
||||||
|
groupData: {}, // 集团数据
|
||||||
|
factoryData: [] // 工厂数据
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
ytdData: {
|
||||||
|
handler() {
|
||||||
|
this.processChartData();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
sortChange(value) {
|
||||||
|
this.$emit('sort-change', value);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 判断rate对应的flag值(<1为0,>1为1)
|
||||||
|
* @param {number} rate 处理后的rate值(已*100)
|
||||||
|
* @returns {0|1} flag值
|
||||||
|
*/
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心处理函数:在所有数据都准备好后,才组装 chartData
|
||||||
|
*/
|
||||||
|
processChartData() {
|
||||||
|
// 1. 处理集团数据 - 提取各字段到对应数组
|
||||||
|
this.groupData = this.ytdData.group || { rate: 0, diff: 0, real: 0, target: 0 };
|
||||||
|
|
||||||
|
// 集团各维度数据数组(单条数据,对应凯盛新能)
|
||||||
|
const groupTarget = [this.groupData.target]; // 预算值数组
|
||||||
|
const groupDiff = [this.groupData.diff]; // 差值数组
|
||||||
|
const groupReal = [this.groupData.real]; // 实际值数组
|
||||||
|
const groupRate = [this.groupData.rate]; // 完成率数组
|
||||||
|
// 新增:集团rate对应的flag
|
||||||
|
const groupFlag = [this.getRateFlag(groupRate[0], groupReal[0], groupTarget[0])];
|
||||||
|
|
||||||
|
console.log('集团数据数组:', {
|
||||||
|
groupTarget,
|
||||||
|
groupDiff,
|
||||||
|
groupReal,
|
||||||
|
groupRate,
|
||||||
|
groupFlag,
|
||||||
|
rawGroupData: this.groupData
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 处理工厂数据 - 提取每个工厂的对应字段到数组
|
||||||
|
this.factoryData = this.ytdData.factory || [];
|
||||||
|
// 提取工厂名称数组
|
||||||
|
const factoryNames = this.factoryData.map(item => item.title || '');
|
||||||
|
// 提取工厂各维度数据数组
|
||||||
|
const factoryBudget = this.factoryData.map(item => item.budget || 0);
|
||||||
|
const factoryReal = this.factoryData.map(item => item.real || 0);
|
||||||
|
const factoryRate = this.factoryData.map(item => item.rate || 0);
|
||||||
|
const factoryDiff = this.factoryData.map(item => item.diff || 0);
|
||||||
|
// 新增:每个工厂rate对应的flag数组
|
||||||
|
const factoryFlags = this.factoryData.map(item => this.getRateFlag(item.rate, item.real, item.budget));
|
||||||
|
|
||||||
|
// 3. 组装最终的chartData(供子组件使用)
|
||||||
|
this.chartData = {
|
||||||
|
// 集团数据(对应凯盛新能)
|
||||||
|
group: {
|
||||||
|
locations: ['凯盛新能'], // 集团名称
|
||||||
|
targets: groupTarget, // 集团预算值数组
|
||||||
|
diff: groupDiff, // 集团差值数组
|
||||||
|
reals: groupReal, // 集团实际值数组
|
||||||
|
rate: groupRate, // 集团完成率数组
|
||||||
|
flags: groupFlag // 新增:集团rate对应的flag
|
||||||
|
},
|
||||||
|
// 工厂数据
|
||||||
|
factory: {
|
||||||
|
locations: factoryNames, // 工厂名称数组
|
||||||
|
targets: factoryBudget, // 工厂预算数组
|
||||||
|
reals: factoryReal, // 工厂实际值数组
|
||||||
|
rates: factoryRate, // 工厂完成率数组
|
||||||
|
diff: factoryDiff, // 工厂差值数组
|
||||||
|
flags: factoryFlags // 新增:工厂rate对应的flags数组
|
||||||
|
},
|
||||||
|
// 原始数据备份(方便后续使用)
|
||||||
|
rawData: {
|
||||||
|
group: this.groupData,
|
||||||
|
factory: this.factoryData
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('最终处理后的图表数据:', this.chartData);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</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>
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
<template>
|
||||||
|
<div class="lineBottom" style="height: 180px; width: 100%">
|
||||||
|
<operatingLineBarSaleSingle :refName=" 'totalOperating' " :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBarSaleSingle from './operatingLineBarSaleSingle.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBarSaleSingle },
|
||||||
|
props: ["detailData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
locations() {
|
||||||
|
return ['预算', '实际'];
|
||||||
|
},
|
||||||
|
chartD() {
|
||||||
|
// 背景图片路径(若不需要可注释)
|
||||||
|
// const bgImageUrl = require('@/assets/img/labelBg.png');
|
||||||
|
console.log('this.detailData', this.detailData);
|
||||||
|
const rate = this.detailData?.rate || 0
|
||||||
|
const diff = this.detailData?.diff || 0
|
||||||
|
console.log('diff', diff);
|
||||||
|
|
||||||
|
const seriesData = [
|
||||||
|
{
|
||||||
|
value: this.detailData?.budget || 0,
|
||||||
|
flag: 1, // 实际项:达标(绿色)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: this.detailData?.real || 0,
|
||||||
|
flag: this.detailData?.flag, // 实际项:达标(绿色)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
allPlaceNames: ['预算', '实际'],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 60,
|
||||||
|
barCategoryGap: '50%',
|
||||||
|
data: seriesData,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const currentFlag = params.data.flag || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
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)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
console.log('data', data);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFangSC;
|
||||||
|
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;
|
||||||
|
margin-right: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
width: 283px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0b58ff;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 8px;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button.active {
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
background: #3071ff;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
337
src/views/home/accountsReceivableComponents/operatingTopBar.vue
Normal file
337
src/views/home/accountsReceivableComponents/operatingTopBar.vue
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<div class="lineBottom" style="height: 320px; width: 100%">
|
||||||
|
<operatingLineBar :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSaleGroup.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentDataSource() {
|
||||||
|
console.log('yyyy', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData.group
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
console.log('this.chartData', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData.group.locations
|
||||||
|
},
|
||||||
|
// 根据按钮切换生成对应的 chartData
|
||||||
|
chartD() {
|
||||||
|
// 销量场景数据
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
const diff = this.currentDataSource.diff[0]
|
||||||
|
const rate = this.currentDataSource.rate[0]
|
||||||
|
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.targets // 目标销量(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图,含达标状态)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||||
|
const safeFlag = data.flags;
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
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)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals // 实际销量(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 毛利率场景数据
|
||||||
|
const grossProfitData = {
|
||||||
|
series: [
|
||||||
|
// 1. 完成率(折线图)
|
||||||
|
{
|
||||||
|
name: '完成率',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
lineStyle: { color: 'rgba(40, 138, 255, .5)', width: 2 },
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderColor: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
radius: 4
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.2,
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||||
|
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: [106.7, 96.9, 106.5, 106.1, 93.8, 105.9], // 毛利率完成率(%)
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 2. 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: [30, 32, 31, 33, 32, 34] // 目标毛利率(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const safeFlag = [1, 0, 1, 1, 0, 1]; // 达标状态
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
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)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: [32, 31, 33, 35, 30, 36] // 实际毛利率(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据按钮状态返回对应数据
|
||||||
|
return this.activeButton === 0 ? salesData : grossProfitData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end; // 标题左、右侧容器右,整体两端对齐
|
||||||
|
align-items: center; // 垂直居中,避免上下错位
|
||||||
|
gap: 16px; // 标题与右侧容器的最小间距,防止拥挤
|
||||||
|
width: 100%; // 确保占满父容器,实现两端对齐
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 标题固定在左侧,不挤压右侧空间
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; // 图例和按钮组垂直居中
|
||||||
|
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||||
|
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 图例:在右侧容器内横向排列
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px; // 图例项之间间距,避免重叠
|
||||||
|
align-items: center;
|
||||||
|
// 移除原margin-left,避免位置偏移
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap; // 防止图例文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图例颜色
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
width: 283px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
border-radius: 12px;
|
||||||
|
// 移除原margin-right,由右侧容器统一控制
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0b58ff;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 8px; // 确保文字间距生效
|
||||||
|
padding-left: 8px; // 抵消letter-spacing导致的文字左偏
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button.active {
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
background: #3071ff;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
202
src/views/home/accountsReceivableComponents/totalOverview.vue
Normal file
202
src/views/home/accountsReceivableComponents/totalOverview.vue
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" 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; gap: 8px;">
|
||||||
|
<div class="dashboard">
|
||||||
|
<div class="title">
|
||||||
|
累计完成率
|
||||||
|
</div>
|
||||||
|
<div class="number">
|
||||||
|
<div class="yield">
|
||||||
|
{{ ytdData?.rate || 0}}%
|
||||||
|
</div>
|
||||||
|
<div class="mom">
|
||||||
|
同比{{ ytdData?.yoyRate || 0}}%
|
||||||
|
<img v-if="ytdData?.yoyRate >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="">
|
||||||
|
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="electricityGauge">
|
||||||
|
<electricityGauge :id=" 'totalG' " :detailData="ytdData" id="totalGauge"></electricityGauge>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
<div class="line" style="padding: 0px;">
|
||||||
|
<verticalBarChart :refName=" 'totalVerticalBarChart' " :detailData="ytdData">
|
||||||
|
</verticalBarChart>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import electricityGauge from './electricityGauge.vue'
|
||||||
|
import verticalBarChart from './verticalBarChart.vue'
|
||||||
|
|
||||||
|
// import * as echarts from 'echarts'
|
||||||
|
// import rawItem from './raw-Item.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, electricityGauge, verticalBarChart },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
ytdData: { // 接收父组件传递的设备数据数组
|
||||||
|
type: Object,
|
||||||
|
default: () => {} // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
title: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
month: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
itemData: {
|
||||||
|
handler(newValue, oldValue) {
|
||||||
|
// this.updateChart()
|
||||||
|
},
|
||||||
|
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// computed: {
|
||||||
|
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||||
|
// sortedItemData() {
|
||||||
|
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||||
|
// const totalCostItems = this.itemData.filter(item =>
|
||||||
|
// item.name && item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 过滤出不包含“总成本”的项
|
||||||
|
// const otherItems = this.itemData.filter(item =>
|
||||||
|
// !item.name || !item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 合并:总成本项在前,其他项在后
|
||||||
|
// return [...totalCostItems, ...otherItems];
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 264px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
.title {
|
||||||
|
// width: 190px;
|
||||||
|
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;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 46px;
|
||||||
|
color: #0B58FF;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
width: 500px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .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> -->
|
||||||
226
src/views/home/accountsReceivableComponents/verticalBarChart.vue
Normal file
226
src/views/home/accountsReceivableComponents/verticalBarChart.vue
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
<template>
|
||||||
|
<div style="width: 100%; height: 210px;position: relative;">
|
||||||
|
<div style='font-size: 16px;position: absolute;right: 20px;top:10px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{detailData.rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:detailData.flags>0?'#30B590':'#FF9423'}" >{{detailData.diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 210px;"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
refName: {
|
||||||
|
type: String,
|
||||||
|
default: () => 'verticalBarChart',
|
||||||
|
},
|
||||||
|
detailData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
detailData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs[this.refName];
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const diff = this.detailData.diff || 0
|
||||||
|
const rate = this.detailData.rate || 0
|
||||||
|
const flagValue = this.getRateFlag(this.detailData.rate, this.detailData.real, this.detailData.target) || 0
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 40,
|
||||||
|
bottom: 15,
|
||||||
|
right: 80,
|
||||||
|
left: 10,
|
||||||
|
containLabel: true,
|
||||||
|
show: false // 隐藏grid背景,避免干扰
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
// 横向柱状图的x轴必须设为数值轴,否则无法正常展示数值
|
||||||
|
type: 'value',
|
||||||
|
// offset: 0,
|
||||||
|
// boundaryGap: true ,
|
||||||
|
// boundaryGap: [10, 0], // 可根据需要开启,控制轴的留白
|
||||||
|
axisTick: { show: false },
|
||||||
|
min: 0,
|
||||||
|
//
|
||||||
|
splitNumber: 4,
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
// data: xData // 数值轴不需要手动设置data,由series的数据自动生成
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'category',
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.75)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true, // 显示Y轴轴线(关键)
|
||||||
|
lineStyle: {
|
||||||
|
color: '#E5E6EB', // 轴线颜色(浅灰色,可自定义)
|
||||||
|
width: 1, // 轴线宽度
|
||||||
|
type: 'solid' // 实线(可选:dashed虚线、dotted点线)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisTick: { show: false },
|
||||||
|
// padding: [300, 100, 100, 100],
|
||||||
|
data: ['实际', '预算'] // y轴分类:实际、预算
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
// name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 24,
|
||||||
|
// barCategoryGap: '50', // 柱子之间的间距(相对于柱子宽度)
|
||||||
|
// 数据长度与yAxis的分类数量匹配(实际、预算各一个值)
|
||||||
|
data: [{
|
||||||
|
value: this.detailData.real,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'right',
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: flagValue === 1
|
||||||
|
? {
|
||||||
|
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)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0]
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
value: this.detailData.target,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'right',
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
// 预算的渐变颜色(蓝系渐变)
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 1, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: '#82CCFF' }, // 浅蓝
|
||||||
|
{ offset: 1, color: '#4B9DFF' } // 深蓝
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
},],
|
||||||
|
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px; width: 100%;">
|
||||||
|
<!-- 销量模块(直接传递整合了flag的salesData) -->
|
||||||
|
<div class="dashboard left" @click="handleDashboardClick('/salesVolumeAnalysis/salesVolumeAnalysisBase')">
|
||||||
|
<div style='position: relative;'>
|
||||||
|
<div class="title">
|
||||||
|
销量·万㎡
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{ytdAnalysis[0].rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:ytdAnalysis[0].flags>0?'#30B590':'#FF9423'}" >{{ytdAnalysis[0].diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-wrap">
|
||||||
|
<operatingSingleBar :detailData="salesData"></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 单价模块(直接传递整合了flag的unitPriceData) -->
|
||||||
|
<div class="dashboard right" @click="handleDashboardClick('/unitPriceAnalysis/unitPriceAnalysisBase')">
|
||||||
|
<div style='position: relative;'>
|
||||||
|
<div class="title">
|
||||||
|
单价·元/㎡
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{ytdAnalysis[1].rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:ytdAnalysis[1].flags>0?'#30B590':'#FF9423'}" >{{ytdAnalysis[1].diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-wrap">
|
||||||
|
<operatingSingleBar :detailData="unitPriceData"></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import operatingSingleBar from './operatingSingleBar.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, operatingSingleBar },
|
||||||
|
props: {
|
||||||
|
ytdAnalysis: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [
|
||||||
|
{ title: "销量", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
{ title: "单价", budget: 0, real: 0, rate: 0, diff: 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
month: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
// 初始化数据包含flag字段
|
||||||
|
salesData: { title: "销量", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 },
|
||||||
|
unitPriceData: { title: "单价", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
ytdAnalysis: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.updateChart(newVal)
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateChart(this.ytdAnalysis)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleDashboardClick(path) {
|
||||||
|
this.$router.push({
|
||||||
|
path: path,
|
||||||
|
query: {
|
||||||
|
factory: this.$route.query.factory ? this.$route.query.factory : 5,
|
||||||
|
dateData: this.dateData
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 判断flag的核心方法
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateChart(data) {
|
||||||
|
// 数据兜底
|
||||||
|
const salesItem = Array.isArray(data) && data[0] ? data[0] : { title: "销量", budget: 0, real: 0, rate: 0, diff: 0 };
|
||||||
|
const unitPriceItem = Array.isArray(data) && data[1] ? data[1] : { title: "单价", budget: 0, real: 0, rate: 0, diff: 0 };
|
||||||
|
|
||||||
|
// 核心修改:将flag整合到数据对象中,无需单独定义salesFlag/unitPriceFlag
|
||||||
|
this.salesData = {
|
||||||
|
...salesItem, // 合并原有字段
|
||||||
|
flag: this.getRateFlag(salesItem.rate, salesItem.real, salesItem.budget) // 新增flag字段
|
||||||
|
};
|
||||||
|
|
||||||
|
this.unitPriceData = {
|
||||||
|
...unitPriceItem, // 合并原有字段
|
||||||
|
flag: this.getRateFlag(unitPriceItem.rate, unitPriceItem.real, unitPriceItem.budget) // 新增flag字段
|
||||||
|
};
|
||||||
|
|
||||||
|
// 调试:确认整合后的数据
|
||||||
|
console.log('整合flag后的销量数据:', this.salesData);
|
||||||
|
console.log('整合flag后的单价数据:', this.unitPriceData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topItem-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 300px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
margin: 0 4px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-wrap {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 30px;
|
||||||
|
height: 32px;
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
width: 97px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard.left {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard.right {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<el-form ref="form" :rules="rules" label-width="110px" :model="form">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="客户名称" prop="name">
|
||||||
|
<el-input v-model="form.name"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="客户编码" prop="code">
|
||||||
|
<el-input v-model="form.code"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="所属基地" prop="daySpan">
|
||||||
|
<el-select v-model="form.daySpan" placeholder="请选择" style="width: 100%;">
|
||||||
|
<el-option label="否" :value= '0' ></el-option>
|
||||||
|
<el-option label="是" :value= '1' ></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
// import { getGroupClasses, updateGroupClasses, createGroupClasses, getCode } from '@/api/base/groupClasses'
|
||||||
|
export default {
|
||||||
|
name: 'CustomerInfoAdd',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
code: '',
|
||||||
|
daySpan:'',
|
||||||
|
},
|
||||||
|
isEdit: false, //是否是编辑
|
||||||
|
rules: {
|
||||||
|
name: [{ required: true, message: '请输入客户名称', trigger: 'blur' }],
|
||||||
|
code: [{ required: true, message: '请输入客户编码', trigger: 'blur' }],
|
||||||
|
daySpan: [{ required: true, message: '请选择所属基地', trigger: 'change' }],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
init(id) {
|
||||||
|
if (id) {
|
||||||
|
this.isEdit = true
|
||||||
|
this.form.id = id
|
||||||
|
// getGroupClasses(id).then((res) => {
|
||||||
|
// if (res.code === 0) {
|
||||||
|
// this.form = res.data
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitForm() {
|
||||||
|
this.$refs['form'].validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
if (this.isEdit) {
|
||||||
|
//编辑
|
||||||
|
updateGroupClasses({ ...this.form }).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$modal.msgSuccess("操作成功");
|
||||||
|
this.$emit('successSubmit')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
createGroupClasses({ ...this.form }).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$modal.msgSuccess("操作成功");
|
||||||
|
this.$emit('successSubmit')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
formClear() {
|
||||||
|
this.$refs.form.resetFields()
|
||||||
|
this.isEdit = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
101
src/views/home/basicInfoConfiguration/components/groupKeyAdd.vue
Normal file
101
src/views/home/basicInfoConfiguration/components/groupKeyAdd.vue
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<template>
|
||||||
|
<el-form ref="form" :rules="rules" label-width="120px" :model="form">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="重点工作" prop="name">
|
||||||
|
<el-input v-model="form.name"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="单位" prop="daySpan">
|
||||||
|
<el-select v-model="form.daySpan" placeholder="请选择" style="width: 100%;">
|
||||||
|
<el-option label="个" :value= '0' ></el-option>
|
||||||
|
<el-option label="件" :value= '1' ></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="所属年份" prop="daySpan1">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="value3"
|
||||||
|
type="year"
|
||||||
|
placeholder="选择年"
|
||||||
|
style="width: 100%;">
|
||||||
|
</el-date-picker>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="累计值计算方式" prop="daySpan2">
|
||||||
|
<el-select v-model="form.daySpan" placeholder="请选择" style="width: 100%;">
|
||||||
|
<el-option label="月" :value= '0' ></el-option>
|
||||||
|
<el-option label="日" :value= '1' ></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
// import { getGroupClasses, updateGroupClasses, createGroupClasses, getCode } from '@/api/base/groupClasses'
|
||||||
|
export default {
|
||||||
|
name: 'groupKeyAdd',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
code: '',
|
||||||
|
daySpan:'',
|
||||||
|
},
|
||||||
|
isEdit: false, //是否是编辑
|
||||||
|
rules: {
|
||||||
|
name: [{ required: true, message: '请输入重点工作', trigger: 'blur' }],
|
||||||
|
daySpan: [{ required: true, message: '请选择单位', trigger: 'change' }],
|
||||||
|
daySpan1: [{ required: true, message: '请选择所属年份', trigger: 'change' }],
|
||||||
|
daySpan2: [{ required: true, message: '请选择累计值计算方式', trigger: 'change' }],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
init(id) {
|
||||||
|
if (id) {
|
||||||
|
this.isEdit = true
|
||||||
|
this.form.id = id
|
||||||
|
// getGroupClasses(id).then((res) => {
|
||||||
|
// if (res.code === 0) {
|
||||||
|
// this.form = res.data
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitForm() {
|
||||||
|
this.$refs['form'].validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
if (this.isEdit) {
|
||||||
|
//编辑
|
||||||
|
updateGroupClasses({ ...this.form }).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$modal.msgSuccess("操作成功");
|
||||||
|
this.$emit('successSubmit')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
createGroupClasses({ ...this.form }).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$modal.msgSuccess("操作成功");
|
||||||
|
this.$emit('successSubmit')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
formClear() {
|
||||||
|
this.$refs.form.resetFields()
|
||||||
|
this.isEdit = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<el-form ref="form" :rules="rules" label-width="110px" :model="form">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="产品名称" prop="name">
|
||||||
|
<el-input v-model="form.name"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="规格" prop="code">
|
||||||
|
<el-input v-model="form.code"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="工艺" prop="daySpan">
|
||||||
|
<el-select v-model="form.daySpan" placeholder="请选择" style="width: 100%;">
|
||||||
|
<el-option label="否" :value= '0' ></el-option>
|
||||||
|
<el-option label="是" :value= '1' ></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
// import { getGroupClasses, updateGroupClasses, createGroupClasses, getCode } from '@/api/base/groupClasses'
|
||||||
|
export default {
|
||||||
|
name: 'ProductInfoAdd',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
code: '',
|
||||||
|
daySpan:'',
|
||||||
|
},
|
||||||
|
isEdit: false, //是否是编辑
|
||||||
|
rules: {
|
||||||
|
name: [{ required: true, message: '请输入产品名称', trigger: 'blur' }],
|
||||||
|
code: [{ required: true, message: '请输入产品规格', trigger: 'blur' }],
|
||||||
|
daySpan: [{ required: true, message: '请选择工艺', trigger: 'change' }],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
init(id) {
|
||||||
|
if (id) {
|
||||||
|
this.isEdit = true
|
||||||
|
this.form.id = id
|
||||||
|
// getGroupClasses(id).then((res) => {
|
||||||
|
// if (res.code === 0) {
|
||||||
|
// this.form = res.data
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitForm() {
|
||||||
|
this.$refs['form'].validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
if (this.isEdit) {
|
||||||
|
//编辑
|
||||||
|
updateGroupClasses({ ...this.form }).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$modal.msgSuccess("操作成功");
|
||||||
|
this.$emit('successSubmit')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
createGroupClasses({ ...this.form }).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$modal.msgSuccess("操作成功");
|
||||||
|
this.$emit('successSubmit')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
formClear() {
|
||||||
|
this.$refs.form.resetFields()
|
||||||
|
this.isEdit = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<search-bar :formConfigs="formConfig" ref="searchBarForm" @headBtnClick="buttonClick" />
|
||||||
|
<base-table v-if="tableData.length" class="right-aside" :table-props="tableProps"
|
||||||
|
:page="listQuery.pageNo" :limit="listQuery.pageSize" :table-data="tableData">
|
||||||
|
<method-btn v-if="tableBtn.length" slot="handleBtn" :width="200" label="操作" :method-list="tableBtn"
|
||||||
|
@clickBtn="handleClick" />
|
||||||
|
</base-table>
|
||||||
|
<pagination :limit.sync="listQuery.pageSize" :page.sync="listQuery.pageNo" :total="total" @pagination="getDataList" :background="true" />
|
||||||
|
<!-- 新增 -->
|
||||||
|
<base-dialog
|
||||||
|
:dialogTitle="addOrEditTitle"
|
||||||
|
:dialogVisible="centervisible"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@confirm="handleConfirm"
|
||||||
|
:before-close="handleCancel"
|
||||||
|
width="50%">
|
||||||
|
<customer-info-add ref="customerList" @successSubmit="successSubmit" />
|
||||||
|
</base-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import CustomerInfoAdd from './components/customerInfoAdd.vue';
|
||||||
|
const tableProps = [
|
||||||
|
{
|
||||||
|
prop: 'name',
|
||||||
|
label: '客户名称'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'code',
|
||||||
|
label: '客户编码'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'jd',
|
||||||
|
label: '所属基地'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export default {
|
||||||
|
name: 'CustomerInfoConfiguration',
|
||||||
|
components:{CustomerInfoAdd},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
formConfig: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '客户名称',
|
||||||
|
placeholder: '客户名称',
|
||||||
|
param: 'cName'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '客户编码',
|
||||||
|
placeholder: '客户编码',
|
||||||
|
param: 'ccode'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
btnName: '查询',
|
||||||
|
name: 'search',
|
||||||
|
color: 'primary',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
btnName: '新增',
|
||||||
|
name: 'add',
|
||||||
|
color: 'success',
|
||||||
|
plain: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
listQuery:{
|
||||||
|
pageSize:20,
|
||||||
|
pageNo:1
|
||||||
|
},
|
||||||
|
total:2,
|
||||||
|
tableData:[{name:'111',id:1}],
|
||||||
|
tableProps,
|
||||||
|
tableBtn:[
|
||||||
|
{
|
||||||
|
type: 'edit',
|
||||||
|
btnName: '编辑',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'delete',
|
||||||
|
btnName: '删除',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
addOrEditTitle: '',
|
||||||
|
centervisible: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
buttonClick(val) {
|
||||||
|
console.log(val)
|
||||||
|
switch (val.btnName) {
|
||||||
|
case 'search':
|
||||||
|
this.listQuery.pageNo = 1;
|
||||||
|
this.getList();
|
||||||
|
break;
|
||||||
|
case 'add':
|
||||||
|
this.addOrEditTitle = '新增';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.customerList.init();
|
||||||
|
});
|
||||||
|
this.centervisible = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getDataList() {
|
||||||
|
|
||||||
|
},
|
||||||
|
handleClick(val) {
|
||||||
|
switch (val.type) {
|
||||||
|
case 'edit':
|
||||||
|
this.addOrEditTitle = '编辑';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.customerList.init(val.data.id);
|
||||||
|
});
|
||||||
|
this.centervisible = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.handleDelete(val.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleCancel() {
|
||||||
|
this.$refs.customerList.formClear();
|
||||||
|
this.centervisible = false;
|
||||||
|
this.addOrEditTitle = '';
|
||||||
|
},
|
||||||
|
handleConfirm() {
|
||||||
|
this.$refs.customerList.submitForm();
|
||||||
|
},
|
||||||
|
successSubmit() {
|
||||||
|
this.handleCancel();
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
handleDelete(row) {
|
||||||
|
this.$modal.confirm('确定删除客户"' + row.name + '吗?').then(function() {
|
||||||
|
deleteModel(row.id).then(response => {
|
||||||
|
that.getList();
|
||||||
|
that.$modal.msgSuccess("删除成功");
|
||||||
|
})
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<search-bar :formConfigs="formConfig" ref="searchBarForm" @headBtnClick="buttonClick" />
|
||||||
|
<base-table v-if="tableData.length" class="right-aside" :table-props="tableProps"
|
||||||
|
:page="listQuery.pageNo" :limit="listQuery.pageSize" :table-data="tableData">
|
||||||
|
<method-btn v-if="tableBtn.length" slot="handleBtn" :width="200" label="操作" :method-list="tableBtn"
|
||||||
|
@clickBtn="handleClick" />
|
||||||
|
</base-table>
|
||||||
|
<pagination :limit.sync="listQuery.pageSize" :page.sync="listQuery.pageNo" :total="total" @pagination="getDataList" :background="true" />
|
||||||
|
<!-- 新增 -->
|
||||||
|
<base-dialog
|
||||||
|
:dialogTitle="addOrEditTitle"
|
||||||
|
:dialogVisible="centervisible"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@confirm="handleConfirm"
|
||||||
|
:before-close="handleCancel"
|
||||||
|
width="50%">
|
||||||
|
<group-key-add ref="groupKey" @successSubmit="successSubmit" />
|
||||||
|
</base-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import GroupKeyAdd from './components/groupKeyAdd.vue';
|
||||||
|
const tableProps = [
|
||||||
|
{
|
||||||
|
prop: 'name',
|
||||||
|
label: '重点工作'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'code1',
|
||||||
|
label: '单位'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'code',
|
||||||
|
label: '所属年份'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'jd',
|
||||||
|
label: '累计值计算方式'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export default {
|
||||||
|
name: 'GroupKeyTaskConfiguration',
|
||||||
|
components:{GroupKeyAdd},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
formConfig: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '重点工作',
|
||||||
|
placeholder: '重点工作',
|
||||||
|
param: 'cName'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'datePicker',
|
||||||
|
label: '所属年份',
|
||||||
|
dateType: 'year',
|
||||||
|
format: 'yyyy',
|
||||||
|
valueFormat: 'yyyy',
|
||||||
|
placeholder: '所属年份',
|
||||||
|
param: 'searchTime1',
|
||||||
|
width: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
btnName: '查询',
|
||||||
|
name: 'search',
|
||||||
|
color: 'primary',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
btnName: '新增',
|
||||||
|
name: 'add',
|
||||||
|
color: 'success',
|
||||||
|
plain: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
listQuery:{
|
||||||
|
pageSize:20,
|
||||||
|
pageNo:1
|
||||||
|
},
|
||||||
|
total:2,
|
||||||
|
tableData:[{name:'111'}],
|
||||||
|
tableProps,
|
||||||
|
tableBtn:[
|
||||||
|
{
|
||||||
|
type: 'edit',
|
||||||
|
btnName: '编辑',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'delete',
|
||||||
|
btnName: '删除',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
addOrEditTitle: '',
|
||||||
|
centervisible: false,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
buttonClick(val) {
|
||||||
|
console.log(val)
|
||||||
|
switch (val.btnName) {
|
||||||
|
case 'search':
|
||||||
|
this.listQuery.pageNo = 1;
|
||||||
|
this.getList();
|
||||||
|
break;
|
||||||
|
case 'add':
|
||||||
|
this.addOrEditTitle = '新增';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.groupKey.init();
|
||||||
|
});
|
||||||
|
this.centervisible = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getDataList() {
|
||||||
|
|
||||||
|
},
|
||||||
|
handleClick(val) {
|
||||||
|
switch (val.type) {
|
||||||
|
case 'edit':
|
||||||
|
this.addOrEditTitle = '编辑';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.groupKey.init(val.data.id);
|
||||||
|
});
|
||||||
|
this.centervisible = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.handleDelete(val.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleCancel() {
|
||||||
|
this.$refs.groupKey.formClear();
|
||||||
|
this.centervisible = false;
|
||||||
|
this.addOrEditTitle = '';
|
||||||
|
},
|
||||||
|
handleConfirm() {
|
||||||
|
this.$refs.groupKey.submitForm();
|
||||||
|
},
|
||||||
|
successSubmit() {
|
||||||
|
this.handleCancel();
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
handleDelete(row) {
|
||||||
|
this.$modal.confirm('确定删除重点工作"' + row.name + '吗?').then(function() {
|
||||||
|
deleteModel(row.id).then(response => {
|
||||||
|
that.getList();
|
||||||
|
that.$modal.msgSuccess("删除成功");
|
||||||
|
})
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<search-bar :formConfigs="formConfig" ref="searchBarForm" @headBtnClick="buttonClick" />
|
||||||
|
<base-table v-if="tableData.length" class="right-aside" :table-props="tableProps"
|
||||||
|
:page="listQuery.pageNo" :limit="listQuery.pageSize" :table-data="tableData">
|
||||||
|
<method-btn v-if="tableBtn.length" slot="handleBtn" :width="200" label="操作" :method-list="tableBtn"
|
||||||
|
@clickBtn="handleClick" />
|
||||||
|
</base-table>
|
||||||
|
<pagination :limit.sync="listQuery.pageSize" :page.sync="listQuery.pageNo" :total="total" @pagination="getDataList" :background="true" />
|
||||||
|
<!-- 新增 -->
|
||||||
|
<base-dialog
|
||||||
|
:dialogTitle="addOrEditTitle"
|
||||||
|
:dialogVisible="centervisible"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@confirm="handleConfirm"
|
||||||
|
:before-close="handleCancel"
|
||||||
|
width="50%">
|
||||||
|
<product-info-add ref="prodectInfo" @successSubmit="successSubmit" />
|
||||||
|
</base-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ProductInfoAdd from './components/productInfoAdd.vue';
|
||||||
|
const tableProps = [
|
||||||
|
{
|
||||||
|
prop: 'name',
|
||||||
|
label: '产品名称'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'code',
|
||||||
|
label: '工艺'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'jd',
|
||||||
|
label: '规格'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export default {
|
||||||
|
name: 'ProductInfoConfiguration',
|
||||||
|
components:{ProductInfoAdd},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
formConfig: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '产品名称',
|
||||||
|
placeholder: '产品名称',
|
||||||
|
param: 'cName'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
btnName: '查询',
|
||||||
|
name: 'search',
|
||||||
|
color: 'primary',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
btnName: '新增',
|
||||||
|
name: 'add',
|
||||||
|
color: 'success',
|
||||||
|
plain: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
listQuery:{
|
||||||
|
pageSize:20,
|
||||||
|
pageNo:1
|
||||||
|
},
|
||||||
|
total:2,
|
||||||
|
tableData:[{name:'111'}],
|
||||||
|
tableProps,
|
||||||
|
tableBtn:[
|
||||||
|
{
|
||||||
|
type: 'edit',
|
||||||
|
btnName: '编辑',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'delete',
|
||||||
|
btnName: '删除',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
addOrEditTitle: '',
|
||||||
|
centervisible: false,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
buttonClick(val) {
|
||||||
|
console.log(val)
|
||||||
|
switch (val.btnName) {
|
||||||
|
case 'search':
|
||||||
|
this.listQuery.pageNo = 1;
|
||||||
|
this.getList();
|
||||||
|
break;
|
||||||
|
case 'add':
|
||||||
|
this.addOrEditTitle = '新增';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.prodectInfo.init();
|
||||||
|
});
|
||||||
|
this.centervisible = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getDataList() {
|
||||||
|
|
||||||
|
},
|
||||||
|
handleClick(val) {
|
||||||
|
switch (val.type) {
|
||||||
|
case 'edit':
|
||||||
|
this.addOrEditTitle = '编辑';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.prodectInfo.init(val.data.id);
|
||||||
|
});
|
||||||
|
this.centervisible = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.handleDelete(val.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleCancel() {
|
||||||
|
this.$refs.prodectInfo.formClear();
|
||||||
|
this.centervisible = false;
|
||||||
|
this.addOrEditTitle = '';
|
||||||
|
},
|
||||||
|
handleConfirm() {
|
||||||
|
this.$refs.prodectInfo.submitForm();
|
||||||
|
},
|
||||||
|
successSubmit() {
|
||||||
|
this.handleCancel();
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
handleDelete(row) {
|
||||||
|
this.$modal.confirm('确定删除产品"' + row.name + '吗?').then(function() {
|
||||||
|
deleteModel(row.id).then(response => {
|
||||||
|
that.getList();
|
||||||
|
that.$modal.msgSuccess("删除成功");
|
||||||
|
})
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -39,7 +39,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
// 图表样式配置项,可以抽离出来方便管理
|
// 图表样式配置项,可以抽离出来方便管理
|
||||||
chartConfig: {
|
chartConfig: {
|
||||||
manageCost: {
|
'管理费用': {
|
||||||
name: '管理费用',
|
name: '管理费用',
|
||||||
lineColor: 'rgba(11, 88, 255, .5)',
|
lineColor: 'rgba(11, 88, 255, .5)',
|
||||||
itemColor: 'rgba(11, 88, 255, .5)',
|
itemColor: 'rgba(11, 88, 255, .5)',
|
||||||
@@ -49,7 +49,7 @@ export default {
|
|||||||
{ offset: 1, color: 'rgba(18, 255, 245, 0)' },
|
{ offset: 1, color: 'rgba(18, 255, 245, 0)' },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
saleCost: {
|
'销售费用': {
|
||||||
name: '销售费用',
|
name: '销售费用',
|
||||||
lineColor: 'rgba(54, 181, 138, .5)',
|
lineColor: 'rgba(54, 181, 138, .5)',
|
||||||
itemColor: 'rgba(54, 181, 138, .5)',
|
itemColor: 'rgba(54, 181, 138, .5)',
|
||||||
@@ -59,7 +59,7 @@ export default {
|
|||||||
{ offset: 1, color: 'rgba(18, 255, 245, 0)' },
|
{ offset: 1, color: 'rgba(18, 255, 245, 0)' },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
financeCost: {
|
'财务费用': {
|
||||||
name: '财务费用',
|
name: '财务费用',
|
||||||
lineColor: 'rgba(255, 132, 0, .5)',
|
lineColor: 'rgba(255, 132, 0, .5)',
|
||||||
itemColor: 'rgba(255, 132, 0, .5)',
|
itemColor: 'rgba(255, 132, 0, .5)',
|
||||||
@@ -78,7 +78,7 @@ export default {
|
|||||||
// 从 cost.line 中获取任意一个有数据的键的 keys 作为 X 轴
|
// 从 cost.line 中获取任意一个有数据的键的 keys 作为 X 轴
|
||||||
const lineData = this.line || {};
|
const lineData = this.line || {};
|
||||||
const firstKey = Object.keys(lineData)[0];
|
const firstKey = Object.keys(lineData)[0];
|
||||||
return firstKey ? Object.keys(lineData[firstKey]) : [];
|
return firstKey ? Object.keys(lineData[firstKey].real) : [];
|
||||||
},
|
},
|
||||||
// 动态生成 series 数据
|
// 动态生成 series 数据
|
||||||
chartSeries() {
|
chartSeries() {
|
||||||
@@ -94,7 +94,7 @@ export default {
|
|||||||
return Object.keys(this.chartConfig).map(key => {
|
return Object.keys(this.chartConfig).map(key => {
|
||||||
const config = this.chartConfig[key];
|
const config = this.chartConfig[key];
|
||||||
// 确保数据顺序和 X 轴一致
|
// 确保数据顺序和 X 轴一致
|
||||||
const dataValues = xAxisKeys.map(date => lineData[key] ? lineData[key][date] : 0);
|
const dataValues = xAxisKeys.map(date => lineData[key] ? lineData[key].real[date] : 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: config.name,
|
name: config.name,
|
||||||
|
|||||||
@@ -90,7 +90,7 @@
|
|||||||
</el-upload>
|
</el-upload>
|
||||||
<div slot="footer" class="dialog-footer">
|
<div slot="footer" class="dialog-footer">
|
||||||
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
||||||
<el-button @click="cancelBtn">取 消</el-button>
|
<el-button @click="upload.open = false">取 消</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</Container>
|
</Container>
|
||||||
@@ -512,10 +512,6 @@ export default {
|
|||||||
this.$message.error('上传失败!')
|
this.$message.error('上传失败!')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cancelBtn() {
|
|
||||||
this.upload.open = false
|
|
||||||
this.$refs.upload.clearFiles();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ export default {
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 5%;
|
right: 5%;
|
||||||
top: 10%;
|
top: 25px;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div style="flex: 1">
|
<div style="flex: 1">
|
||||||
<Container name="销售重点指标" icon="cockpitItemIcon" size="topBasic" topSize="basic">
|
<Container name="销售重点指标" icon="cockpitItemIcon" size="topBasic" topSize="basic" :isShowTab='true' @tabChange='tabChange'>
|
||||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
<div class="kpi-content" style="padding: 14px 16px; display: flex;flex-direction: column; width: 100%;">
|
<div class="kpi-content" style="padding: 14px 16px; display: flex;flex-direction: column; width: 100%;">
|
||||||
<!-- 2. .top 保持 flex,无需固定高度,自动跟随子元素拉伸 -->
|
<!-- 2. .top 保持 flex,无需固定高度,自动跟随子元素拉伸 -->
|
||||||
<div class="top" style="display: flex; width: 100%;">
|
<div class="top" style="display: flex; width: 100%;">
|
||||||
<top-item :sale="sale" :dateData="dateData" />
|
<top-item :sale="saleData" :dateData="dateData" />
|
||||||
</div>
|
</div>
|
||||||
<div class="bottom"
|
<div class="bottom"
|
||||||
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
||||||
@@ -39,16 +39,39 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
chart: null
|
chart: null,
|
||||||
|
saleData:{},
|
||||||
|
currentTap:'month'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
sale: {
|
||||||
|
handler(newVal) {
|
||||||
|
if(this.currentTap === 'month') {
|
||||||
|
this.saleData = this.sale.mon
|
||||||
|
}else{
|
||||||
|
this.saleData = this.sale.total
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||||
// this.$nextTick(() => this.updateChart())
|
// this.$nextTick(() => this.updateChart())
|
||||||
|
this.saleData = this.sale.month
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
tabChange(val) {
|
||||||
|
if(val === 'month') {
|
||||||
|
this.currentTap = 'month'
|
||||||
|
this.saleData = this.sale.mon
|
||||||
|
}else{
|
||||||
|
this.currentTap = 'total'
|
||||||
|
this.saleData = this.sale.total
|
||||||
|
}
|
||||||
|
console.log(this.saleData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export default {
|
|||||||
label: { backgroundColor: '#6a7985' }
|
label: { backgroundColor: '#6a7985' }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
grid: { top: 35, bottom: 20, right: 25, left: 70 },
|
grid: { top: 35, bottom: 3, right: 15, left: 10, containLabel: true},
|
||||||
xAxis: [
|
xAxis: [
|
||||||
{
|
{
|
||||||
type: 'category',
|
type: 'category',
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<div class="number">{{ item.targetValue }}</div>
|
<div class="number">{{ item.targetValue }}</div>
|
||||||
<div class="title">上月</div>
|
<div class="title">{{currentTap==='month'?'上月':'上年'}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
<div class="number" :style="{ color: getColor(item.currentValue, item.targetValue) }">
|
<div class="number" :style="{ color: getColor(item.currentValue, item.targetValue) }">
|
||||||
{{ item.currentValue }}
|
{{ item.currentValue }}
|
||||||
</div>
|
</div>
|
||||||
<div class="title">本月</div>
|
<div class="title">{{currentTap==='month'?'本月':'本年'}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
@@ -46,6 +46,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
},
|
},
|
||||||
|
currentTap: {
|
||||||
|
type: String,
|
||||||
|
default: 'month'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div style="flex: 1">
|
<div style="flex: 1">
|
||||||
<!-- 传入点击切换的状态到Container组件 -->
|
<!-- 传入点击切换的状态到Container组件 -->
|
||||||
<Container name="财务重点指标" nameTwo="费用重点指标" icon="cockpitItemIcon" size="topBasic" @switchTab="handleTabSwitch">
|
<Container name="财务重点指标" nameTwo="费用重点指标" icon="cockpitItemIcon" size="topBasic" @switchTab="handleTabSwitch" @tabChange='tabChange'>
|
||||||
<div class="bottom-left-content" style="display: flex;gap: 9px;padding: 14px 16px;flex-direction: column;">
|
<div class="bottom-left-content" style="display: flex;gap: 9px;padding: 14px 16px;flex-direction: column;">
|
||||||
<!-- 根据activeTab状态,切换显示采购/存货内容 -->
|
<!-- 根据activeTab状态,切换显示采购/存货内容 -->
|
||||||
<template v-if="activeTab === 'purchase'">
|
<template v-if="activeTab === 'purchase'">
|
||||||
<!-- 采购重点指标对应的内容 -->
|
<!-- 采购重点指标对应的内容 -->
|
||||||
<coreBottomLeftItem :dateData="dateData" :finance="finance"></coreBottomLeftItem>
|
<coreBottomLeftItem :dateData="dateData" :finance="financeData"></coreBottomLeftItem>
|
||||||
<div class="bottom"
|
<div class="bottom"
|
||||||
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
||||||
<coreBottomBar :dateData="dateData" :line="finance.line"></coreBottomBar>
|
<coreBottomBar :dateData="dateData" :line="finance.line"></coreBottomBar>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else-if="activeTab === 'inventory'">
|
<template v-else-if="activeTab === 'inventory'">
|
||||||
<!-- 存货重点指标对应的内容 -->
|
<!-- 存货重点指标对应的内容 -->
|
||||||
<costItem :dateData="dateData" :cost="cost"></costItem>
|
<costItem :dateData="dateData" :cost="costData" :currentTap='currentTap'></costItem>
|
||||||
<div class="bottom"
|
<div class="bottom"
|
||||||
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
||||||
<CostsBottomBar :line="cost.line" :dateData="dateData">
|
<CostsBottomBar :line="cost.line" :dateData="dateData">
|
||||||
@@ -52,13 +52,16 @@ export default {
|
|||||||
dateData: {
|
dateData: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
activeTab: 'purchase', // 激活的标签:purchase=采购,inventory=存货
|
activeTab: 'purchase', // 激活的标签:purchase=采购,inventory=存货
|
||||||
showChart: true, // 控制图表是否显示
|
showChart: true, // 控制图表是否显示
|
||||||
chart: null // 图表实例
|
chart: null, // 图表实例
|
||||||
|
financeData:{},
|
||||||
|
costData:{},
|
||||||
|
currentTap: 'month'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -71,7 +74,28 @@ export default {
|
|||||||
this.updateChart()
|
this.updateChart()
|
||||||
},
|
},
|
||||||
deep: true
|
deep: true
|
||||||
|
},
|
||||||
|
finance: {
|
||||||
|
handler() {
|
||||||
|
if(this.currentTap === 'month') {
|
||||||
|
this.financeData = this.finance.mon
|
||||||
|
}else{
|
||||||
|
this.financeData = this.finance.total
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
cost: {
|
||||||
|
handler() {
|
||||||
|
if(this.currentTap === 'month') {
|
||||||
|
this.costData = this.cost.mon
|
||||||
|
}else{
|
||||||
|
this.costData = this.cost.total
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// 初始化图表
|
// 初始化图表
|
||||||
@@ -85,6 +109,17 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
tabChange(val) {
|
||||||
|
if(val === 'month') {
|
||||||
|
this.currentTap = 'month'
|
||||||
|
this.financeData = this.finance.mon
|
||||||
|
this.costData = this.cost.mon
|
||||||
|
}else{
|
||||||
|
this.currentTap = 'total'
|
||||||
|
this.financeData = this.finance.total
|
||||||
|
this.costData = this.cost.total
|
||||||
|
}
|
||||||
|
},
|
||||||
// 处理标题点击切换标签
|
// 处理标题点击切换标签
|
||||||
handleTabSwitch(tabType) {
|
handleTabSwitch(tabType) {
|
||||||
this.activeTab = tabType // tabType由Container组件传递:'purchase'或'inventory'
|
this.activeTab = tabType // tabType由Container组件传递:'purchase'或'inventory'
|
||||||
|
|||||||
@@ -16,7 +16,17 @@
|
|||||||
<span class="title-text">{{ nameTwo }}</span>
|
<span class="title-text">{{ nameTwo }}</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<span class="change-text" :class="{ 'change-text-right': isLeftTransparent }">点击切换</span>
|
<!-- <span class="change-text" :class="{ 'change-text-right': isLeftTransparent }">点击切换</span> -->
|
||||||
|
<div class="tab-group">
|
||||||
|
<!-- 月度Tab:点击切换状态,动态绑定样式 -->
|
||||||
|
<div class="tab-item" :class="{ active: activeTab === 'month' }" @click="handleTabClick('month')">
|
||||||
|
月度
|
||||||
|
</div>
|
||||||
|
<!-- 累计Tab:点击切换状态,动态绑定样式 -->
|
||||||
|
<div class="tab-item" :class="{ active: activeTab === 'total' }" @click="handleTabClick('total')">
|
||||||
|
累计
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="container-body">
|
<div class="container-body">
|
||||||
@@ -40,10 +50,18 @@ export default {
|
|||||||
return {
|
return {
|
||||||
// 初始状态:左侧不透明(1),右侧透明(0.3)
|
// 初始状态:左侧不透明(1),右侧透明(0.3)
|
||||||
isLeftTransparent: true, // 左侧透明度状态(true=1,false=0.3)
|
isLeftTransparent: true, // 左侧透明度状态(true=1,false=0.3)
|
||||||
isRightTransparent: false // 右侧透明度状态(true=1,false=0.3)
|
isRightTransparent: false, // 右侧透明度状态(true=1,false=0.3)
|
||||||
|
activeTab: 'month', // 默认激活的Tab为月度
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
handleTabClick(tabType) {
|
||||||
|
this.activeTab = tabType;
|
||||||
|
// 向父组件派发Tab切换事件,传递当前选中的Tab类型
|
||||||
|
this.$emit('tabChange', tabType);
|
||||||
|
// 可选:同时传递更详细的信息(如标签名)
|
||||||
|
// this.$emit('tabChange', { type: tabType, name: tabType === 'month' ? '月度' : '累计' });
|
||||||
|
},
|
||||||
// 点击左侧标题:左侧保持不透明,右侧变透明,并派发采购标签事件
|
// 点击左侧标题:左侧保持不透明,右侧变透明,并派发采购标签事件
|
||||||
handleLeftClick() {
|
handleLeftClick() {
|
||||||
this.isLeftTransparent = true; // 左侧不透明
|
this.isLeftTransparent = true; // 左侧不透明
|
||||||
@@ -151,7 +169,7 @@ export default {
|
|||||||
background: linear-gradient(90deg, #FFFFFF 0%, rgba(253, 255, 255, 0) 100%);
|
background: linear-gradient(90deg, #FFFFFF 0%, rgba(253, 255, 255, 0) 100%);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
left: 240px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -258,4 +276,41 @@ export default {
|
|||||||
// text-align: left;
|
// text-align: left;
|
||||||
// font-style: normal;
|
// font-style: normal;
|
||||||
}
|
}
|
||||||
|
.tab-group {
|
||||||
|
display: inline-flex;
|
||||||
|
position: absolute;
|
||||||
|
right: 5%;
|
||||||
|
top:20px;
|
||||||
|
z-index: 9999;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
gap: 8px; // Tab之间的间距
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab基础样式(统一)
|
||||||
|
.tab-item {
|
||||||
|
padding: 0 24px;
|
||||||
|
width: 79px;
|
||||||
|
height: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.2s ease; // 样式切换动画
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未激活的Tab样式(原first-child样式)
|
||||||
|
.tab-item:not(.active) {
|
||||||
|
background: #ECF4FE;
|
||||||
|
color: #0B58FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 激活的Tab样式(原last-child样式)
|
||||||
|
.tab-item.active {
|
||||||
|
background: #3071FF;
|
||||||
|
color: #F9FCFF;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
134
src/views/home/components/heatBarChart.vue
Normal file
134
src/views/home/components/heatBarChart.vue
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 214px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
lineData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
xData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
lineData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.lineData,'lineData');
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
console.log('linedaata',this.lineData)
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 60,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data:this.xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: 'kcal/kg',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
},
|
||||||
|
],
|
||||||
|
series: []
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -77,7 +77,7 @@ font-style: normal;">指标详情</div>
|
|||||||
</el-upload>
|
</el-upload>
|
||||||
<div slot="footer" class="dialog-footer">
|
<div slot="footer" class="dialog-footer">
|
||||||
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
||||||
<el-button @click="cancelBtn">取 消</el-button>
|
<el-button @click="upload.open = false">取 消</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</Container>
|
</Container>
|
||||||
@@ -387,10 +387,6 @@ export default {
|
|||||||
console.error('文件上传出错:', error)
|
console.error('文件上传出错:', error)
|
||||||
this.$message.error('上传失败!')
|
this.$message.error('上传失败!')
|
||||||
}
|
}
|
||||||
},
|
|
||||||
cancelBtn(){
|
|
||||||
this.upload.open = false
|
|
||||||
this.$refs.upload.clearFiles();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
316
src/views/home/components/keyProductContainer.vue
Normal file
316
src/views/home/components/keyProductContainer.vue
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
<template>
|
||||||
|
<div class="cockpitContainer" :class="['cockpitContainer__' + size]">
|
||||||
|
<div class="container-top">
|
||||||
|
<!-- 左侧标题:点击切换到采购标签,并更新透明度 -->
|
||||||
|
<div class="content-top-left title-wrapper" @click="handleLeftClick"
|
||||||
|
:style="{ opacity: isLeftTransparent ? 1 : 0.3 }">
|
||||||
|
<svg-icon class="title-icon" style="font-size: 32px; margin-left: 16px" :icon-class="icon" />
|
||||||
|
<span class="title-text">{{ name }}</span>
|
||||||
|
<!-- <span v-if="!isLeftTransparent" class="change-text">点击切换</span> -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- 右侧标题:点击切换到存货标签,并更新透明度 -->
|
||||||
|
<div class="content-top-right title-wrapper" v-if="nameTwo" @click="handleRightClick"
|
||||||
|
:style="{ opacity: isRightTransparent ? 1 : 0.3 }">
|
||||||
|
<svg-icon class="title-icon" style="font-size: 32px; margin-left: 16px" :icon-class="iconTwo || icon" />
|
||||||
|
<span class="title-text">{{ nameTwo }}</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- <span class="change-text" :class="{ 'change-text-right': isLeftTransparent }">点击切换</span> -->
|
||||||
|
<div class="tab-group">
|
||||||
|
<!-- 月度Tab:点击切换状态,动态绑定样式 -->
|
||||||
|
<div class="tab-item" :class="{ active: activeTab === 'month' }" @click="handleTabClick('month')">
|
||||||
|
月度
|
||||||
|
</div>
|
||||||
|
<!-- 累计Tab:点击切换状态,动态绑定样式 -->
|
||||||
|
<div class="tab-item" :class="{ active: activeTab === 'total' }" @click="handleTabClick('total')">
|
||||||
|
累计
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="container-body">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Container',
|
||||||
|
components: {},
|
||||||
|
props: {
|
||||||
|
name: { type: String, required: true },
|
||||||
|
nameTwo: { type: String, required: false },
|
||||||
|
size: { type: String, default: 'default' },
|
||||||
|
icon: { type: String, default: '' },
|
||||||
|
iconTwo: { type: String, default: '' }
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 初始状态:左侧不透明(1),右侧透明(0.3)
|
||||||
|
isLeftTransparent: true, // 左侧透明度状态(true=1,false=0.3)
|
||||||
|
isRightTransparent: false, // 右侧透明度状态(true=1,false=0.3)
|
||||||
|
activeTab: 'month', // 默认激活的Tab为月度
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleTabClick(tabType) {
|
||||||
|
this.activeTab = tabType;
|
||||||
|
// 向父组件派发Tab切换事件,传递当前选中的Tab类型
|
||||||
|
this.$emit('tabChange', tabType);
|
||||||
|
// 可选:同时传递更详细的信息(如标签名)
|
||||||
|
// this.$emit('tabChange', { type: tabType, name: tabType === 'month' ? '月度' : '累计' });
|
||||||
|
},
|
||||||
|
// 点击左侧标题:左侧保持不透明,右侧变透明,并派发采购标签事件
|
||||||
|
handleLeftClick() {
|
||||||
|
this.isLeftTransparent = true; // 左侧不透明
|
||||||
|
this.isRightTransparent = false; // 右侧透明
|
||||||
|
this.$emit('switchTab', 'product'); // 通知父组件切换到采购内容
|
||||||
|
},
|
||||||
|
// 点击右侧标题:右侧保持不透明,左侧变透明,并派发存货标签事件
|
||||||
|
handleRightClick() {
|
||||||
|
this.isLeftTransparent = false; // 左侧透明
|
||||||
|
this.isRightTransparent = true; // 右侧不透明
|
||||||
|
this.$emit('switchTab', 'heat'); // 通知父组件切换到存货内容
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
// 样式保持不变,确保透明度过渡生效
|
||||||
|
.cockpitContainer {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.container-top {
|
||||||
|
position: relative;
|
||||||
|
height: 60px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-top-left {
|
||||||
|
width: 566px;
|
||||||
|
height: 60px;
|
||||||
|
background: linear-gradient(90deg, #FFFFFF 0%, rgba(253, 255, 255, 0) 100%);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 1;
|
||||||
|
transition: opacity 0.3s ease; // 透明度过渡动画
|
||||||
|
}
|
||||||
|
.title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
// margin-left: 10px;
|
||||||
|
/* 垂直居中关键属性 */
|
||||||
|
height: 100%;
|
||||||
|
/* 继承父容器高度,确保垂直居中范围 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
font-size: 30px;
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-left: 10px;
|
||||||
|
/* 图标和文字之间的间距 */
|
||||||
|
flex-shrink: 0;
|
||||||
|
/* 防止图标被压缩 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 移除固定行高,避免影响垂直对齐
|
||||||
|
// line-height: 60px;
|
||||||
|
}
|
||||||
|
/* 左侧标题 - 左上角折现边框 */
|
||||||
|
.content-top-left::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid;
|
||||||
|
border-image: linear-gradient(277deg, rgba(255, 255, 255, 0), rgba(92, 140, 255, 1)) 1 1;
|
||||||
|
clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%, 0 20px);
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 左侧标题 - 左上角折现细节 */
|
||||||
|
.content-top-left::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
background: #E1f0fd;
|
||||||
|
border-top: 1px solid rgba(92, 140, 255, 1);
|
||||||
|
border-left: 1px solid rgba(92, 140, 255, 1);
|
||||||
|
transform: rotate(135deg) translate(-50%, -50%);
|
||||||
|
transform-origin: top left;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-top-right {
|
||||||
|
width: 368px;
|
||||||
|
height: 60px;
|
||||||
|
background: linear-gradient(90deg, #FFFFFF 0%, rgba(253, 255, 255, 0) 100%);
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 240px;
|
||||||
|
z-index: 10;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.3s ease; // 透明度过渡动画
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右侧标题 - 左上角折现边框 */
|
||||||
|
.content-top-right::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid;
|
||||||
|
border-image: linear-gradient(277deg, rgba(255, 255, 255, 0), rgba(92, 140, 255, 1)) 1 1;
|
||||||
|
clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%, 0 20px);
|
||||||
|
z-index: 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右侧标题 - 左上角折现细节 */
|
||||||
|
.content-top-right::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
background: #E1f0fd;
|
||||||
|
border-top: 1px solid rgba(92, 140, 255, 1);
|
||||||
|
border-left: 1px solid rgba(92, 140, 255, 1);
|
||||||
|
transform: rotate(135deg) translate(-50%, -50%);
|
||||||
|
transform-origin: top left;
|
||||||
|
z-index: 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
margin-left: 6px;
|
||||||
|
height: 32px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #000000;
|
||||||
|
// line-height: 60px;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__topBasic {
|
||||||
|
background: url(../../../assets/img/top-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bottomBasic {
|
||||||
|
background: url(../../../assets/img/bottom-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-body {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-body {
|
||||||
|
padding: 20px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.change-text{
|
||||||
|
position: absolute;
|
||||||
|
top: 26px;
|
||||||
|
left: 300px;
|
||||||
|
z-index: 999;
|
||||||
|
// width: 48px;
|
||||||
|
// height: 17px;
|
||||||
|
// margin-left: 80px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 17px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
.change-text-right {
|
||||||
|
// position: absolute;
|
||||||
|
// top: 26px;
|
||||||
|
left: auto; // 清除左侧定位
|
||||||
|
right: 30px;
|
||||||
|
// z-index: 999;
|
||||||
|
// // width: 48px;
|
||||||
|
// // height: 17px;
|
||||||
|
// // margin-left: 80px;
|
||||||
|
// font-family: PingFangSC, PingFang SC;
|
||||||
|
// font-weight: 400;
|
||||||
|
// font-size: 12px;
|
||||||
|
// color: #0B58FF;
|
||||||
|
// line-height: 17px;
|
||||||
|
// text-align: left;
|
||||||
|
// font-style: normal;
|
||||||
|
}
|
||||||
|
.tab-group {
|
||||||
|
display: inline-flex;
|
||||||
|
position: absolute;
|
||||||
|
right: 0px;
|
||||||
|
top:20px;
|
||||||
|
z-index: 9999;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
gap: 8px; // Tab之间的间距
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab基础样式(统一)
|
||||||
|
.tab-item {
|
||||||
|
padding: 0 24px;
|
||||||
|
width: 79px;
|
||||||
|
height: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.2s ease; // 样式切换动画
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未激活的Tab样式(原first-child样式)
|
||||||
|
.tab-item:not(.active) {
|
||||||
|
background: #ECF4FE;
|
||||||
|
color: #0B58FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 激活的Tab样式(原last-child样式)
|
||||||
|
.tab-item.active {
|
||||||
|
background: #3071FF;
|
||||||
|
color: #F9FCFF;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,33 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<div style="flex: 1">
|
<div style="flex: 1">
|
||||||
<Container name="生产重点指标" icon="cockpitItemIcon" size="topBasic" topSize="basic">
|
<Container name="生产重点指标" nameTwo="热耗" icon="cockpitItemIcon" size="topBasic" topSize="basic" @switchTab="handleTabSwitch" @tabChange='tabChange'>
|
||||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
<div class="kpi-content" style="padding: 14px 16px; display: flex;flex-direction: column; width: 100%;">
|
<div class="kpi-content" style="padding: 14px 16px; display: flex;flex-direction: column; width: 100%;">
|
||||||
<!-- 2. .top 保持 flex,无需固定高度,自动跟随子元素拉伸 -->
|
<!-- 2. .top 保持 flex,无需固定高度,自动跟随子元素拉伸 -->
|
||||||
<div class="top" style="display: flex; width: 100%;">
|
<template v-if="activeTab === 'product'">
|
||||||
<top-item :rawItemList='productData' :dateData="dateData" />
|
<div class="top" style="display: flex; width: 100%;">
|
||||||
</div>
|
<top-item :rawItemList='productData' :dateData="dateData" />
|
||||||
<div class="bottom" style="display: flex;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
</div>
|
||||||
<!-- <top-item /> -->
|
<div class="bottom" style="display: flex;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
||||||
<coreBottomBar :lineData="productData.line" :dateData="dateData" />
|
<!-- <top-item /> -->
|
||||||
|
<coreBottomBar :lineData="product.line" :dateData="dateData" />
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="activeTab === 'heat'">
|
||||||
|
<div class="bottom" style="background-color: rgba(249, 252, 255, 1);">
|
||||||
|
<div class="bottom" style="margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
||||||
|
<div style='text-align: center;margin: 5px;font-size: 18px;font-weight: 400;color: #000;'>1200t/d</div>
|
||||||
|
<heatBar :lineData="heatData['1200t']" :xData="['桐城','洛阳','江苏','秦皇岛']" :dateData="dateData" />
|
||||||
|
</div>
|
||||||
|
<div style='text-align: center;margin: 5px;font-size: 18px;font-weight: 400;color: #000;'>650t/d</div>
|
||||||
|
<heatBar :lineData="heatData['650t']" :xData="['宜兴','自贡','漳州']" :dateData="dateData" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import Container from './container.vue'
|
import Container from './keyProductContainer.vue'
|
||||||
// import * as echarts from 'echarts'
|
// import * as echarts from 'echarts'
|
||||||
import topItem from './top-product-item.vue'
|
import topItem from './top-product-item.vue'
|
||||||
import coreBottomBar from './productBottomBar.vue'
|
import coreBottomBar from './productBottomBar.vue'
|
||||||
|
import heatBar from './heatBarChart.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ProductionStatus',
|
name: 'ProductionStatus',
|
||||||
components: { Container, topItem, coreBottomBar },
|
components: { Container, topItem, coreBottomBar, heatBar },
|
||||||
// mixins: [resize],
|
// mixins: [resize],
|
||||||
props: {
|
props: {
|
||||||
productData: {
|
product: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {} // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
heat: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {} // 默认空数组,避免报错
|
default: () => {} // 默认空数组,避免报错
|
||||||
},
|
},
|
||||||
@@ -35,13 +51,45 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => { } // 默认空数组,避免报错
|
default: () => { } // 默认空数组,避免报错
|
||||||
},
|
},
|
||||||
|
heat: {
|
||||||
|
type: Object,
|
||||||
|
default: () => { }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
chart: null
|
chart: null,
|
||||||
|
activeTab: 'product',
|
||||||
|
currentTap: 'month',
|
||||||
|
productData:{},
|
||||||
|
heatData:{}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
// 切换标签时更新图表
|
||||||
|
activeTab(newVal) {
|
||||||
|
// this.$nextTick(() => this.updateChart())
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
handler() {
|
||||||
|
if(this.currentTap === 'month') {
|
||||||
|
this.productData = this.product.mon
|
||||||
|
}else{
|
||||||
|
this.productData = this.product.total
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
heat: {
|
||||||
|
handler() {
|
||||||
|
if(this.currentTap === 'month') {
|
||||||
|
this.heatData = this.heat.mon
|
||||||
|
}else{
|
||||||
|
this.heatData = this.heat.total
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||||
@@ -49,6 +97,22 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
// 处理标题点击切换标签
|
||||||
|
handleTabSwitch(tabType) {
|
||||||
|
this.activeTab = tabType // tabType由Container组件传递:'purchase'或'inventory'
|
||||||
|
this.showChart = true // 切换时默认显示图表(可根据需求调整)
|
||||||
|
},
|
||||||
|
tabChange(val) {
|
||||||
|
if(val === 'month') {
|
||||||
|
this.currentTap = 'month'
|
||||||
|
this.productData = this.product.mon
|
||||||
|
this.heatData = this.heat.mon
|
||||||
|
}else{
|
||||||
|
this.currentTap = 'total'
|
||||||
|
this.productData = this.product.total
|
||||||
|
this.heatData = this.heat.total
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
'bg-green': item.currentValue >= item.targetValue
|
'bg-green': item.currentValue >= item.targetValue
|
||||||
}" style="font-size: 12px;display: flex;align-items: center;justify-content: flex-end;">
|
}" style="font-size: 12px;display: flex;align-items: center;justify-content: flex-end;">
|
||||||
<div class="title">完成率</div>
|
<div class="title">完成率</div>
|
||||||
<div class="yield" style="font-size: 18px;margin-bottom: 4px;">
|
<div class="yield" style="font-size: 22px;margin-bottom: 4px;">
|
||||||
{{ item.progress }}
|
{{ item.progress }}
|
||||||
</div>
|
</div>
|
||||||
<div class="unit">%</div>
|
<div class="unit">%</div>
|
||||||
@@ -359,11 +359,12 @@ export default {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin-top:4px;
|
margin-top: 10px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
font-family: PingFangSC, PingFang SC;
|
font-family: PingFangSC, PingFang SC;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
margin-right: 8px;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
line-height: 12px;
|
line-height: 12px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
@@ -371,7 +372,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.unit {
|
.unit {
|
||||||
margin-top: 4px;
|
margin-top: 10px;
|
||||||
margin-right: 14px;
|
margin-right: 14px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
font-family: PingFangSC, PingFang SC;
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
|||||||
246
src/views/home/depreciationAnalysis/depreciationAnalysis.vue
Normal file
246
src/views/home/depreciationAnalysis/depreciationAnalysis.vue
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
<template>
|
||||||
|
<div id="dayReport" class="dayReport" :style="styles">
|
||||||
|
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||||
|
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||||
|
<ReportHeader :dateData="dateData" top-title="应收账款" :is-full-screen="isFullScreen"
|
||||||
|
@screenfullChange="screenfullChange" @timeRangeChange="handleTimeChange" />
|
||||||
|
<div class="main-body" style="
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
padding: 0px 16px 0 272px;
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div class="top" style="margin-top: -20px; display: flex; gap: 16px">
|
||||||
|
<div class="top-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns:1624px;
|
||||||
|
">
|
||||||
|
<operatingLineChart :dateData="dateData" :monthData="monthData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||||
|
<div class="left-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns: 1624px;
|
||||||
|
">
|
||||||
|
<operatingLineChartCumulative :dateData="dateData" :ytdData="ytdData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import ReportHeader from "../components/noRouterHeader.vue";
|
||||||
|
import { Sidebar } from "../../../layout/components";
|
||||||
|
import screenfull from "screenfull";
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
import operatingLineChart from "../depreciationAnalysisComponents/operatingLineChart";
|
||||||
|
import operatingLineChartCumulative from "../depreciationAnalysisComponents/operatingLineChartCumulative.vue";
|
||||||
|
|
||||||
|
import { getSalesRevenueGroupData } from '@/api/cockpit'
|
||||||
|
export default {
|
||||||
|
name: "DepreciationAnalysis",
|
||||||
|
components: {
|
||||||
|
ReportHeader,
|
||||||
|
operatingLineChartCumulative,
|
||||||
|
operatingLineChart,
|
||||||
|
Sidebar,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
weekArr: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
|
||||||
|
isFullScreen: false,
|
||||||
|
timer: null,
|
||||||
|
beilv: 1,
|
||||||
|
value: 100,
|
||||||
|
sort:1,
|
||||||
|
selectDate:{},
|
||||||
|
monthData: {},
|
||||||
|
ytdData:{},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.init();
|
||||||
|
this.windowWidth(document.documentElement.clientWidth);
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
theme: (state) => state.settings.theme,
|
||||||
|
sideTheme: (state) => state.settings.sideTheme,
|
||||||
|
sidebar: (state) => state.app.sidebar,
|
||||||
|
device: (state) => state.app.device,
|
||||||
|
needTagsView: (state) => state.settings.tagsView,
|
||||||
|
fixedHeader: (state) => state.settings.fixedHeader,
|
||||||
|
}),
|
||||||
|
classObj() {
|
||||||
|
return {
|
||||||
|
hideSidebar: !this.sidebar.opened,
|
||||||
|
openSidebar: this.sidebar.opened,
|
||||||
|
withoutAnimation: this.sidebar.withoutAnimation,
|
||||||
|
mobile: this.device === "mobile",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
variables() {
|
||||||
|
return variables;
|
||||||
|
},
|
||||||
|
// ...mapGetters(['sidebar']),
|
||||||
|
styles() {
|
||||||
|
const v = Math.floor(this.value * this.beilv * 100) / 10000;
|
||||||
|
return {
|
||||||
|
transform: `scale(${v})`,
|
||||||
|
transformOrigin: "left top",
|
||||||
|
// overflow: hidden;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
clientWidth(val) {
|
||||||
|
if (!this.timer) {
|
||||||
|
this.clientWidth = val;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
this.timer = true;
|
||||||
|
let _this = this;
|
||||||
|
setTimeout(function () {
|
||||||
|
_this.timer = false;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
// 这里可以添加修改时的方法
|
||||||
|
this.windowWidth(val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.destroy();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const _this = this;
|
||||||
|
_this.beilv = document.documentElement.clientWidth / 1920;
|
||||||
|
window.onresize = () => {
|
||||||
|
return (() => {
|
||||||
|
_this.clientWidth = `${document.documentElement.clientWidth}`;
|
||||||
|
this.beilv = _this.clientWidth / 1920;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// sortChange(value) {
|
||||||
|
// this.sort = value
|
||||||
|
// this.getData()
|
||||||
|
// },
|
||||||
|
getData() {
|
||||||
|
getSalesRevenueGroupData({
|
||||||
|
startTime: this.dateData.startTime,
|
||||||
|
endTime: this.dateData.endTime,
|
||||||
|
sort: this.sort,
|
||||||
|
index: undefined,
|
||||||
|
factory: undefined
|
||||||
|
// timeDim: obj.mode
|
||||||
|
}).then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
this.monthData= res.data.month
|
||||||
|
this.ytdData = res.data.ytd
|
||||||
|
|
||||||
|
// this.saleData = res.data.SaleData
|
||||||
|
// this.premiumProduct = res.data.premiumProduct
|
||||||
|
// this.salesTrendMap = res.data.salesTrendMap
|
||||||
|
// this.grossMarginTrendMap = res.data.grossMarginTrendMap
|
||||||
|
// this.salesProportion = res.data.salesProportion ? res.data.salesProportion : {}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleTimeChange(obj) {
|
||||||
|
console.log(obj, 'obj');
|
||||||
|
this.dateData= obj
|
||||||
|
this.getData()
|
||||||
|
},
|
||||||
|
handleClickOutside() {
|
||||||
|
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
||||||
|
},
|
||||||
|
windowWidth(value) {
|
||||||
|
this.clientWidth = value;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
},
|
||||||
|
change() {
|
||||||
|
this.isFullScreen = screenfull.isFullscreen;
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.on("change", this.change);
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.off("change", this.change);
|
||||||
|
},
|
||||||
|
// 全屏
|
||||||
|
screenfullChange() {
|
||||||
|
console.log("screenfull.enabled", screenfull.isEnabled);
|
||||||
|
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.toggle(this.$refs.dayReportB);
|
||||||
|
},
|
||||||
|
// 导出
|
||||||
|
// exportPDF() {
|
||||||
|
// this.$message.success('正在导出,请稍等!')
|
||||||
|
// const element = document.getElementById('dayRepDom')
|
||||||
|
// element.style.display = 'block'
|
||||||
|
// const fileName = '株洲碲化镉生产日报' + moment().format('yyMMDD') + '.pdf'
|
||||||
|
// html2canvas(element, {
|
||||||
|
// dpi: 300, // Set to 300 DPI
|
||||||
|
// scale: 3 // Adjusts your resolution
|
||||||
|
// }).then(function(canvas) {
|
||||||
|
// const imgWidth = 595.28
|
||||||
|
// const imgHeight = 841.89
|
||||||
|
// const pageData = canvas.toDataURL('image/jpeg', 1.0)
|
||||||
|
// const PDF = new JsPDF('', 'pt', [imgWidth, imgHeight])
|
||||||
|
// PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
|
||||||
|
// setTimeout(() => {
|
||||||
|
// PDF.save(fileName) // 导出文件名
|
||||||
|
// }, 1000)
|
||||||
|
// })
|
||||||
|
// element.style.display = 'none'
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "~@/assets/styles/mixin.scss";
|
||||||
|
@import "~@/assets/styles/variables.scss";
|
||||||
|
.dayReport {
|
||||||
|
width: 1920px;
|
||||||
|
height: 1080px;
|
||||||
|
background: url("../../../assets/img/backp.png") no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
.hideSidebar .fixed-header {
|
||||||
|
width: calc(100% - 54px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarHide .fixed-header {
|
||||||
|
width: calc(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile .fixed-header {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
419
src/views/home/depreciationAnalysisComponents/Header.vue
Normal file
419
src/views/home/depreciationAnalysisComponents/Header.vue
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
<template>
|
||||||
|
<header class="report-header">
|
||||||
|
<!-- 左侧区域:logo + 标题 -->
|
||||||
|
<div class="left-content">
|
||||||
|
<img style="height: 36px;" src="../../../assets/img/cnbm.png" alt="benmaLogo" >
|
||||||
|
<div class="top-title">{{ topTitle }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="center-content">
|
||||||
|
<!-- 循环 pageRoutes,不再硬编码文字 -->
|
||||||
|
<div class="item" v-for="(page, index) in pageRoutes" :key="index" @click="goToPage(page.path, index)">
|
||||||
|
<span class="item-text">{{ page.text }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- :class="{ 'no-skew': activeIndex === index }
|
||||||
|
" -->
|
||||||
|
<!-- 右侧区域:全屏按钮 -->
|
||||||
|
<div class="right-content">
|
||||||
|
<el-button type="text" class="screen-btn" :title="isFullScreen ? '退出全屏' : '全屏'" @click="changeFullScreen">
|
||||||
|
<svg-icon style="color: #0B58FF;" v-if="isFullScreen" icon-class="unFullScreenView" />
|
||||||
|
<svg-icon style="color: #0B58FF;" v-else icon-class="fullScreenView" />
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 时间选择区域:日/月/年按钮 + label + 日期选择器 -->
|
||||||
|
<div class="timeType">
|
||||||
|
<!-- <div class="item" v-for="(item, index) in timeTypes" :key="index" @click="activeTime = index"
|
||||||
|
:class="{ 'no-skew': activeTime === index }">
|
||||||
|
<span class="item-text">{{ item.text }}</span>
|
||||||
|
</div> -->
|
||||||
|
<div class="dateP">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">月份选择</span>
|
||||||
|
</div>
|
||||||
|
<el-date-picker v-model="date" :type="getPickerType" :placeholder="getPickerPlaceholder"
|
||||||
|
class="custom-date-picker" style="width: 132px;height: 29px;" @change="emitTimeRange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment'
|
||||||
|
export default {
|
||||||
|
name: 'Header',
|
||||||
|
props: {
|
||||||
|
isFullScreen: { type: Boolean, default: false },
|
||||||
|
topTitle: { type: String, default: '' }
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentTime: '',
|
||||||
|
timeTimer: null,
|
||||||
|
date: undefined,
|
||||||
|
activeIndex: -1,
|
||||||
|
activeTime: 1, // 0=日,1=月,2=年(默认选中“日”)
|
||||||
|
pageRoutes: [
|
||||||
|
{ text: '营业收入', path: '/operatingRevenue/operatingRevenueIndex' },
|
||||||
|
{ text: '利润分析', path: '/profitAnalysis' },
|
||||||
|
{ text: '产销率库存分析', path: '/PSIAnal' },
|
||||||
|
{ text: '成本分析', path: '/cost/cost' },
|
||||||
|
{ text: '驾驶舱报表', path: '/cockpit' }
|
||||||
|
],
|
||||||
|
// 定义时间类型配置:text=按钮文字,pickerType=选择器类型,placeholder=占位符
|
||||||
|
timeTypes: [
|
||||||
|
{ text: '日', pickerType: 'date', placeholder: '选择日期' },
|
||||||
|
{ text: '月', pickerType: 'month', placeholder: '选择月份' },
|
||||||
|
{ text: '年', pickerType: 'year', placeholder: '选择年份' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 动态获取日期选择器类型
|
||||||
|
getPickerType() {
|
||||||
|
return this.timeTypes[this.activeTime].pickerType;
|
||||||
|
},
|
||||||
|
// 动态获取日期选择器占位符
|
||||||
|
getPickerPlaceholder() {
|
||||||
|
return this.timeTypes[this.activeTime].placeholder;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goToPage(path, index) {
|
||||||
|
// 1. 跳转到对应路由
|
||||||
|
this.$router.push(path);
|
||||||
|
// 2. 更新activeIndex,让当前点击项高亮
|
||||||
|
this.activeIndex = index;
|
||||||
|
},
|
||||||
|
changeFullScreen() { this.$emit('screenfullChange') },
|
||||||
|
padZero(num) { return num < 10 ? '0' + num : num },
|
||||||
|
/**
|
||||||
|
* 核心方法1:根据维度计算时间区间(首次进入时基于赋值的当月日期,计算“当月第一天0点→次月第一天0点”)
|
||||||
|
* @returns {Object} 包含 start(开始时间)、end(结束时间)、dimension(维度)的区间对象
|
||||||
|
*/
|
||||||
|
calculateTimeRange() {
|
||||||
|
let startTime = 0;
|
||||||
|
let endTime = 0;
|
||||||
|
const mode = this.activeTime + 1; // 1=日,2=月,3=年
|
||||||
|
const defaultMoment = moment(); // 默认当前时间
|
||||||
|
|
||||||
|
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. 日维度:00:00:00 → 23:59:59(无毫秒)
|
||||||
|
if (this.activeTime === 0) {
|
||||||
|
startTime = targetMoment.startOf('day').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('day').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 月维度:当月1日00:00:00 → 当月最后一天23:59:59(无毫秒)
|
||||||
|
else if (this.activeTime === 1) {
|
||||||
|
startTime = targetMoment.startOf('month').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('month').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 年维度:当年1月1日00:00:00 → 当年最后一天23:59:59(无毫秒)
|
||||||
|
else if (this.activeTime === 2) {
|
||||||
|
startTime = targetMoment.startOf('year').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('year').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调试输出:验证是否去掉毫秒
|
||||||
|
console.log('时间范围计算结果:', {
|
||||||
|
mode,
|
||||||
|
startTime: moment(startTime * 1000).format('YYYY-MM-DD HH:mm:ss'), // 格式:2025-11-30 00:00:00
|
||||||
|
endTime: moment(endTime * 1000).format('YYYY-MM-DD HH:mm:ss'), // 格式:2025-11-30 23:59:59(无毫秒)
|
||||||
|
startTimeStamp: startTime, // 秒级时间戳(如:1764422400)
|
||||||
|
endTimeStamp: endTime // 秒级时间戳(如:1764508799)
|
||||||
|
});
|
||||||
|
|
||||||
|
return { startTime, endTime, mode };
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 核心方法2:传递时间区间给父组件(首次进入时触发,传递“当月第一天0点→次月第一天0点”)
|
||||||
|
*/
|
||||||
|
emitTimeRange() {
|
||||||
|
const timeRange = this.calculateTimeRange();
|
||||||
|
this.$emit('timeRangeChange', timeRange);
|
||||||
|
// 调试用:查看首次传递的区间(如{start: "2025-10-01T00:00:00", end: "2025-11-01T00:00:00", dimension: "月"})
|
||||||
|
console.log('当前时间区间:', timeRange);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 维度切换时:清空选择的日期,并传递当前维度的默认区间
|
||||||
|
activeTime(newVal, oldVal) {
|
||||||
|
if (newVal !== oldVal) {
|
||||||
|
this.date = undefined;
|
||||||
|
// this.emitTimeRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 核心逻辑:首次进入页面,计算当月默认日期并赋值给选择器,同时传递区间
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = this.padZero(now.getMonth() + 1); // 月份从0开始,+1后补零(如1月→01)
|
||||||
|
// 赋值当月默认日期(格式:YYYY-MM,适配month类型选择器)
|
||||||
|
this.date = `${year}-${month}`;
|
||||||
|
// 确保选择器渲染完成后,传递“当月第一天0点→次月第一天0点”的区间
|
||||||
|
this.$nextTick(() => this.emitTimeRange());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
/* 原有样式不变,仅补充label文字的倾斜抵消样式 */
|
||||||
|
@font-face {
|
||||||
|
font-family: "YouSheBiaoTiHei";
|
||||||
|
src: url('../../../assets/fonts/YouSheBiaoTiHe.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-header {
|
||||||
|
height: 117px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
background: url('../../../assets/img/topTitle.png') no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: 0 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
/* 确保timeType绝对定位生效 */
|
||||||
|
|
||||||
|
.left-content {
|
||||||
|
margin-top: 11px;
|
||||||
|
margin-left: 44px;
|
||||||
|
height: 55px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-title {
|
||||||
|
height: 55px;
|
||||||
|
font-family: "YouSheBiaoTiHei", sans-serif;
|
||||||
|
font-size: 42px;
|
||||||
|
color: #1E1651;
|
||||||
|
line-height: 55px;
|
||||||
|
letter-spacing: 6px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 18px;
|
||||||
|
margin-left: 70px;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
width: 180px;
|
||||||
|
height: 50px;
|
||||||
|
background: #E1EEFC;
|
||||||
|
transform: skew(-20deg);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #1E1651;
|
||||||
|
line-height: 50px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0px 13px 16px 0px rgba(179, 217, 255, 0.43),
|
||||||
|
0px 2px 4px 0px rgba(92, 140, 255, 0.25),
|
||||||
|
inset 0px -43px 13px 0px rgba(255, 255, 255, 0.51);
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(20deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.no-skew {
|
||||||
|
background: none !important;
|
||||||
|
transform: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
color: #1E1651;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 垂直居中,避免元素高低错位 */
|
||||||
|
top: 42px;
|
||||||
|
right:10px;
|
||||||
|
margin-top: 18px;
|
||||||
|
gap: 0;
|
||||||
|
/* 清除间隙,让按钮与选择器紧密连接 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item {
|
||||||
|
width: 40px;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(236, 244, 254, 1);
|
||||||
|
transform: skew(-20deg);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(11, 88, 255, 1);
|
||||||
|
line-height: 28px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
/* 选中按钮与未选中按钮倾斜角度统一,避免切换时跳动 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item .item-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(20deg);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item.no-skew {
|
||||||
|
background: rgba(11, 88, 255, 1);
|
||||||
|
color: rgba(249, 252, 255, 1);
|
||||||
|
transform: skew(-20deg) !important;
|
||||||
|
/* 统一倾斜角度,修复原30deg的错位 */
|
||||||
|
box-shadow: 0 2px 8px rgba(11, 88, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item.no-skew .item-text {
|
||||||
|
transform: skew(20deg) !important;
|
||||||
|
/* 同步统一文字倾斜角度 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateP {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateP .label {
|
||||||
|
width: 165px;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(236, 244, 254, 1);
|
||||||
|
transform: skew(-25deg);
|
||||||
|
/* 与按钮倾斜角度统一(原30deg改为25deg,避免视觉错位) */
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 28px;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 补充:label文字抵消倾斜(原代码遗漏,导致文字倾斜) */
|
||||||
|
.dateP .label-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(25deg);
|
||||||
|
/* 与label倾斜角度相反,确保文字正立 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-right: 4px;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-time {
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 24px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screen-btn {
|
||||||
|
width: 26px;
|
||||||
|
margin-left: 300px;
|
||||||
|
color: #00fff0;
|
||||||
|
font-size: 26px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 日期选择器样式保持不变 */
|
||||||
|
::v-deep .custom-date-picker {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
width: 165px !important;
|
||||||
|
height: 28px !important;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 !important;
|
||||||
|
|
||||||
|
/* 1. 调整输入框文字:确保行高与输入框高度一致,垂直居中 */
|
||||||
|
.el-input__inner {
|
||||||
|
height: 28px !important;
|
||||||
|
width: 165px !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>
|
||||||
282
src/views/home/depreciationAnalysisComponents/container.vue
Normal file
282
src/views/home/depreciationAnalysisComponents/container.vue
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
<template>
|
||||||
|
<div class="cockpitContainer" :class="['cockpitContainer__' + size]">
|
||||||
|
<div class="content-top" :class="['content-top__' + topSize]">
|
||||||
|
<!-- 使用 flex 容器包裹图标和文字,实现垂直居中 -->
|
||||||
|
<div class="title-wrapper">
|
||||||
|
<svg-icon class="title-icon" :icon-class="icon" />
|
||||||
|
<span class="title-text">
|
||||||
|
{{ name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cockpitContainer-body">
|
||||||
|
<slot>
|
||||||
|
<div class="test-body">something test....</div>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Container',
|
||||||
|
components: {},
|
||||||
|
// eslint-disable-next-line vue/require-prop-types
|
||||||
|
props: ['name', 'size', 'icon', 'topSize'],
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.cockpitContainer {
|
||||||
|
display: inline-block;
|
||||||
|
// width: 100%;
|
||||||
|
// height: 100%;
|
||||||
|
padding: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.content-top {
|
||||||
|
height: 60px;
|
||||||
|
|
||||||
|
.title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 10px;
|
||||||
|
/* 垂直居中关键属性 */
|
||||||
|
height: 100%;
|
||||||
|
/* 继承父容器高度,确保垂直居中范围 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
font-size: 30px;
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
/* 图标和文字之间的间距 */
|
||||||
|
flex-shrink: 0;
|
||||||
|
/* 防止图标被压缩 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 移除固定行高,避免影响垂直对齐
|
||||||
|
// line-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// width: 547px;
|
||||||
|
// background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
&__basic {
|
||||||
|
// width: 547px;
|
||||||
|
background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__middle {
|
||||||
|
background: url(../../../assets/img/topTileMiddle.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__large {
|
||||||
|
background: url(../../../assets/img/topTitleLargeBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__KFAPTopTitle {
|
||||||
|
background: url(../../../assets/img/KFAPTopTitle.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiTopTitleBasic {
|
||||||
|
background: url(../../../assets/img/psiTopTitleBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__rawTopTitleLarge {
|
||||||
|
background: url(../../../assets/img/rawTopTitleLarge.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__topBasic {
|
||||||
|
background: url(../../../assets/img/top-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bottomBasic {
|
||||||
|
background: url(../../../assets/img/bottom-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingBasic {
|
||||||
|
background: url(../../../assets/img/operating-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingLarge {
|
||||||
|
background: url(../../../assets/img/operating-large.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__profitTopBasic {
|
||||||
|
background: url(../../../assets/img/profitTopBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__profitMiddleBasic {
|
||||||
|
background: url(../../../assets/img/profitMiddleBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiBasicBg {
|
||||||
|
background: url(../../../assets/img/psiBasicBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiMiddleBg {
|
||||||
|
background: url(../../../assets/img/psiMiddleBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingRevenueBg {
|
||||||
|
background: url(../../../assets/img/operatingRevenueBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__costBasicBg {
|
||||||
|
background: url(../../../assets/img/costBasicBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__rawTopBg {
|
||||||
|
background: url(../../../assets/img/rawTopBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__opLargeBg {
|
||||||
|
background: url(../../../assets/img/opLargeBg.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>
|
||||||
298
src/views/home/depreciationAnalysisComponents/dataTrend.vue
Normal file
298
src/views/home/depreciationAnalysisComponents/dataTrend.vue
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="数据趋势" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="right" style="
|
||||||
|
height: 191px;
|
||||||
|
display: flex;
|
||||||
|
width: 1595px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<dataTrendBar @changeItem="handleChange" :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import dataTrendBar from "./dataTrendBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, dataTrendBar },
|
||||||
|
props: {
|
||||||
|
trend: {
|
||||||
|
type: Array,
|
||||||
|
// 默认值与实际数据结构一致(12个月)
|
||||||
|
default: () => [
|
||||||
|
// { title: "2025年01月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年02月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年03月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年04月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年05月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年06月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年07月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年08月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年09月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年10月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年11月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年12月", budget: 0, real: 0, rate: 0, diff: 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: {
|
||||||
|
months: [], // 月份数组(2025年01月 - 2025年12月)
|
||||||
|
rates: [], // 每月完成率(百分比)
|
||||||
|
reals: [], // 每月实际值
|
||||||
|
budgets: [],// 每月预算值
|
||||||
|
diffs: [], // 每月差值
|
||||||
|
flags: [] // 每月达标标识(≥100 → 1,<100 → 0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
trend: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.processTrendData(newVal);
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.processTrendData(this.trend);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleChange(value) {
|
||||||
|
this.$emit("handleChange", value);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 处理趋势数据(适配12个月的数组结构)
|
||||||
|
* @param {Array} trendData - 原始趋势数组(12个月)
|
||||||
|
*/
|
||||||
|
processTrendData(trendData) {
|
||||||
|
// 数据兜底:确保是数组且长度为12
|
||||||
|
const validTrend = Array.isArray(trendData)
|
||||||
|
? trendData
|
||||||
|
: []
|
||||||
|
|
||||||
|
// 初始化空数组
|
||||||
|
const months = [];
|
||||||
|
const rates = [];
|
||||||
|
const reals = [];
|
||||||
|
const budgets = [];
|
||||||
|
const diffs = [];
|
||||||
|
const flags = [];
|
||||||
|
|
||||||
|
// 遍历12个月数据
|
||||||
|
validTrend.forEach(item => {
|
||||||
|
// 基础数据提取(兜底处理)
|
||||||
|
const month = item.title ?? '';
|
||||||
|
const budget = Number(item.budget) || 0;
|
||||||
|
const real = Number(item.real) || 0;
|
||||||
|
const rate = Number(item.rate) || 0;
|
||||||
|
const diff = Number(item.diff) || 0;
|
||||||
|
|
||||||
|
// 计算达标标识(≥100 → 1,<100 → 0)
|
||||||
|
const flag = this.getRateFlag(rate, real, budget);
|
||||||
|
|
||||||
|
// 填充数组
|
||||||
|
months.push(month);
|
||||||
|
rates.push(rate); // 转为百分比并取整
|
||||||
|
reals.push(real);
|
||||||
|
budgets.push(budget);
|
||||||
|
diffs.push(diff);
|
||||||
|
flags.push(flag);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新chartData(响应式)
|
||||||
|
this.chartData = {
|
||||||
|
months,
|
||||||
|
rates,
|
||||||
|
reals,
|
||||||
|
budgets,
|
||||||
|
diffs,
|
||||||
|
flags
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('处理后的趋势数据:', this.chartData);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算达标标识
|
||||||
|
* @param {Number} rate - 完成率(原始值,如1.2 → 120%)
|
||||||
|
* @returns {Number} 1: 达标(≥100%),0: 未达标(<100%)
|
||||||
|
*/
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</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>
|
||||||
|
/* 全局 tooltip 样式 */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
477
src/views/home/depreciationAnalysisComponents/dataTrendBar.vue
Normal file
477
src/views/home/depreciationAnalysisComponents/dataTrendBar.vue
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<!-- 新增行容器:包裹“各基地情况”和barTop -->
|
||||||
|
<div class="header-row">
|
||||||
|
<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">
|
||||||
|
<span class="legend-icon square achieved"></span>
|
||||||
|
实际·达标
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square unachieved"></span>
|
||||||
|
实际·未达标
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<div class="item-button category-btn">
|
||||||
|
<span class="item-text">类目选择</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>
|
||||||
|
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||||
|
<operatingLineBar :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSale.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
isDropdownShow: false,
|
||||||
|
selectedProfit: '营业收入', // 选中的名称,初始为null
|
||||||
|
profitOptions: [
|
||||||
|
'营业收入',
|
||||||
|
'单价',
|
||||||
|
'销量',
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// profitOptions() {
|
||||||
|
// return this.categoryData.map(item => item.name) || [];
|
||||||
|
// },
|
||||||
|
currentDataSource() {
|
||||||
|
console.log('yyyy', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
console.log('this.chartData', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData.months
|
||||||
|
},
|
||||||
|
// 根据按钮切换生成对应的 chartData
|
||||||
|
chartD() {
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
console.log(this.currentDataSource, 'currentDataSource');
|
||||||
|
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
// 1. 完成率(折线图)
|
||||||
|
{
|
||||||
|
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(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: data.rates, // 完成率(%)
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 2. 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||||
|
barWidth: 14,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.budgets // 目标销量(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图,含达标状态)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 14,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
// 固定label尺寸:68px×20px
|
||||||
|
width: 68,
|
||||||
|
height: 20,
|
||||||
|
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||||
|
formatter: (params) => {
|
||||||
|
const diff = data.diffs || [];
|
||||||
|
const flags = data.flags || [];
|
||||||
|
const currentDiff = diff[params.dataIndex] || 0;
|
||||||
|
const currentFlag = flags[params.dataIndex] || 0;
|
||||||
|
|
||||||
|
const prefix = currentFlag === 1 ? '+' : '-';
|
||||||
|
|
||||||
|
// 根据标志位选择不同的样式类
|
||||||
|
if (currentFlag === 1) {
|
||||||
|
// 达标 - 使用 rate-achieved 样式
|
||||||
|
return `{achieved|${currentDiff}}{text|差值}`;
|
||||||
|
} else {
|
||||||
|
// 未达标 - 使用 rate-unachieved 样式
|
||||||
|
return `{unachieved|${currentDiff}}{text|差值}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||||
|
{ offset: 0.2, color: '#ffffff' },
|
||||||
|
{ offset: 1, color: '#ffffff' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
shadowColor: 'rgba(191,203,215,0.5)',
|
||||||
|
shadowBlur: 2,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowOffsetY: 2,
|
||||||
|
borderRadius: 4,
|
||||||
|
borderColor: '#BFCBD577',
|
||||||
|
borderWidth: 0,
|
||||||
|
lineHeight: 20,
|
||||||
|
rich: {
|
||||||
|
text: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 10, 5, 0],
|
||||||
|
align: 'center',
|
||||||
|
color: '#464646',
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
},
|
||||||
|
achieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#76DABE', // 与达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
},
|
||||||
|
// 未达标样式
|
||||||
|
unachieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||||
|
const safeFlag = data.flags;
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
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)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals // 实际销量(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 根据按钮状态返回对应数据
|
||||||
|
return salesData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectProfit(item) {
|
||||||
|
this.selectedProfit = item;
|
||||||
|
this.isDropdownShow = false;
|
||||||
|
this.$emit("changeItem", item);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
// 新增:头部行容器,实现一行排列
|
||||||
|
.header-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end; // 左右两端对齐
|
||||||
|
align-items: center; // 垂直居中
|
||||||
|
// width: 100%;
|
||||||
|
margin-bottom: 8px; // 与下方图表区保留间距(可根据需求调整)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 各基地情况标题样式
|
||||||
|
.base-title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0 0 0 16px; // 保留原有内边距
|
||||||
|
white-space: nowrap; // 防止文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
// 移除原有flex和justify-content,由header-row控制
|
||||||
|
width: auto; // 自适应宽度
|
||||||
|
// 保留原有align-items,确保内部元素垂直居中
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; // 图例和按钮组垂直居中
|
||||||
|
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||||
|
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 图例:在右侧容器内横向排列
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||||
|
.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;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-btn {
|
||||||
|
width: 75px;
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-bottom-left-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-btn {
|
||||||
|
width: 123px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 18px 0 8px;
|
||||||
|
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: 123px;
|
||||||
|
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>
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="chartBox" style="width: 100%; height: 108px; position: relative;">
|
||||||
|
<div :id="id" style="width: 100%; height:100%;"></div>
|
||||||
|
<div class="bottomTip">
|
||||||
|
<div class="precent">
|
||||||
|
<span class="precentNum">{{ detailData.rate || 0 }}% </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'EnergyConsumption',
|
||||||
|
// components: { Container },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
detailData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
// electricComu: 0,
|
||||||
|
// steamComu: 20, // 调整为符合max范围的数值(0-8)
|
||||||
|
// // electricity: [120, 150, 130, 180, 160, 200, 190],
|
||||||
|
// // steam: [80, 95, 85, 110, 100, 120, 115],
|
||||||
|
// // dates: ['1日', '2日', '3日', '4日', '5日', '6日', '7日']
|
||||||
|
})
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
default: () => ('monthG')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// electricityChart: null,
|
||||||
|
// steamChart: null,
|
||||||
|
// specialTicks: [2, 4, 6, 8], // 统一的刻度显示
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
detailData: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true, // 初始化时立即执行
|
||||||
|
handler() {
|
||||||
|
this.updateGauges()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initGauges()
|
||||||
|
// window.addEventListener('resize', this.handleResize)
|
||||||
|
this.observeContainerResize()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
observeContainerResize() {
|
||||||
|
// 修复:获取正确的容器(组件内的.gauge-container)
|
||||||
|
const container = this.$el.querySelector('.gauge-container')
|
||||||
|
if (container && window.ResizeObserver) {
|
||||||
|
this.resizeObserver = new ResizeObserver(entries => {
|
||||||
|
if (this.electricityChart) {
|
||||||
|
this.electricityChart.resize() // 直接触发resize,无需防抖
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.resizeObserver.observe(container)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initGauges() {
|
||||||
|
// console.log('this.id',this.id);
|
||||||
|
|
||||||
|
// 初始化电气图表实例
|
||||||
|
const electricityDom = document.getElementById(this.id)
|
||||||
|
if (electricityDom) {
|
||||||
|
// 修复:正确创建并存储图表实例
|
||||||
|
this.electricityChart = echarts.init(electricityDom)
|
||||||
|
// 首次更新数据
|
||||||
|
this.updateGauges()
|
||||||
|
}
|
||||||
|
// 蒸汽图表若未使用,可注释/删除
|
||||||
|
// const steamDom = document.getElementById('steamGauge')
|
||||||
|
// if (steamDom) {
|
||||||
|
// this.steamChart = echarts.init(steamDom)
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
updateGauges() {
|
||||||
|
// 修复:先判断实例是否存在,再更新配置
|
||||||
|
if (!this.electricityChart) return
|
||||||
|
|
||||||
|
// 修复:兜底获取rate值,确保数值有效
|
||||||
|
const rate = Number(this.detailData?.rate) || 0
|
||||||
|
console.log('当前rate值:', rate); // 调试:确认rate值正确
|
||||||
|
|
||||||
|
// 关键:第二个参数传true,清空原有配置,强制更新
|
||||||
|
this.electricityChart.setOption(this.getElectricityGaugeOption(rate), true)
|
||||||
|
},
|
||||||
|
// 用电量仪表盘配置(保留原有样式,优化数值范围)
|
||||||
|
getElectricityGaugeOption(value) {
|
||||||
|
const electricityGradient = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: '#0B58FF' },
|
||||||
|
{ offset: 1, color: '#32FFCD' }
|
||||||
|
])
|
||||||
|
|
||||||
|
return {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '月度',
|
||||||
|
type: 'gauge',
|
||||||
|
radius: '95', // 修复:添加%,避免数值错误
|
||||||
|
center: ['50%', '90%'],
|
||||||
|
startAngle: 180,
|
||||||
|
endAngle: 0,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
splitNumber: 4,
|
||||||
|
label: { show: false },
|
||||||
|
progress: {
|
||||||
|
show: true,
|
||||||
|
overlap: false,
|
||||||
|
roundCap: true,
|
||||||
|
clip: false,
|
||||||
|
width: 14,
|
||||||
|
itemStyle: { color: electricityGradient }
|
||||||
|
},
|
||||||
|
pointer: {
|
||||||
|
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
|
||||||
|
length: '75%',
|
||||||
|
width: 16,
|
||||||
|
itemStyle: { color: '#288AFF' },
|
||||||
|
offsetCenter: [0, '10%']
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
roundCap: true,
|
||||||
|
lineStyle: { width: 12, color: [[1, '#E6EBF7']] }
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
length: 10,
|
||||||
|
lineStyle: { width: 5, color: '#D6DAE5' },
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
splitNumber: 2,
|
||||||
|
length: 6,
|
||||||
|
lineStyle: { width: 2, color: '#D6DAE5' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
detail: { show: false },
|
||||||
|
data: [{ value: value, unit: '' }] // 确保数值正确传入
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 未使用的蒸汽仪表盘可注释/删除
|
||||||
|
// getSteamGaugeOption(value) { ... }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
.chartBox {
|
||||||
|
.bottomTip {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.precent {
|
||||||
|
line-height: 3px;
|
||||||
|
|
||||||
|
&::after,
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 54px;
|
||||||
|
height: 5px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
margin-right: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用电量线条颜色
|
||||||
|
.precent::before {
|
||||||
|
|
||||||
|
background: linear-gradient(90deg, rgba(40, 138, 255, 0) 0%, rgba(12, 125, 254, 0.4) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ::after:从 透明 到 rgba(12, 125, 254, 0.4)(90度渐变,左到右) */
|
||||||
|
.precent::after {
|
||||||
|
background: linear-gradient(90deg, rgba(12, 125, 254, 0.4) 0%, rgba(40, 138, 255, 0) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蒸汽线条颜色
|
||||||
|
// .steam-precent::after,
|
||||||
|
// .steam-precent::before {
|
||||||
|
// background: linear-gradient(90deg, rgba(11, 168, 255, 0.26) 0%, rgba(54, 239, 230, 1) 100%);
|
||||||
|
// }
|
||||||
|
|
||||||
|
.precentNum {
|
||||||
|
display: inline-block;
|
||||||
|
// width: 52px;
|
||||||
|
height: 22px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蒸汽数字颜色
|
||||||
|
.steam-num {
|
||||||
|
color: rgba(54, 239, 230, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" 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; gap: 8px;">
|
||||||
|
<div class="dashboard">
|
||||||
|
<div class="title">
|
||||||
|
{{ month }}月完成率
|
||||||
|
</div>
|
||||||
|
<div class="number">
|
||||||
|
<div class="yield">
|
||||||
|
{{ monthData?.rate || 0 }}%
|
||||||
|
</div>
|
||||||
|
<div class="mom">
|
||||||
|
环比{{ monthData?.momRate }}%
|
||||||
|
<img v-if="monthData?.momRate >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="">
|
||||||
|
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="electricityGauge">
|
||||||
|
<electricityGauge :detailData="monthData" id="month"></electricityGauge>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
<div class="line" style="padding: 0px;">
|
||||||
|
<verticalBarChart :detailData="monthData">
|
||||||
|
</verticalBarChart>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import electricityGauge from './electricityGauge.vue'
|
||||||
|
import verticalBarChart from './verticalBarChart.vue'
|
||||||
|
|
||||||
|
// import * as echarts from 'echarts'
|
||||||
|
// import rawItem from './raw-Item.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, electricityGauge, verticalBarChart },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
monthData: { // 接收父组件传递的设备数据数组
|
||||||
|
type: Object,
|
||||||
|
default: () => {} // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
title: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
month: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// itemData: {
|
||||||
|
// handler(newValue, oldValue) {
|
||||||
|
// // this.updateChart()
|
||||||
|
// },
|
||||||
|
// deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
// computed: {
|
||||||
|
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||||
|
// sortedItemData() {
|
||||||
|
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||||
|
// const totalCostItems = this.itemData.filter(item =>
|
||||||
|
// item.name && item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 过滤出不包含“总成本”的项
|
||||||
|
// const otherItems = this.itemData.filter(item =>
|
||||||
|
// !item.name || !item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 合并:总成本项在前,其他项在后
|
||||||
|
// return [...totalCostItems, ...otherItems];
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 264px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
// width: 190px;
|
||||||
|
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;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 46px;
|
||||||
|
color: #0B58FF;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
width: 500px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .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> -->
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px; width: 100%;">
|
||||||
|
<!-- 销量模块(直接传递整合了flag的salesData) -->
|
||||||
|
<div class="dashboard left" @click="handleDashboardClick('/salesVolumeAnalysis/salesVolumeAnalysisBase')">
|
||||||
|
<div style='position: relative;'>
|
||||||
|
<div class="title">
|
||||||
|
销量·万㎡
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{monthAnalysis[0].rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:monthAnalysis[0].flags>0?'#30B590':'#FF9423'}" >{{monthAnalysis[0].diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-wrap">
|
||||||
|
<operatingSingleBar :detailData="salesData"></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 单价模块(直接传递整合了flag的unitPriceData) -->
|
||||||
|
<div class="dashboard right" @click="handleDashboardClick('/unitPriceAnalysis/unitPriceAnalysisBase')">
|
||||||
|
<div style='position: relative;'>
|
||||||
|
<div class="title">
|
||||||
|
单价·元/㎡
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{monthAnalysis[1].rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:monthAnalysis[1].flags>0?'#30B590':'#FF9423'}" >{{monthAnalysis[1].diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-wrap">
|
||||||
|
<operatingSingleBar :detailData="unitPriceData"></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import operatingSingleBar from './operatingSingleBar.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, operatingSingleBar },
|
||||||
|
props: {
|
||||||
|
monthAnalysis: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [
|
||||||
|
{ title: "销量", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
{ title: "单价", budget: 0, real: 0, rate: 0, diff: 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
factory: {
|
||||||
|
type: [String,Number],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
month: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
// 初始化数据包含flag字段
|
||||||
|
salesData: { title: "销量", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 },
|
||||||
|
unitPriceData: { title: "单价", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
monthAnalysis: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.updateChart(newVal)
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateChart(this.monthAnalysis)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleDashboardClick(path) {
|
||||||
|
this.$router.push({
|
||||||
|
path: path,
|
||||||
|
query: {
|
||||||
|
factory: this.$route.query.factory ? this.$route.query.factory : this.factory,
|
||||||
|
dateData: this.dateData
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 判断flag的核心方法
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateChart(data) {
|
||||||
|
// 数据兜底
|
||||||
|
const salesItem = Array.isArray(data) && data[0] ? data[0] : { title: "销量", budget: 0, real: 0, rate: 0, diff: 0 };
|
||||||
|
const unitPriceItem = Array.isArray(data) && data[1] ? data[1] : { title: "单价", budget: 0, real: 0, rate: 0, diff: 0 };
|
||||||
|
|
||||||
|
// 核心修改:将flag整合到数据对象中,无需单独定义salesFlag/unitPriceFlag
|
||||||
|
this.salesData = {
|
||||||
|
...salesItem, // 合并原有字段
|
||||||
|
flag: this.getRateFlag(salesItem.rate, salesItem.real, salesItem.budget) // 新增flag字段
|
||||||
|
};
|
||||||
|
|
||||||
|
this.unitPriceData = {
|
||||||
|
...unitPriceItem, // 合并原有字段
|
||||||
|
flag: this.getRateFlag(unitPriceItem.rate, unitPriceItem.real, unitPriceItem.budget) // 新增flag字段
|
||||||
|
};
|
||||||
|
|
||||||
|
// 调试:确认整合后的数据
|
||||||
|
console.log('整合flag后的销量数据:', this.salesData);
|
||||||
|
console.log('整合flag后的单价数据:', this.unitPriceData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topItem-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 300px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
margin: 0 4px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-wrap {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 30px;
|
||||||
|
height: 32px;
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
width: 97px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard.left {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard.right {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
503
src/views/home/depreciationAnalysisComponents/operatingBar.vue
Normal file
503
src/views/home/depreciationAnalysisComponents/operatingBar.vue
Normal file
@@ -0,0 +1,503 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<div class="header-row">
|
||||||
|
<div class="base-title">各基地情况</div>
|
||||||
|
<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">
|
||||||
|
<span class="legend-icon square achieved"></span>实际·达标
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square unachieved"></span>实际·未达标
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<div class="item-button category-btn">
|
||||||
|
<span class="item-text">展示顺序</span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-container">
|
||||||
|
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||||
|
<span class="item-text profit-text">{{ selectedSort || '请选择' }}</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.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||||
|
<operatingLineBar :dateData="dateData" :chartData="chartD" style="height: 100%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSale.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData", 'dateData'],
|
||||||
|
emits: ['sort-change'], // 声明事件(Vue3 推荐)
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
isDropdownShow: false,
|
||||||
|
selectedSort: '实际值:高~低', // 选中的label
|
||||||
|
selectedSortValue: 1, // 选中的value,用于排序逻辑
|
||||||
|
profitOptions: [
|
||||||
|
{ label: '实际值:高~低', value: 1 },
|
||||||
|
{ label: '实际值:低~高', value: 2 },
|
||||||
|
{ label: '完成率:高~低', value: 3 },
|
||||||
|
{ label: '完成率:低~高', value: 4 },
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 排序后的数据源(核心:根据selectedSortValue重新排序)
|
||||||
|
currentDataSource() {
|
||||||
|
if (!this.chartData?.factory) return {};
|
||||||
|
|
||||||
|
// 深拷贝原始数据,避免修改原数据
|
||||||
|
const factory = JSON.parse(JSON.stringify(this.chartData.factory));
|
||||||
|
if (!factory.locations.length || !this.selectedSortValue) return factory;
|
||||||
|
|
||||||
|
// 构建带索引的数组,方便同步所有字段排序
|
||||||
|
const dataWithIndex = factory.locations.map((name, index) => ({
|
||||||
|
index,
|
||||||
|
name,
|
||||||
|
real: factory.reals[index],
|
||||||
|
target: factory.targets[index],
|
||||||
|
rate: factory.rates[index],
|
||||||
|
diff: factory.diff[index],
|
||||||
|
flag: factory.flags[index]
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 根据选中的排序规则排序
|
||||||
|
switch (this.selectedSortValue) {
|
||||||
|
case 1: // 实际值:高~低
|
||||||
|
dataWithIndex.sort((a, b) => b.real - a.real);
|
||||||
|
break;
|
||||||
|
case 2: // 实际值:低~高
|
||||||
|
dataWithIndex.sort((a, b) => a.real - b.real);
|
||||||
|
break;
|
||||||
|
case 3: // 预算值:高~低
|
||||||
|
dataWithIndex.sort((a, b) => b.rate - a.rate);
|
||||||
|
break;
|
||||||
|
case 4: // 预算值:低~高
|
||||||
|
dataWithIndex.sort((a, b) => a.rate - b.rate);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步更新所有数组
|
||||||
|
factory.locations = dataWithIndex.map(item => item.name);
|
||||||
|
factory.reals = dataWithIndex.map(item => item.real);
|
||||||
|
factory.targets = dataWithIndex.map(item => item.target);
|
||||||
|
factory.rates = dataWithIndex.map(item => item.rate);
|
||||||
|
factory.diff = dataWithIndex.map(item => item.diff);
|
||||||
|
factory.flags = dataWithIndex.map(item => item.flag);
|
||||||
|
|
||||||
|
return factory;
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
return this.currentDataSource.locations || [];
|
||||||
|
},
|
||||||
|
// 最终传递给图表的排序后数据
|
||||||
|
chartD() {
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
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(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: data.rates || [],
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 40,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.targets || []
|
||||||
|
},
|
||||||
|
// 实际(柱状图)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 40,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [32, 0],
|
||||||
|
width: 100,
|
||||||
|
height: 22,
|
||||||
|
formatter: (params) => {
|
||||||
|
const diff = data.diff || [];
|
||||||
|
const flags = data.flags || [];
|
||||||
|
const currentDiff = diff[params.dataIndex] || 0;
|
||||||
|
const currentFlag = flags[params.dataIndex] || 0;
|
||||||
|
|
||||||
|
const prefix = currentFlag === 1 ? '+' : '-';
|
||||||
|
|
||||||
|
// 根据标志位选择不同的样式类
|
||||||
|
if (currentFlag === 1) {
|
||||||
|
// 达标 - 使用 rate-achieved 样式
|
||||||
|
return `{achieved|${currentDiff}}{text|差值}`;
|
||||||
|
} else {
|
||||||
|
// 未达标 - 使用 rate-unachieved 样式
|
||||||
|
return `{unachieved|${currentDiff}}{text|差值}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||||
|
{ offset: 0.2, color: '#ffffff' },
|
||||||
|
{ offset: 1, color: '#ffffff' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
shadowColor: 'rgba(191,203,215,0.5)',
|
||||||
|
shadowBlur: 2,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowOffsetY: 2,
|
||||||
|
borderRadius: 4,
|
||||||
|
borderColor: '#BFCBD577',
|
||||||
|
borderWidth: 0,
|
||||||
|
lineHeight: 26,
|
||||||
|
rich: {
|
||||||
|
text: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 10, 5, 0],
|
||||||
|
align: 'center',
|
||||||
|
color: '#464646',
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
achieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#76DABE', // 与达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
// 未达标样式
|
||||||
|
unachieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 14,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const safeFlag = data.flags || [];
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
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)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals || []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
return salesData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectProfit(item) {
|
||||||
|
// 更新选中的label和value
|
||||||
|
this.selectedSort = item.label;
|
||||||
|
this.selectedSortValue = item.value;
|
||||||
|
this.isDropdownShow = false;
|
||||||
|
// 向父组件传递排序事件(可选,保持原有逻辑)
|
||||||
|
this.$emit('sort-change', item.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 监听父组件传入的chartData变化,重置选中状态(可选)
|
||||||
|
watch: {
|
||||||
|
'chartData.factory': {
|
||||||
|
handler() {
|
||||||
|
// 若需要切换数据源后重置排序,可取消注释
|
||||||
|
// this.selectedSort = null;
|
||||||
|
// this.selectedSortValue = null;
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.header-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0 0 0 16px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
width: auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
margin-right: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
fontSize: 12px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-btn {
|
||||||
|
width: 75px;
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-bottom-left-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-btn {
|
||||||
|
width: 123px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 18px 0 8px;
|
||||||
|
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: 123px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.dropdown-option {
|
||||||
|
padding: 6px 12px;
|
||||||
|
fontSize: 12px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
color: #3071ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChipBottom" id="cockpitEffChipBottom" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
series: [],
|
||||||
|
allPlaceNames: []
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
validator: (value) => {
|
||||||
|
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChipBottom;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatter: (params) => {
|
||||||
|
let html = `${params[0].axisValue}<br/>`;
|
||||||
|
params.forEach(item => {
|
||||||
|
const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
);
|
||||||
|
html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 60,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
splitNumber: 4,
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: (value) => Math.ceil((value.max || 0) * 1.1),
|
||||||
|
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
scale:true,
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
series: [],
|
||||||
|
allPlaceNames: []
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
validator: (value) => {
|
||||||
|
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 60,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
// {
|
||||||
|
// type: 'value',
|
||||||
|
// nameTextStyle: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// align: 'left'
|
||||||
|
// },
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
// axisTick: { show: false },
|
||||||
|
// axisLabel: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// formatter: '{value}%'
|
||||||
|
// },
|
||||||
|
// splitLine: { show: false },
|
||||||
|
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
// splitNumber: 4
|
||||||
|
// }
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null, // 存储图表实例
|
||||||
|
resizeHandler: null, // 存储resize事件处理函数
|
||||||
|
// 核心:基地名称与序号的映射表(固定顺序)
|
||||||
|
baseNameToIndexMap: {
|
||||||
|
'宜兴': 7,
|
||||||
|
'漳州': 8,
|
||||||
|
'自贡': 3,
|
||||||
|
'桐城': 2,
|
||||||
|
'洛阳': 9,
|
||||||
|
'合肥': 5,
|
||||||
|
'宿迁': 6,
|
||||||
|
'秦皇岛': 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.initChart(); // 初始化图表(只执行一次)
|
||||||
|
this.updateChart(); // 更新图表数据
|
||||||
|
});
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
this.updateChart(); // 仅更新数据,不重新创建实例
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 组件销毁时清理资源
|
||||||
|
this.destroyChart();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 初始化图表(只在mounted中执行一次)
|
||||||
|
initChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只创建一次图表实例
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
// 绑定点击事件(只绑定一次,永久生效)
|
||||||
|
this.myChart.getZr().on('click', (params) => {
|
||||||
|
console.log('params', params);
|
||||||
|
|
||||||
|
// 提取点击的基地名称
|
||||||
|
// const itemName = params.name;
|
||||||
|
let itemName = undefined
|
||||||
|
// 根据映射表获取对应的序号(未匹配到则返回0或其他默认值)
|
||||||
|
let pointInPixel = [params.offsetX, params.offsetY];
|
||||||
|
if (this.myChart.containPixel('grid', pointInPixel)) {
|
||||||
|
let pointInGrid = this.myChart.convertFromPixel({
|
||||||
|
seriesIndex: 0
|
||||||
|
}, pointInPixel);
|
||||||
|
let xIndex = pointInGrid[0]; //索引
|
||||||
|
let handleIndex = Number(xIndex);
|
||||||
|
let seriesObj = this.myChart.getOption(); //图表object对象
|
||||||
|
var op = this.myChart.getOption();
|
||||||
|
//获得图表中点击的列
|
||||||
|
itemName = op.xAxis[0].data[handleIndex]; //获取点击的列名
|
||||||
|
console.log(itemName, 'monthmonthmonth');
|
||||||
|
console.log(handleIndex, seriesObj);
|
||||||
|
};
|
||||||
|
const baseIndex = this.baseNameToIndexMap[itemName] || 0;
|
||||||
|
|
||||||
|
console.log(`你点击了【${itemName}】(序号:${baseIndex})`);
|
||||||
|
if (itemName === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 路由跳转时携带序号(或名称+序号)
|
||||||
|
this.$router.push({
|
||||||
|
path: 'operatingRevenueBase',
|
||||||
|
query: { // 使用query传递参数(推荐),也可使用params
|
||||||
|
// baseName: itemName,
|
||||||
|
factory: baseIndex,
|
||||||
|
dateData: this.dateData
|
||||||
|
}
|
||||||
|
// 若仍需用base作为参数:
|
||||||
|
// base: itemName,
|
||||||
|
// params: { baseIndex: baseIndex }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定义resize处理函数(命名函数,方便移除)
|
||||||
|
this.resizeHandler = () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
};
|
||||||
|
// 绑定resize事件(只绑定一次)
|
||||||
|
window.addEventListener('resize', this.resizeHandler);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新图表数据(数据变化时执行)
|
||||||
|
updateChart() {
|
||||||
|
if (!this.myChart) {
|
||||||
|
return; // 实例未初始化则返回
|
||||||
|
}
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || [];
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 5,
|
||||||
|
right: 20,
|
||||||
|
left: 25,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
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],
|
||||||
|
// 可选:X轴标签显示“序号+名称”(如“1 宜兴”)
|
||||||
|
// formatter: (value) => {
|
||||||
|
// const index = this.baseNameToIndexMap[value] || '';
|
||||||
|
// return index ? `${index} ${value}` : value;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries
|
||||||
|
};
|
||||||
|
|
||||||
|
// 只更新配置,不重新创建实例
|
||||||
|
this.myChart.setOption(option, true); // 第二个参数true表示清空原有配置,避免数据残留
|
||||||
|
},
|
||||||
|
|
||||||
|
// 销毁图表资源
|
||||||
|
destroyChart() {
|
||||||
|
// 移除resize事件
|
||||||
|
if (this.resizeHandler) {
|
||||||
|
window.removeEventListener('resize', this.resizeHandler);
|
||||||
|
}
|
||||||
|
// 销毁图表实例
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
this.myChart = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 380px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
console.log('chartData', this.chartData);
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
console.log('xData', xData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom:20,
|
||||||
|
right: 10,
|
||||||
|
left: 25,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
//
|
||||||
|
splitNumber: 4,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 200px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
refName: {
|
||||||
|
type: String,
|
||||||
|
default: () => 'cockpitEffChip',
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs[this.refName];
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
console.log('chartData', this.chartData);
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
console.log('xData', xData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
// axisPointer: {
|
||||||
|
// type: 'cross',
|
||||||
|
// label: {
|
||||||
|
// backgroundColor: '#6a7985'
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 20,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 5,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
// name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true, // 显示Y轴轴线(关键)
|
||||||
|
lineStyle: {
|
||||||
|
color: '#E5E6EB', // 轴线颜色(浅灰色,可自定义)
|
||||||
|
width: 1, // 轴线宽度
|
||||||
|
type: 'solid' // 实线(可选:dashed虚线、dotted点线)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 2,
|
||||||
|
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)' } },
|
||||||
|
},
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,349 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="当月数据对比" icon="cockpitItemIcon" size="operatingLarge" topSize="large">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="left" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 348px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
">
|
||||||
|
集团情况
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{chartData.group.rate[0]}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.group.flags>0?'#30B590':'#FF9423'}" >{{chartData.group.diff[0]}}</span></span>
|
||||||
|
</div>
|
||||||
|
<operatingTopBar :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
<div class="right" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 1220px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<!-- <top-item /> -->
|
||||||
|
<operatingBar :dateData="dateData" :chartData="chartData" @sort-change="sortChange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import operatingBar from "./operatingBar.vue";
|
||||||
|
import operatingTopBar from "./operatingTopBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, operatingBar, operatingTopBar },
|
||||||
|
props: {
|
||||||
|
monthData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
group: {
|
||||||
|
rate: 0,
|
||||||
|
diff: 0,
|
||||||
|
real: 0,
|
||||||
|
target: 0
|
||||||
|
},
|
||||||
|
factory: []
|
||||||
|
}),
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: null, // 初始化 chartData 为 null
|
||||||
|
groupData: {}, // 集团数据
|
||||||
|
factoryData: [] // 工厂数据
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
monthData: {
|
||||||
|
handler() {
|
||||||
|
this.processChartData();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
sortChange(value) {
|
||||||
|
this.$emit('sort-change', value);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 判断rate对应的flag值(<1为0,>1为1)
|
||||||
|
* @param {number} rate 处理后的rate值(已*100)
|
||||||
|
* @returns {0|1} flag值
|
||||||
|
*/
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 核心处理函数:在所有数据都准备好后,才组装 chartData
|
||||||
|
*/
|
||||||
|
processChartData() {
|
||||||
|
// 1. 处理集团数据 - 提取各字段到对应数组
|
||||||
|
this.groupData = this.monthData.group || { rate: 0, diff: 0, real: 0, target: 0 };
|
||||||
|
|
||||||
|
// 集团各维度数据数组(单条数据,对应凯盛新能)
|
||||||
|
const groupTarget = [this.groupData.target]; // 预算值数组
|
||||||
|
const groupDiff = [this.groupData.diff]; // 差值数组
|
||||||
|
const groupReal = [this.groupData.real]; // 实际值数组
|
||||||
|
const groupRate = [this.groupData.rate]; // 完成率数组
|
||||||
|
// 新增:集团rate对应的flag
|
||||||
|
const groupFlag = [this.getRateFlag(groupRate[0], groupReal[0], groupTarget[0])];
|
||||||
|
|
||||||
|
console.log('集团数据数组:', {
|
||||||
|
groupTarget,
|
||||||
|
groupDiff,
|
||||||
|
groupReal,
|
||||||
|
groupRate,
|
||||||
|
groupFlag,
|
||||||
|
rawGroupData: this.groupData
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 处理工厂数据 - 提取每个工厂的对应字段到数组
|
||||||
|
this.factoryData = this.monthData.factory || [];
|
||||||
|
// 提取工厂名称数组
|
||||||
|
const factoryNames = this.factoryData.map(item => item.title || '');
|
||||||
|
// 提取工厂各维度数据数组
|
||||||
|
const factoryBudget = this.factoryData.map(item => item.budget || 0);
|
||||||
|
const factoryReal = this.factoryData.map(item => item.real || 0);
|
||||||
|
const factoryRate = this.factoryData.map(item => item.rate || 0);
|
||||||
|
const factoryDiff = this.factoryData.map(item => item.diff || 0);
|
||||||
|
// 新增:每个工厂rate对应的flag数组
|
||||||
|
const factoryFlags = this.factoryData.map(item => this.getRateFlag(item.rate, item.real, item.budget));
|
||||||
|
|
||||||
|
// 3. 组装最终的chartData(供子组件使用)
|
||||||
|
this.chartData = {
|
||||||
|
// 集团数据(对应凯盛新能)
|
||||||
|
group: {
|
||||||
|
locations: ['凯盛新能'], // 集团名称
|
||||||
|
targets: groupTarget, // 集团预算值数组
|
||||||
|
diff: groupDiff, // 集团差值数组
|
||||||
|
reals: groupReal, // 集团实际值数组
|
||||||
|
rate: groupRate, // 集团完成率数组
|
||||||
|
flags: groupFlag // 新增:集团rate对应的flag
|
||||||
|
},
|
||||||
|
// 工厂数据
|
||||||
|
factory: {
|
||||||
|
locations: factoryNames, // 工厂名称数组
|
||||||
|
targets: factoryBudget, // 工厂预算数组
|
||||||
|
reals: factoryReal, // 工厂实际值数组
|
||||||
|
rates: factoryRate, // 工厂完成率数组
|
||||||
|
diff: factoryDiff, // 工厂差值数组
|
||||||
|
flags: factoryFlags // 新增:工厂rate对应的flags数组
|
||||||
|
},
|
||||||
|
// 原始数据备份(方便后续使用)
|
||||||
|
rawData: {
|
||||||
|
group: this.groupData,
|
||||||
|
factory: this.factoryData
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('最终处理后的图表数据:', this.chartData);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</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>
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="累计数据对比" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="left" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 348px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
">
|
||||||
|
集团情况
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{chartData.group.rate[0]}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.group.flags>0?'#30B590':'#FF9423'}" >{{chartData.group.diff[0]}}</span></span>
|
||||||
|
</div>
|
||||||
|
<operatingTopBar :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
<div class="right" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 1220px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<!-- <top-item /> -->
|
||||||
|
<operatingBar :dateData="dateData" @sort-change="sortChange" :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import operatingBar from "./operatingBar.vue";
|
||||||
|
import operatingTopBar from "./operatingTopBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, operatingBar, operatingTopBar },
|
||||||
|
props: {
|
||||||
|
salesTrendMap: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
ytdData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: null, // 初始化 chartData 为 null
|
||||||
|
groupData: {}, // 集团数据
|
||||||
|
factoryData: [] // 工厂数据
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
ytdData: {
|
||||||
|
handler() {
|
||||||
|
this.processChartData();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
sortChange(value) {
|
||||||
|
this.$emit('sort-change', value);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 判断rate对应的flag值(<1为0,>1为1)
|
||||||
|
* @param {number} rate 处理后的rate值(已*100)
|
||||||
|
* @returns {0|1} flag值
|
||||||
|
*/
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心处理函数:在所有数据都准备好后,才组装 chartData
|
||||||
|
*/
|
||||||
|
processChartData() {
|
||||||
|
// 1. 处理集团数据 - 提取各字段到对应数组
|
||||||
|
this.groupData = this.ytdData.group || { rate: 0, diff: 0, real: 0, target: 0 };
|
||||||
|
|
||||||
|
// 集团各维度数据数组(单条数据,对应凯盛新能)
|
||||||
|
const groupTarget = [this.groupData.target]; // 预算值数组
|
||||||
|
const groupDiff = [this.groupData.diff]; // 差值数组
|
||||||
|
const groupReal = [this.groupData.real]; // 实际值数组
|
||||||
|
const groupRate = [this.groupData.rate]; // 完成率数组
|
||||||
|
// 新增:集团rate对应的flag
|
||||||
|
const groupFlag = [this.getRateFlag(groupRate[0], groupReal[0], groupTarget[0])];
|
||||||
|
|
||||||
|
console.log('集团数据数组:', {
|
||||||
|
groupTarget,
|
||||||
|
groupDiff,
|
||||||
|
groupReal,
|
||||||
|
groupRate,
|
||||||
|
groupFlag,
|
||||||
|
rawGroupData: this.groupData
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 处理工厂数据 - 提取每个工厂的对应字段到数组
|
||||||
|
this.factoryData = this.ytdData.factory || [];
|
||||||
|
// 提取工厂名称数组
|
||||||
|
const factoryNames = this.factoryData.map(item => item.title || '');
|
||||||
|
// 提取工厂各维度数据数组
|
||||||
|
const factoryBudget = this.factoryData.map(item => item.budget || 0);
|
||||||
|
const factoryReal = this.factoryData.map(item => item.real || 0);
|
||||||
|
const factoryRate = this.factoryData.map(item => item.rate || 0);
|
||||||
|
const factoryDiff = this.factoryData.map(item => item.diff || 0);
|
||||||
|
// 新增:每个工厂rate对应的flag数组
|
||||||
|
const factoryFlags = this.factoryData.map(item => this.getRateFlag(item.rate, item.real, item.budget));
|
||||||
|
|
||||||
|
// 3. 组装最终的chartData(供子组件使用)
|
||||||
|
this.chartData = {
|
||||||
|
// 集团数据(对应凯盛新能)
|
||||||
|
group: {
|
||||||
|
locations: ['凯盛新能'], // 集团名称
|
||||||
|
targets: groupTarget, // 集团预算值数组
|
||||||
|
diff: groupDiff, // 集团差值数组
|
||||||
|
reals: groupReal, // 集团实际值数组
|
||||||
|
rate: groupRate, // 集团完成率数组
|
||||||
|
flags: groupFlag // 新增:集团rate对应的flag
|
||||||
|
},
|
||||||
|
// 工厂数据
|
||||||
|
factory: {
|
||||||
|
locations: factoryNames, // 工厂名称数组
|
||||||
|
targets: factoryBudget, // 工厂预算数组
|
||||||
|
reals: factoryReal, // 工厂实际值数组
|
||||||
|
rates: factoryRate, // 工厂完成率数组
|
||||||
|
diff: factoryDiff, // 工厂差值数组
|
||||||
|
flags: factoryFlags // 新增:工厂rate对应的flags数组
|
||||||
|
},
|
||||||
|
// 原始数据备份(方便后续使用)
|
||||||
|
rawData: {
|
||||||
|
group: this.groupData,
|
||||||
|
factory: this.factoryData
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('最终处理后的图表数据:', this.chartData);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</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>
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
<template>
|
||||||
|
<div class="lineBottom" style="height: 180px; width: 100%">
|
||||||
|
<operatingLineBarSaleSingle :refName=" 'totalOperating' " :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBarSaleSingle from './operatingLineBarSaleSingle.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBarSaleSingle },
|
||||||
|
props: ["detailData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
locations() {
|
||||||
|
return ['预算', '实际'];
|
||||||
|
},
|
||||||
|
chartD() {
|
||||||
|
// 背景图片路径(若不需要可注释)
|
||||||
|
// const bgImageUrl = require('@/assets/img/labelBg.png');
|
||||||
|
console.log('this.detailData', this.detailData);
|
||||||
|
const rate = this.detailData?.rate || 0
|
||||||
|
const diff = this.detailData?.diff || 0
|
||||||
|
console.log('diff', diff);
|
||||||
|
|
||||||
|
const seriesData = [
|
||||||
|
{
|
||||||
|
value: this.detailData?.budget || 0,
|
||||||
|
flag: 1, // 实际项:达标(绿色)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: this.detailData?.real || 0,
|
||||||
|
flag: this.detailData?.flag, // 实际项:达标(绿色)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
allPlaceNames: ['预算', '实际'],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 60,
|
||||||
|
barCategoryGap: '50%',
|
||||||
|
data: seriesData,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const currentFlag = params.data.flag || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
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)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
console.log('data', data);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFangSC;
|
||||||
|
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;
|
||||||
|
margin-right: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
width: 283px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0b58ff;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 8px;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button.active {
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
background: #3071ff;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,337 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<div class="lineBottom" style="height: 320px; width: 100%">
|
||||||
|
<operatingLineBar :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSaleGroup.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentDataSource() {
|
||||||
|
console.log('yyyy', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData.group
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
console.log('this.chartData', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData.group.locations
|
||||||
|
},
|
||||||
|
// 根据按钮切换生成对应的 chartData
|
||||||
|
chartD() {
|
||||||
|
// 销量场景数据
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
const diff = this.currentDataSource.diff[0]
|
||||||
|
const rate = this.currentDataSource.rate[0]
|
||||||
|
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.targets // 目标销量(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图,含达标状态)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||||
|
const safeFlag = data.flags;
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
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)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals // 实际销量(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 毛利率场景数据
|
||||||
|
const grossProfitData = {
|
||||||
|
series: [
|
||||||
|
// 1. 完成率(折线图)
|
||||||
|
{
|
||||||
|
name: '完成率',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
lineStyle: { color: 'rgba(40, 138, 255, .5)', width: 2 },
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderColor: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
radius: 4
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.2,
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||||
|
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: [106.7, 96.9, 106.5, 106.1, 93.8, 105.9], // 毛利率完成率(%)
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 2. 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: [30, 32, 31, 33, 32, 34] // 目标毛利率(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const safeFlag = [1, 0, 1, 1, 0, 1]; // 达标状态
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
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)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: [32, 31, 33, 35, 30, 36] // 实际毛利率(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据按钮状态返回对应数据
|
||||||
|
return this.activeButton === 0 ? salesData : grossProfitData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end; // 标题左、右侧容器右,整体两端对齐
|
||||||
|
align-items: center; // 垂直居中,避免上下错位
|
||||||
|
gap: 16px; // 标题与右侧容器的最小间距,防止拥挤
|
||||||
|
width: 100%; // 确保占满父容器,实现两端对齐
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 标题固定在左侧,不挤压右侧空间
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; // 图例和按钮组垂直居中
|
||||||
|
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||||
|
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 图例:在右侧容器内横向排列
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px; // 图例项之间间距,避免重叠
|
||||||
|
align-items: center;
|
||||||
|
// 移除原margin-left,避免位置偏移
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap; // 防止图例文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图例颜色
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
width: 283px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
border-radius: 12px;
|
||||||
|
// 移除原margin-right,由右侧容器统一控制
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0b58ff;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 8px; // 确保文字间距生效
|
||||||
|
padding-left: 8px; // 抵消letter-spacing导致的文字左偏
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button.active {
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
background: #3071ff;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
202
src/views/home/depreciationAnalysisComponents/totalOverview.vue
Normal file
202
src/views/home/depreciationAnalysisComponents/totalOverview.vue
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" 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; gap: 8px;">
|
||||||
|
<div class="dashboard">
|
||||||
|
<div class="title">
|
||||||
|
累计完成率
|
||||||
|
</div>
|
||||||
|
<div class="number">
|
||||||
|
<div class="yield">
|
||||||
|
{{ ytdData?.rate || 0}}%
|
||||||
|
</div>
|
||||||
|
<div class="mom">
|
||||||
|
同比{{ ytdData?.yoyRate || 0}}%
|
||||||
|
<img v-if="ytdData?.yoyRate >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="">
|
||||||
|
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="electricityGauge">
|
||||||
|
<electricityGauge :id=" 'totalG' " :detailData="ytdData" id="totalGauge"></electricityGauge>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
<div class="line" style="padding: 0px;">
|
||||||
|
<verticalBarChart :refName=" 'totalVerticalBarChart' " :detailData="ytdData">
|
||||||
|
</verticalBarChart>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import electricityGauge from './electricityGauge.vue'
|
||||||
|
import verticalBarChart from './verticalBarChart.vue'
|
||||||
|
|
||||||
|
// import * as echarts from 'echarts'
|
||||||
|
// import rawItem from './raw-Item.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, electricityGauge, verticalBarChart },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
ytdData: { // 接收父组件传递的设备数据数组
|
||||||
|
type: Object,
|
||||||
|
default: () => {} // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
title: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
month: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
itemData: {
|
||||||
|
handler(newValue, oldValue) {
|
||||||
|
// this.updateChart()
|
||||||
|
},
|
||||||
|
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// computed: {
|
||||||
|
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||||
|
// sortedItemData() {
|
||||||
|
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||||
|
// const totalCostItems = this.itemData.filter(item =>
|
||||||
|
// item.name && item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 过滤出不包含“总成本”的项
|
||||||
|
// const otherItems = this.itemData.filter(item =>
|
||||||
|
// !item.name || !item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 合并:总成本项在前,其他项在后
|
||||||
|
// return [...totalCostItems, ...otherItems];
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 264px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
.title {
|
||||||
|
// width: 190px;
|
||||||
|
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;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 46px;
|
||||||
|
color: #0B58FF;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
width: 500px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .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> -->
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
<template>
|
||||||
|
<div style="width: 100%; height: 210px;position: relative;">
|
||||||
|
<div style='font-size: 16px;position: absolute;right: 20px;top:10px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{detailData.rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:detailData.flags>0?'#30B590':'#FF9423'}" >{{detailData.diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 210px;"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
refName: {
|
||||||
|
type: String,
|
||||||
|
default: () => 'verticalBarChart',
|
||||||
|
},
|
||||||
|
detailData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
detailData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs[this.refName];
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const diff = this.detailData.diff || 0
|
||||||
|
const rate = this.detailData.rate || 0
|
||||||
|
const flagValue = this.getRateFlag(this.detailData.rate, this.detailData.real, this.detailData.target) || 0
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 40,
|
||||||
|
bottom: 15,
|
||||||
|
right: 80,
|
||||||
|
left: 10,
|
||||||
|
containLabel: true,
|
||||||
|
show: false // 隐藏grid背景,避免干扰
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
// 横向柱状图的x轴必须设为数值轴,否则无法正常展示数值
|
||||||
|
type: 'value',
|
||||||
|
// offset: 0,
|
||||||
|
// boundaryGap: true ,
|
||||||
|
// boundaryGap: [10, 0], // 可根据需要开启,控制轴的留白
|
||||||
|
axisTick: { show: false },
|
||||||
|
min: 0,
|
||||||
|
//
|
||||||
|
splitNumber: 4,
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
// data: xData // 数值轴不需要手动设置data,由series的数据自动生成
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'category',
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.75)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true, // 显示Y轴轴线(关键)
|
||||||
|
lineStyle: {
|
||||||
|
color: '#E5E6EB', // 轴线颜色(浅灰色,可自定义)
|
||||||
|
width: 1, // 轴线宽度
|
||||||
|
type: 'solid' // 实线(可选:dashed虚线、dotted点线)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisTick: { show: false },
|
||||||
|
// padding: [300, 100, 100, 100],
|
||||||
|
data: ['实际', '预算'] // y轴分类:实际、预算
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
// name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 24,
|
||||||
|
// barCategoryGap: '50', // 柱子之间的间距(相对于柱子宽度)
|
||||||
|
// 数据长度与yAxis的分类数量匹配(实际、预算各一个值)
|
||||||
|
data: [{
|
||||||
|
value: this.detailData.real,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'right',
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: flagValue === 1
|
||||||
|
? {
|
||||||
|
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)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0]
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
value: this.detailData.target,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'right',
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
// 预算的渐变颜色(蓝系渐变)
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 1, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: '#82CCFF' }, // 浅蓝
|
||||||
|
{ offset: 1, color: '#4B9DFF' } // 深蓝
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
},],
|
||||||
|
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px; width: 100%;">
|
||||||
|
<!-- 销量模块(直接传递整合了flag的salesData) -->
|
||||||
|
<div class="dashboard left" @click="handleDashboardClick('/salesVolumeAnalysis/salesVolumeAnalysisBase')">
|
||||||
|
<div style='position: relative;'>
|
||||||
|
<div class="title">
|
||||||
|
销量·万㎡
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{ytdAnalysis[0].rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:ytdAnalysis[0].flags>0?'#30B590':'#FF9423'}" >{{ytdAnalysis[0].diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-wrap">
|
||||||
|
<operatingSingleBar :detailData="salesData"></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 单价模块(直接传递整合了flag的unitPriceData) -->
|
||||||
|
<div class="dashboard right" @click="handleDashboardClick('/unitPriceAnalysis/unitPriceAnalysisBase')">
|
||||||
|
<div style='position: relative;'>
|
||||||
|
<div class="title">
|
||||||
|
单价·元/㎡
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{ytdAnalysis[1].rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:ytdAnalysis[1].flags>0?'#30B590':'#FF9423'}" >{{ytdAnalysis[1].diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-wrap">
|
||||||
|
<operatingSingleBar :detailData="unitPriceData"></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import operatingSingleBar from './operatingSingleBar.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, operatingSingleBar },
|
||||||
|
props: {
|
||||||
|
ytdAnalysis: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [
|
||||||
|
{ title: "销量", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
{ title: "单价", budget: 0, real: 0, rate: 0, diff: 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
month: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
// 初始化数据包含flag字段
|
||||||
|
salesData: { title: "销量", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 },
|
||||||
|
unitPriceData: { title: "单价", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
ytdAnalysis: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.updateChart(newVal)
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateChart(this.ytdAnalysis)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleDashboardClick(path) {
|
||||||
|
this.$router.push({
|
||||||
|
path: path,
|
||||||
|
query: {
|
||||||
|
factory: this.$route.query.factory ? this.$route.query.factory : 5,
|
||||||
|
dateData: this.dateData
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 判断flag的核心方法
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateChart(data) {
|
||||||
|
// 数据兜底
|
||||||
|
const salesItem = Array.isArray(data) && data[0] ? data[0] : { title: "销量", budget: 0, real: 0, rate: 0, diff: 0 };
|
||||||
|
const unitPriceItem = Array.isArray(data) && data[1] ? data[1] : { title: "单价", budget: 0, real: 0, rate: 0, diff: 0 };
|
||||||
|
|
||||||
|
// 核心修改:将flag整合到数据对象中,无需单独定义salesFlag/unitPriceFlag
|
||||||
|
this.salesData = {
|
||||||
|
...salesItem, // 合并原有字段
|
||||||
|
flag: this.getRateFlag(salesItem.rate, salesItem.real, salesItem.budget) // 新增flag字段
|
||||||
|
};
|
||||||
|
|
||||||
|
this.unitPriceData = {
|
||||||
|
...unitPriceItem, // 合并原有字段
|
||||||
|
flag: this.getRateFlag(unitPriceItem.rate, unitPriceItem.real, unitPriceItem.budget) // 新增flag字段
|
||||||
|
};
|
||||||
|
|
||||||
|
// 调试:确认整合后的数据
|
||||||
|
console.log('整合flag后的销量数据:', this.salesData);
|
||||||
|
console.log('整合flag后的单价数据:', this.unitPriceData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topItem-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 300px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
margin: 0 4px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-wrap {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 30px;
|
||||||
|
height: 32px;
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
width: 97px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard.left {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard.right {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
<template>
|
||||||
|
<div id="dayReport" class="dayReport" :style="styles">
|
||||||
|
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||||
|
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||||
|
<ReportHeader :dateData="dateData" top-title="电费分析" :is-full-screen="isFullScreen"
|
||||||
|
@screenfullChange="screenfullChange" @timeRangeChange="handleTimeChange" />
|
||||||
|
<div class="main-body" style="
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
padding: 0px 16px 0 272px;
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div class="top" style="margin-top: -20px; display: flex; gap: 16px">
|
||||||
|
<div class="top-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns:1624px;
|
||||||
|
">
|
||||||
|
<operatingLineChart :dateData="dateData" :monData="monData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||||
|
<div class="left-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns: 1624px;
|
||||||
|
">
|
||||||
|
<operatingLineChartCumulative :dateData="dateData" :totalData="totalData" />
|
||||||
|
<!-- <keyWork /> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="centerImg" style="
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1; /* 确保在 backp 之上、内容之下 */
|
||||||
|
"></div> -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import ReportHeader from "../components/noRouterHeader.vue";
|
||||||
|
import { Sidebar } from "../../../layout/components";
|
||||||
|
import screenfull from "screenfull";
|
||||||
|
// import operatingSalesRevenue from "./operatingComponents/operatingSalesRevenue";
|
||||||
|
// import premProdStatus from "./components/premProdStatus.vue";
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
import operatingLineChart from "../electricityCostAnalysisComponents/operatingLineChart";
|
||||||
|
import operatingLineChartCumulative from "../electricityCostAnalysisComponents/operatingLineChartCumulative.vue";
|
||||||
|
|
||||||
|
import { getProfitAnalysisTotalList } from '@/api/cockpit'
|
||||||
|
import moment from "moment";
|
||||||
|
export default {
|
||||||
|
name: "DayReport",
|
||||||
|
components: {
|
||||||
|
ReportHeader,
|
||||||
|
operatingLineChartCumulative,
|
||||||
|
operatingLineChart,
|
||||||
|
// premProdStatus,
|
||||||
|
Sidebar,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
weekArr: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
|
||||||
|
isFullScreen: false,
|
||||||
|
timer: null,
|
||||||
|
beilv: 1,
|
||||||
|
value: 100,
|
||||||
|
dateData: {},
|
||||||
|
monData: [],
|
||||||
|
totalData: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.init();
|
||||||
|
this.windowWidth(document.documentElement.clientWidth);
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
theme: (state) => state.settings.theme,
|
||||||
|
sideTheme: (state) => state.settings.sideTheme,
|
||||||
|
sidebar: (state) => state.app.sidebar,
|
||||||
|
device: (state) => state.app.device,
|
||||||
|
needTagsView: (state) => state.settings.tagsView,
|
||||||
|
fixedHeader: (state) => state.settings.fixedHeader,
|
||||||
|
}),
|
||||||
|
classObj() {
|
||||||
|
return {
|
||||||
|
hideSidebar: !this.sidebar.opened,
|
||||||
|
openSidebar: this.sidebar.opened,
|
||||||
|
withoutAnimation: this.sidebar.withoutAnimation,
|
||||||
|
mobile: this.device === "mobile",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
variables() {
|
||||||
|
return variables;
|
||||||
|
},
|
||||||
|
// ...mapGetters(['sidebar']),
|
||||||
|
styles() {
|
||||||
|
const v = Math.floor(this.value * this.beilv * 100) / 10000;
|
||||||
|
return {
|
||||||
|
transform: `scale(${v})`,
|
||||||
|
transformOrigin: "left top",
|
||||||
|
// overflow: hidden;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
clientWidth(val) {
|
||||||
|
if (!this.timer) {
|
||||||
|
this.clientWidth = val;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
this.timer = true;
|
||||||
|
let _this = this;
|
||||||
|
setTimeout(function () {
|
||||||
|
_this.timer = false;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
// 这里可以添加修改时的方法
|
||||||
|
this.windowWidth(val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.destroy();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const _this = this;
|
||||||
|
_this.beilv = document.documentElement.clientWidth / 1920;
|
||||||
|
window.onresize = () => {
|
||||||
|
return (() => {
|
||||||
|
_this.clientWidth = `${document.documentElement.clientWidth}`;
|
||||||
|
this.beilv = _this.clientWidth / 1920;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getData() {
|
||||||
|
getProfitAnalysisTotalList({
|
||||||
|
startTime: this.dateData.startTime,
|
||||||
|
endTime: this.dateData.endTime,
|
||||||
|
analysisObject: [
|
||||||
|
"利润总额"
|
||||||
|
],
|
||||||
|
levelId: 1,
|
||||||
|
// timeDim: this.dateData.mode
|
||||||
|
}).then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
this.monData = res.data.currentMonthData
|
||||||
|
this.totalData = res.data.totalMonthData
|
||||||
|
// this.totalData = res.data.totalData
|
||||||
|
// this.saleData = res.data.SaleData
|
||||||
|
// this.premiumProduct = res.data.premiumProduct
|
||||||
|
// this.salesTrendMap = res.data.salesTrendMap
|
||||||
|
// this.grossMarginTrendMap = res.data.grossMarginTrendMap
|
||||||
|
// this.salesProportion = res.data.salesProportion ? res.data.salesProportion : {}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleTimeChange(obj) {
|
||||||
|
// console.log(obj, 'obj');
|
||||||
|
this.dateData= obj
|
||||||
|
this.getData()
|
||||||
|
},
|
||||||
|
handleClickOutside() {
|
||||||
|
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
||||||
|
},
|
||||||
|
windowWidth(value) {
|
||||||
|
this.clientWidth = value;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
},
|
||||||
|
change() {
|
||||||
|
this.isFullScreen = screenfull.isFullscreen;
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.on("change", this.change);
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.off("change", this.change);
|
||||||
|
},
|
||||||
|
// 全屏
|
||||||
|
screenfullChange() {
|
||||||
|
console.log("screenfull.enabled", screenfull.isEnabled);
|
||||||
|
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.toggle(this.$refs.dayReportB);
|
||||||
|
},
|
||||||
|
// 导出
|
||||||
|
// exportPDF() {
|
||||||
|
// this.$message.success('正在导出,请稍等!')
|
||||||
|
// const element = document.getElementById('dayRepDom')
|
||||||
|
// element.style.display = 'block'
|
||||||
|
// const fileName = '株洲碲化镉生产日报' + moment().format('yyMMDD') + '.pdf'
|
||||||
|
// html2canvas(element, {
|
||||||
|
// dpi: 300, // Set to 300 DPI
|
||||||
|
// scale: 3 // Adjusts your resolution
|
||||||
|
// }).then(function(canvas) {
|
||||||
|
// const imgWidth = 595.28
|
||||||
|
// const imgHeight = 841.89
|
||||||
|
// const pageData = canvas.toDataURL('image/jpeg', 1.0)
|
||||||
|
// const PDF = new JsPDF('', 'pt', [imgWidth, imgHeight])
|
||||||
|
// PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
|
||||||
|
// setTimeout(() => {
|
||||||
|
// PDF.save(fileName) // 导出文件名
|
||||||
|
// }, 1000)
|
||||||
|
// })
|
||||||
|
// element.style.display = 'none'
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "~@/assets/styles/mixin.scss";
|
||||||
|
@import "~@/assets/styles/variables.scss";
|
||||||
|
|
||||||
|
.dayReport {
|
||||||
|
width: 1920px;
|
||||||
|
height: 1080px;
|
||||||
|
background: url("../../../assets/img/backp.png") no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideSidebar .fixed-header {
|
||||||
|
width: calc(100% - 54px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarHide .fixed-header {
|
||||||
|
width: calc(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile .fixed-header {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,351 @@
|
|||||||
|
<template>
|
||||||
|
<div id="dayReport" class="dayReport" :style="styles">
|
||||||
|
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||||
|
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||||
|
<ReportHeader :dateData="dateData" size="psi" @timeRangeChange="handleTimeChange" top-title="基地电费分析"
|
||||||
|
:is-full-screen="isFullScreen" @screenfullChange="screenfullChange" />
|
||||||
|
<div class="main-body" style="
|
||||||
|
margin-top: -20px;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
padding: 0px 16px 0 272px;
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div class="top" style="display: flex; gap: 16px">
|
||||||
|
<div class="top-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns: 1624px;
|
||||||
|
">
|
||||||
|
<changeBase :factory="factory" @baseChange="selectChange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="top" style="display: flex; gap: 16px;margin-top: -20px;">
|
||||||
|
<div class="left-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns: 804px 804px;
|
||||||
|
">
|
||||||
|
<monthlyOverview :month="month" :monData="monData" :title="'月度概览'" />
|
||||||
|
<totalOverview :totalData="totalData" :title="'累计概览'" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="middle" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||||
|
<div class="left-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns: 1624px;
|
||||||
|
">
|
||||||
|
<relatedIndicatorsAnalysis :dateData="dateData" :factory="factory" :relatedData="relatedData"
|
||||||
|
:title="'相关指标分析'" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bottom" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||||
|
<div class="left-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns: 1624px;
|
||||||
|
">
|
||||||
|
<dataTrend @getData="changeItem" :trendData="trend" :title="'数据趋势'" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="centerImg" style="
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1; /* 确保在 backp 之上、内容之下 */
|
||||||
|
"></div> -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import ReportHeader from "../components/noRouterHeader.vue";
|
||||||
|
import { Sidebar } from "../../../layout/components";
|
||||||
|
import screenfull from "screenfull";
|
||||||
|
import changeBase from "../components/changeBase.vue";
|
||||||
|
import monthlyOverview from "../electricityCostAnalysisComponents/monthlyOverview.vue";
|
||||||
|
import totalOverview from "../electricityCostAnalysisComponents/totalOverview.vue";
|
||||||
|
// import totalOverview from "../operatingComponents/totalOverview.vue";
|
||||||
|
import relatedIndicatorsAnalysis from "../electricityCostAnalysisComponents/relatedIndicatorsAnalysis.vue";
|
||||||
|
import dataTrend from "../electricityCostAnalysisComponents/dataTrend.vue";
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
import { getProfitAnalysisTotalList } from '@/api/cockpit'
|
||||||
|
// import PSDO from "./components/PSDO.vue";
|
||||||
|
// import psiLineChart from "./components/psiLineChart.vue";
|
||||||
|
|
||||||
|
// import coreBottomLeft from "./components/coreBottomLeft.vue";
|
||||||
|
// import orderProgress from "./components/orderProgress.vue";
|
||||||
|
// import keyWork from "./components/keyWork.vue";
|
||||||
|
import moment from "moment";
|
||||||
|
// import html2canvas from 'html2canvas'
|
||||||
|
// import JsPDF from 'jspdf'
|
||||||
|
export default {
|
||||||
|
name: "DayReport",
|
||||||
|
components: {
|
||||||
|
ReportHeader,
|
||||||
|
changeBase,
|
||||||
|
monthlyOverview,
|
||||||
|
Sidebar,
|
||||||
|
totalOverview,
|
||||||
|
dataTrend,
|
||||||
|
relatedIndicatorsAnalysis
|
||||||
|
// psiLineChart
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isFullScreen: false,
|
||||||
|
timer: null,
|
||||||
|
beilv: 1,
|
||||||
|
month: '',
|
||||||
|
value: 100,
|
||||||
|
factory: 5,
|
||||||
|
dateData: {},
|
||||||
|
monData: {},
|
||||||
|
totalData: {},
|
||||||
|
trend: [],
|
||||||
|
relatedData: [],
|
||||||
|
trendName: '利润总额',
|
||||||
|
// cusProData: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.init();
|
||||||
|
this.windowWidth(document.documentElement.clientWidth);
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
theme: (state) => state.settings.theme,
|
||||||
|
sideTheme: (state) => state.settings.sideTheme,
|
||||||
|
sidebar: (state) => state.app.sidebar,
|
||||||
|
device: (state) => state.app.device,
|
||||||
|
needTagsView: (state) => state.settings.tagsView,
|
||||||
|
fixedHeader: (state) => state.settings.fixedHeader,
|
||||||
|
}),
|
||||||
|
classObj() {
|
||||||
|
return {
|
||||||
|
hideSidebar: !this.sidebar.opened,
|
||||||
|
openSidebar: this.sidebar.opened,
|
||||||
|
withoutAnimation: this.sidebar.withoutAnimation,
|
||||||
|
mobile: this.device === "mobile",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
variables() {
|
||||||
|
return variables;
|
||||||
|
},
|
||||||
|
// ...mapGetters(['sidebar']),
|
||||||
|
styles() {
|
||||||
|
const v = Math.floor(this.value * this.beilv * 100) / 10000;
|
||||||
|
return {
|
||||||
|
transform: `scale(${v})`,
|
||||||
|
transformOrigin: "left top",
|
||||||
|
// overflow: hidden;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
clientWidth(val) {
|
||||||
|
if (!this.timer) {
|
||||||
|
this.clientWidth = val;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
this.timer = true;
|
||||||
|
let _this = this;
|
||||||
|
setTimeout(function () {
|
||||||
|
_this.timer = false;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
// 这里可以添加修改时的方法
|
||||||
|
this.windowWidth(val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.destroy();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const _this = this;
|
||||||
|
_this.beilv = document.documentElement.clientWidth / 1920;
|
||||||
|
window.onresize = () => {
|
||||||
|
return (() => {
|
||||||
|
_this.clientWidth = `${document.documentElement.clientWidth}`;
|
||||||
|
this.beilv = _this.clientWidth / 1920;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
if(this.$route.query.factory){
|
||||||
|
this.factory =Number(this.$route.query.factory)
|
||||||
|
}else if(this.$store.getters.levelList.length > 0 && this.$store.getters.levelList[0].id !== 1) {
|
||||||
|
this.factory = this.$store.getters.levelList[0].id
|
||||||
|
}else{
|
||||||
|
this.factory = this.$store.getters.levelList[1].id
|
||||||
|
}
|
||||||
|
this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleChange(value) {
|
||||||
|
this.index = value
|
||||||
|
this.getData()
|
||||||
|
},
|
||||||
|
changeItem(item) {
|
||||||
|
console.log('item', item);
|
||||||
|
|
||||||
|
this.trendName = item
|
||||||
|
this.getData()
|
||||||
|
},
|
||||||
|
getData() {
|
||||||
|
const requestParams = {
|
||||||
|
startTime: this.dateData.startTime,
|
||||||
|
endTime: this.dateData.endTime,
|
||||||
|
// index: this.index,
|
||||||
|
// sort: 1,
|
||||||
|
trendName: this.trendName,
|
||||||
|
analysisObject: [
|
||||||
|
"利润总额",
|
||||||
|
],
|
||||||
|
// paramList: ['制造成本', '财务费用', '销售费用', '管理费用', '运费'],
|
||||||
|
levelId: this.factory,
|
||||||
|
// baseId: Number(this.factory),
|
||||||
|
};
|
||||||
|
// 调用接口
|
||||||
|
getProfitAnalysisTotalList(requestParams).then((res) => {
|
||||||
|
this.monData = res.data.currentMonthData.find(item => {
|
||||||
|
return item.name === "利润总额";
|
||||||
|
});
|
||||||
|
console.log('this.monData', this.monData);
|
||||||
|
|
||||||
|
this.totalData = res.data.totalMonthData.find(item => {
|
||||||
|
return item.name === "利润总额";
|
||||||
|
});
|
||||||
|
// this.relatedMon = res.data.relatedMon
|
||||||
|
this.relatedData = {
|
||||||
|
relatedMon: res.data.currentMonthData.filter(item => {
|
||||||
|
return item.name !== "利润总额";
|
||||||
|
}), // 兜底月度数据
|
||||||
|
relatedTotal: res.data.totalMonthData.filter(item => {
|
||||||
|
return item.name !== "利润总额";
|
||||||
|
}) // 兜底累计数据
|
||||||
|
}
|
||||||
|
this.trend = res.data.dataTrend
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleTimeChange(obj) {
|
||||||
|
this.month = obj.targetMonth
|
||||||
|
this.dateData = {
|
||||||
|
startTime: obj.startTime,
|
||||||
|
endTime: obj.endTime,
|
||||||
|
// mode: obj.mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getData()
|
||||||
|
},
|
||||||
|
selectChange(data) {
|
||||||
|
console.log('选中的数据:', data);
|
||||||
|
this.factory = data
|
||||||
|
if (this.dateData.startTime && this.dateData.endTime) {
|
||||||
|
this.getData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleClickOutside() {
|
||||||
|
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
||||||
|
},
|
||||||
|
windowWidth(value) {
|
||||||
|
this.clientWidth = value;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
},
|
||||||
|
change() {
|
||||||
|
this.isFullScreen = screenfull.isFullscreen;
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.on("change", this.change);
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.off("change", this.change);
|
||||||
|
},
|
||||||
|
// 全屏
|
||||||
|
screenfullChange() {
|
||||||
|
console.log("screenfull.enabled", screenfull.isEnabled);
|
||||||
|
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.toggle(this.$refs.dayReportB);
|
||||||
|
},
|
||||||
|
changeDate(val) {
|
||||||
|
this.date = val;
|
||||||
|
// this.weekDay = this.weekArr[moment(this.date).format('e')]
|
||||||
|
// this.getData()
|
||||||
|
if (this.date === moment().format("yyyy-MM-DD")) {
|
||||||
|
this.loopTime();
|
||||||
|
} else {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 导出
|
||||||
|
// () {
|
||||||
|
// this.$message.success('正在导出,请稍等!')
|
||||||
|
// const element = document.getElementById('dayRepDom')
|
||||||
|
// element.style.display = 'block'
|
||||||
|
// const fileName = '株洲碲化镉生产日报' + moment().format('yyMMDD') + '.pdf'
|
||||||
|
// html2canvas(element, {
|
||||||
|
// dpi: 300, // Set to 300 DPI
|
||||||
|
// scale: 3 // Adjusts your resolution
|
||||||
|
// }).then(function(canvas) {
|
||||||
|
// const imgWidth = 595.28
|
||||||
|
// const imgHeight = 841.89
|
||||||
|
// const pageData = canvas.toDataURL('image/jpeg', 1.0)
|
||||||
|
// const PDF = new JsPDF('', 'pt', [imgWidth, imgHeight])
|
||||||
|
// PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
|
||||||
|
// setTimeout(() => {
|
||||||
|
// PDF.save(fileName) // 导出文件名
|
||||||
|
// }, 1000)
|
||||||
|
// })
|
||||||
|
// element.style.display = 'none'
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "~@/assets/styles/mixin.scss";
|
||||||
|
@import "~@/assets/styles/variables.scss";
|
||||||
|
|
||||||
|
.dayReport {
|
||||||
|
width: 1920px;
|
||||||
|
height: 1080px;
|
||||||
|
background: url("../../../assets/img/backp.png") no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideSidebar .fixed-header {
|
||||||
|
width: calc(100% - 54px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarHide .fixed-header {
|
||||||
|
width: calc(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile .fixed-header {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
419
src/views/home/electricityCostAnalysisComponents/Header.vue
Normal file
419
src/views/home/electricityCostAnalysisComponents/Header.vue
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
<template>
|
||||||
|
<header class="report-header">
|
||||||
|
<!-- 左侧区域:logo + 标题 -->
|
||||||
|
<div class="left-content">
|
||||||
|
<img style="height: 36px;" src="../../../assets/img/cnbm.png" alt="benmaLogo" >
|
||||||
|
<div class="top-title">{{ topTitle }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="center-content">
|
||||||
|
<!-- 循环 pageRoutes,不再硬编码文字 -->
|
||||||
|
<div class="item" v-for="(page, index) in pageRoutes" :key="index" @click="goToPage(page.path, index)">
|
||||||
|
<span class="item-text">{{ page.text }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- :class="{ 'no-skew': activeIndex === index }
|
||||||
|
" -->
|
||||||
|
<!-- 右侧区域:全屏按钮 -->
|
||||||
|
<div class="right-content">
|
||||||
|
<el-button type="text" class="screen-btn" :title="isFullScreen ? '退出全屏' : '全屏'" @click="changeFullScreen">
|
||||||
|
<svg-icon style="color: #0B58FF;" v-if="isFullScreen" icon-class="unFullScreenView" />
|
||||||
|
<svg-icon style="color: #0B58FF;" v-else icon-class="fullScreenView" />
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 时间选择区域:日/月/年按钮 + label + 日期选择器 -->
|
||||||
|
<div class="timeType">
|
||||||
|
<!-- <div class="item" v-for="(item, index) in timeTypes" :key="index" @click="activeTime = index"
|
||||||
|
:class="{ 'no-skew': activeTime === index }">
|
||||||
|
<span class="item-text">{{ item.text }}</span>
|
||||||
|
</div> -->
|
||||||
|
<div class="dateP">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">月份选择</span>
|
||||||
|
</div>
|
||||||
|
<el-date-picker v-model="date" :type="getPickerType" :placeholder="getPickerPlaceholder"
|
||||||
|
class="custom-date-picker" style="width: 132px;height: 29px;" @change="emitTimeRange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment'
|
||||||
|
export default {
|
||||||
|
name: 'Header',
|
||||||
|
props: {
|
||||||
|
isFullScreen: { type: Boolean, default: false },
|
||||||
|
topTitle: { type: String, default: '' }
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentTime: '',
|
||||||
|
timeTimer: null,
|
||||||
|
date: undefined,
|
||||||
|
activeIndex: -1,
|
||||||
|
activeTime: 1, // 0=日,1=月,2=年(默认选中“日”)
|
||||||
|
pageRoutes: [
|
||||||
|
{ text: '营业收入', path: '/operatingRevenue/operatingRevenueIndex' },
|
||||||
|
{ text: '利润分析', path: '/profitAnalysis' },
|
||||||
|
{ text: '产销率库存分析', path: '/PSIAnal' },
|
||||||
|
{ text: '成本分析', path: '/cost/cost' },
|
||||||
|
{ text: '驾驶舱报表', path: '/cockpit' }
|
||||||
|
],
|
||||||
|
// 定义时间类型配置:text=按钮文字,pickerType=选择器类型,placeholder=占位符
|
||||||
|
timeTypes: [
|
||||||
|
{ text: '日', pickerType: 'date', placeholder: '选择日期' },
|
||||||
|
{ text: '月', pickerType: 'month', placeholder: '选择月份' },
|
||||||
|
{ text: '年', pickerType: 'year', placeholder: '选择年份' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 动态获取日期选择器类型
|
||||||
|
getPickerType() {
|
||||||
|
return this.timeTypes[this.activeTime].pickerType;
|
||||||
|
},
|
||||||
|
// 动态获取日期选择器占位符
|
||||||
|
getPickerPlaceholder() {
|
||||||
|
return this.timeTypes[this.activeTime].placeholder;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goToPage(path, index) {
|
||||||
|
// 1. 跳转到对应路由
|
||||||
|
this.$router.push(path);
|
||||||
|
// 2. 更新activeIndex,让当前点击项高亮
|
||||||
|
this.activeIndex = index;
|
||||||
|
},
|
||||||
|
changeFullScreen() { this.$emit('screenfullChange') },
|
||||||
|
padZero(num) { return num < 10 ? '0' + num : num },
|
||||||
|
/**
|
||||||
|
* 核心方法1:根据维度计算时间区间(首次进入时基于赋值的当月日期,计算“当月第一天0点→次月第一天0点”)
|
||||||
|
* @returns {Object} 包含 start(开始时间)、end(结束时间)、dimension(维度)的区间对象
|
||||||
|
*/
|
||||||
|
calculateTimeRange() {
|
||||||
|
let startTime = 0;
|
||||||
|
let endTime = 0;
|
||||||
|
const mode = this.activeTime + 1; // 1=日,2=月,3=年
|
||||||
|
const defaultMoment = moment(); // 默认当前时间
|
||||||
|
|
||||||
|
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. 日维度:00:00:00 → 23:59:59(无毫秒)
|
||||||
|
if (this.activeTime === 0) {
|
||||||
|
startTime = targetMoment.startOf('day').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('day').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 月维度:当月1日00:00:00 → 当月最后一天23:59:59(无毫秒)
|
||||||
|
else if (this.activeTime === 1) {
|
||||||
|
startTime = targetMoment.startOf('month').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('month').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 年维度:当年1月1日00:00:00 → 当年最后一天23:59:59(无毫秒)
|
||||||
|
else if (this.activeTime === 2) {
|
||||||
|
startTime = targetMoment.startOf('year').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('year').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调试输出:验证是否去掉毫秒
|
||||||
|
console.log('时间范围计算结果:', {
|
||||||
|
mode,
|
||||||
|
startTime: moment(startTime * 1000).format('YYYY-MM-DD HH:mm:ss'), // 格式:2025-11-30 00:00:00
|
||||||
|
endTime: moment(endTime * 1000).format('YYYY-MM-DD HH:mm:ss'), // 格式:2025-11-30 23:59:59(无毫秒)
|
||||||
|
startTimeStamp: startTime, // 秒级时间戳(如:1764422400)
|
||||||
|
endTimeStamp: endTime // 秒级时间戳(如:1764508799)
|
||||||
|
});
|
||||||
|
|
||||||
|
return { startTime, endTime, mode };
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 核心方法2:传递时间区间给父组件(首次进入时触发,传递“当月第一天0点→次月第一天0点”)
|
||||||
|
*/
|
||||||
|
emitTimeRange() {
|
||||||
|
const timeRange = this.calculateTimeRange();
|
||||||
|
this.$emit('timeRangeChange', timeRange);
|
||||||
|
// 调试用:查看首次传递的区间(如{start: "2025-10-01T00:00:00", end: "2025-11-01T00:00:00", dimension: "月"})
|
||||||
|
console.log('当前时间区间:', timeRange);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 维度切换时:清空选择的日期,并传递当前维度的默认区间
|
||||||
|
activeTime(newVal, oldVal) {
|
||||||
|
if (newVal !== oldVal) {
|
||||||
|
this.date = undefined;
|
||||||
|
// this.emitTimeRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 核心逻辑:首次进入页面,计算当月默认日期并赋值给选择器,同时传递区间
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = this.padZero(now.getMonth() + 1); // 月份从0开始,+1后补零(如1月→01)
|
||||||
|
// 赋值当月默认日期(格式:YYYY-MM,适配month类型选择器)
|
||||||
|
this.date = `${year}-${month}`;
|
||||||
|
// 确保选择器渲染完成后,传递“当月第一天0点→次月第一天0点”的区间
|
||||||
|
this.$nextTick(() => this.emitTimeRange());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
/* 原有样式不变,仅补充label文字的倾斜抵消样式 */
|
||||||
|
@font-face {
|
||||||
|
font-family: "YouSheBiaoTiHei";
|
||||||
|
src: url('../../../assets/fonts/YouSheBiaoTiHe.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-header {
|
||||||
|
height: 117px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
background: url('../../../assets/img/topTitle.png') no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: 0 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
/* 确保timeType绝对定位生效 */
|
||||||
|
|
||||||
|
.left-content {
|
||||||
|
margin-top: 11px;
|
||||||
|
margin-left: 44px;
|
||||||
|
height: 55px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-title {
|
||||||
|
height: 55px;
|
||||||
|
font-family: "YouSheBiaoTiHei", sans-serif;
|
||||||
|
font-size: 42px;
|
||||||
|
color: #1E1651;
|
||||||
|
line-height: 55px;
|
||||||
|
letter-spacing: 6px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 18px;
|
||||||
|
margin-left: 70px;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
width: 180px;
|
||||||
|
height: 50px;
|
||||||
|
background: #E1EEFC;
|
||||||
|
transform: skew(-20deg);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #1E1651;
|
||||||
|
line-height: 50px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0px 13px 16px 0px rgba(179, 217, 255, 0.43),
|
||||||
|
0px 2px 4px 0px rgba(92, 140, 255, 0.25),
|
||||||
|
inset 0px -43px 13px 0px rgba(255, 255, 255, 0.51);
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(20deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.no-skew {
|
||||||
|
background: none !important;
|
||||||
|
transform: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
color: #1E1651;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 垂直居中,避免元素高低错位 */
|
||||||
|
top: 42px;
|
||||||
|
right:10px;
|
||||||
|
margin-top: 18px;
|
||||||
|
gap: 0;
|
||||||
|
/* 清除间隙,让按钮与选择器紧密连接 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item {
|
||||||
|
width: 40px;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(236, 244, 254, 1);
|
||||||
|
transform: skew(-20deg);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(11, 88, 255, 1);
|
||||||
|
line-height: 28px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
/* 选中按钮与未选中按钮倾斜角度统一,避免切换时跳动 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item .item-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(20deg);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item.no-skew {
|
||||||
|
background: rgba(11, 88, 255, 1);
|
||||||
|
color: rgba(249, 252, 255, 1);
|
||||||
|
transform: skew(-20deg) !important;
|
||||||
|
/* 统一倾斜角度,修复原30deg的错位 */
|
||||||
|
box-shadow: 0 2px 8px rgba(11, 88, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item.no-skew .item-text {
|
||||||
|
transform: skew(20deg) !important;
|
||||||
|
/* 同步统一文字倾斜角度 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateP {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateP .label {
|
||||||
|
width: 165px;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(236, 244, 254, 1);
|
||||||
|
transform: skew(-25deg);
|
||||||
|
/* 与按钮倾斜角度统一(原30deg改为25deg,避免视觉错位) */
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 28px;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 补充:label文字抵消倾斜(原代码遗漏,导致文字倾斜) */
|
||||||
|
.dateP .label-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(25deg);
|
||||||
|
/* 与label倾斜角度相反,确保文字正立 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-right: 4px;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-time {
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 24px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screen-btn {
|
||||||
|
width: 26px;
|
||||||
|
margin-left: 300px;
|
||||||
|
color: #00fff0;
|
||||||
|
font-size: 26px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 日期选择器样式保持不变 */
|
||||||
|
::v-deep .custom-date-picker {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
width: 165px !important;
|
||||||
|
height: 28px !important;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 !important;
|
||||||
|
|
||||||
|
/* 1. 调整输入框文字:确保行高与输入框高度一致,垂直居中 */
|
||||||
|
.el-input__inner {
|
||||||
|
height: 28px !important;
|
||||||
|
width: 165px !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>
|
||||||
271
src/views/home/electricityCostAnalysisComponents/container.vue
Normal file
271
src/views/home/electricityCostAnalysisComponents/container.vue
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
<template>
|
||||||
|
<div class="cockpitContainer" :class="['cockpitContainer__' + size]">
|
||||||
|
<div class="content-top" :class="['content-top__' + topSize]">
|
||||||
|
<!-- 使用 flex 容器包裹图标和文字,实现垂直居中 -->
|
||||||
|
<div class="title-wrapper">
|
||||||
|
<svg-icon class="title-icon" :icon-class="icon" />
|
||||||
|
<span class="title-text">
|
||||||
|
{{ name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cockpitContainer-body">
|
||||||
|
<slot>
|
||||||
|
<div class="test-body">something test....</div>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Container',
|
||||||
|
components: {},
|
||||||
|
// eslint-disable-next-line vue/require-prop-types
|
||||||
|
props: ['name', 'size', 'icon', 'topSize'],
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.cockpitContainer {
|
||||||
|
display: inline-block;
|
||||||
|
// width: 100%;
|
||||||
|
// height: 100%;
|
||||||
|
padding: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.content-top {
|
||||||
|
height: 60px;
|
||||||
|
.title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 10px;
|
||||||
|
/* 垂直居中关键属性 */
|
||||||
|
height: 100%;
|
||||||
|
/* 继承父容器高度,确保垂直居中范围 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
font-size: 30px;
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
/* 图标和文字之间的间距 */
|
||||||
|
flex-shrink: 0;
|
||||||
|
/* 防止图标被压缩 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 移除固定行高,避免影响垂直对齐
|
||||||
|
// line-height: 60px;
|
||||||
|
}
|
||||||
|
// width: 547px;
|
||||||
|
// background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
&__basic {
|
||||||
|
// width: 547px;
|
||||||
|
background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__middle {
|
||||||
|
background: url(../../../assets/img/topTileMiddle.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__large {
|
||||||
|
background: url(../../../assets/img/topTitleLargeBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__KFAPTopTitle {
|
||||||
|
background: url(../../../assets/img/KFAPTopTitle.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiTopTitleBasic {
|
||||||
|
background: url(../../../assets/img/psiTopTitleBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
&__rawTopTitleLarge {
|
||||||
|
background: url(../../../assets/img/rawTopTitleLarge.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__topBasic {
|
||||||
|
background: url(../../../assets/img/top-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bottomBasic {
|
||||||
|
background: url(../../../assets/img/bottom-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingBasic {
|
||||||
|
background: url(../../../assets/img/operating-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingLarge {
|
||||||
|
background: url(../../../assets/img/operating-large.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__profitTopBasic {
|
||||||
|
background: url(../../../assets/img/profitTopBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__profitMiddleBasic {
|
||||||
|
background: url(../../../assets/img/profitMiddleBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiBasicBg {
|
||||||
|
background: url(../../../assets/img/psiBasicBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiMiddleBg {
|
||||||
|
background: url(../../../assets/img/psiMiddleBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingRevenueBg {
|
||||||
|
background: url(../../../assets/img/operatingRevenueBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__costBasicBg {
|
||||||
|
background: url(../../../assets/img/costBasicBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
&__rawTopBg {
|
||||||
|
background: url(../../../assets/img/rawTopBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
// &__left {
|
||||||
|
// background: url(../../../../../../../assets/img/left.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__energyConsumption {
|
||||||
|
// background: url(../../../../../../../assets/img/energyConsumption.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__left2 {
|
||||||
|
// background: url(../../assets/left_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__left3 {
|
||||||
|
// background: url(../../assets/left_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__mid2 {
|
||||||
|
// background: url(../../assets/mid_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__mid3 {
|
||||||
|
// background: url(../../assets/mid_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right1 {
|
||||||
|
// background: url(../../assets/right_1.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right2 {
|
||||||
|
// background: url(../../assets/right_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right3 {
|
||||||
|
// background: url(../../assets/right_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekRight2 {
|
||||||
|
// background: url(../../assets/week_right_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekMidTop {
|
||||||
|
// background: url(../../assets/week-mid-top.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekMidMid {
|
||||||
|
// background: url(../../assets/week-mid-mid.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: ' ';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
// background: inherit;
|
||||||
|
/* 设置模糊,不用 filter */
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-body {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
250
src/views/home/electricityCostAnalysisComponents/dataTrend.vue
Normal file
250
src/views/home/electricityCostAnalysisComponents/dataTrend.vue
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="数据趋势" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="right" style="
|
||||||
|
height: 191px;
|
||||||
|
display: flex;
|
||||||
|
width: 1595px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<!-- 直接使用计算属性 chartData,无需手动更新 -->
|
||||||
|
<dataTrendBar @handleGetItemData="getData" :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import dataTrendBar from "./dataTrendBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, dataTrendBar },
|
||||||
|
props: {
|
||||||
|
trendData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 移除:原 chartData 定义,改为计算属性
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// 移除:原 watch 监听配置,计算属性自动响应 trendData 变化
|
||||||
|
computed: {
|
||||||
|
/**
|
||||||
|
* chartData 计算属性:自动响应 trendData 变化,格式化并提取各字段数组
|
||||||
|
* @returns {Object} 包含6个独立数组的格式化数据
|
||||||
|
*/
|
||||||
|
chartData() {
|
||||||
|
// 初始化6个独立数组
|
||||||
|
const timeArr = []; // 格式化后的年月数组
|
||||||
|
const valueArr = []; // 实际值数组
|
||||||
|
const diffValueArr = []; // 差异值数组
|
||||||
|
const targetValueArr = []; // 预算值数组
|
||||||
|
const proportionArr = []; // 占比数组
|
||||||
|
const completedArr = []; // 完成率数组
|
||||||
|
|
||||||
|
// 遍历传入的 trendData 数组(响应式依赖,变化时自动重算)
|
||||||
|
this.trendData.forEach((item) => {
|
||||||
|
// 1. 格式化时间并推入时间数组
|
||||||
|
const yearMonth = this.formatTimeToYearMonth(item.time);
|
||||||
|
timeArr.push(yearMonth);
|
||||||
|
|
||||||
|
// 2. 提取其他字段,兜底为0(防止null/undefined影响图表渲染)
|
||||||
|
valueArr.push(item.value ?? 0);
|
||||||
|
diffValueArr.push(item.diffValue ?? 0);
|
||||||
|
targetValueArr.push(item.targetValue ?? 0);
|
||||||
|
proportionArr.push(item.proportion ?? 0);
|
||||||
|
completedArr.push(item.completed ?? 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 组装并返回格式化后的数据(结构与原一致)
|
||||||
|
return {
|
||||||
|
time: timeArr,
|
||||||
|
value: valueArr,
|
||||||
|
diffValue: diffValueArr,
|
||||||
|
targetValue: targetValueArr,
|
||||||
|
proportion: proportionArr,
|
||||||
|
completed: completedArr,
|
||||||
|
rawData: this.trendData, // 透传原始数据,方便子组件使用
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 格式化时间戳为年月格式(YYYY-MM)
|
||||||
|
* @param {Number} timestamp 13位毫秒级时间戳
|
||||||
|
* @returns {String} 格式化后的年月字符串(如:2025-10)
|
||||||
|
*/
|
||||||
|
formatTimeToYearMonth(timestamp) {
|
||||||
|
if (!timestamp || isNaN(timestamp)) {
|
||||||
|
return ""; // 容错:非有效时间戳返回空字符串
|
||||||
|
}
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, "0"); // 月份从0开始,补0至2位
|
||||||
|
return `${year}-${month}`;
|
||||||
|
},
|
||||||
|
getData(value) {
|
||||||
|
this.$emit('getData', value)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</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>
|
||||||
|
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,479 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<!-- 新增行容器:包裹“各基地情况”和barTop -->
|
||||||
|
<div class="header-row">
|
||||||
|
<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">
|
||||||
|
<span class="legend-icon square achieved"></span>
|
||||||
|
实际·达标
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square unachieved"></span>
|
||||||
|
实际·未达标
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<div class="item-button category-btn">
|
||||||
|
<span class="item-text">类目选择</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>
|
||||||
|
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||||
|
<operatingLineBar :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSale.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isDropdownShow: false,
|
||||||
|
selectedProfit: '利润总额', // 选中的名称,初始为null
|
||||||
|
profitOptions: [
|
||||||
|
'利润总额',
|
||||||
|
'销量',
|
||||||
|
'单价',
|
||||||
|
'制造成本',
|
||||||
|
'管理费用',
|
||||||
|
'销售费用',
|
||||||
|
'财务费用',
|
||||||
|
'非经营性利润',
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// profitOptions() {
|
||||||
|
// return this.categoryData.map(item => item.name) || [];
|
||||||
|
// },
|
||||||
|
currentDataSource() {
|
||||||
|
return this.chartData
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
return this.chartData.time
|
||||||
|
},
|
||||||
|
// 根据按钮切换生成对应的 chartData
|
||||||
|
chartD() {
|
||||||
|
// 销量场景数据
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
console.log(this.currentDataSource, 'currentDataSource');
|
||||||
|
console.log('this.currentDataSource', data);
|
||||||
|
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
// 1. 完成率(折线图)
|
||||||
|
{
|
||||||
|
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(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: data.proportion || [], // 完成率(%)
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 2. 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||||
|
barWidth: 14,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.targetValue || [] // 目标销量(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图,含达标状态)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 14,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
// 固定label尺寸:68px×20px
|
||||||
|
width: 68,
|
||||||
|
height: 20,
|
||||||
|
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||||
|
formatter: (params) => {
|
||||||
|
const diff = data.diffValue || [];
|
||||||
|
const flags = data.completed || [];
|
||||||
|
const currentDiff = diff[params.dataIndex] || 0;
|
||||||
|
const currentFlag = flags[params.dataIndex] || 0;
|
||||||
|
|
||||||
|
const prefix = currentFlag === 1 ? '+' : '-';
|
||||||
|
|
||||||
|
// 根据标志位选择不同的样式类
|
||||||
|
if (currentFlag === 1) {
|
||||||
|
// 达标 - 使用 rate-achieved 样式
|
||||||
|
return `{achieved|${currentDiff}}{text|差值}`;
|
||||||
|
} else {
|
||||||
|
// 未达标 - 使用 rate-unachieved 样式
|
||||||
|
return `{unachieved|${currentDiff}}{text|差值}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||||
|
{ offset: 0.2, color: '#ffffff' },
|
||||||
|
{ offset: 1, color: '#ffffff' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
shadowColor: 'rgba(191,203,215,0.5)',
|
||||||
|
shadowBlur: 2,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowOffsetY: 2,
|
||||||
|
borderRadius: 4,
|
||||||
|
borderColor: '#BFCBD577',
|
||||||
|
borderWidth: 0,
|
||||||
|
lineHeight: 20,
|
||||||
|
rich: {
|
||||||
|
text: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 10, 5, 0],
|
||||||
|
align: 'center',
|
||||||
|
color: '#464646',
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
},
|
||||||
|
achieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#76DABE', // 与达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
},
|
||||||
|
// 未达标样式
|
||||||
|
unachieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||||
|
const safeFlag = data.completed || [];
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
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)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.value || [] // 实际销量(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
return salesData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectProfit(item) {
|
||||||
|
this.selectedProfit = item;
|
||||||
|
this.isDropdownShow = false;
|
||||||
|
this.$emit('handleGetItemData', item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
// 新增:头部行容器,实现一行排列
|
||||||
|
.header-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end; // 左右两端对齐
|
||||||
|
align-items: center; // 垂直居中
|
||||||
|
// width: 100%;
|
||||||
|
margin-bottom: 8px; // 与下方图表区保留间距(可根据需求调整)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 各基地情况标题样式
|
||||||
|
.base-title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0 0 0 16px; // 保留原有内边距
|
||||||
|
white-space: nowrap; // 防止文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
// 移除原有flex和justify-content,由header-row控制
|
||||||
|
width: auto; // 自适应宽度
|
||||||
|
// 保留原有align-items,确保内部元素垂直居中
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; // 图例和按钮组垂直居中
|
||||||
|
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||||
|
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 图例:在右侧容器内横向排列
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.dropdown-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 999; // 提高z-index,确保菜单不被遮挡
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-btn {
|
||||||
|
width: 75px;
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-bottom-left-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-btn {
|
||||||
|
width: 123px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 18px 0 8px;
|
||||||
|
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); // 箭头旋转方向可根据需求调整,比如改为rotate(-90deg)更符合向上展开的视觉
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-options {
|
||||||
|
position: absolute;
|
||||||
|
// 关键修改1:调整top值,让菜单显示在选择框上方,calc(-100% - 2px)表示向上偏移自身100%再加2px间距
|
||||||
|
bottom: 100%;
|
||||||
|
right: 0;
|
||||||
|
// 移除多余的margin-top,避免额外间距
|
||||||
|
// margin-top: 2px;
|
||||||
|
width: 123px;
|
||||||
|
background: #ffffff;
|
||||||
|
// 关键修改2:调整border-radius,让菜单顶部圆角匹配选择框的右上角,底部圆角为0(更美观)
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
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>
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="chartBox" style="width: 100%; height: 108px; position: relative;">
|
||||||
|
<div :id="id" style="width: 100%; height:100%;"></div>
|
||||||
|
<div class="bottomTip">
|
||||||
|
<div class="precent">
|
||||||
|
<span class="precentNum">{{ detailData.completeRate ? detailData.completeRate : 0 }}% </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'EnergyConsumption',
|
||||||
|
// components: { Container },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
detailData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
// electricComu: 0,
|
||||||
|
// steamComu: 20, // 调整为符合max范围的数值(0-8)
|
||||||
|
// // electricity: [120, 150, 130, 180, 160, 200, 190],
|
||||||
|
// // steam: [80, 95, 85, 110, 100, 120, 115],
|
||||||
|
// // dates: ['1日', '2日', '3日', '4日', '5日', '6日', '7日']
|
||||||
|
})
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
default: () => ('monthG')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// electricityChart: null,
|
||||||
|
// steamChart: null,
|
||||||
|
// specialTicks: [2, 4, 6, 8], // 统一的刻度显示
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
detailData: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true, // 初始化时立即执行
|
||||||
|
handler() {
|
||||||
|
this.updateGauges()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initGauges()
|
||||||
|
// window.addEventListener('resize', this.handleResize)
|
||||||
|
this.observeContainerResize()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
observeContainerResize() {
|
||||||
|
// 修复:获取正确的容器(组件内的.gauge-container)
|
||||||
|
const container = this.$el.querySelector('.gauge-container')
|
||||||
|
if (container && window.ResizeObserver) {
|
||||||
|
this.resizeObserver = new ResizeObserver(entries => {
|
||||||
|
if (this.electricityChart) {
|
||||||
|
this.electricityChart.resize() // 直接触发resize,无需防抖
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.resizeObserver.observe(container)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initGauges() {
|
||||||
|
// console.log('this.id',this.id);
|
||||||
|
|
||||||
|
// 初始化电气图表实例
|
||||||
|
const electricityDom = document.getElementById(this.id)
|
||||||
|
if (electricityDom) {
|
||||||
|
// 修复:正确创建并存储图表实例
|
||||||
|
this.electricityChart = echarts.init(electricityDom)
|
||||||
|
// 首次更新数据
|
||||||
|
this.updateGauges()
|
||||||
|
}
|
||||||
|
// 蒸汽图表若未使用,可注释/删除
|
||||||
|
// const steamDom = document.getElementById('steamGauge')
|
||||||
|
// if (steamDom) {
|
||||||
|
// this.steamChart = echarts.init(steamDom)
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
updateGauges() {
|
||||||
|
// 修复:先判断实例是否存在,再更新配置
|
||||||
|
if (!this.electricityChart) return
|
||||||
|
|
||||||
|
// 修复:兜底获取rate值,确保数值有效
|
||||||
|
const rate = Number(this.detailData?.completeRate) || 0
|
||||||
|
console.log('当前rate值:', rate); // 调试:确认rate值正确
|
||||||
|
|
||||||
|
// 关键:第二个参数传true,清空原有配置,强制更新
|
||||||
|
this.electricityChart.setOption(this.getElectricityGaugeOption(rate), true)
|
||||||
|
},
|
||||||
|
// 用电量仪表盘配置(保留原有样式,优化数值范围)
|
||||||
|
getElectricityGaugeOption(value) {
|
||||||
|
const electricityGradient = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: '#0B58FF' },
|
||||||
|
{ offset: 1, color: '#32FFCD' }
|
||||||
|
])
|
||||||
|
|
||||||
|
return {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '月度',
|
||||||
|
type: 'gauge',
|
||||||
|
radius: '95', // 修复:添加%,避免数值错误
|
||||||
|
center: ['50%', '90%'],
|
||||||
|
startAngle: 180,
|
||||||
|
endAngle: 0,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
splitNumber: 4,
|
||||||
|
label: { show: false },
|
||||||
|
progress: {
|
||||||
|
show: true,
|
||||||
|
overlap: false,
|
||||||
|
roundCap: true,
|
||||||
|
clip: false,
|
||||||
|
width: 14,
|
||||||
|
itemStyle: { color: electricityGradient }
|
||||||
|
},
|
||||||
|
pointer: {
|
||||||
|
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
|
||||||
|
length: '75%',
|
||||||
|
width: 16,
|
||||||
|
itemStyle: { color: '#288AFF' },
|
||||||
|
offsetCenter: [0, '10%']
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
roundCap: true,
|
||||||
|
lineStyle: { width: 12, color: [[1, '#E6EBF7']] }
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
length: 10,
|
||||||
|
lineStyle: { width: 5, color: '#D6DAE5' },
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
splitNumber: 2,
|
||||||
|
length: 6,
|
||||||
|
lineStyle: { width: 2, color: '#D6DAE5' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
detail: { show: false },
|
||||||
|
data: [{ value: value, unit: '' }] // 确保数值正确传入
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 未使用的蒸汽仪表盘可注释/删除
|
||||||
|
// getSteamGaugeOption(value) { ... }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
.chartBox {
|
||||||
|
.bottomTip {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.precent {
|
||||||
|
line-height: 3px;
|
||||||
|
|
||||||
|
&::after,
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 54px;
|
||||||
|
height: 5px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
margin-right: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用电量线条颜色
|
||||||
|
.precent::before {
|
||||||
|
|
||||||
|
background: linear-gradient(90deg, rgba(40, 138, 255, 0) 0%, rgba(12, 125, 254, 0.4) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ::after:从 透明 到 rgba(12, 125, 254, 0.4)(90度渐变,左到右) */
|
||||||
|
.precent::after {
|
||||||
|
background: linear-gradient(90deg, rgba(12, 125, 254, 0.4) 0%, rgba(40, 138, 255, 0) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蒸汽线条颜色
|
||||||
|
// .steam-precent::after,
|
||||||
|
// .steam-precent::before {
|
||||||
|
// background: linear-gradient(90deg, rgba(11, 168, 255, 0.26) 0%, rgba(54, 239, 230, 1) 100%);
|
||||||
|
// }
|
||||||
|
|
||||||
|
.precentNum {
|
||||||
|
display: inline-block;
|
||||||
|
// width: 52px;
|
||||||
|
height: 22px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蒸汽数字颜色
|
||||||
|
.steam-num {
|
||||||
|
color: rgba(54, 239, 230, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||||
|
<div class="dashboard">
|
||||||
|
<div class="title">
|
||||||
|
{{ month }}月完成率
|
||||||
|
</div>
|
||||||
|
<div class="number">
|
||||||
|
<div class="yield">
|
||||||
|
{{ formatRate(factoryData?.completeRate) }}%
|
||||||
|
</div>
|
||||||
|
<div class="mom">
|
||||||
|
环比{{ formatRate(factoryData?.thb) }}%
|
||||||
|
<img v-if="factoryData?.thb >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="上升">
|
||||||
|
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="下降">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="electricityGauge">
|
||||||
|
<electricityGauge id="month" :detailData="factoryData"></electricityGauge>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
<div class="line" style="padding: 0px;">
|
||||||
|
<!-- 传递包含flag的factoryData给柱状图组件 -->
|
||||||
|
<verticalBarChart :detailData="factoryData"></verticalBarChart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import electricityGauge from './electricityGauge.vue'
|
||||||
|
import verticalBarChart from './verticalBarChart.vue'
|
||||||
|
// 引入箭头图片(根据实际路径调整,若模板中直接用路径可注释)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, electricityGauge, verticalBarChart },
|
||||||
|
props: {
|
||||||
|
monData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
month: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
/**
|
||||||
|
* 自动提取monData中的工厂数据,并新增flag字段
|
||||||
|
*/
|
||||||
|
factoryData() { // 整合原始数据 + 计算flag
|
||||||
|
return {
|
||||||
|
completeRate: this.monData.proportion ? Number(this.monData.proportion) : 0,
|
||||||
|
diff: this.monData.diffValue,
|
||||||
|
real: this.monData.value,
|
||||||
|
target: this.monData.targetValue,
|
||||||
|
thb: this.monData.thb,
|
||||||
|
// ...rawData,
|
||||||
|
flag: this.monData.completed // 新增flag字段
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 格式化百分比数值:处理空值/非数字,兜底为0
|
||||||
|
*/
|
||||||
|
formatRate(value) {
|
||||||
|
if (isNaN(value) || value === null || value === undefined) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 判断完成率对应的flag值(<100为0,≥100为1)
|
||||||
|
* @param {number} rate 完成率(原始值,如89代表89%)
|
||||||
|
* @returns {0|1} flag值
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 264px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 46px;
|
||||||
|
color: #0B58FF;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 箭头样式优化
|
||||||
|
.arrow {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
width: 500px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" 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; gap: 8px;">
|
||||||
|
<div class="dashboard left">
|
||||||
|
<div class="title">
|
||||||
|
销量·万元
|
||||||
|
</div>
|
||||||
|
<div class="line">
|
||||||
|
<operatingSingleBar></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dashboard right">
|
||||||
|
<div class="title">
|
||||||
|
单价·万元
|
||||||
|
</div>
|
||||||
|
<div class="line">
|
||||||
|
<operatingSingleBar></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import operatingSingleBar from './operatingSingleBar.vue'
|
||||||
|
import verticalBarChart from './verticalBarChart.vue'
|
||||||
|
|
||||||
|
// import * as echarts from 'echarts'
|
||||||
|
// import rawItem from './raw-Item.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, operatingSingleBar, verticalBarChart },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
itemData: { // 接收父组件传递的设备数据数组
|
||||||
|
type: Array,
|
||||||
|
default: () => [] // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
title: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
month: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
itemData: {
|
||||||
|
handler(newValue, oldValue) {
|
||||||
|
// this.updateChart()
|
||||||
|
},
|
||||||
|
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// computed: {
|
||||||
|
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||||
|
// sortedItemData() {
|
||||||
|
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||||
|
// const totalCostItems = this.itemData.filter(item =>
|
||||||
|
// item.name && item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 过滤出不包含“总成本”的项
|
||||||
|
// const otherItems = this.itemData.filter(item =>
|
||||||
|
// !item.name || !item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 合并:总成本项在前,其他项在后
|
||||||
|
// return [...totalCostItems, ...otherItems];
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 382px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
// width: 190px;
|
||||||
|
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;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
// width: 190px;
|
||||||
|
height: 32px;
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
width: 120px;
|
||||||
|
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;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// .line {
|
||||||
|
// width: 500px;
|
||||||
|
// height: 205px;
|
||||||
|
// background: #F9FCFF;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .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> -->
|
||||||
@@ -0,0 +1,503 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<div class="header-row">
|
||||||
|
<div class="base-title">各基地情况</div>
|
||||||
|
<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">
|
||||||
|
<span class="legend-icon square achieved"></span>实际·达标
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square unachieved"></span>实际·未达标
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<div class="item-button category-btn">
|
||||||
|
<span class="item-text">展示顺序</span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-container">
|
||||||
|
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||||
|
<span class="item-text profit-text">{{ selectedSort || '请选择' }}</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.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||||
|
<operatingLineBar :dateData="dateData" :chartData="chartD" style="height: 100%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSale.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData",'dateData'],
|
||||||
|
emits: ['sort-change'], // 声明事件(Vue3 推荐)
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
isDropdownShow: false,
|
||||||
|
selectedSort: '实际值:高~低', // 选中的label
|
||||||
|
selectedSortValue: 1, // 选中的value,用于排序逻辑
|
||||||
|
profitOptions: [
|
||||||
|
{ label: '实际值:高~低', value: 1 },
|
||||||
|
{ label: '实际值:低~高', value: 2 },
|
||||||
|
{ label: '完成率:高~低', value: 3 },
|
||||||
|
{ label: '完成率:低~高', value: 4 },
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 排序后的数据源(核心:根据selectedSortValue重新排序)
|
||||||
|
currentDataSource() {
|
||||||
|
if (!this.chartData) return {};
|
||||||
|
|
||||||
|
// 深拷贝原始数据,避免修改原数据
|
||||||
|
const factory = JSON.parse(JSON.stringify(this.chartData));
|
||||||
|
if (!factory.locations.length || !this.selectedSortValue) return factory;
|
||||||
|
|
||||||
|
// 构建带索引的数组,方便同步所有字段排序
|
||||||
|
const dataWithIndex = factory.locations.map((name, index) => ({
|
||||||
|
index,
|
||||||
|
name,
|
||||||
|
real: factory.reals[index],
|
||||||
|
target: factory.targets[index],
|
||||||
|
rate: factory.rate[index],
|
||||||
|
diff: factory.diff[index],
|
||||||
|
flag: factory.flags[index]
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 根据选中的排序规则排序
|
||||||
|
switch (this.selectedSortValue) {
|
||||||
|
case 1: // 实际值:高~低
|
||||||
|
dataWithIndex.sort((a, b) => b.real - a.real);
|
||||||
|
break;
|
||||||
|
case 2: // 实际值:低~高
|
||||||
|
dataWithIndex.sort((a, b) => a.real - b.real);
|
||||||
|
break;
|
||||||
|
case 3: // 预算值:高~低
|
||||||
|
dataWithIndex.sort((a, b) => b.rate - a.rate);
|
||||||
|
break;
|
||||||
|
case 4: // 预算值:低~高
|
||||||
|
dataWithIndex.sort((a, b) => a.rate - b.rate);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步更新所有数组
|
||||||
|
factory.locations = dataWithIndex.map(item => item.name);
|
||||||
|
factory.reals = dataWithIndex.map(item => item.real);
|
||||||
|
factory.targets = dataWithIndex.map(item => item.target);
|
||||||
|
factory.rates = dataWithIndex.map(item => item.rate);
|
||||||
|
factory.diff = dataWithIndex.map(item => item.diff);
|
||||||
|
factory.flags = dataWithIndex.map(item => item.flag);
|
||||||
|
|
||||||
|
return factory;
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
return this.currentDataSource.locations || [];
|
||||||
|
},
|
||||||
|
// 最终传递给图表的排序后数据
|
||||||
|
chartD() {
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
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(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: data.rate || [],
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 40,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.targets || []
|
||||||
|
},
|
||||||
|
// 实际(柱状图)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 40,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [32, 0],
|
||||||
|
width: 100,
|
||||||
|
height: 22,
|
||||||
|
formatter: (params) => {
|
||||||
|
const diff = data.diff || [];
|
||||||
|
const flags = data.flags || [];
|
||||||
|
const currentDiff = diff[params.dataIndex] || 0;
|
||||||
|
const currentFlag = flags[params.dataIndex] || 0;
|
||||||
|
|
||||||
|
const prefix = currentFlag === 1 ? '+' : '-';
|
||||||
|
|
||||||
|
// 根据标志位选择不同的样式类
|
||||||
|
if (currentFlag === 1) {
|
||||||
|
// 达标 - 使用 rate-achieved 样式
|
||||||
|
return `{achieved|${currentDiff}}{text|差值}`;
|
||||||
|
} else {
|
||||||
|
// 未达标 - 使用 rate-unachieved 样式
|
||||||
|
return `{unachieved|${currentDiff}}{text|差值}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||||
|
{ offset: 0.2, color: '#ffffff' },
|
||||||
|
{ offset: 1, color: '#ffffff' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
shadowColor: 'rgba(191,203,215,0.5)',
|
||||||
|
shadowBlur: 2,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowOffsetY: 2,
|
||||||
|
borderRadius: 4,
|
||||||
|
borderColor: '#BFCBD577',
|
||||||
|
borderWidth: 0,
|
||||||
|
lineHeight: 26,
|
||||||
|
rich: {
|
||||||
|
text: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 10, 5, 0],
|
||||||
|
align: 'center',
|
||||||
|
color: '#464646',
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
achieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#76DABE', // 与达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
// 未达标样式
|
||||||
|
unachieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 14
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const safeFlag = data.flags || [];
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
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)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals || []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
return salesData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectProfit(item) {
|
||||||
|
// 更新选中的label和value
|
||||||
|
this.selectedSort = item.label;
|
||||||
|
this.selectedSortValue = item.value;
|
||||||
|
this.isDropdownShow = false;
|
||||||
|
// 向父组件传递排序事件(可选,保持原有逻辑)
|
||||||
|
this.$emit('sort-change', item.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 监听父组件传入的chartData变化,重置选中状态(可选)
|
||||||
|
watch: {
|
||||||
|
'chartData.factory': {
|
||||||
|
handler() {
|
||||||
|
// 若需要切换数据源后重置排序,可取消注释
|
||||||
|
// this.selectedSort = null;
|
||||||
|
// this.selectedSortValue = null;
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.header-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0 0 0 16px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
width: auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
margin-right: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-btn {
|
||||||
|
width: 75px;
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-bottom-left-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-btn {
|
||||||
|
width: 123px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 18px 0 8px;
|
||||||
|
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: 123px;
|
||||||
|
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>
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChipBottom" id="cockpitEffChipBottom" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
series: [],
|
||||||
|
allPlaceNames: []
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
validator: (value) => {
|
||||||
|
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChipBottom;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatter: (params) => {
|
||||||
|
let html = `${params[0].axisValue}<br/>`;
|
||||||
|
params.forEach(item => {
|
||||||
|
const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
);
|
||||||
|
html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 60,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
splitNumber: 4,
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: (value) => Math.ceil((value.max || 0) * 1.1),
|
||||||
|
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
scale:true,
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
series: [],
|
||||||
|
allPlaceNames: []
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
validator: (value) => {
|
||||||
|
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 60,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
// {
|
||||||
|
// type: 'value',
|
||||||
|
// nameTextStyle: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// align: 'left'
|
||||||
|
// },
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
// axisTick: { show: false },
|
||||||
|
// axisLabel: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// formatter: '{value}%'
|
||||||
|
// },
|
||||||
|
// splitLine: { show: false },
|
||||||
|
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
// splitNumber: 4
|
||||||
|
// }
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null, // 存储图表实例
|
||||||
|
resizeHandler: null, // 存储resize事件处理函数
|
||||||
|
// 核心:基地名称与序号的映射表(固定顺序)
|
||||||
|
baseNameToIndexMap: {
|
||||||
|
'宜兴': 7,
|
||||||
|
'漳州': 8,
|
||||||
|
'自贡': 3,
|
||||||
|
'桐城': 2,
|
||||||
|
'洛阳': 9,
|
||||||
|
'合肥': 5,
|
||||||
|
'宿迁': 6,
|
||||||
|
'秦皇岛': 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.initChart(); // 初始化图表(只执行一次)
|
||||||
|
this.updateChart(); // 更新图表数据
|
||||||
|
});
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
this.updateChart(); // 仅更新数据,不重新创建实例
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 组件销毁时清理资源
|
||||||
|
this.destroyChart();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 初始化图表(只在mounted中执行一次)
|
||||||
|
initChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
this.myChart.getZr().on('click', (params) => {
|
||||||
|
console.log('params', params);
|
||||||
|
|
||||||
|
// 提取点击的基地名称
|
||||||
|
// const itemName = params.name;
|
||||||
|
let itemName = undefined
|
||||||
|
// 根据映射表获取对应的序号(未匹配到则返回0或其他默认值)
|
||||||
|
let pointInPixel = [params.offsetX, params.offsetY];
|
||||||
|
if (this.myChart.containPixel('grid', pointInPixel)) {
|
||||||
|
let pointInGrid = this.myChart.convertFromPixel({
|
||||||
|
seriesIndex: 0
|
||||||
|
}, pointInPixel);
|
||||||
|
let xIndex = pointInGrid[0]; //索引
|
||||||
|
let handleIndex = Number(xIndex);
|
||||||
|
let seriesObj = this.myChart.getOption(); //图表object对象
|
||||||
|
var op = this.myChart.getOption();
|
||||||
|
//获得图表中点击的列
|
||||||
|
itemName = op.xAxis[0].data[handleIndex]; //获取点击的列名
|
||||||
|
console.log(itemName, 'monthmonthmonth');
|
||||||
|
console.log(handleIndex, seriesObj);
|
||||||
|
};
|
||||||
|
const baseIndex = this.baseNameToIndexMap[itemName] || 0;
|
||||||
|
|
||||||
|
console.log(`你点击了【${itemName}】(序号:${baseIndex})`);
|
||||||
|
if (itemName === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 路由跳转时携带序号(或名称+序号)
|
||||||
|
this.$router.push({
|
||||||
|
path: 'totalProfitBase',
|
||||||
|
query: { // 使用query传递参数(推荐),也可使用params
|
||||||
|
// baseName: itemName,
|
||||||
|
factory: baseIndex,
|
||||||
|
dateData: this.dateData
|
||||||
|
}
|
||||||
|
// 若仍需用base作为参数:
|
||||||
|
// base: itemName,
|
||||||
|
// params: { baseIndex: baseIndex }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定义resize处理函数(命名函数,方便移除)
|
||||||
|
this.resizeHandler = () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
};
|
||||||
|
// 绑定resize事件(只绑定一次)
|
||||||
|
window.addEventListener('resize', this.resizeHandler);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新图表数据(数据变化时执行)
|
||||||
|
updateChart() {
|
||||||
|
if (!this.myChart) {
|
||||||
|
return; // 实例未初始化则返回
|
||||||
|
}
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || [];
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 5,
|
||||||
|
right: 20,
|
||||||
|
left: 25,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
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],
|
||||||
|
// 可选:X轴标签显示“序号+名称”(如“1 宜兴”)
|
||||||
|
// formatter: (value) => {
|
||||||
|
// const index = this.baseNameToIndexMap[value] || '';
|
||||||
|
// return index ? `${index} ${value}` : value;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries
|
||||||
|
};
|
||||||
|
|
||||||
|
// 只更新配置,不重新创建实例
|
||||||
|
this.myChart.setOption(option, true); // 第二个参数true表示清空原有配置,避免数据残留
|
||||||
|
},
|
||||||
|
|
||||||
|
// 销毁图表资源
|
||||||
|
destroyChart() {
|
||||||
|
// 移除resize事件
|
||||||
|
if (this.resizeHandler) {
|
||||||
|
window.removeEventListener('resize', this.resizeHandler);
|
||||||
|
}
|
||||||
|
// 销毁图表实例
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
this.myChart = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 380px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
console.log('chartData', this.chartData);
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
console.log('xData', xData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 20,
|
||||||
|
right: 10,
|
||||||
|
left: 25,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
//
|
||||||
|
splitNumber: 4,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 200px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
console.log('chartData', this.chartData);
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
console.log('xData', xData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
// axisPointer: {
|
||||||
|
// type: 'cross',
|
||||||
|
// label: {
|
||||||
|
// backgroundColor: '#6a7985'
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 25,
|
||||||
|
bottom: 25,
|
||||||
|
right: 10,
|
||||||
|
left: 2,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
// offset: 10
|
||||||
|
// boundaryGap: ['50%', '50%'],
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
// name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true, // 显示Y轴轴线(关键)
|
||||||
|
lineStyle: {
|
||||||
|
color: '#E5E6EB', // 轴线颜色(浅灰色,可自定义)
|
||||||
|
width: 1, // 轴线宽度
|
||||||
|
type: 'solid' // 实线(可选:dashed虚线、dotted点线)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 2,
|
||||||
|
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)' } },
|
||||||
|
},
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,313 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="当月数据对比" icon="cockpitItemIcon" size="operatingLarge" topSize="large">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="left" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 348px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
">
|
||||||
|
集团情况
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{chartData.topBarData.rate[0]}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.topBarData.flags>0?'#30B590':'#FF9423'}" >{{chartData.topBarData.diff[0]}}</span></span>
|
||||||
|
</div>
|
||||||
|
<operatingTopBar :chartData="chartData?.topBarData || {}" />
|
||||||
|
</div>
|
||||||
|
<div class="right" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 1220px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<operatingBar :dateData="dateData" :chartData="chartData?.barData || {}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import operatingBar from "./operatingBar.vue";
|
||||||
|
import operatingTopBar from "./operatingTopBar.vue";
|
||||||
|
|
||||||
|
// 序号→地名映射表(levelId=序号)
|
||||||
|
const baseIndexToNameMap = {
|
||||||
|
7: "宜兴",
|
||||||
|
8: "漳州",
|
||||||
|
3: "自贡",
|
||||||
|
2: "桐城",
|
||||||
|
9: "洛阳",
|
||||||
|
5: "合肥",
|
||||||
|
10: "秦皇岛",
|
||||||
|
6: "宿迁"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, operatingBar, operatingTopBar },
|
||||||
|
props: {
|
||||||
|
monData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: {
|
||||||
|
topBarData: { // levelId=1的整合数据
|
||||||
|
locations: [], // 固定为["凯盛新能"]
|
||||||
|
diff: [], // 差值数组
|
||||||
|
targets: [], // 预算值数组
|
||||||
|
reals: [], // 实际值数组
|
||||||
|
rate: [], // 完成率数组
|
||||||
|
flags: [] // 完成状态数组(0/1)
|
||||||
|
},
|
||||||
|
barData: { // levelId≠1的整合数据
|
||||||
|
locations: [], // levelId对应的baseIndexToNameMap中的地名
|
||||||
|
diff: [], // 对应差值数组
|
||||||
|
targets: [], // 预算值数组
|
||||||
|
reals: [], // 实际值数组
|
||||||
|
rate: [], // 完成率数组
|
||||||
|
flags: [] // 完成状态数组(0/1)
|
||||||
|
// baseIndexes: []// 对应levelId(序号)数组
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
monData: {
|
||||||
|
handler() {
|
||||||
|
this.processChartData();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 核心方法:按levelId匹配地名生成locations
|
||||||
|
*/
|
||||||
|
processChartData() {
|
||||||
|
// 初始化空数据结构
|
||||||
|
const initTopBarData = {
|
||||||
|
locations: [], diff: [], targets: [], reals: [], rate: [], flags: []
|
||||||
|
};
|
||||||
|
const initBarData = { locations: [], diff: [], targets: [], reals: [], rate: [], flags: [] };
|
||||||
|
|
||||||
|
if (!Array.isArray(this.monData) || this.monData.length === 0) {
|
||||||
|
this.chartData = { topBarData: initTopBarData, barData: initBarData };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 处理levelId=1的整合数据(逻辑不变)
|
||||||
|
const level1Data = this.monData.filter(item => item.levelId === 1);
|
||||||
|
const topBarData = { ...initTopBarData };
|
||||||
|
level1Data.forEach(item => {
|
||||||
|
if (!item.name) return;
|
||||||
|
topBarData.locations = ["凯盛新能"]; // levelId=1固定为凯盛新能
|
||||||
|
topBarData.diff.push(item.diffValue || 0);
|
||||||
|
topBarData.targets.push(item.targetValue || 0);
|
||||||
|
topBarData.reals.push(item.value || 0);
|
||||||
|
topBarData.rate.push(item.proportion || 0);
|
||||||
|
topBarData.flags.push(item.completed ? 1 : 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 处理levelId≠1的整合数据(核心:levelId匹配地名)
|
||||||
|
const barData = { ...initBarData };
|
||||||
|
// 筛选有效数据:levelId≠1 且 levelId在baseIndexToNameMap中
|
||||||
|
const validOtherData = this.monData.filter(item => {
|
||||||
|
return item.levelId !== 1 && baseIndexToNameMap.hasOwnProperty(item.levelId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 遍历有效数据,填充locations(levelId→地名)
|
||||||
|
validOtherData.forEach(item => {
|
||||||
|
// 根据levelId(序号)从映射表获取对应地名
|
||||||
|
const baseName = baseIndexToNameMap[item.levelId];
|
||||||
|
if (baseName) { // 确保地名和原始名称有效
|
||||||
|
// barData.names.push(item.name); // 保留monData中的原始名称
|
||||||
|
barData.locations.push(baseName); // locations=levelId对应的地名(如levelId=7→宜兴)
|
||||||
|
barData.diff.push(item.diffValue || 0);
|
||||||
|
barData.targets.push(item.targetValue || 0);
|
||||||
|
barData.reals.push(item.value || 0);
|
||||||
|
barData.rate.push(item.proportion || 0);
|
||||||
|
barData.flags.push(item.completed ? 1 : 0);
|
||||||
|
// barData.baseIndexes.push(Number(item.levelId) || 0); // 序号转数字
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. 更新chartData
|
||||||
|
this.chartData = { topBarData, barData };
|
||||||
|
console.log('levelId=1数据:', this.chartData.topBarData);
|
||||||
|
console.log('levelId≠1数据(locations=levelId对应地名):', this.chartData.barData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</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>
|
||||||
@@ -0,0 +1,313 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="累计数据对比" icon="cockpitItemIcon" size="operatingLarge" topSize="large">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="left" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 348px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
">
|
||||||
|
集团情况
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{chartData.topBarData.rate[0]}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.topBarData.flags>0?'#30B590':'#FF9423'}" >{{chartData.topBarData.diff[0]}}</span></span>
|
||||||
|
</div>
|
||||||
|
<operatingTopBar :chartData="chartData?.topBarData || {}" />
|
||||||
|
</div>
|
||||||
|
<div class="right" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 1220px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<operatingBar :dateData="dateData" :chartData="chartData?.barData || {}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import operatingBar from "./operatingBar.vue";
|
||||||
|
import operatingTopBar from "./operatingTopBar.vue";
|
||||||
|
|
||||||
|
// 序号→地名映射表(levelId=序号)
|
||||||
|
const baseIndexToNameMap = {
|
||||||
|
7: "宜兴",
|
||||||
|
8: "漳州",
|
||||||
|
3: "自贡",
|
||||||
|
2: "桐城",
|
||||||
|
9: "洛阳",
|
||||||
|
5: "合肥",
|
||||||
|
10: "秦皇岛",
|
||||||
|
6: "宿迁"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, operatingBar, operatingTopBar },
|
||||||
|
props: {
|
||||||
|
totalData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: {
|
||||||
|
topBarData: { // levelId=1的整合数据
|
||||||
|
locations: [], // 固定为["凯盛新能"]
|
||||||
|
diff: [], // 差值数组
|
||||||
|
targets: [], // 预算值数组
|
||||||
|
reals: [], // 实际值数组
|
||||||
|
rate: [], // 完成率数组
|
||||||
|
flags: [] // 完成状态数组(0/1)
|
||||||
|
},
|
||||||
|
barData: { // levelId≠1的整合数据
|
||||||
|
locations: [], // levelId对应的baseIndexToNameMap中的地名
|
||||||
|
diff: [], // 对应差值数组
|
||||||
|
targets: [], // 预算值数组
|
||||||
|
reals: [], // 实际值数组
|
||||||
|
rate: [], // 完成率数组
|
||||||
|
flags: [] // 完成状态数组(0/1)
|
||||||
|
// baseIndexes: []// 对应levelId(序号)数组
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
totalData: {
|
||||||
|
handler() {
|
||||||
|
this.processChartData();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 核心方法:按levelId匹配地名生成locations
|
||||||
|
*/
|
||||||
|
processChartData() {
|
||||||
|
// 初始化空数据结构
|
||||||
|
const initTopBarData = {
|
||||||
|
locations: [], diff: [], targets: [], reals: [], rate: [], flags: []
|
||||||
|
};
|
||||||
|
const initBarData = { locations: [], diff: [], targets: [], reals: [], rate: [], flags: [] };
|
||||||
|
|
||||||
|
if (!Array.isArray(this.totalData) || this.totalData.length === 0) {
|
||||||
|
this.chartData = { topBarData: initTopBarData, barData: initBarData };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 处理levelId=1的整合数据(逻辑不变)
|
||||||
|
const level1Data = this.totalData.filter(item => item.levelId === 1);
|
||||||
|
const topBarData = { ...initTopBarData };
|
||||||
|
level1Data.forEach(item => {
|
||||||
|
if (!item.name) return;
|
||||||
|
topBarData.locations = ["凯盛新能"]; // levelId=1固定为凯盛新能
|
||||||
|
topBarData.diff.push(item.diffValue || 0);
|
||||||
|
topBarData.targets.push(item.targetValue || 0);
|
||||||
|
topBarData.reals.push(item.value || 0);
|
||||||
|
topBarData.rate.push(item.proportion || 0);
|
||||||
|
topBarData.flags.push(item.completed ? 1 : 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 处理levelId≠1的整合数据(核心:levelId匹配地名)
|
||||||
|
const barData = { ...initBarData };
|
||||||
|
// 筛选有效数据:levelId≠1 且 levelId在baseIndexToNameMap中
|
||||||
|
const validOtherData = this.totalData.filter(item => {
|
||||||
|
return item.levelId !== 1 && baseIndexToNameMap.hasOwnProperty(item.levelId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 遍历有效数据,填充locations(levelId→地名)
|
||||||
|
validOtherData.forEach(item => {
|
||||||
|
// 根据levelId(序号)从映射表获取对应地名
|
||||||
|
const baseName = baseIndexToNameMap[item.levelId];
|
||||||
|
if (baseName) { // 确保地名和原始名称有效
|
||||||
|
// barData.names.push(item.name); // 保留monData中的原始名称
|
||||||
|
barData.locations.push(baseName); // locations=levelId对应的地名(如levelId=7→宜兴)
|
||||||
|
barData.diff.push(item.diffValue || 0);
|
||||||
|
barData.targets.push(item.targetValue || 0);
|
||||||
|
barData.reals.push(item.value || 0);
|
||||||
|
barData.rate.push(item.proportion || 0);
|
||||||
|
barData.flags.push(item.completed ? 1 : 0);
|
||||||
|
// barData.baseIndexes.push(Number(item.levelId) || 0); // 序号转数字
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. 更新chartData
|
||||||
|
this.chartData = { topBarData, barData };
|
||||||
|
console.log('levelId=1数据:', this.chartData.topBarData);
|
||||||
|
console.log('levelId≠1数据(locations=levelId对应地名):', this.chartData.barData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</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>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<template>
|
||||||
|
<div class="lineBottom" style="height: 160px; width: 100%">
|
||||||
|
<operatingLineBarSaleSingle :refName="'totalOperating'" :chartData="chartD" style="height: 100%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBarSaleSingle from './operatingLineBarSaleSingle.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBarSaleSingle },
|
||||||
|
props: ["detailData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
locations() {
|
||||||
|
return ['预算', '实际'];
|
||||||
|
},
|
||||||
|
chartD() {
|
||||||
|
// 背景图片路径(若不需要可注释)
|
||||||
|
// const bgImageUrl = require('@/assets/img/labelBg.png');
|
||||||
|
const rate = this.detailData?.proportion? Number(this.detailData?.proportion) : 0
|
||||||
|
const diff = this.detailData?.diffValue || 0
|
||||||
|
console.log('diff', diff);
|
||||||
|
|
||||||
|
const seriesData = [
|
||||||
|
{
|
||||||
|
value: this.detailData?.targetValue || 0,
|
||||||
|
flag: 1, // 实际项:达标(绿色)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: this.detailData?.value || 0,
|
||||||
|
flag: this.detailData?.completed, // 实际项:达标(绿色)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
allPlaceNames: ['预算', '实际'],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 60,
|
||||||
|
barCategoryGap: '50%',
|
||||||
|
data: seriesData,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const currentFlag = params.data.flag || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
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)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
console.log('data', data);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFangSC;
|
||||||
|
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;
|
||||||
|
margin-right: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
width: 283px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0b58ff;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 8px;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button.active {
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
background: #3071ff;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,365 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<div class="lineBottom" style="height: 320px; width: 100%">
|
||||||
|
<operatingLineBar :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSaleGroup.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentDataSource() {
|
||||||
|
console.log('yyyy', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
console.log('this.1111', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData.locations
|
||||||
|
},
|
||||||
|
// 根据按钮切换生成对应的 chartData
|
||||||
|
chartD() {
|
||||||
|
// 销量场景数据
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
const diff = data.diff[0]
|
||||||
|
const rate = data.rate[0]
|
||||||
|
console.log(this.currentDataSource, 'currentDataSource');
|
||||||
|
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
// 1. 完成率(折线图)
|
||||||
|
// {
|
||||||
|
// 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(40, 138, 255, 0)' }
|
||||||
|
// ])
|
||||||
|
// },
|
||||||
|
// data: data.rates, // 完成率(%)
|
||||||
|
// symbol: 'circle',
|
||||||
|
// symbolSize: 6
|
||||||
|
// },
|
||||||
|
// 2. 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.targets // 目标销量(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图,含达标状态)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||||
|
const safeFlag = data.flags;
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
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)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals // 实际销量(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 毛利率场景数据
|
||||||
|
const grossProfitData = {
|
||||||
|
series: [
|
||||||
|
// 1. 完成率(折线图)
|
||||||
|
{
|
||||||
|
name: '完成率',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
lineStyle: { color: 'rgba(40, 138, 255, .5)', width: 2 },
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderColor: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
radius: 4
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.2,
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||||
|
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: [106.7, 96.9, 106.5, 106.1, 93.8, 105.9], // 毛利率完成率(%)
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 2. 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 14,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: [30, 32, 31, 33, 32, 34] // 目标毛利率(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 14,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const safeFlag = [1, 0, 1, 1, 0, 1]; // 达标状态
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
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)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: [32, 31, 33, 35, 30, 36] // 实际毛利率(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据按钮状态返回对应数据
|
||||||
|
return this.activeButton === 0 ? salesData : grossProfitData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end; // 标题左、右侧容器右,整体两端对齐
|
||||||
|
align-items: center; // 垂直居中,避免上下错位
|
||||||
|
gap: 16px; // 标题与右侧容器的最小间距,防止拥挤
|
||||||
|
width: 100%; // 确保占满父容器,实现两端对齐
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 标题固定在左侧,不挤压右侧空间
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; // 图例和按钮组垂直居中
|
||||||
|
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||||
|
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 图例:在右侧容器内横向排列
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px; // 图例项之间间距,避免重叠
|
||||||
|
align-items: center;
|
||||||
|
// 移除原margin-left,避免位置偏移
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap; // 防止图例文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图例颜色
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
width: 283px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
border-radius: 12px;
|
||||||
|
// 移除原margin-right,由右侧容器统一控制
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0b58ff;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 8px; // 确保文字间距生效
|
||||||
|
padding-left: 8px; // 抵消letter-spacing导致的文字左偏
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button.active {
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
background: #3071ff;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :isShowTab="true" :name="title" icon="cockpitItemIcon" size="opLargeBg" topSize="large"
|
||||||
|
@tabChange="handleChange">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px">
|
||||||
|
<div
|
||||||
|
v-for="item in sortedIndicators"
|
||||||
|
:key="item.key"
|
||||||
|
class="dashboard"
|
||||||
|
@click="item.route && handleDashboardClick(item.route)"
|
||||||
|
>
|
||||||
|
<div class="title">
|
||||||
|
{{ item.name }}·{{ item.unit }}
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;text-align: right;padding-right: 5px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{item.data.proportion}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:item.data.completed>0?'#30B590':'#FF9423'}" >{{item.data.diffValue}}</span></span>
|
||||||
|
</div>
|
||||||
|
<operatingSingleBar :detailData="item.data"></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from '../components/container.vue'
|
||||||
|
import operatingSingleBar from './operatingSingleBar.vue'
|
||||||
|
|
||||||
|
// import * as echarts from 'echarts'
|
||||||
|
// import rawItem from './raw-Item.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, operatingSingleBar },
|
||||||
|
props: {
|
||||||
|
// 接收父组件传递的 月度+累计 组合数据
|
||||||
|
relatedData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
relatedMon: [], // 月度数据(数组格式,存储销量/单价等数据)
|
||||||
|
relatedTotal: [] // 累计数据(数组格式,存储销量/单价等数据)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
factory: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 可选:动态标题
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
// 核心:当前激活的数据集(月度/累计),默认初始化月度数据
|
||||||
|
activeData: this.relatedData.relatedMon || []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
indicatorDefs() {
|
||||||
|
return [
|
||||||
|
{ key: 'sales', name: '销量', unit: '万㎡', route: '/salesVolumeAnalysis/salesVolumeAnalysisBase'},
|
||||||
|
{ key: 'price', name: '单价', unit: '元/㎡', route: '/unitPriceAnalysis/unitPriceAnalysisBase'},
|
||||||
|
{ key: 'mfgCost', name: '制造成本', unit: '元/㎡', route: '/productionCostAnalysis/productionCostAnalysisBase'},
|
||||||
|
{ key: 'mgmtFee', name: '管理费用', unit: '万元', route: '/expenseAnalysis/expenseAnalysisBase'},
|
||||||
|
{ key: 'salesFee', name: '销售费用', unit: '万元', route: '/expenseAnalysis/expenseAnalysisBase'},
|
||||||
|
{ key: 'finFee', name: '财务费用', unit: '万元', route: '/expenseAnalysis/expenseAnalysisBase'},
|
||||||
|
{ key: 'nonOpProfit', name: '非经营性利润', unit: '万元', route: null}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
indicators() {
|
||||||
|
const fallback = { targetValue: 0, value: 0, completed: 0, diffValue: 0 }
|
||||||
|
const list = (Array.isArray(this.activeData) ? this.activeData : [])
|
||||||
|
|
||||||
|
return this.indicatorDefs.map(def => {
|
||||||
|
const data = list.find(item => item && item.name === def.name) || fallback
|
||||||
|
return {
|
||||||
|
...def,
|
||||||
|
data,
|
||||||
|
sortValue: Number((data && data.value) ?? 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
sortedIndicators() {
|
||||||
|
const unitOrder = ['万㎡', '元/㎡', '万元']
|
||||||
|
const unitRank = (u) => {
|
||||||
|
const idx = unitOrder.indexOf(u)
|
||||||
|
return idx === -1 ? 999 : idx
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.indicators.slice().sort((a, b) => {
|
||||||
|
const ur = unitRank(a.unit) - unitRank(b.unit)
|
||||||
|
if (ur !== 0) return ur
|
||||||
|
const vr = (b.sortValue ?? -Infinity) - (a.sortValue ?? -Infinity)
|
||||||
|
if (vr !== 0) return vr
|
||||||
|
return String(a.key).localeCompare(String(b.key))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 可选:监听 relatedData 初始变化(若父组件异步传递数据,确保 activeData 同步更新)
|
||||||
|
relatedData: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.activeData = newVal.relatedMon || [];
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
console.log('组件挂载时的激活数据:', this.activeData);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleDashboardClick(path) {
|
||||||
|
this.$router.push({
|
||||||
|
path: path,
|
||||||
|
query: {
|
||||||
|
factory: this.$route.query.factory ? this.$route.query.factory : this.factory,
|
||||||
|
dateData: this.dateData
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Tab 切换处理函数
|
||||||
|
* @param {String} value 切换值('month' = 月度,'total' = 累计,可根据实际Tab值调整)
|
||||||
|
*/
|
||||||
|
handleChange(value) {
|
||||||
|
console.log('Tab 切换值:', value);
|
||||||
|
// 根据 Tab 值更新当前激活的数据集
|
||||||
|
if (value === 'month') {
|
||||||
|
// 切换为月度数据
|
||||||
|
this.activeData = this.relatedData.relatedMon || [];
|
||||||
|
} else {
|
||||||
|
// 切换为累计数据(非 month 均视为累计,可根据实际需求调整判断条件)
|
||||||
|
this.activeData = this.relatedData.relatedTotal || [];
|
||||||
|
}
|
||||||
|
console.log('当前激活数据集:', this.activeData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 220px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
// width: 190px;
|
||||||
|
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;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
// width: 190px;
|
||||||
|
height: 32px;
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
width: 120px;
|
||||||
|
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;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" 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; gap: 8px;">
|
||||||
|
<div class="dashboard">
|
||||||
|
<div class="title">
|
||||||
|
累计完成率
|
||||||
|
</div>
|
||||||
|
<div class="number">
|
||||||
|
<div class="yield">
|
||||||
|
{{ formatRate(factoryData?.completeRate) }}%
|
||||||
|
</div>
|
||||||
|
<div class="mom">
|
||||||
|
同比{{ formatRate(factoryData?.thb) }}%
|
||||||
|
<img v-if="factoryData?.thb >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="上升">
|
||||||
|
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="下降">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="electricityGauge">
|
||||||
|
<electricityGauge id="year" :detailData="factoryData"></electricityGauge>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
<div class="line" style="padding: 0px;">
|
||||||
|
<!-- 传递包含flag的factoryData给柱状图组件 -->
|
||||||
|
<verticalBarChart :detailData="factoryData"></verticalBarChart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import electricityGauge from './electricityGauge.vue'
|
||||||
|
import verticalBarChart from './verticalBarChart.vue'
|
||||||
|
|
||||||
|
// import * as echarts from 'echarts'
|
||||||
|
// import rawItem from './raw-Item.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, electricityGauge, verticalBarChart },
|
||||||
|
props: {
|
||||||
|
totalData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
month: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
/**
|
||||||
|
* 自动提取monData中的工厂数据,并新增flag字段
|
||||||
|
*/
|
||||||
|
factoryData() { // 整合原始数据 + 计算flag
|
||||||
|
return {
|
||||||
|
completeRate: this.totalData.proportion ? Number(this.totalData.proportion) : 0,
|
||||||
|
diff: this.totalData.diffValue,
|
||||||
|
real: this.totalData.value,
|
||||||
|
target: this.totalData.targetValue,
|
||||||
|
thb: this.totalData.thb,
|
||||||
|
// ...rawData,
|
||||||
|
flag: this.totalData.completed// 新增flag字段
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 格式化百分比数值:处理空值/非数字,兜底为0
|
||||||
|
*/
|
||||||
|
formatRate(value) {
|
||||||
|
if (isNaN(value) || value === null || value === undefined) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 判断完成率对应的flag值(<100为0,≥100为1)
|
||||||
|
* @param {number} rate 完成率(原始值,如89代表89%)
|
||||||
|
* @returns {0|1} flag值
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 264px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
// width: 190px;
|
||||||
|
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;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 46px;
|
||||||
|
color: #0B58FF;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
width: 500px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .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> -->
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
<template>
|
||||||
|
<div style="width: 100%; height: 210px;position: relative;">
|
||||||
|
<div style='font-size: 16px;position: absolute;right: 20px;top:10px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{detailData.completeRate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:detailData.flag>0?'#30B590':'#FF9423'}" >{{detailData.diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 210px;"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
refName: {
|
||||||
|
type: String,
|
||||||
|
default: 'verticalBarChart',
|
||||||
|
},
|
||||||
|
detailData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
completeRate: 0,
|
||||||
|
diff: 0,
|
||||||
|
real: 0,
|
||||||
|
target: 0,
|
||||||
|
thb: 0,
|
||||||
|
flag: 0
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => this.updateChart());
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
detailData: {
|
||||||
|
handler() {
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs[this.refName];
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('this.detailData', this.detailData);
|
||||||
|
|
||||||
|
// 修复:优化实例销毁逻辑,避免重复dispose
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.clear(); // 先清空,再重新渲染
|
||||||
|
} else {
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解构数据,避免重复取值
|
||||||
|
const { diff, completeRate, real, target, flag } = this.detailData;
|
||||||
|
// 确保数值为数字类型
|
||||||
|
const realValue = Number(real) || 0;
|
||||||
|
const targetValue = Number(target) || 0;
|
||||||
|
const diffValue = Number(diff) || 0;
|
||||||
|
const rateValue = Number(completeRate) || 0;
|
||||||
|
const flagValue = Number(flag) || 0;
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: { backgroundColor: '#6a7985' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 40,
|
||||||
|
bottom: 15,
|
||||||
|
right: 80,
|
||||||
|
left: 10,
|
||||||
|
containLabel: true,
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'value',
|
||||||
|
axisTick: { show: false },
|
||||||
|
min: 0,
|
||||||
|
splitNumber: 4,
|
||||||
|
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] }
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'category',
|
||||||
|
axisLabel: { color: 'rgba(0, 0, 0, 0.75)', fontSize: 12, interval: 0, padding: [5, 0, 0, 0] },
|
||||||
|
axisLine: { show: true, lineStyle: { color: '#E5E6EB', width: 1, type: 'solid' } },
|
||||||
|
axisTick: { show: false },
|
||||||
|
data: ['实际', '预算']
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 24,
|
||||||
|
// 修复:拆分数据项,确保每个柱子的样式独立生效
|
||||||
|
data: [
|
||||||
|
// 实际值柱子(核心:绑定flag颜色)
|
||||||
|
{
|
||||||
|
value: realValue,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'right',
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
// 修复:flag颜色判断独立绑定到实际值柱子
|
||||||
|
itemStyle: {
|
||||||
|
color: flagValue === 1
|
||||||
|
? {
|
||||||
|
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)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 预算值柱子(固定蓝色渐变)
|
||||||
|
{
|
||||||
|
value: targetValue,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'right',
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 1, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: '#82CCFF' },
|
||||||
|
{ offset: 1, color: '#4B9DFF' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.myChart.setOption(option, true); // 新增:true表示替换所有配置,避免缓存
|
||||||
|
|
||||||
|
// 优化:防抖resize,避免频繁触发
|
||||||
|
const resizeHandler = () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
};
|
||||||
|
window.removeEventListener('resize', resizeHandler); // 先移除再添加,避免重复绑定
|
||||||
|
window.addEventListener('resize', resizeHandler);
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', resizeHandler);
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
this.myChart = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" 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; gap: 8px;">
|
||||||
|
<div class="dashboard left">
|
||||||
|
<div class="title">
|
||||||
|
销量·万元
|
||||||
|
</div>
|
||||||
|
<div class="line">
|
||||||
|
<operatingSingleBar></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dashboard right">
|
||||||
|
<div class="title">
|
||||||
|
单价·万元
|
||||||
|
</div>
|
||||||
|
<div class="line">
|
||||||
|
<operatingSingleBar></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import operatingSingleBar from './operatingSingleBar.vue'
|
||||||
|
import verticalBarChart from './verticalBarChart.vue'
|
||||||
|
|
||||||
|
// import * as echarts from 'echarts'
|
||||||
|
// import rawItem from './raw-Item.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, operatingSingleBar, verticalBarChart },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
itemData: { // 接收父组件传递的设备数据数组
|
||||||
|
type: Array,
|
||||||
|
default: () => [] // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
title: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
month: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
itemData: {
|
||||||
|
handler(newValue, oldValue) {
|
||||||
|
// this.updateChart()
|
||||||
|
},
|
||||||
|
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// computed: {
|
||||||
|
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||||
|
// sortedItemData() {
|
||||||
|
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||||
|
// const totalCostItems = this.itemData.filter(item =>
|
||||||
|
// item.name && item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 过滤出不包含“总成本”的项
|
||||||
|
// const otherItems = this.itemData.filter(item =>
|
||||||
|
// !item.name || !item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 合并:总成本项在前,其他项在后
|
||||||
|
// return [...totalCostItems, ...otherItems];
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 382px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
// width: 190px;
|
||||||
|
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;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
// width: 190px;
|
||||||
|
height: 32px;
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
width: 120px;
|
||||||
|
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;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// .line {
|
||||||
|
// width: 500px;
|
||||||
|
// height: 205px;
|
||||||
|
// background: #F9FCFF;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .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> -->
|
||||||
@@ -19,9 +19,9 @@
|
|||||||
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="electricityGauge">
|
<!-- <div class="electricityGauge">
|
||||||
<electricityGauge :detailData="monthData" id="month"></electricityGauge>
|
<electricityGauge :detailData="monthData" id="month"></electricityGauge>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="line" style="padding: 0px;">
|
<div class="line" style="padding: 0px;">
|
||||||
<verticalBarChart :detailData="monthData">
|
<verticalBarChart :detailData="monthData">
|
||||||
@@ -142,33 +142,27 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.number {
|
.number {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
// width: 190px;
|
|
||||||
height: 32px;
|
|
||||||
font-family: YouSheBiaoTiHei;
|
font-family: YouSheBiaoTiHei;
|
||||||
font-size: 32px;
|
font-size: 46px;
|
||||||
color: #0B58FF;
|
color: #0B58FF;
|
||||||
line-height: 32px;
|
|
||||||
letter-spacing: 2px;
|
letter-spacing: 2px;
|
||||||
text-align: left;
|
text-align: center;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mom {
|
.mom {
|
||||||
// width: 97px;
|
|
||||||
height: 18px;
|
height: 18px;
|
||||||
font-family: PingFangSC, PingFang SC;
|
font-family: PingFangSC, PingFang SC;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 18px;
|
font-size: 20px;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
text-align: left;
|
text-align: center;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
z-index: 1000;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,33 +2,21 @@
|
|||||||
<div style="flex: 1">
|
<div style="flex: 1">
|
||||||
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
<div class="topItem-container" style="display: flex; gap: 8px; width: 100%;">
|
<div class="topItem-container" style="display: flex; gap: 8px">
|
||||||
<!-- 管理费用模块 -->
|
<div
|
||||||
<div class="dashboard">
|
v-for="item in sortedIndicators"
|
||||||
|
:key="item.key"
|
||||||
|
class="dashboard"
|
||||||
|
@click="item.route && handleDashboardClick(item.route)"
|
||||||
|
>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
管理费用·万元
|
{{ item.name }}·{{ item.unit }}
|
||||||
</div>
|
</div>
|
||||||
<div class="chart-wrap">
|
<div style='font-size: 16px;text-align: right;padding-right: 5px;'>
|
||||||
<operatingSingleBar :detailData="manageCostData"></operatingSingleBar>
|
<span>完成率:<span style='color: #0B58FF;'>{{item.detailData.rate}}%</span></span>
|
||||||
</div>
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:item.detailData.flag>0?'#30B590':'#FF9423'}" >{{item.detailData.diff}}</span></span>
|
||||||
</div>
|
|
||||||
<!-- 销售费用模块 -->
|
|
||||||
<div class="dashboard">
|
|
||||||
<div class="title">
|
|
||||||
销售费用·万元
|
|
||||||
</div>
|
|
||||||
<div class="chart-wrap">
|
|
||||||
<operatingSingleBar :detailData="saleCostData"></operatingSingleBar>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 财务费用模块 -->
|
|
||||||
<div class="dashboard">
|
|
||||||
<div class="title">
|
|
||||||
财务费用·万元
|
|
||||||
</div>
|
|
||||||
<div class="chart-wrap">
|
|
||||||
<operatingSingleBar :detailData="financeCostData"></operatingSingleBar>
|
|
||||||
</div>
|
</div>
|
||||||
|
<operatingSingleBar :detailData="item.detailData"></operatingSingleBar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,69 +51,66 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {}
|
||||||
// 初始化费用数据(包含flag字段)
|
},
|
||||||
manageCostData: { title: "管理费用", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 },
|
computed: {
|
||||||
saleCostData: { title: "销售费用", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 },
|
indicatorDefs() {
|
||||||
financeCostData: { title: "财务费用", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 }
|
return [
|
||||||
}
|
{ key: 'mgmtFee', name: '管理费用', unit: '万元'},
|
||||||
|
{ key: 'salesFee', name: '销售费用', unit: '万元'},
|
||||||
|
{ key: 'finFee', name: '财务费用', unit: '万元'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
indicators() {
|
||||||
|
let _this = this
|
||||||
|
const fallback = { budget: 0, real: 0, rate: 0, diff: 0, flag: 0 }
|
||||||
|
const list = (Array.isArray(_this.monthAnalysis) ? _this.monthAnalysis : [])
|
||||||
|
return _this.indicatorDefs.map(def => {
|
||||||
|
const data = list.find(item => item && item.title === def.name) || fallback
|
||||||
|
const detailData = {
|
||||||
|
...data,
|
||||||
|
flag: _this.getRateFlag(data.rate, data.real, data.budget),
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...def,
|
||||||
|
detailData,
|
||||||
|
sortValue: Number((data && data.real) ?? 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
sortedIndicators() {
|
||||||
|
const unitOrder = ['万元']
|
||||||
|
const unitRank = (u) => {
|
||||||
|
const idx = unitOrder.indexOf(u)
|
||||||
|
return idx === -1 ? 999 : idx
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.indicators.slice().sort((a, b) => {
|
||||||
|
const ur = unitRank(a.unit) - unitRank(b.unit)
|
||||||
|
if (ur !== 0) return ur
|
||||||
|
const vr = (b.sortValue ?? -Infinity) - (a.sortValue ?? -Infinity)
|
||||||
|
if (vr !== 0) return vr
|
||||||
|
return String(a.key).localeCompare(String(b.key))
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
monthAnalysis: {
|
monthAnalysis: {
|
||||||
handler(newVal) {
|
handler(newVal) {},
|
||||||
this.updateCostData(newVal)
|
|
||||||
},
|
|
||||||
deep: true,
|
deep: true,
|
||||||
immediate: true // 初始化立即执行
|
immediate: true // 初始化立即执行
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {},
|
||||||
this.updateCostData(this.monthAnalysis)
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
// 达标标识判断(≥100返回1,<100返回0)
|
getRateFlag(rate, real, target) {
|
||||||
getRateFlag(rate, real, target) {
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
// 1. 完成率 >= 100 => 达标
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
if (rate >= 100) return 1;
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
// 其他情况 => 未达标
|
||||||
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
return 0;
|
||||||
if (rate === 0 && target === 0) return 1;
|
|
||||||
|
|
||||||
// 其他情况 => 未达标
|
|
||||||
return 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 处理费用数据
|
|
||||||
updateCostData(data) {
|
|
||||||
// 数据兜底:确保是数组且长度≥3
|
|
||||||
const validData = Array.isArray(data) && data.length >= 3
|
|
||||||
? data
|
|
||||||
: this.$props.monthAnalysis;
|
|
||||||
|
|
||||||
// 提取三个费用项数据(兜底处理)
|
|
||||||
const manageItem = validData[0] || { title: "管理费用", budget: 0, real: 0, rate: 0, diff: 0 };
|
|
||||||
const saleItem = validData[1] || { title: "销售费用", budget: 0, real: 0, rate: 0, diff: 0 };
|
|
||||||
const financeItem = validData[2] || { title: "财务费用", budget: 0, real: 0, rate: 0, diff: 0 };
|
|
||||||
|
|
||||||
// 整合flag字段
|
|
||||||
this.manageCostData = {
|
|
||||||
...manageItem,
|
|
||||||
flag: this.getRateFlag(manageItem.rate, manageItem.real, manageItem.budget)
|
|
||||||
};
|
|
||||||
this.saleCostData = {
|
|
||||||
...saleItem,
|
|
||||||
flag: this.getRateFlag(saleItem.rate, saleItem.real, saleItem.budget)
|
|
||||||
};
|
|
||||||
this.financeCostData = {
|
|
||||||
...financeItem,
|
|
||||||
flag: this.getRateFlag(financeItem.rate, financeItem.real, financeItem.budget)
|
|
||||||
};
|
|
||||||
// 调试日志
|
|
||||||
console.log('管理费用数据:', this.manageCostData);
|
|
||||||
console.log('销售费用数据:', this.saleCostData);
|
|
||||||
console.log('财务费用数据:', this.financeCostData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,7 +154,6 @@ getRateFlag(rate, real, target) {
|
|||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
letter-spacing: 2px;
|
letter-spacing: 2px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 图表容器:适配高度
|
// 图表容器:适配高度
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lineBottom" style="height: 100%; width: 100%">
|
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||||
<operatingLineBar :dateData="dateData" :chartData="chartD" style="height: 99%; width: 100%" />
|
<operatingLineBar :dateData="dateData" :chartData="chartD" style="height: 100%; width: 100%" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -152,7 +152,7 @@ export default {
|
|||||||
name: '预算',
|
name: '预算',
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
yAxisIndex: 0,
|
yAxisIndex: 0,
|
||||||
barWidth: 14,
|
barWidth: 40,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: {
|
color: {
|
||||||
type: 'linear',
|
type: 'linear',
|
||||||
@@ -172,13 +172,13 @@ export default {
|
|||||||
name: '实际',
|
name: '实际',
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
yAxisIndex: 0,
|
yAxisIndex: 0,
|
||||||
barWidth: 14,
|
barWidth: 40,
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
position: 'top',
|
position: 'top',
|
||||||
offset: [30, 0],
|
offset: [32, 0],
|
||||||
width: 68,
|
width: 100,
|
||||||
height: 20,
|
height: 22,
|
||||||
formatter: (params) => {
|
formatter: (params) => {
|
||||||
const diff = data.diff || [];
|
const diff = data.diff || [];
|
||||||
const flags = data.flags || [];
|
const flags = data.flags || [];
|
||||||
@@ -212,23 +212,21 @@ export default {
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
borderColor: '#BFCBD577',
|
borderColor: '#BFCBD577',
|
||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
lineHeight: 20,
|
lineHeight: 26,
|
||||||
rich: {
|
rich: {
|
||||||
text: {
|
text: {
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
padding: [5, 10, 5, 0],
|
padding: [5, 10, 5, 0],
|
||||||
align: 'center',
|
align: 'center',
|
||||||
color: '#464646',
|
color: '#464646',
|
||||||
fontSize: 11,
|
fontSize: 14
|
||||||
lineHeight: 20
|
|
||||||
},
|
},
|
||||||
achieved: {
|
achieved: {
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
padding: [5, 0, 5, 10],
|
padding: [5, 0, 5, 10],
|
||||||
align: 'center',
|
align: 'center',
|
||||||
color: '#76DABE', // 与达标的 offset: 1 颜色一致
|
color: '#76DABE', // 与达标的 offset: 1 颜色一致
|
||||||
fontSize: 11,
|
fontSize: 14
|
||||||
lineHeight: 20
|
|
||||||
},
|
},
|
||||||
// 未达标样式
|
// 未达标样式
|
||||||
unachieved: {
|
unachieved: {
|
||||||
@@ -236,8 +234,7 @@ export default {
|
|||||||
padding: [5, 0, 5, 10],
|
padding: [5, 0, 5, 10],
|
||||||
align: 'center',
|
align: 'center',
|
||||||
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
|
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
|
||||||
fontSize: 11,
|
fontSize: 14
|
||||||
lineHeight: 20
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -133,11 +133,12 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
top: 30,
|
top: 30,
|
||||||
bottom: 30,
|
bottom: 5,
|
||||||
right: 70,
|
right: 20,
|
||||||
left: 60
|
left: 25,
|
||||||
|
containLabel: true
|
||||||
},
|
},
|
||||||
xAxis: [
|
xAxis: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 380px;"></div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
@@ -85,7 +85,7 @@ export default {
|
|||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
top: 30,
|
top: 30,
|
||||||
bottom: 5,
|
bottom: 20,
|
||||||
right: 10,
|
right: 10,
|
||||||
left: 25,
|
left: 25,
|
||||||
containLabel: true
|
containLabel: true
|
||||||
|
|||||||
@@ -85,8 +85,8 @@ export default {
|
|||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
top: 25,
|
top: 25,
|
||||||
bottom: 30,
|
bottom: 25,
|
||||||
right: 0,
|
right: 10,
|
||||||
left: 2,
|
left: 2,
|
||||||
containLabel: true
|
containLabel: true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,17 +10,20 @@
|
|||||||
background-color: rgba(249, 252, 255, 1);
|
background-color: rgba(249, 252, 255, 1);
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
">
|
">
|
||||||
<div style="
|
<div style="
|
||||||
font-weight: 400;
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
line-height: 18px;
|
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
padding: 16px 0 0 16px;
|
|
||||||
">
|
">
|
||||||
集团情况
|
集团情况
|
||||||
</div>
|
</div>
|
||||||
|
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{chartData.group.rate[0]}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.group.flags>0?'#30B590':'#FF9423'}" >{{chartData.group.diff[0]}}</span></span>
|
||||||
|
</div>
|
||||||
<operatingTopBar :chartData="chartData" />
|
<operatingTopBar :chartData="chartData" />
|
||||||
</div>
|
</div>
|
||||||
<div class="right" style="
|
<div class="right" style="
|
||||||
|
|||||||
@@ -10,17 +10,20 @@
|
|||||||
background-color: rgba(249, 252, 255, 1);
|
background-color: rgba(249, 252, 255, 1);
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
">
|
">
|
||||||
<div style="
|
<div style="
|
||||||
font-weight: 400;
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
line-height: 18px;
|
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
padding: 16px 0 0 16px;
|
|
||||||
">
|
">
|
||||||
集团情况
|
集团情况
|
||||||
</div>
|
</div>
|
||||||
|
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{chartData.group.rate[0]}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.group.flags>0?'#30B590':'#FF9423'}" >{{chartData.group.diff[0]}}</span></span>
|
||||||
|
</div>
|
||||||
<operatingTopBar :chartData="chartData" />
|
<operatingTopBar :chartData="chartData" />
|
||||||
</div>
|
</div>
|
||||||
<div class="right" style="
|
<div class="right" style="
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="lineBottom" style="height: 180px; width: 100%">
|
<div class="lineBottom" style="height: 160px; width: 100%">
|
||||||
<operatingLineBarSaleSingle :refName="'totalOperating'" :chartData="chartD" style="height: 99%; width: 100%" />
|
<operatingLineBarSaleSingle :refName="'totalOperating'" :chartData="chartD" style="height: 100%; width: 100%" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ export default {
|
|||||||
// const bgImageUrl = require('@/assets/img/labelBg.png');
|
// const bgImageUrl = require('@/assets/img/labelBg.png');
|
||||||
const rate = this.detailData?.rate || 0
|
const rate = this.detailData?.rate || 0
|
||||||
const diff = this.detailData?.diff || 0
|
const diff = this.detailData?.diff || 0
|
||||||
console.log('diff', diff);
|
console.log('detailData========================', this.detailData);
|
||||||
|
|
||||||
const seriesData = [
|
const seriesData = [
|
||||||
{
|
{
|
||||||
@@ -35,59 +35,6 @@ export default {
|
|||||||
show: true,
|
show: true,
|
||||||
position: 'top',
|
position: 'top',
|
||||||
offset: [0, 0],
|
offset: [0, 0],
|
||||||
// 固定label尺寸:68px×20px
|
|
||||||
width: 68,
|
|
||||||
height: 20,
|
|
||||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
|
||||||
formatter: function (params) {
|
|
||||||
return `{value|完成率}{rate|${rate}%}`;
|
|
||||||
},
|
|
||||||
// 核心样式:匹配CSS需求
|
|
||||||
backgroundColor: {
|
|
||||||
type: 'linear',
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
x2: 0,
|
|
||||||
y2: 1,
|
|
||||||
colorStops: [
|
|
||||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' }, // 顶部0px位置:阴影最强
|
|
||||||
// { offset: 0.1, color: 'rgba(205, 215, 224, 0.4)' }, // 1px位置:阴影减弱(对应1px)
|
|
||||||
// { offset: 0.15, color: 'rgba(205, 215, 224, 0.6)' }, // 3px位置:阴影几乎消失(对应3px扩散)
|
|
||||||
{ offset: 0.2, color: '#ffffff' }, // 主体白色
|
|
||||||
{ offset: 1, color: '#ffffff' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
// 外阴影:0px 2px 2px 0px rgba(191,203,215,0.5)
|
|
||||||
shadowColor: 'rgba(191,203,215,0.5)',
|
|
||||||
shadowBlur: 2,
|
|
||||||
shadowOffsetX: 0,
|
|
||||||
shadowOffsetY: 2,
|
|
||||||
// 圆角:4px
|
|
||||||
borderRadius: 4,
|
|
||||||
// 移除边框
|
|
||||||
borderColor: '#BFCBD577',
|
|
||||||
borderWidth: 0,
|
|
||||||
// 文字垂直居中(针对富文本)
|
|
||||||
lineHeight: 20,
|
|
||||||
rich: {
|
|
||||||
value: {
|
|
||||||
// 缩小宽度和内边距,适配68px容器
|
|
||||||
width: 'auto', // 自动宽度,替代固定40px
|
|
||||||
padding: [5, 0, 5, 10], // 缩小内边距
|
|
||||||
align: 'center',
|
|
||||||
color: '#464646', // 文字灰色
|
|
||||||
fontSize: 11, // 缩小字体,适配小尺寸
|
|
||||||
lineHeight: 20 // 垂直居中
|
|
||||||
},
|
|
||||||
rate: {
|
|
||||||
width: 'auto',
|
|
||||||
padding: [5, 10, 5, 0],
|
|
||||||
align: 'center',
|
|
||||||
color: '#0B58FF', // 数字蓝色
|
|
||||||
fontSize: 11,
|
|
||||||
lineHeight: 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: {
|
color: {
|
||||||
@@ -109,69 +56,6 @@ export default {
|
|||||||
show: true,
|
show: true,
|
||||||
position: 'top',
|
position: 'top',
|
||||||
offset: [0, 0],
|
offset: [0, 0],
|
||||||
// 固定label尺寸:68px×20px
|
|
||||||
width: 68,
|
|
||||||
height: 20,
|
|
||||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
|
||||||
formatter: (params) => {
|
|
||||||
|
|
||||||
// const flags = flags || [];
|
|
||||||
const currentDiff = diff || 0;
|
|
||||||
const currentFlag = this.detailData?.flag || 0;
|
|
||||||
// const prefix = currentFlag === 1 ? '+' : '-';
|
|
||||||
// 根据标志位选择不同的样式类
|
|
||||||
if (currentFlag === 1) {
|
|
||||||
// 达标 - 使用 rate-achieved 样式
|
|
||||||
return `{achieved|${currentDiff}}{text|差值}`;
|
|
||||||
} else {
|
|
||||||
// 未达标 - 使用 rate-unachieved 样式
|
|
||||||
return `{unachieved|${currentDiff}}{text|差值}`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
backgroundColor: {
|
|
||||||
type: 'linear',
|
|
||||||
x: 0, y: 0, x2: 0, y2: 1,
|
|
||||||
colorStops: [
|
|
||||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
|
||||||
{ offset: 0.2, color: '#ffffff' },
|
|
||||||
{ offset: 1, color: '#ffffff' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
shadowColor: 'rgba(191,203,215,0.5)',
|
|
||||||
shadowBlur: 2,
|
|
||||||
shadowOffsetX: 0,
|
|
||||||
shadowOffsetY: 2,
|
|
||||||
borderRadius: 4,
|
|
||||||
borderColor: '#BFCBD577',
|
|
||||||
borderWidth: 0,
|
|
||||||
lineHeight: 20,
|
|
||||||
rich: {
|
|
||||||
text: {
|
|
||||||
width: 'auto',
|
|
||||||
padding: [5, 10, 5, 0],
|
|
||||||
align: 'center',
|
|
||||||
color: '#464646',
|
|
||||||
fontSize: 11,
|
|
||||||
lineHeight: 20
|
|
||||||
},
|
|
||||||
achieved: {
|
|
||||||
width: 'auto',
|
|
||||||
padding: [5, 0, 5, 10],
|
|
||||||
align: 'center',
|
|
||||||
color: '#76DABE', // 与达标的 offset: 1 颜色一致
|
|
||||||
fontSize: 11,
|
|
||||||
lineHeight: 20
|
|
||||||
},
|
|
||||||
// 未达标样式
|
|
||||||
unachieved: {
|
|
||||||
width: 'auto',
|
|
||||||
padding: [5, 0, 5, 10],
|
|
||||||
align: 'center',
|
|
||||||
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
|
|
||||||
fontSize: 11,
|
|
||||||
lineHeight: 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
borderRadius: [4, 4, 0, 0],
|
borderRadius: [4, 4, 0, 0],
|
||||||
@@ -185,11 +69,12 @@ export default {
|
|||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
barWidth: 24,
|
barWidth: 60,
|
||||||
barCategoryGap: '50%',
|
barCategoryGap: '50%',
|
||||||
data: seriesData,
|
data: seriesData,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: (params) => {
|
color: (params) => {
|
||||||
|
console.log('params=================', params);
|
||||||
const currentFlag = params.data.flag || 0;
|
const currentFlag = params.data.flag || 0;
|
||||||
return currentFlag === 1
|
return currentFlag === 1
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -73,65 +73,9 @@ export default {
|
|||||||
yAxisIndex: 0, // 左侧Y轴(万元)
|
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
position: 'top',
|
position: 'top'
|
||||||
offset: [-30, 0],
|
|
||||||
// 固定label尺寸:68px×20px
|
|
||||||
width: 68,
|
|
||||||
height: 20,
|
|
||||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
|
||||||
formatter: (params) => {
|
|
||||||
// const { rate = 0, diff = 0 } = params.data || {};
|
|
||||||
return `{value|完成率}{rate|${rate}%}`;
|
|
||||||
},
|
|
||||||
// formatter: `{value|完成率}{rate|${rate}%}`,
|
|
||||||
// 核心样式:匹配CSS需求
|
|
||||||
backgroundColor: {
|
|
||||||
type: 'linear',
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
x2: 0,
|
|
||||||
y2: 1,
|
|
||||||
colorStops: [
|
|
||||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' }, // 顶部0px位置:阴影最强
|
|
||||||
// { offset: 0.1, color: 'rgba(205, 215, 224, 0.4)' }, // 1px位置:阴影减弱(对应1px)
|
|
||||||
// { offset: 0.15, color: 'rgba(205, 215, 224, 0.6)' }, // 3px位置:阴影几乎消失(对应3px扩散)
|
|
||||||
{ offset: 0.2, color: '#ffffff' }, // 主体白色
|
|
||||||
{ offset: 1, color: '#ffffff' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
// 外阴影:0px 2px 2px 0px rgba(191,203,215,0.5)
|
|
||||||
shadowColor: 'rgba(191,203,215,0.5)',
|
|
||||||
shadowBlur: 2,
|
|
||||||
shadowOffsetX: 0,
|
|
||||||
shadowOffsetY: 2,
|
|
||||||
// 圆角:4px
|
|
||||||
borderRadius: 4,
|
|
||||||
// 移除边框
|
|
||||||
borderColor: '#BFCBD577',
|
|
||||||
borderWidth: 0,
|
|
||||||
// 文字垂直居中(针对富文本)
|
|
||||||
lineHeight: 20,
|
|
||||||
rich: {
|
|
||||||
value: {
|
|
||||||
// 缩小宽度和内边距,适配68px容器
|
|
||||||
width: 'auto', // 自动宽度,替代固定40px
|
|
||||||
padding: [5, 0, 5, 10], // 缩小内边距
|
|
||||||
align: 'center',
|
|
||||||
color: '#464646', // 文字灰色
|
|
||||||
fontSize: 11, // 缩小字体,适配小尺寸
|
|
||||||
lineHeight: 20 // 垂直居中
|
|
||||||
},
|
|
||||||
rate: {
|
|
||||||
width: 'auto',
|
|
||||||
padding: [5, 10, 5, 0],
|
|
||||||
align: 'center',
|
|
||||||
color: '#0B58FF', // 数字蓝色
|
|
||||||
fontSize: 11,
|
|
||||||
lineHeight: 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
barWidth: 14,
|
barWidth: 65,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: {
|
color: {
|
||||||
type: 'linear',
|
type: 'linear',
|
||||||
@@ -153,73 +97,9 @@ export default {
|
|||||||
yAxisIndex: 0,
|
yAxisIndex: 0,
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
position: 'top',
|
position: 'top'
|
||||||
offset: [30, 0],
|
|
||||||
// 固定label尺寸:68px×20px
|
|
||||||
width: 68,
|
|
||||||
height: 20,
|
|
||||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
|
||||||
formatter: (params) => {
|
|
||||||
|
|
||||||
// const flags = flags || [];
|
|
||||||
const currentDiff = diff || 0;
|
|
||||||
const currentFlag = data.flags[0] || 0;
|
|
||||||
// const prefix = currentFlag === 1 ? '+' : '-';
|
|
||||||
// 根据标志位选择不同的样式类
|
|
||||||
if (currentFlag === 1) {
|
|
||||||
// 达标 - 使用 rate-achieved 样式
|
|
||||||
return `{achieved|${currentDiff}}{text|差值}`;
|
|
||||||
} else {
|
|
||||||
// 未达标 - 使用 rate-unachieved 样式
|
|
||||||
return `{unachieved|${currentDiff}}{text|差值}`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
backgroundColor: {
|
|
||||||
type: 'linear',
|
|
||||||
x: 0, y: 0, x2: 0, y2: 1,
|
|
||||||
colorStops: [
|
|
||||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
|
||||||
{ offset: 0.2, color: '#ffffff' },
|
|
||||||
{ offset: 1, color: '#ffffff' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
shadowColor: 'rgba(191,203,215,0.5)',
|
|
||||||
shadowBlur: 2,
|
|
||||||
shadowOffsetX: 0,
|
|
||||||
shadowOffsetY: 2,
|
|
||||||
borderRadius: 4,
|
|
||||||
borderColor: '#BFCBD577',
|
|
||||||
borderWidth: 0,
|
|
||||||
lineHeight: 20,
|
|
||||||
rich: {
|
|
||||||
text: {
|
|
||||||
width: 'auto',
|
|
||||||
padding: [5, 10, 5, 0],
|
|
||||||
align: 'center',
|
|
||||||
color: '#464646',
|
|
||||||
fontSize: 11,
|
|
||||||
lineHeight: 20
|
|
||||||
},
|
|
||||||
achieved: {
|
|
||||||
width: 'auto',
|
|
||||||
padding: [5, 0, 5, 10],
|
|
||||||
align: 'center',
|
|
||||||
color: '#76DABE', // 与达标的 offset: 1 颜色一致
|
|
||||||
fontSize: 11,
|
|
||||||
lineHeight: 20
|
|
||||||
},
|
|
||||||
// 未达标样式
|
|
||||||
unachieved: {
|
|
||||||
width: 'auto',
|
|
||||||
padding: [5, 0, 5, 10],
|
|
||||||
align: 'center',
|
|
||||||
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
|
|
||||||
fontSize: 11,
|
|
||||||
lineHeight: 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
barWidth: 14,
|
barWidth: 65,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: (params) => {
|
color: (params) => {
|
||||||
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||||
|
|||||||
@@ -19,9 +19,9 @@
|
|||||||
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="electricityGauge">
|
<!-- <div class="electricityGauge">
|
||||||
<electricityGauge :id="'totalG'" :detailData="ytdData" id="totalGauge"></electricityGauge>
|
<electricityGauge :id="'totalG'" :detailData="ytdData" id="totalGauge"></electricityGauge>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="line" style="padding: 0px;">
|
<div class="line" style="padding: 0px;">
|
||||||
<verticalBarChart :refName="'totalVerticalBarChart'" :detailData="ytdData">
|
<verticalBarChart :refName="'totalVerticalBarChart'" :detailData="ytdData">
|
||||||
@@ -142,32 +142,26 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.number {
|
.number {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
// width: 190px;
|
|
||||||
height: 32px;
|
|
||||||
font-family: YouSheBiaoTiHei;
|
font-family: YouSheBiaoTiHei;
|
||||||
font-size: 32px;
|
font-size: 46px;
|
||||||
color: #0B58FF;
|
color: #0B58FF;
|
||||||
line-height: 32px;
|
|
||||||
letter-spacing: 2px;
|
letter-spacing: 2px;
|
||||||
text-align: left;
|
text-align: center;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mom {
|
.mom {
|
||||||
// width: 97px;
|
|
||||||
height: 18px;
|
height: 18px;
|
||||||
font-family: PingFangSC, PingFang SC;
|
font-family: PingFangSC, PingFang SC;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 18px;
|
font-size: 20px;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
text-align: left;
|
text-align: center;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
z-index: 1000;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 210px;"></div>
|
<div style="width: 100%; height: 210px;position: relative;">
|
||||||
|
<div style='font-size: 16px;position: absolute;right: 20px;top:10px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{detailData.rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:flag>0?'#30B590':'#FF9423'}" >{{detailData.diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 210px;"></div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
@@ -8,7 +14,8 @@ export default {
|
|||||||
components: {},
|
components: {},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
myChart: null // 存储图表实例,避免重复创建
|
myChart: null, // 存储图表实例,避免重复创建
|
||||||
|
flag:0
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
@@ -43,18 +50,18 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getRateFlag(rate, real, target) {
|
getRateFlag(rate, real, target) {
|
||||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
// 1. 完成率 >= 100 => 达标
|
// 1. 完成率 >= 100 => 达标
|
||||||
if (rate >= 100) return 1;
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
if (rate === 0 && target === 0) return 1;
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
// 其他情况 => 未达标
|
// 其他情况 => 未达标
|
||||||
return 0;
|
return 0;
|
||||||
},
|
},
|
||||||
updateChart() {
|
updateChart() {
|
||||||
const chartDom = this.$refs[this.refName];
|
const chartDom = this.$refs[this.refName];
|
||||||
if (!chartDom) {
|
if (!chartDom) {
|
||||||
@@ -70,6 +77,7 @@ getRateFlag(rate, real, target) {
|
|||||||
const diff = this.detailData.diff || 0
|
const diff = this.detailData.diff || 0
|
||||||
const rate = this.detailData.rate || 0
|
const rate = this.detailData.rate || 0
|
||||||
const flagValue = this.getRateFlag(this.detailData.rate, this.detailData.real, this.detailData.target) || 0
|
const flagValue = this.getRateFlag(this.detailData.rate, this.detailData.real, this.detailData.target) || 0
|
||||||
|
this.flag = flagValue
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
@@ -92,10 +100,10 @@ getRateFlag(rate, real, target) {
|
|||||||
// }
|
// }
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
top: 10,
|
top: 40,
|
||||||
bottom: 30,
|
bottom: 15,
|
||||||
right: 80,
|
right: 80,
|
||||||
left: 30,
|
left: 10,
|
||||||
containLabel: true,
|
containLabel: true,
|
||||||
show: false // 隐藏grid背景,避免干扰
|
show: false // 隐藏grid背景,避免干扰
|
||||||
},
|
},
|
||||||
@@ -153,70 +161,7 @@ getRateFlag(rate, real, target) {
|
|||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
position: 'right',
|
position: 'right',
|
||||||
offset: [0, 25],
|
fontSize: 14
|
||||||
// 固定label尺寸:68px×20px
|
|
||||||
width: 68,
|
|
||||||
height: 20,
|
|
||||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
|
||||||
formatter: (params) => {
|
|
||||||
|
|
||||||
// const flags = flags || [];
|
|
||||||
const currentDiff = diff || 0;
|
|
||||||
const currentFlag = flagValue || 0;
|
|
||||||
// const prefix = currentFlag === 1 ? '+' : '-';
|
|
||||||
// 根据标志位选择不同的样式类
|
|
||||||
if (currentFlag === 1) {
|
|
||||||
// 达标 - 使用 rate-achieved 样式
|
|
||||||
return `{achieved|${currentDiff}}{text|差值}`;
|
|
||||||
} else {
|
|
||||||
// 未达标 - 使用 rate-unachieved 样式
|
|
||||||
return `{unachieved|${currentDiff}}{text|差值}`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
backgroundColor: {
|
|
||||||
type: 'linear',
|
|
||||||
x: 0, y: 0, x2: 0, y2: 1,
|
|
||||||
colorStops: [
|
|
||||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
|
||||||
{ offset: 0.2, color: '#ffffff' },
|
|
||||||
{ offset: 1, color: '#ffffff' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
shadowColor: 'rgba(191,203,215,0.5)',
|
|
||||||
shadowBlur: 2,
|
|
||||||
shadowOffsetX: 0,
|
|
||||||
shadowOffsetY: 2,
|
|
||||||
borderRadius: 4,
|
|
||||||
borderColor: '#BFCBD577',
|
|
||||||
borderWidth: 0,
|
|
||||||
lineHeight: 20,
|
|
||||||
rich: {
|
|
||||||
text: {
|
|
||||||
width: 'auto',
|
|
||||||
padding: [5, 10, 5, 0],
|
|
||||||
align: 'center',
|
|
||||||
color: '#464646',
|
|
||||||
fontSize: 11,
|
|
||||||
lineHeight: 20
|
|
||||||
},
|
|
||||||
achieved: {
|
|
||||||
width: 'auto',
|
|
||||||
padding: [5, 0, 5, 10],
|
|
||||||
align: 'center',
|
|
||||||
color: '#76DABE', // 与达标的 offset: 1 颜色一致
|
|
||||||
fontSize: 11,
|
|
||||||
lineHeight: 20
|
|
||||||
},
|
|
||||||
// 未达标样式
|
|
||||||
unachieved: {
|
|
||||||
width: 'auto',
|
|
||||||
padding: [5, 0, 5, 10],
|
|
||||||
align: 'center',
|
|
||||||
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
|
|
||||||
fontSize: 11,
|
|
||||||
lineHeight: 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: flagValue === 1
|
color: flagValue === 1
|
||||||
@@ -243,60 +188,7 @@ getRateFlag(rate, real, target) {
|
|||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
position: 'right',
|
position: 'right',
|
||||||
offset: [0, 25],
|
fontSize: 14
|
||||||
// 固定label尺寸:68px×20px
|
|
||||||
width: 68,
|
|
||||||
height: 20,
|
|
||||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
|
||||||
formatter: function (params) {
|
|
||||||
return `{value|完成率}{rate|${rate}%}`;
|
|
||||||
},
|
|
||||||
// 核心样式:匹配CSS需求
|
|
||||||
backgroundColor: {
|
|
||||||
type: 'linear',
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
x2: 0,
|
|
||||||
y2: 1,
|
|
||||||
colorStops: [
|
|
||||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' }, // 顶部0px位置:阴影最强
|
|
||||||
// { offset: 0.1, color: 'rgba(205, 215, 224, 0.4)' }, // 1px位置:阴影减弱(对应1px)
|
|
||||||
// { offset: 0.15, color: 'rgba(205, 215, 224, 0.6)' }, // 3px位置:阴影几乎消失(对应3px扩散)
|
|
||||||
{ offset: 0.2, color: '#ffffff' }, // 主体白色
|
|
||||||
{ offset: 1, color: '#ffffff' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
// 外阴影:0px 2px 2px 0px rgba(191,203,215,0.5)
|
|
||||||
shadowColor: 'rgba(191,203,215,0.5)',
|
|
||||||
shadowBlur: 2,
|
|
||||||
shadowOffsetX: 0,
|
|
||||||
shadowOffsetY: 2,
|
|
||||||
// 圆角:4px
|
|
||||||
borderRadius: 4,
|
|
||||||
// 移除边框
|
|
||||||
borderColor: '#BFCBD577',
|
|
||||||
borderWidth: 0,
|
|
||||||
// 文字垂直居中(针对富文本)
|
|
||||||
lineHeight: 20,
|
|
||||||
rich: {
|
|
||||||
value: {
|
|
||||||
// 缩小宽度和内边距,适配68px容器
|
|
||||||
width: 'auto', // 自动宽度,替代固定40px
|
|
||||||
padding: [5, 0, 5, 10], // 缩小内边距
|
|
||||||
align: 'center',
|
|
||||||
color: '#464646', // 文字灰色
|
|
||||||
fontSize: 11, // 缩小字体,适配小尺寸
|
|
||||||
lineHeight: 20 // 垂直居中
|
|
||||||
},
|
|
||||||
rate: {
|
|
||||||
width: 'auto',
|
|
||||||
padding: [5, 10, 5, 0],
|
|
||||||
align: 'center',
|
|
||||||
color: '#0B58FF', // 数字蓝色
|
|
||||||
fontSize: 11,
|
|
||||||
lineHeight: 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
// 预算的渐变颜色(蓝系渐变)
|
// 预算的渐变颜色(蓝系渐变)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user