Compare commits
24 Commits
05fe91618c
...
projects/l
| Author | SHA1 | Date | |
|---|---|---|---|
| 31f23d1d77 | |||
| c1acba7196 | |||
| eebb0804b0 | |||
| 59a1654cbd | |||
| 62340233e2 | |||
| 8839116a9a | |||
| f1116245fc | |||
| a6eaf41099 | |||
| cfcb4f5068 | |||
| 845e5a8af3 | |||
| f6aa736bff | |||
| 835d4efd5b | |||
| 8105e74122 | |||
| d8abdfbfec | |||
| 446078f558 | |||
| 7135ab0e4b | |||
| 0d74e762ce | |||
| 24102f0d0d | |||
| bdd73b8868 | |||
| 0c8bd440ae | |||
| ed0fd63474 | |||
| 9d91188b98 | |||
| f0ac88af3d | |||
| 161d6a1bdf |
6
.env.dev
6
.env.dev
@@ -10,11 +10,13 @@ VUE_APP_TITLE = 洛玻集团驾驶舱
|
||||
# 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://192.168.0.35:8080'
|
||||
VUE_APP_BASE_API = 'http://192.168.0.35:8080'
|
||||
# 闫阳
|
||||
# VUE_APP_BASE_API = 'http://172.16.19.131:7070'
|
||||
|
||||
|
||||
# 路由懒加载
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# 生产环境配置
|
||||
NODE_ENV = production
|
||||
ENV = 'production'
|
||||
|
||||
# 页面标题
|
||||
@@ -14,7 +15,7 @@ VUE_APP_BASE_API = ''
|
||||
PUBLIC_PATH = ''
|
||||
|
||||
# 二级部署路径
|
||||
VUE_APP_APP_NAME ='yudao-admin'
|
||||
# VUE_APP_APP_NAME ='yudao-admin'
|
||||
|
||||
# 多租户的开关
|
||||
VUE_APP_TENANT_ENABLE = true
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
const plugins = [];
|
||||
|
||||
// 生产环境移除 console.log/debug/info,保留 error/warn
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
plugins.push(['transform-remove-console', { exclude: ['error', 'warn'] }]);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
presets: [
|
||||
// https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
],
|
||||
plugins: plugins,
|
||||
'env': {
|
||||
'development': {
|
||||
// babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"code-brick-zj": "^1.1.1",
|
||||
"core-js": "^3.26.0",
|
||||
"crypto-js": "^4.0.0",
|
||||
"diagram-js": "^15.12.0",
|
||||
"echarts": "5.4.0",
|
||||
"element-ui": "2.15.14",
|
||||
"file-saver": "2.0.5",
|
||||
@@ -65,6 +66,7 @@
|
||||
"screenfull": "5.0.2",
|
||||
"sortablejs": "1.10.2",
|
||||
"throttle-debounce": "2.1.0",
|
||||
"video.js": "^8.23.7",
|
||||
"vue": "2.7.14",
|
||||
"vue-count-to": "1.0.13",
|
||||
"vue-cropper": "0.5.8",
|
||||
@@ -83,6 +85,7 @@
|
||||
"@vue/compiler-sfc": "^3.0.1",
|
||||
"@vue/eslint-config-prettier": "^5.0.0",
|
||||
"babel-eslint": "10.1.0",
|
||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||
"bpmn-js": "8.9.0",
|
||||
"bpmn-js-properties-panel": "0.46.0",
|
||||
"chalk": "4.1.0",
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Cache-control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Cache" content="no-cache">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= webpackConfig.name %></title>
|
||||
<!--[if lt IE 11]><script>window.location.href='html/ie.html';</script><![endif]-->
|
||||
|
||||
109
src/api/basicInfoConfig.js
Normal file
109
src/api/basicInfoConfig.js
Normal file
@@ -0,0 +1,109 @@
|
||||
import request from "@/utils/request";
|
||||
// 产品信息
|
||||
export function getProductConfigPage(data) {
|
||||
return request({
|
||||
url: "/lb/product-config/page",
|
||||
method: "get",
|
||||
params: data,
|
||||
});
|
||||
}
|
||||
export function getProductConfig(data) {
|
||||
return request({
|
||||
url: "/lb/product-config/get",
|
||||
method: "get",
|
||||
params: data,
|
||||
});
|
||||
}
|
||||
export function addProductConfig(data) {
|
||||
return request({
|
||||
url: "/lb/product-config/create",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
export function updateProductConfig(data) {
|
||||
return request({
|
||||
url: "/lb/product-config/update",
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
export function delProductConfig(data) {
|
||||
return request({
|
||||
url: "/lb/product-config/delete",
|
||||
method: "delete",
|
||||
params: data,
|
||||
});
|
||||
}
|
||||
// 集团重点工作
|
||||
export function getImportantWorkConfigPage(data) {
|
||||
return request({
|
||||
url: "/lb/important-work-config/page",
|
||||
method: "get",
|
||||
params: data,
|
||||
});
|
||||
}
|
||||
export function getImportantWorkConfig(data) {
|
||||
return request({
|
||||
url: "/lb/important-work-config/get",
|
||||
method: "get",
|
||||
params: data,
|
||||
});
|
||||
}
|
||||
export function addImportantWorkConfig(data) {
|
||||
return request({
|
||||
url: "/lb/important-work-config/create",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
export function updateImportantWorkConfig(data) {
|
||||
return request({
|
||||
url: "/lb/important-work-config/update",
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
export function delImportantWorkConfig(data) {
|
||||
return request({
|
||||
url: "/lb/important-work-config/delete",
|
||||
method: "delete",
|
||||
params: data,
|
||||
});
|
||||
}
|
||||
// 客户信息配置
|
||||
export function getCustomerConfigPage(data) {
|
||||
return request({
|
||||
url: "/lb/customer-config/page",
|
||||
method: "get",
|
||||
params: data,
|
||||
});
|
||||
}
|
||||
export function getCustomerConfig(data) {
|
||||
return request({
|
||||
url: "/lb/customer-config/get",
|
||||
method: "get",
|
||||
params: data,
|
||||
});
|
||||
}
|
||||
export function addCustomerConfig(data) {
|
||||
return request({
|
||||
url: "/lb/customer-config/create",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
export function updateCustomerConfig(data) {
|
||||
return request({
|
||||
url: "/lb/customer-config/update",
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
export function delCustomerConfig(data) {
|
||||
return request({
|
||||
url: "/lb/customer-config/delete",
|
||||
method: "delete",
|
||||
params: data,
|
||||
});
|
||||
}
|
||||
@@ -316,6 +316,24 @@ export function updateTargetYearData(data) {
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
// 预算填报模板下载/导出
|
||||
export function importTemplateYS(data) {
|
||||
return request({
|
||||
url: '/lb/index-target-month/export',
|
||||
method: "post",
|
||||
data: data,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
// 指标填报模板下载/导出
|
||||
export function importTemplateZB(data) {
|
||||
return request({
|
||||
url: '/lb/index-real-month/export',
|
||||
method: "post",
|
||||
data: data,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
export function getRealMonthPage(data) {
|
||||
return request({
|
||||
@@ -346,3 +364,38 @@ export function getDictListData(query) {
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
export function getAccountsReceivableData(data) {
|
||||
return request({
|
||||
url: "/lb/accounts-receivable/getGroupData",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
export function getInventoryData(data) {
|
||||
return request({
|
||||
url: "/lb/inventory/getGroupData",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
export function getElectricityCostAnalysisData(data) {
|
||||
return request({
|
||||
url: "/lb/electricity-cost-analysis/getGroupData",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
export function getDepreciationAnalysisData(data) {
|
||||
return request({
|
||||
url: "/lb/depreciation-analysis/getGroupData",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
export function getElectricityCostAnalysisFData(data) {
|
||||
return request({
|
||||
url: "/lb/electricity-cost-analysis/getFactoryData",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,15 +14,16 @@
|
||||
<i class="el-icon-caret-bottom" />
|
||||
</div>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<router-link to="/user/profile">
|
||||
<!-- <router-link to="/user/profile">
|
||||
<el-dropdown-item>个人中心</el-dropdown-item>
|
||||
</router-link>
|
||||
</router-link> -->
|
||||
<!-- <el-dropdown-item @click.native="setting = true">
|
||||
<span>布局设置</span>
|
||||
</el-dropdown-item> -->
|
||||
<el-dropdown-item divided @click.native="logout">
|
||||
<el-dropdown-item @click.native="logout">
|
||||
<span>退出登录</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item divided @click.native='handleToggle'>切换账号</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
@@ -97,7 +98,12 @@ export default {
|
||||
checkPermi(permissions) {
|
||||
return this.$auth.hasPermi(permissions)
|
||||
return true;
|
||||
}
|
||||
},
|
||||
handleToggle() {
|
||||
this.$store.dispatch('LogOut').then(() => {
|
||||
location.href = getPath('/index');
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -96,7 +96,7 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
|
||||
"/salesVolumeAnalysis",
|
||||
'/procurementGainAnalysis',
|
||||
'/fullCostAnalysis',
|
||||
// '/expenseAnalysis',
|
||||
'/electricityCostAnalysis',
|
||||
"/cost", // cost 根路由
|
||||
"/cost/profitImpactAnalysis", // cost 子菜单(完整路径)
|
||||
];
|
||||
|
||||
@@ -83,7 +83,10 @@ export const DICT_TYPE = {
|
||||
PROMOTION_CONDITION_TYPE: 'promotion_condition_type', // 营销的条件类型枚举
|
||||
|
||||
// ========== 模块 ==========
|
||||
LB_DW: 'lb_dw'
|
||||
LB_DW: 'lb_dw',
|
||||
PROCESS:'process',
|
||||
IMPORTANT_WORK_METHOD:'important_work_method',
|
||||
TARGET_PROPERTY:'target_property'
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,298 +0,0 @@
|
||||
<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 size="psi" top-title="产销率库存分析" :is-full-screen="isFullScreen" @screenfullChange="screenfullChange"
|
||||
@timeRangeChange="handleTimeChange" />
|
||||
<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: 414px 1194px;
|
||||
">
|
||||
<costOverview :productSaleData="productSaleData" />
|
||||
<PSDO :stockVO="stockVO" />
|
||||
</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;
|
||||
">
|
||||
<psiLineChart :saleAndProductData="saleAndProductData" />
|
||||
<!-- <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 costOverview from "./components/costOverview.vue";
|
||||
// import salesDataOverview from "./components/salesDataOverview.vue";
|
||||
import { mapState } from "vuex";
|
||||
import PSDO from "./components/PSDO.vue";
|
||||
import psiLineChart from "./components/psiLineChart.vue";
|
||||
import { getProductSaleAnalysis } from '@/api/cockpit'
|
||||
// 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,
|
||||
costOverview,
|
||||
PSDO,
|
||||
Sidebar,
|
||||
psiLineChart
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
weekArr: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
|
||||
isFullScreen: false,
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
value: 100,
|
||||
coreProductVisualAlarmVO: [],
|
||||
defect: {},
|
||||
centerEqInfo: [
|
||||
{ title: "工单数量", num: 0 },
|
||||
{ title: "总产量/吨", num: 0 },
|
||||
{ title: "生产合格率", num: "0%" },
|
||||
{ title: "设备运行数量", num: 0 },
|
||||
{ title: "累计能耗/kwh", num: 0 },
|
||||
],
|
||||
productSaleData: [],
|
||||
stockVO: {},
|
||||
saleAndProductData:[],
|
||||
};
|
||||
},
|
||||
|
||||
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;
|
||||
})();
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getData(obj) {
|
||||
getProductSaleAnalysis({
|
||||
startTime: obj.startTime,
|
||||
endTime: obj.endTime,
|
||||
mode: obj.mode,
|
||||
}).then((res) => {
|
||||
console.log(res);
|
||||
this.productSaleData = [
|
||||
{
|
||||
name: "销量",
|
||||
targetValue: res.data.productSaleData.saleTarget, // 销量目标:100
|
||||
value: res.data.productSaleData.saleValue, // 实际销量:100
|
||||
proportion: res.data.productSaleData.saleProportion, // 销量占比:1(100%)
|
||||
completed: res.data.productSaleData.saleCompleted
|
||||
},
|
||||
{
|
||||
name: "产量",
|
||||
targetValue: res.data.productSaleData.productTarget, // 产量目标:2000
|
||||
value: res.data.productSaleData.productValue, // 实际产量:1000
|
||||
proportion: res.data.productSaleData.productProportion, // 产量占比:0.5(50%)
|
||||
completed: res.data.productSaleData.productCompleted
|
||||
},
|
||||
{
|
||||
name: "累计完成产销率",
|
||||
targetValue: res.data.productSaleData.productAndSaleTarget, // 数据源中无产销率目标,默认为 null
|
||||
// 产销率 = 销量 / 产量 * 100%(避免除数为0)
|
||||
value: res.data.productSaleData.productAndSaleValue,
|
||||
proportion: res.data.productSaleData.productAndSaleProportion,
|
||||
completed: res.data.productSaleData.productAndSaleCompleted
|
||||
}
|
||||
];
|
||||
this.stockVO = {
|
||||
avgValue: res.data.productSaleData.avgValue,
|
||||
endMonthValue: res.data.productSaleData.endMonthValue,
|
||||
totalDays: res.data.productSaleData.totalDays,
|
||||
stockValues: res.data.productSaleAnalysisBaseFormatVOS
|
||||
}
|
||||
this.saleAndProductData = res.data.productSaleAnalysisBaseFormatVOS
|
||||
})
|
||||
},
|
||||
handleTimeChange(obj) {
|
||||
console.log(obj, 'obj');
|
||||
this.getData(obj)
|
||||
},
|
||||
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);
|
||||
}
|
||||
},
|
||||
// 导出
|
||||
// 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>
|
||||
@@ -39,7 +39,7 @@ import { mapState } from "vuex";
|
||||
import operatingLineChart from "../accountsReceivableComponents/operatingLineChart";
|
||||
import operatingLineChartCumulative from "../accountsReceivableComponents/operatingLineChartCumulative.vue";
|
||||
|
||||
import { getSalesRevenueGroupData } from '@/api/cockpit'
|
||||
import { getAccountsReceivableData } from '@/api/cockpit'
|
||||
export default {
|
||||
name: "AccountsReceivable",
|
||||
components: {
|
||||
@@ -59,6 +59,7 @@ export default {
|
||||
selectDate:{},
|
||||
monthData: {},
|
||||
ytdData:{},
|
||||
dateData: {},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -127,28 +128,16 @@ export default {
|
||||
this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined
|
||||
},
|
||||
methods: {
|
||||
// sortChange(value) {
|
||||
// this.sort = value
|
||||
// this.getData()
|
||||
// },
|
||||
getData() {
|
||||
getSalesRevenueGroupData({
|
||||
getAccountsReceivableData({
|
||||
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) {
|
||||
@@ -198,28 +187,7 @@ export default {
|
||||
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>
|
||||
|
||||
@@ -1,298 +0,0 @@
|
||||
<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>
|
||||
@@ -154,6 +154,18 @@ export default {
|
||||
},
|
||||
// 未使用的蒸汽仪表盘可注释/删除
|
||||
// getSteamGaugeOption(value) { ... }
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 销毁 ResizeObserver,避免内存泄漏
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect();
|
||||
this.resizeObserver = null;
|
||||
}
|
||||
// 销毁图表实例
|
||||
if (this.electricityChart) {
|
||||
this.electricityChart.dispose();
|
||||
this.electricityChart = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
<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> -->
|
||||
@@ -1,221 +0,0 @@
|
||||
<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>
|
||||
@@ -174,69 +174,7 @@ export default {
|
||||
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,
|
||||
}
|
||||
}
|
||||
show: false
|
||||
},
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
@@ -264,6 +202,78 @@ export default {
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.reals || []
|
||||
},
|
||||
// 实际差值标签(独立scatter系列,zlevel=1确保标签在最上层)
|
||||
{
|
||||
name: '__实际差值标签',
|
||||
type: 'scatter',
|
||||
yAxisIndex: 0,
|
||||
zlevel: 1,
|
||||
symbolSize: 0,
|
||||
tooltip: {
|
||||
show: false
|
||||
},
|
||||
data: (data.reals || []).map((value, index) => ({
|
||||
value: value,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [32, 0],
|
||||
width: 100,
|
||||
height: 22,
|
||||
formatter: () => {
|
||||
const diff = data.diff || [];
|
||||
const flags = data.flags || [];
|
||||
const currentDiff = diff[index] || 0;
|
||||
const currentFlag = flags[index] || 0;
|
||||
if (currentFlag === 1) {
|
||||
return `{achieved|${currentDiff}}{text|差值}`;
|
||||
} else {
|
||||
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',
|
||||
fontSize: 14,
|
||||
},
|
||||
unachieved: {
|
||||
width: 'auto',
|
||||
padding: [5, 0, 5, 10],
|
||||
align: 'center',
|
||||
color: '#F9A44A',
|
||||
fontSize: 14,
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -158,18 +158,6 @@ export default {
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -154,18 +154,6 @@ export default {
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ export default {
|
||||
return {
|
||||
myChart: null, // 存储图表实例
|
||||
resizeHandler: null, // 存储resize事件处理函数
|
||||
isMounted: false,
|
||||
// 核心:基地名称与序号的映射表(固定顺序)
|
||||
baseNameToIndexMap: {
|
||||
'宜兴': 7,
|
||||
@@ -35,6 +36,7 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.isMounted = true;
|
||||
this.$nextTick(() => {
|
||||
this.initChart(); // 初始化图表(只执行一次)
|
||||
this.updateChart(); // 更新图表数据
|
||||
@@ -43,11 +45,11 @@ export default {
|
||||
watch: {
|
||||
chartData: {
|
||||
handler() {
|
||||
if (!this.isMounted) return;
|
||||
console.log(this.chartData, 'chartData');
|
||||
this.updateChart(); // 仅更新数据,不重新创建实例
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
deep: true
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
@@ -59,7 +61,7 @@ export default {
|
||||
initChart() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
if (process.env.NODE_ENV === 'development') console.warn('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -94,18 +96,6 @@ export default {
|
||||
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处理函数(命名函数,方便移除)
|
||||
|
||||
@@ -8,7 +8,8 @@ export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 存储图表实例,避免重复创建
|
||||
myChart: null, // 存储图表实例,避免重复创建
|
||||
isMounted: false
|
||||
};
|
||||
},
|
||||
props: {
|
||||
@@ -24,6 +25,7 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.isMounted = true;
|
||||
this.$nextTick(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
@@ -34,18 +36,18 @@ export default {
|
||||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||
chartData: {
|
||||
handler() {
|
||||
if (!this.isMounted) return;
|
||||
console.log(this.chartData,'chartData');
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
immediate: true // 初始化时立即执行
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateChart() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
if (process.env.NODE_ENV === 'development') console.warn('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -154,18 +156,6 @@ export default {
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -147,18 +147,6 @@ export default {
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
width: 1220px;
|
||||
background-color: rgba(249, 252, 255, 1);
|
||||
">
|
||||
<!-- <top-item /> -->
|
||||
<operatingBar :dateData="dateData" :chartData="chartData" @sort-change="sortChange" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,23 +87,6 @@ export default {
|
||||
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
|
||||
*/
|
||||
@@ -118,7 +100,7 @@ getRateFlag(rate, real, target) {
|
||||
const groupReal = [this.groupData.real]; // 实际值数组
|
||||
const groupRate = [this.groupData.rate]; // 完成率数组
|
||||
// 新增:集团rate对应的flag
|
||||
const groupFlag = [this.getRateFlag(groupRate[0], groupReal[0], groupTarget[0])];
|
||||
const groupFlag = [this.groupData.rate >= 100 ? 1 : 0];
|
||||
|
||||
console.log('集团数据数组:', {
|
||||
groupTarget,
|
||||
@@ -139,7 +121,7 @@ getRateFlag(rate, real, target) {
|
||||
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));
|
||||
const factoryFlags = this.factoryData.map(item => item.rate >= 100 ? 1 : 0);
|
||||
|
||||
// 3. 组装最终的chartData(供子组件使用)
|
||||
this.chartData = {
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
width: 1220px;
|
||||
background-color: rgba(249, 252, 255, 1);
|
||||
">
|
||||
<!-- <top-item /> -->
|
||||
<operatingBar :dateData="dateData" @sort-change="sortChange" :chartData="chartData" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -83,23 +82,6 @@ export default {
|
||||
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
|
||||
@@ -114,7 +96,7 @@ getRateFlag(rate, real, target) {
|
||||
const groupReal = [this.groupData.real]; // 实际值数组
|
||||
const groupRate = [this.groupData.rate]; // 完成率数组
|
||||
// 新增:集团rate对应的flag
|
||||
const groupFlag = [this.getRateFlag(groupRate[0], groupReal[0], groupTarget[0])];
|
||||
const groupFlag = [this.groupData.rate >= 100 ? 1 : 0];
|
||||
|
||||
console.log('集团数据数组:', {
|
||||
groupTarget,
|
||||
@@ -135,7 +117,7 @@ getRateFlag(rate, real, target) {
|
||||
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));
|
||||
const factoryFlags = this.factoryData.map(item => item.rate >= 100 ? 1 : 0);
|
||||
|
||||
// 3. 组装最终的chartData(供子组件使用)
|
||||
this.chartData = {
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
<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>
|
||||
@@ -1,202 +0,0 @@
|
||||
<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> -->
|
||||
@@ -1,226 +0,0 @@
|
||||
<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>
|
||||
@@ -1,217 +0,0 @@
|
||||
<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>
|
||||
@@ -12,10 +12,9 @@
|
||||
</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-form-item label="所属基地" prop="factoryList">
|
||||
<el-select v-model="form.factoryList" placeholder="请选择" style="width: 100%;" multiple>
|
||||
<el-option v-for='item in facList' :key='item.value' :label="item.name" :value= 'item.value' ></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -23,7 +22,7 @@
|
||||
</el-form>
|
||||
</template>
|
||||
<script>
|
||||
// import { getGroupClasses, updateGroupClasses, createGroupClasses, getCode } from '@/api/base/groupClasses'
|
||||
import { addCustomerConfig, updateCustomerConfig, getCustomerConfig } from '@/api/basicInfoConfig'
|
||||
export default {
|
||||
name: 'CustomerInfoAdd',
|
||||
data() {
|
||||
@@ -32,14 +31,24 @@ export default {
|
||||
id: '',
|
||||
name: '',
|
||||
code: '',
|
||||
daySpan:'',
|
||||
factoryList:'',
|
||||
},
|
||||
isEdit: false, //是否是编辑
|
||||
rules: {
|
||||
name: [{ required: true, message: '请输入客户名称', trigger: 'blur' }],
|
||||
code: [{ required: true, message: '请输入客户编码', trigger: 'blur' }],
|
||||
daySpan: [{ required: true, message: '请选择所属基地', trigger: 'change' }],
|
||||
}
|
||||
factoryList: [{ required: true, message: '请选择所属基地', trigger: 'change' }],
|
||||
},
|
||||
facList:[
|
||||
{name:'宜兴',value:7},
|
||||
{name:'漳州',value:8},
|
||||
{name:'自贡',value:3},
|
||||
{name:'桐城',value:2},
|
||||
{name:'洛阳',value:9},
|
||||
{name:'合肥',value:5},
|
||||
{name:'宿迁',value:6},
|
||||
{name:'秦皇岛',value:10}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -47,11 +56,11 @@ export default {
|
||||
if (id) {
|
||||
this.isEdit = true
|
||||
this.form.id = id
|
||||
// getGroupClasses(id).then((res) => {
|
||||
// if (res.code === 0) {
|
||||
// this.form = res.data
|
||||
// }
|
||||
// })
|
||||
getCustomerConfig({id}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.form = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
submitForm() {
|
||||
@@ -59,14 +68,14 @@ export default {
|
||||
if (valid) {
|
||||
if (this.isEdit) {
|
||||
//编辑
|
||||
updateGroupClasses({ ...this.form }).then((res) => {
|
||||
updateCustomerConfig({ ...this.form }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$modal.msgSuccess("操作成功");
|
||||
this.$emit('successSubmit')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
createGroupClasses({ ...this.form }).then((res) => {
|
||||
addCustomerConfig({ ...this.form }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$modal.msgSuccess("操作成功");
|
||||
this.$emit('successSubmit')
|
||||
|
||||
@@ -2,22 +2,21 @@
|
||||
<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 label="重点工作" prop="importantWork">
|
||||
<el-input v-model="form.importantWork"></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-form-item label="单位" prop="unit">
|
||||
<el-select v-model="form.unit" placeholder="请选择" style="width: 100%;">
|
||||
<el-option v-for='item in unitList' :key='item.value' :label="item.label" :value= 'item.value' ></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="所属年份" prop="daySpan1">
|
||||
<el-form-item label="所属年份" prop="time">
|
||||
<el-date-picker
|
||||
v-model="value3"
|
||||
v-model="form.time"
|
||||
type="year"
|
||||
placeholder="选择年"
|
||||
style="width: 100%;">
|
||||
@@ -25,10 +24,16 @@
|
||||
</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-form-item label="累计值计算方式" prop="calculateMethod">
|
||||
<el-select v-model="form.calculateMethod" placeholder="请选择" style="width: 100%;">
|
||||
<el-option v-for='item in calculateMethodList' :key='item.value' :label="item.label" :value= 'item.value' ></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="数值目标属性" prop="targetProperty">
|
||||
<el-select v-model="form.targetProperty" placeholder="请选择" style="width: 100%;">
|
||||
<el-option v-for='item in targetPropertyList' :key='item.value' :label="item.label" :value= 'item.value' ></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -36,24 +41,32 @@
|
||||
</el-form>
|
||||
</template>
|
||||
<script>
|
||||
// import { getGroupClasses, updateGroupClasses, createGroupClasses, getCode } from '@/api/base/groupClasses'
|
||||
import { addImportantWorkConfig, updateImportantWorkConfig, getImportantWorkConfig } from '@/api/basicInfoConfig'
|
||||
import {getDictDatas } from '@/utils/dict'
|
||||
import moment from 'moment';
|
||||
export default {
|
||||
name: 'groupKeyAdd',
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
id: '',
|
||||
name: '',
|
||||
code: '',
|
||||
daySpan:'',
|
||||
importantWork: '',
|
||||
unit: '',
|
||||
time:'',
|
||||
calculateMethod:'',
|
||||
targetProperty:'',
|
||||
},
|
||||
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' }],
|
||||
}
|
||||
importantWork: [{ required: true, message: '请输入重点工作', trigger: 'blur' }],
|
||||
unit: [{ required: true, message: '请选择单位', trigger: 'change' }],
|
||||
time: [{ required: true, message: '请选择所属年份', trigger: 'change' }],
|
||||
calculateMethod: [{ required: true, message: '请选择累计值计算方式', trigger: 'change' }],
|
||||
targetProperty: [{ required: true, message: '请选择数值目标属性', trigger: 'change' }],
|
||||
},
|
||||
unitList:getDictDatas('lb_dw'),
|
||||
calculateMethodList:getDictDatas('important_work_method'),
|
||||
targetPropertyList:getDictDatas('target_property')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -61,26 +74,27 @@ export default {
|
||||
if (id) {
|
||||
this.isEdit = true
|
||||
this.form.id = id
|
||||
// getGroupClasses(id).then((res) => {
|
||||
// if (res.code === 0) {
|
||||
// this.form = res.data
|
||||
// }
|
||||
// })
|
||||
getImportantWorkConfig({id}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.form = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
submitForm() {
|
||||
this.$refs['form'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.form.time = moment(this.form.time).endOf('year').endOf('month').endOf('day').unix() * 1000
|
||||
if (this.isEdit) {
|
||||
//编辑
|
||||
updateGroupClasses({ ...this.form }).then((res) => {
|
||||
updateImportantWorkConfig({ ...this.form }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$modal.msgSuccess("操作成功");
|
||||
this.$emit('successSubmit')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
createGroupClasses({ ...this.form }).then((res) => {
|
||||
addImportantWorkConfig({ ...this.form }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$modal.msgSuccess("操作成功");
|
||||
this.$emit('successSubmit')
|
||||
|
||||
@@ -2,20 +2,19 @@
|
||||
<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 label="产品名称" prop="product">
|
||||
<el-input v-model="form.product"></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 label="规格" prop="spec">
|
||||
<el-input v-model="form.spec"></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-form-item label="工艺" prop="process">
|
||||
<el-select v-model="form.process" placeholder="请选择" style="width: 100%;">
|
||||
<el-option v-for='item in processList' :key='item.value' :label="item.label" :value= 'item.value' ></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -23,23 +22,23 @@
|
||||
</el-form>
|
||||
</template>
|
||||
<script>
|
||||
// import { getGroupClasses, updateGroupClasses, createGroupClasses, getCode } from '@/api/base/groupClasses'
|
||||
import { addProductConfig, updateProductConfig, getProductConfig } from '@/api/basicInfoConfig'
|
||||
import {getDictDatas } from '@/utils/dict'
|
||||
export default {
|
||||
name: 'ProductInfoAdd',
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
id: '',
|
||||
name: '',
|
||||
code: '',
|
||||
daySpan:'',
|
||||
product: '',
|
||||
spec: '',
|
||||
process:'',
|
||||
},
|
||||
isEdit: false, //是否是编辑
|
||||
rules: {
|
||||
name: [{ required: true, message: '请输入产品名称', trigger: 'blur' }],
|
||||
code: [{ required: true, message: '请输入产品规格', trigger: 'blur' }],
|
||||
daySpan: [{ required: true, message: '请选择工艺', trigger: 'change' }],
|
||||
}
|
||||
product: [{ required: true, message: '请输入产品名称', trigger: 'blur' }]
|
||||
},
|
||||
processList:getDictDatas('process')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -47,11 +46,13 @@ export default {
|
||||
if (id) {
|
||||
this.isEdit = true
|
||||
this.form.id = id
|
||||
// getGroupClasses(id).then((res) => {
|
||||
// if (res.code === 0) {
|
||||
// this.form = res.data
|
||||
// }
|
||||
// })
|
||||
getProductConfig({id}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.form = res.data
|
||||
}
|
||||
})
|
||||
}else{
|
||||
this.form.id = ''
|
||||
}
|
||||
},
|
||||
submitForm() {
|
||||
@@ -59,14 +60,14 @@ export default {
|
||||
if (valid) {
|
||||
if (this.isEdit) {
|
||||
//编辑
|
||||
updateGroupClasses({ ...this.form }).then((res) => {
|
||||
updateProductConfig({ ...this.form }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$modal.msgSuccess("操作成功");
|
||||
this.$emit('successSubmit')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
createGroupClasses({ ...this.form }).then((res) => {
|
||||
addProductConfig({ ...this.form }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$modal.msgSuccess("操作成功");
|
||||
this.$emit('successSubmit')
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
<script>
|
||||
import CustomerInfoAdd from './components/customerInfoAdd.vue';
|
||||
import { getCustomerConfigPage,delCustomerConfig } from '@/api/basicInfoConfig';
|
||||
const tableProps = [
|
||||
{
|
||||
prop: 'name',
|
||||
@@ -32,8 +33,9 @@ const tableProps = [
|
||||
label: '客户编码'
|
||||
},
|
||||
{
|
||||
prop: 'jd',
|
||||
label: '所属基地'
|
||||
prop: 'factoryNameList',
|
||||
label: '所属基地',
|
||||
filter: (val) => val ? val.join(',') :'-'
|
||||
},
|
||||
];
|
||||
export default {
|
||||
@@ -42,17 +44,33 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
formConfig: [
|
||||
{
|
||||
type: 'select',
|
||||
label: '所属基地',
|
||||
selectOptions: [
|
||||
{ id: 2, name: '桐城' },
|
||||
{ id: 3, name: '自贡' },
|
||||
{ id: 5, name: '合肥' },
|
||||
{ id: 6, name: '宿迁' },
|
||||
{ id: 7, name: '宜兴' },
|
||||
{ id: 8, name: '漳州' },
|
||||
{ id: 9, name: '洛阳' },
|
||||
{ id: 10, name: '秦皇岛' },
|
||||
],
|
||||
param: 'factory',
|
||||
onchange: true
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
label: '客户名称',
|
||||
placeholder: '客户名称',
|
||||
param: 'cName'
|
||||
param: 'name'
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
label: '客户编码',
|
||||
placeholder: '客户编码',
|
||||
param: 'ccode'
|
||||
param: 'code'
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
@@ -72,11 +90,14 @@ export default {
|
||||
}
|
||||
],
|
||||
listQuery:{
|
||||
factory:'',
|
||||
name:'',
|
||||
code:'',
|
||||
pageSize:20,
|
||||
pageNo:1
|
||||
},
|
||||
total:2,
|
||||
tableData:[{name:'111',id:1}],
|
||||
total:0,
|
||||
tableData:[],
|
||||
tableProps,
|
||||
tableBtn:[
|
||||
{
|
||||
@@ -92,13 +113,19 @@ export default {
|
||||
centervisible: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getDataList();
|
||||
},
|
||||
methods: {
|
||||
buttonClick(val) {
|
||||
console.log(val)
|
||||
switch (val.btnName) {
|
||||
case 'search':
|
||||
this.listQuery.name = val.name
|
||||
this.listQuery.code = val.code
|
||||
this.listQuery.factory = val.factory
|
||||
this.listQuery.pageNo = 1;
|
||||
this.getList();
|
||||
this.getDataList();
|
||||
break;
|
||||
case 'add':
|
||||
this.addOrEditTitle = '新增';
|
||||
@@ -110,7 +137,15 @@ export default {
|
||||
}
|
||||
},
|
||||
getDataList() {
|
||||
|
||||
getCustomerConfigPage({...this.listQuery}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.tableData = res.data.list;
|
||||
this.total = res.data.total;
|
||||
}else{
|
||||
this.tableData = [];
|
||||
this.total = 0;
|
||||
}
|
||||
})
|
||||
},
|
||||
handleClick(val) {
|
||||
switch (val.type) {
|
||||
@@ -135,14 +170,15 @@ export default {
|
||||
},
|
||||
successSubmit() {
|
||||
this.handleCancel();
|
||||
this.getList();
|
||||
this.getDataList();
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
this.$modal.confirm('确定删除客户"' + row.name + '吗?').then(function() {
|
||||
deleteModel(row.id).then(response => {
|
||||
that.getList();
|
||||
that.$modal.msgSuccess("删除成功");
|
||||
let _this = this;
|
||||
_this.$modal.confirm('确定删除客户"' + row.name + '吗?').then(function() {
|
||||
delCustomerConfig({id:row.id}).then(response => {
|
||||
_this.$modal.msgSuccess("删除成功");
|
||||
_this.getDataList();
|
||||
})
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
@@ -22,22 +22,33 @@
|
||||
|
||||
<script>
|
||||
import GroupKeyAdd from './components/groupKeyAdd.vue';
|
||||
import { getImportantWorkConfigPage,delImportantWorkConfig } from '@/api/basicInfoConfig';
|
||||
import { publicFormatter } from '@/utils/dict';
|
||||
import moment from 'moment';
|
||||
const tableProps = [
|
||||
{
|
||||
prop: 'name',
|
||||
prop: 'importantWork',
|
||||
label: '重点工作'
|
||||
},
|
||||
{
|
||||
prop: 'code1',
|
||||
label: '单位'
|
||||
prop: 'unit',
|
||||
label: '单位',
|
||||
filter: publicFormatter('lb_dw')
|
||||
},
|
||||
{
|
||||
prop: 'code',
|
||||
label: '所属年份'
|
||||
prop: 'time',
|
||||
label: '所属年份',
|
||||
filter: (val) => val ? moment(val).format('YYYY') : '-'
|
||||
},
|
||||
{
|
||||
prop: 'jd',
|
||||
label: '累计值计算方式'
|
||||
prop: 'calculateMethod',
|
||||
label: '累计值计算方式',
|
||||
filter: publicFormatter('important_work_method')
|
||||
},
|
||||
{
|
||||
prop: 'targetProperty',
|
||||
label: '数值目标属性',
|
||||
filter: publicFormatter('target_property')
|
||||
},
|
||||
];
|
||||
export default {
|
||||
@@ -50,7 +61,7 @@ export default {
|
||||
type: 'input',
|
||||
label: '重点工作',
|
||||
placeholder: '重点工作',
|
||||
param: 'cName'
|
||||
param: 'importantWork'
|
||||
},
|
||||
{
|
||||
type: 'datePicker',
|
||||
@@ -59,7 +70,7 @@ export default {
|
||||
format: 'yyyy',
|
||||
valueFormat: 'yyyy',
|
||||
placeholder: '所属年份',
|
||||
param: 'searchTime1',
|
||||
param: 'time',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
@@ -80,11 +91,13 @@ export default {
|
||||
}
|
||||
],
|
||||
listQuery:{
|
||||
importantWork:'',
|
||||
time:'',
|
||||
pageSize:20,
|
||||
pageNo:1
|
||||
},
|
||||
total:2,
|
||||
tableData:[{name:'111'}],
|
||||
total:0,
|
||||
tableData:[],
|
||||
tableProps,
|
||||
tableBtn:[
|
||||
{
|
||||
@@ -101,13 +114,19 @@ export default {
|
||||
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.searchBarForm.formInline.time = moment().endOf('year').endOf('month').endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
||||
this.listQuery.time = moment().endOf('year').endOf('month').endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
||||
this.getDataList();
|
||||
},
|
||||
methods: {
|
||||
buttonClick(val) {
|
||||
console.log(val)
|
||||
switch (val.btnName) {
|
||||
case 'search':
|
||||
this.listQuery.importantWork = val.importantWork;
|
||||
this.listQuery.time = val.time ? moment(val.time).endOf('year').endOf('month').endOf('day').format('YYYY-MM-DD HH:mm:ss') : '';
|
||||
this.listQuery.pageNo = 1;
|
||||
this.getList();
|
||||
this.getDataList();
|
||||
break;
|
||||
case 'add':
|
||||
this.addOrEditTitle = '新增';
|
||||
@@ -119,7 +138,15 @@ export default {
|
||||
}
|
||||
},
|
||||
getDataList() {
|
||||
|
||||
getImportantWorkConfigPage({...this.listQuery}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.tableData = res.data.list;
|
||||
this.total = res.data.total;
|
||||
}else{
|
||||
this.tableData = [];
|
||||
this.total = 0;
|
||||
}
|
||||
})
|
||||
},
|
||||
handleClick(val) {
|
||||
switch (val.type) {
|
||||
@@ -144,14 +171,16 @@ export default {
|
||||
},
|
||||
successSubmit() {
|
||||
this.handleCancel();
|
||||
this.getList();
|
||||
this.getDataList();
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
this.$modal.confirm('确定删除重点工作"' + row.name + '吗?').then(function() {
|
||||
deleteModel(row.id).then(response => {
|
||||
that.getList();
|
||||
that.$modal.msgSuccess("删除成功");
|
||||
let _this = this;
|
||||
_this.$modal.confirm('确定删除重点工作"' + row.importantWork + '吗?').then(function() {
|
||||
delImportantWorkConfig({id:row.id}).then(response => {
|
||||
_this.$modal.msgSuccess("删除成功");
|
||||
_this.getDataList();
|
||||
|
||||
})
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
@@ -22,17 +22,20 @@
|
||||
|
||||
<script>
|
||||
import ProductInfoAdd from './components/productInfoAdd.vue';
|
||||
import { getProductConfigPage,delProductConfig } from '@/api/basicInfoConfig';
|
||||
import { publicFormatter } from '@/utils/dict';
|
||||
const tableProps = [
|
||||
{
|
||||
prop: 'name',
|
||||
prop: 'product',
|
||||
label: '产品名称'
|
||||
},
|
||||
{
|
||||
prop: 'code',
|
||||
label: '工艺'
|
||||
prop: 'process',
|
||||
label: '工艺',
|
||||
filter: publicFormatter('process')
|
||||
},
|
||||
{
|
||||
prop: 'jd',
|
||||
prop: 'spec',
|
||||
label: '规格'
|
||||
},
|
||||
];
|
||||
@@ -46,7 +49,7 @@ export default {
|
||||
type: 'input',
|
||||
label: '产品名称',
|
||||
placeholder: '产品名称',
|
||||
param: 'cName'
|
||||
param: 'product'
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
@@ -66,11 +69,12 @@ export default {
|
||||
}
|
||||
],
|
||||
listQuery:{
|
||||
product:'',
|
||||
pageSize:20,
|
||||
pageNo:1
|
||||
},
|
||||
total:2,
|
||||
tableData:[{name:'111'}],
|
||||
total:0,
|
||||
tableData:[],
|
||||
tableProps,
|
||||
tableBtn:[
|
||||
{
|
||||
@@ -87,13 +91,16 @@ export default {
|
||||
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getDataList();
|
||||
},
|
||||
methods: {
|
||||
buttonClick(val) {
|
||||
console.log(val)
|
||||
switch (val.btnName) {
|
||||
case 'search':
|
||||
this.listQuery.product = val.product;
|
||||
this.listQuery.pageNo = 1;
|
||||
this.getList();
|
||||
this.getDataList();
|
||||
break;
|
||||
case 'add':
|
||||
this.addOrEditTitle = '新增';
|
||||
@@ -105,7 +112,17 @@ export default {
|
||||
}
|
||||
},
|
||||
getDataList() {
|
||||
|
||||
getProductConfigPage({
|
||||
...this.listQuery
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.tableData = res.data.list;
|
||||
this.total = res.data.total;
|
||||
}else{
|
||||
this.tableData = [];
|
||||
this.total = 0;
|
||||
}
|
||||
})
|
||||
},
|
||||
handleClick(val) {
|
||||
switch (val.type) {
|
||||
@@ -130,14 +147,15 @@ export default {
|
||||
},
|
||||
successSubmit() {
|
||||
this.handleCancel();
|
||||
this.getList();
|
||||
this.getDataList();
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
this.$modal.confirm('确定删除产品"' + row.name + '吗?').then(function() {
|
||||
deleteModel(row.id).then(response => {
|
||||
that.getList();
|
||||
that.$modal.msgSuccess("删除成功");
|
||||
let _this = this;
|
||||
_this.$modal.confirm('确定删除产品"' + row.product + '吗?').then(function() {
|
||||
delProductConfig({id:row.id}).then(response => {
|
||||
_this.getDataList();
|
||||
_this.$modal.msgSuccess("删除成功");
|
||||
})
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="top-three" style="
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
grid-template-columns:416px 1192px;
|
||||
grid-template-columns:186px 1422px;
|
||||
">
|
||||
<indicatorCalendar :timeType="timeType" :calendarObj='calendarObj'/>
|
||||
<indicatorDetails :timeType="timeType" @updateLeft='getData' @updateLevel='getLevel'/>
|
||||
@@ -26,7 +26,6 @@
|
||||
gap: 12px;
|
||||
grid-template-columns:416px 1192px;
|
||||
">
|
||||
|
||||
</div>
|
||||
</div> -->
|
||||
<!-- <div class="centerImg" style="
|
||||
@@ -39,7 +38,6 @@
|
||||
"></div> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import ReportHeader from "./components/budgetHeader.vue";
|
||||
@@ -47,12 +45,10 @@ import { Sidebar } from "../../layout/components";
|
||||
import screenfull from "screenfull";
|
||||
import indicatorCalendar from "./components/budgetCalendar.vue";
|
||||
import indicatorDetails from "./components/budgetDetails.vue";
|
||||
|
||||
// import premProdStatus from "./components/premProdStatus.vue";
|
||||
import { mapState } from "vuex";
|
||||
// import operatingLineChart from "../operatingComponents/operatingLineChart";
|
||||
// import operatingLineChartCumulative from "../operatingComponents/operatingLineChartCumulative.vue";
|
||||
|
||||
import { getSalesRevenueGroupData } from '@/api/cockpit'
|
||||
import moment from "moment";
|
||||
import {getCalendar, getCalendarYear} from '@/api/cockpit';
|
||||
@@ -83,7 +79,6 @@ export default {
|
||||
levelId: null
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
this.init();
|
||||
this.windowWidth(document.documentElement.clientWidth);
|
||||
@@ -203,7 +198,6 @@ export default {
|
||||
// 全屏
|
||||
screenfullChange() {
|
||||
console.log("screenfull.enabled", screenfull.isEnabled);
|
||||
|
||||
if (!screenfull.isEnabled) {
|
||||
this.$message({
|
||||
message: "you browser can not work",
|
||||
@@ -249,12 +243,10 @@ export default {
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
.sidebarHide .fixed-header {
|
||||
width: calc(100%);
|
||||
}
|
||||
|
||||
.mobile .fixed-header {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -87,11 +87,14 @@ export default {
|
||||
|
||||
// 如果没有 X 轴数据,则返回空数组
|
||||
if (xAxisKeys.length === 0) {
|
||||
return [];
|
||||
return {};
|
||||
}
|
||||
let obj = {
|
||||
unit:'万元',
|
||||
series:[]
|
||||
}
|
||||
|
||||
// 遍历配置项,生成 series
|
||||
return Object.keys(this.chartConfig).map(key => {
|
||||
obj.series = Object.keys(this.chartConfig).map(key => {
|
||||
const config = this.chartConfig[key];
|
||||
// 确保数据顺序和 X 轴一致
|
||||
const dataValues = xAxisKeys.map(date => lineData[key] ? lineData[key].real[date] : 0);
|
||||
@@ -114,6 +117,7 @@ export default {
|
||||
data: dataValues
|
||||
};
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
@@ -126,6 +130,7 @@ export default {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 12px;
|
||||
margin: 7px 0;
|
||||
|
||||
.barTop {
|
||||
display: flex;
|
||||
|
||||
@@ -8,13 +8,17 @@
|
||||
|
||||
<!-- 右侧区域:全屏按钮 -->
|
||||
<div class="right-content">
|
||||
<el-dropdown trigger="click">
|
||||
<el-button type="text" class="logout-btn" :title="'退出'">
|
||||
<svg-icon style="color: #0B58FF;" icon-class="logout" />
|
||||
</el-button>
|
||||
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
|
||||
<div class="avatar-wrapper">
|
||||
<img :src="require(`../../../assets/images/choicepart/avatar.png`)" class="user-avatar">
|
||||
<span v-if="nickname" class="user-nickname">{{ nickname }}</span>
|
||||
<i class="el-icon-caret-bottom" />
|
||||
</div>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item @click.native='logout'>退出登录</el-dropdown-item>
|
||||
<el-dropdown-item @click.native='handleToggle'>切换账号</el-dropdown-item>
|
||||
<el-dropdown-item @click.native="logout">
|
||||
<span>退出登录</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item divided @click.native='handleToggle'>切换账号</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
<el-button type="text" class="screen-btn" @click="changeHomeSider">
|
||||
@@ -44,6 +48,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import moment from 'moment'
|
||||
import {getPath} from "@/utils/ruoyi";
|
||||
export default {
|
||||
@@ -57,6 +62,9 @@ export default {
|
||||
default: () => ({})
|
||||
},
|
||||
},
|
||||
computed:{
|
||||
...mapGetters(['nickname']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentTime: '',
|
||||
@@ -371,14 +379,35 @@ export default {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.avatar-container {
|
||||
margin-right: 30px;
|
||||
|
||||
.logout-btn {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
font-size: 30px;
|
||||
padding: 0;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.avatar-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.user-avatar {
|
||||
cursor: pointer;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.user-nickname{
|
||||
margin-left: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.el-icon-caret-bottom {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: -20px;
|
||||
top: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 日期选择器样式保持不变 */
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
<!--
|
||||
* @Date: 2020-12-14 09:07:03
|
||||
* @LastEditors: zhp
|
||||
* @LastEditTime: 2024-09-05 09:50:14
|
||||
* @FilePath: \mt-bus-fe\src\views\OperationalOverview\components\baseTable.vue
|
||||
* @Description:
|
||||
-->
|
||||
<template>
|
||||
<div class="visual-base-table-container">
|
||||
<el-table :max-height="maxHeight" ref="scroll_Table" @mouseenter.native="autoScroll(true)"
|
||||
@mouseleave.native="autoScroll(false)" v-loading="isLoading"
|
||||
:header-cell-style="{ background: 'rgba(218, 226, 237, 1)', color: 'rgba(0, 0, 0, .6)',padding:'3px 2px'}" :row-style="setRowStyle"
|
||||
:data="renderData" border style="width: 100%; background: transparent">
|
||||
<el-table-column v-if="page && limit && showIndex" prop="_pageIndex" label="序号" :width="70" align="center" />
|
||||
<el-table-column v-for="item in renderTableHeadList" :key="item.prop" :show-overflow-tooltip="showOverflow"
|
||||
<el-table-column v-if="page && limit && showIndex" prop="_pageIndex" label="序号" :width="60" align="center" />
|
||||
<el-table-column v-for="item in renderTableHeadList" :key="item.prop" :show-overflow-tooltip="showOverflow" :width='item.width || ""'
|
||||
v-bind="item">
|
||||
<template slot-scope="scope">
|
||||
|
||||
@@ -39,7 +32,7 @@ export default {
|
||||
maxHeight: {
|
||||
type: [Number, String], // 支持数字(如300)或字符串(如'300px')
|
||||
required: false,
|
||||
default: 200 // 原固定值,作为默认 fallback
|
||||
default: 200 // 原固定值,作为默认 fallback
|
||||
},
|
||||
tableData: {
|
||||
type: Array,
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="预算填报日历" icon="cockpitItemIcon" size="calendarBg" topSize="calendarTitleBg">
|
||||
<Container name="填报总览" icon="cockpitItemIcon" size="calendarBg" topSize="calendarTitleBg">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 14px; display: flex;flex-direction: column; width: 100%;">
|
||||
<!-- 2. .top 保持 flex,无需固定高度,自动跟随子元素拉伸 -->
|
||||
<div class="bottom"
|
||||
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);height: 844px;padding: 26px 16px;">
|
||||
style="width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);height: 844px;padding: 26px 0px;">
|
||||
<!-- 动态生成12个月的容器:优化flex布局,缩小行间距 -->
|
||||
<div class="month-list"
|
||||
style="display: flex; gap: 16px; flex-wrap: wrap; align-content: flex-start; row-gap: 8px;">
|
||||
<div class="month-list">
|
||||
<!-- 循环生成12个月:通过判断当前月份索引,添加current类 -->
|
||||
<div class="monthItem" :class="{
|
||||
'has-data': calendar.haveData,
|
||||
@@ -91,19 +90,19 @@ export default {
|
||||
|
||||
// 基础月份样式
|
||||
.monthItem {
|
||||
width: 164px;
|
||||
height: 42px;
|
||||
width: 100px;
|
||||
height: 57px;
|
||||
border-radius: 4px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 20px;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
line-height: 42px;
|
||||
line-height: 57px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
transition: all 0.2s ease; // 过渡效果,样式切换更平滑
|
||||
border: 2px solid transparent; // 透明边框,避免选中时布局偏移
|
||||
margin: 0; // 清除默认外边距,进一步缩小缝隙
|
||||
margin: 0 auto 10px; // 清除默认外边距,进一步缩小缝隙
|
||||
}
|
||||
|
||||
// 有数据的样式(背景色#D1E8FF)
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button style="background-color: #0B58FF;" type="primary" @click="onSubmit">查询</el-button>
|
||||
<el-button type="primary" plain size="medium" @click='downLoadExcel'>模板下载</el-button>
|
||||
<el-button type="primary" plain size="medium" @click='importExcel'>导入</el-button>
|
||||
<el-button type="primary" plain size="medium" @click='exportExcel'>导出</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@@ -73,24 +75,26 @@
|
||||
:key="tableKey"></base-table>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
|
||||
<el-upload ref="upload" :limit="1" accept=".xlsx, .xls" action="#" :disabled="upload.isUploading"
|
||||
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body @close="handleImportDialogClose">
|
||||
<el-upload ref="upload" :limit="1" accept=".xlsx, .xls" action="#" :disabled="upload.httpUploading"
|
||||
:on-change="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<div class="el-upload__tip text-center" slot="tip">
|
||||
<span>仅允许导入xls、xlsx格式文件。</span>
|
||||
<span>仅允许导入{{timeType==='month'?'月':'年'}}预算!仅允许导入xls、xlsx格式文件!</span>
|
||||
</div>
|
||||
<div class="el-upload__tip" slot="tip">
|
||||
<el-radio-group v-model="upload.timeDim">
|
||||
<el-radio :label="2">月预算</el-radio>
|
||||
<el-radio :label="3">年预算</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</el-upload>
|
||||
<div v-if="upload.httpUploading" class="upload-progress-wrap">
|
||||
<div class="upload-progress-track">
|
||||
<div class="upload-progress-bar"></div>
|
||||
</div>
|
||||
<span class="upload-progress-text">正在上传</span>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
||||
<el-button @click="upload.open = false">取 消</el-button>
|
||||
<el-button type="primary" :loading="upload.httpUploading" :disabled="upload.httpUploading" @click="submitFileForm">确 定</el-button>
|
||||
<el-button :disabled="upload.httpUploading" @click="cancelBtn">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</Container>
|
||||
@@ -99,11 +103,12 @@
|
||||
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
import { getLevelStruc, getTargetMonthPage, updateTargetMonthData, getTargetYearPage, updateTargetYearData,copyLastMonthData, copyLastYearData} from '@/api/cockpit'
|
||||
import { getLevelStruc, getTargetMonthPage, updateTargetMonthData, getTargetYearPage, updateTargetYearData,copyLastMonthData, copyLastYearData,importTemplateYS} from '@/api/cockpit'
|
||||
import inputArea from './inputArea.vue' // 导入输入组件
|
||||
import { publicFormatter } from '@/utils/dict';
|
||||
import {getAccessToken, getTenantId} from '@/utils/auth'
|
||||
import axios from 'axios';
|
||||
import moment from 'moment';
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: {
|
||||
@@ -132,11 +137,10 @@ export default {
|
||||
open: false,
|
||||
// 弹出层标题
|
||||
title: "预算填报导入",
|
||||
// 是否禁用上传
|
||||
isUploading: false,
|
||||
fileList:[],
|
||||
currentFile:null,
|
||||
timeDim: 2
|
||||
// HTTP 上传中(点击确定后 axios 上传,展示不确定进度条)
|
||||
httpUploading: false
|
||||
},
|
||||
getDataList: null, // 动态切换的查询接口
|
||||
updateData: null, // 动态切换的更新接口
|
||||
@@ -359,6 +363,8 @@ export default {
|
||||
});
|
||||
},
|
||||
handleLevelChange(id) {
|
||||
this.form.levelId = id;
|
||||
this.getDataPage()
|
||||
this.$emit('updateLevel', id)
|
||||
},
|
||||
// 表格单元格数据变更回调
|
||||
@@ -388,7 +394,7 @@ export default {
|
||||
console.log('当前编辑状态:', isEdit, '当前时间维度:', this.timeType);
|
||||
// 基础表格列配置(只读模式使用)
|
||||
const baseTableProps = [
|
||||
// { prop: 'type', label: '指标类型', align: 'center' },
|
||||
{ prop: 'type', label: '指标类型', align: 'center' },
|
||||
{ prop: 'name', label: '指标名称', align: 'center' },
|
||||
{ prop: 'unit', label: '单位', align: 'center', filter: publicFormatter('lb_dw') },
|
||||
{ prop: 'target', label: '预算值', align: 'center' },
|
||||
@@ -455,15 +461,53 @@ export default {
|
||||
}
|
||||
// 计算选中日期对应的时间戳
|
||||
this.calculateTimeStamp(val);
|
||||
// 重新请求数据
|
||||
this.getDataPage();
|
||||
},
|
||||
// 导入
|
||||
importExcel() {
|
||||
this.upload.httpUploading = false
|
||||
this.upload.open = true
|
||||
},
|
||||
downLoadExcel() {
|
||||
importTemplateYS({
|
||||
levelId:this.form.levelId,
|
||||
startTime: this.form.startTime,
|
||||
endTime: this.form.endTime,
|
||||
pageSize: 1000,
|
||||
template:1,
|
||||
pageNo: this.form.pageNo,
|
||||
timeDim:this.timeType === 'month'?2:3
|
||||
}).then(response => {
|
||||
let fileName = '';
|
||||
let factoryName = this.levelLList.filter(item => item.id == this.form.levelId)[0].name;
|
||||
if (this.timeType === 'month') {
|
||||
fileName = factoryName+'_月_预算导入模板.xls';
|
||||
}else{
|
||||
fileName = factoryName+'_年_预算导入模板.xls';
|
||||
}
|
||||
this.$download.excel(response, fileName);
|
||||
});
|
||||
},
|
||||
exportExcel() {
|
||||
importTemplateYS({
|
||||
levelId:this.form.levelId,
|
||||
startTime: this.form.startTime,
|
||||
endTime: this.form.endTime,
|
||||
pageSize: 1000,
|
||||
template:0,
|
||||
pageNo: this.form.pageNo,
|
||||
timeDim:this.timeType === 'month'?2:3
|
||||
}).then(response => {
|
||||
let mon = this.timeType=== 'month'?moment(this.form.endTime).format('YYYYMM'):moment(this.form.endTime).format('YYYY');
|
||||
let factoryName = this.levelLList.filter(item => item.id == this.form.levelId)[0].name;
|
||||
let fileName = this.timeType=== 'month'?mon + factoryName + '月度预算表.xls':mon + factoryName + '年度预算表.xls';
|
||||
this.$download.excel(response, fileName);
|
||||
});
|
||||
},
|
||||
// 文件上传中处理
|
||||
handleFileUploadProgress(file, fileList) {
|
||||
console.log('文件上传中:',file, fileList)
|
||||
this.upload.isUploading = true;
|
||||
this.upload.fileList = fileList;
|
||||
this.upload.currentFile = file.raw;
|
||||
},
|
||||
@@ -471,13 +515,17 @@ export default {
|
||||
importTemplate() {},
|
||||
// 提交上传文件
|
||||
async submitFileForm() {
|
||||
if (!this.upload.currentFile) {
|
||||
return this.$message.error('请先选择要上传的文件!')
|
||||
}
|
||||
if (this.upload.httpUploading) {
|
||||
return
|
||||
}
|
||||
this.upload.httpUploading = true
|
||||
try {
|
||||
if (!this.upload.currentFile) {
|
||||
return this.$message.error('请先选择要上传的文件!')
|
||||
}
|
||||
const formData = new FormData()
|
||||
formData.append('file', this.upload.currentFile) // 文件字段
|
||||
formData.append('timeDim', this.upload.timeDim) // 年月维度字段
|
||||
formData.append('timeDim', this.timeType === 'month'?2:3) // 年月维度字段
|
||||
formData.append('reportDate', this.form.endTime) // 时间维度字段
|
||||
formData.append('levelId', this.form.levelId) // 层级维度字段
|
||||
const response = await axios({
|
||||
@@ -489,17 +537,15 @@ export default {
|
||||
'Authorization': "Bearer " + getAccessToken(),
|
||||
'tenant-id': getTenantId(),
|
||||
},
|
||||
timeout: 30000
|
||||
timeout: 300000
|
||||
})
|
||||
// 4. 处理响应结果
|
||||
if (response.data.code === 0) {
|
||||
this.$message.success('文件上传成功!')
|
||||
// 重置表单
|
||||
this.upload.fileList = []
|
||||
this.upload.timeDim = 2
|
||||
this.upload.currentFile = null
|
||||
this.upload.open = false
|
||||
this.upload.isUploading = false
|
||||
this.$refs.upload.clearFiles();
|
||||
this.getDataPage();
|
||||
this.$emit('updateLeft')
|
||||
@@ -510,13 +556,66 @@ export default {
|
||||
// 5. 异常处理
|
||||
console.error('文件上传出错:', error)
|
||||
this.$message.error('上传失败!')
|
||||
} finally {
|
||||
this.upload.httpUploading = false
|
||||
}
|
||||
},
|
||||
cancelBtn() {
|
||||
if (this.upload.httpUploading) {
|
||||
return
|
||||
}
|
||||
this.upload.open = false
|
||||
},
|
||||
handleImportDialogClose() {
|
||||
this.upload.currentFile = null
|
||||
this.upload.fileList = []
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.upload) {
|
||||
this.$refs.upload.clearFiles()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.upload-progress-wrap {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.upload-progress-track {
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
background: rgba(11, 88, 255, 0.12);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.upload-progress-bar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 38%;
|
||||
border-radius: 4px;
|
||||
background: linear-gradient(90deg, #0b58ff, #5b9aff, #0b58ff);
|
||||
animation: upload-indeterminate-slide 1.35s ease-in-out infinite;
|
||||
}
|
||||
@keyframes upload-indeterminate-slide {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(320%);
|
||||
}
|
||||
}
|
||||
.upload-progress-text {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.55);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// 月份列表容器样式(保留原有配置,若无使用可忽略)
|
||||
.month-list {
|
||||
// 内联样式已优化行间距,此处可留空或补充
|
||||
|
||||
@@ -7,13 +7,17 @@
|
||||
|
||||
<!-- 右侧区域:全屏按钮 -->
|
||||
<div class="right-content">
|
||||
<el-dropdown trigger="click">
|
||||
<el-button type="text" class="logout-btn" :title="'退出'">
|
||||
<svg-icon style="color: #0B58FF;" icon-class="logout" />
|
||||
</el-button>
|
||||
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
|
||||
<div class="avatar-wrapper">
|
||||
<img :src="require(`../../../assets/images/choicepart/avatar.png`)" class="user-avatar">
|
||||
<span v-if="nickname" class="user-nickname">{{ nickname }}</span>
|
||||
<i class="el-icon-caret-bottom" />
|
||||
</div>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item @click.native='logout'>退出登录</el-dropdown-item>
|
||||
<el-dropdown-item @click.native='handleToggle'>切换账号</el-dropdown-item>
|
||||
<el-dropdown-item @click.native="logout">
|
||||
<span>退出登录</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item divided @click.native='handleToggle'>切换账号</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
<el-button type="text" class="return-btn" :title="'返回'" @click="handleReturn">
|
||||
@@ -43,6 +47,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import moment from 'moment'; // 引入moment
|
||||
import {getPath} from "@/utils/ruoyi";
|
||||
export default {
|
||||
@@ -57,6 +62,9 @@ export default {
|
||||
},
|
||||
isBudget: { type: Boolean, default: false },
|
||||
},
|
||||
computed:{
|
||||
...mapGetters(['nickname']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentTime: '',
|
||||
@@ -360,12 +368,35 @@ export default {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 28px;
|
||||
padding: 0;
|
||||
}
|
||||
.avatar-container {
|
||||
margin-right: 30px;
|
||||
|
||||
.avatar-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.user-avatar {
|
||||
cursor: pointer;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.user-nickname{
|
||||
margin-left: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.el-icon-caret-bottom {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: -20px;
|
||||
top: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 自定义下拉框样式(替换原有日期选择器样式) */
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="right">
|
||||
<div class="number" :style="{ color: getTargetColor(item.currentValue, item.targetValue) }">
|
||||
<div class="number" :style="{ color: getTargetColor(item.progress) }">
|
||||
{{ item.currentValue }}
|
||||
</div>
|
||||
<div class="title">实际值</div>
|
||||
@@ -21,15 +21,15 @@
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" :style="{
|
||||
width: item.progress + '%',
|
||||
background: getTargetColor(item.currentValue, item.targetValue)
|
||||
background: getTargetColor(item.progress)
|
||||
}"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="yield" style="display: flex;justify-content: space-between;">
|
||||
<div class="progress-percent" :style="{ color: getTargetColor(item.currentValue, item.targetValue) }">完成率
|
||||
<div class="progress-percent" :style="{ color: getTargetColor(item.progress) }">完成率
|
||||
</div>
|
||||
<div class="progress-percent" :style="{ color: getTargetColor(item.currentValue, item.targetValue) }">
|
||||
<div class="progress-percent" :style="{ color: getTargetColor(item.progress) }">
|
||||
{{ item.progress }}%
|
||||
</div>
|
||||
</div>
|
||||
@@ -95,8 +95,8 @@ export default {
|
||||
};
|
||||
});
|
||||
},
|
||||
getTargetColor(currentValue, targetValue) {
|
||||
return currentValue >= targetValue
|
||||
getTargetColor(progress) {
|
||||
return progress >= 100
|
||||
? "rgba(98, 213, 180, 1)"
|
||||
: "rgba(249, 164, 74, 1)";
|
||||
}
|
||||
|
||||
@@ -57,10 +57,10 @@ export default {
|
||||
activeButton: 0,
|
||||
// 定义按钮与 line 数据中 key 的映射关系
|
||||
buttonToDataKey: [
|
||||
'单价',
|
||||
'净价',
|
||||
'销量',
|
||||
'双镀销量' // 注意:数据中的 key 是“双镀面板”,按钮显示的是“双镀产品”
|
||||
{name:'单价',unit:'元/㎡'},
|
||||
{name:'净价',unit:'元/㎡'},
|
||||
{name:'销量',unit:'万㎡'},
|
||||
{name:'双镀销量',unit:'万㎡'}
|
||||
]
|
||||
};
|
||||
},
|
||||
@@ -69,7 +69,7 @@ export default {
|
||||
xAxisData() {
|
||||
const lineData = this.line || {};
|
||||
// 获取当前激活按钮对应的数据
|
||||
const currentDataKey = this.buttonToDataKey[this.activeButton];
|
||||
const currentDataKey = this.buttonToDataKey[this.activeButton].name;
|
||||
const currentIndicatorData = lineData[currentDataKey];
|
||||
|
||||
// 使用 'target' 的键作为 x 轴,如果 'target' 不存在,则使用 'real' 的键
|
||||
@@ -83,19 +83,20 @@ export default {
|
||||
// 根据激活按钮动态返回对应 series 数据
|
||||
currentSeries() {
|
||||
const lineData = this.line || {};
|
||||
const currentDataKey = this.buttonToDataKey[this.activeButton];
|
||||
const currentDataKey = this.buttonToDataKey[this.activeButton].name;
|
||||
const chartData = lineData[currentDataKey];
|
||||
|
||||
if (!chartData) {
|
||||
return [];
|
||||
return {};
|
||||
}
|
||||
|
||||
// 提取目标和实际数据的值,并确保顺序与 X 轴一致
|
||||
const xAxisKeys = this.xAxisData;
|
||||
const targetDataValues = xAxisKeys.map(date => chartData.target ? chartData.target[date] : 0);
|
||||
const realDataValues = xAxisKeys.map(date => chartData.real ? chartData.real[date] : 0);
|
||||
|
||||
return [
|
||||
let obj = {
|
||||
unit:this.buttonToDataKey[this.activeButton].unit,
|
||||
series:[
|
||||
{
|
||||
name: '预算',
|
||||
type: 'line',
|
||||
@@ -136,7 +137,8 @@ export default {
|
||||
},
|
||||
data: realDataValues
|
||||
}
|
||||
];
|
||||
]}
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<coreBottomLeftItem :dateData="dateData" :purchase="purchase">
|
||||
</coreBottomLeftItem>
|
||||
<div class="content-right" style="background: #F9FCFF;padding: 15px 12px;">
|
||||
<base-table style="height: 180px;width: 260px;" :page="1" :limit="10" :show-index="true" :beilv="1"
|
||||
:tableConfig="tableProps" :table-data="maintenanceTasks" />
|
||||
<base-table style="height: 221px;width: 260px;" :page="1" :limit="10" :show-index="true" :beilv="1"
|
||||
:tableConfig="tableProps" :table-data="maintenanceTasks" :maxHeight='220'/>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
@@ -43,20 +43,13 @@ export default {
|
||||
maintenanceTasks: [
|
||||
{ id: 1, name: '纯碱', number: '1313,252', },
|
||||
{ id: 2, name: '硅砂', number: '14,252', },
|
||||
{ id: 2, name: '白云石', number: '23,252', },
|
||||
{ id: 2, name: '石灰石', number: '34,421', },
|
||||
{ id: 2, name: '氧化铝', number: '1,251.34', },
|
||||
{ id: 2, name: '氢氧化铝', number: '14,252', },
|
||||
|
||||
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
|
||||
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
|
||||
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
|
||||
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
|
||||
// { id: 2, eqName: '螺杆挤出', taskName: '例行维护', },
|
||||
{ id: 3, name: '白云石', number: '23,252', },
|
||||
{ id: 4, name: '石灰石', number: '34,421', },
|
||||
{ id: 5, name: '氧化铝', number: '1,251.34', },
|
||||
{ id: 6, name: '氢氧化铝', number: '14,252', }
|
||||
|
||||
],
|
||||
tableProps: [
|
||||
// { prop: 'id', label: '序号', width: 50, align: 'center' },
|
||||
{ prop: 'name', label: '物料', align: 'left' },
|
||||
{ prop: 'number', label: '库存/吨', align: 'left' },
|
||||
]
|
||||
@@ -102,7 +95,7 @@ export default {
|
||||
return {
|
||||
id: index + 1, // id 从 1 开始自增
|
||||
name: name, // 物料名称
|
||||
number: String(value) // 将数值转换为字符串,以匹配 "number" 字段
|
||||
number: value ? String(value) : '-' // 将数值转换为字符串,以匹配 "number" 字段
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
</div>
|
||||
<div class="bottom"
|
||||
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
||||
<!-- <top-item /> -->
|
||||
<coreBottomBar :line="sale.line" :dateData="dateData" />
|
||||
|
||||
</div>
|
||||
@@ -19,7 +18,6 @@
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
// import * as echarts from 'echarts'
|
||||
import topItem from './top-item.vue'
|
||||
import coreBottomBar from './coreBottomBar.vue'
|
||||
|
||||
@@ -57,8 +55,6 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
this.saleData = this.sale.month
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -1,130 +1,131 @@
|
||||
<template>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="height: 219px; width: 100%;"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'Container',
|
||||
components: {},
|
||||
// 接收父组件传递的 series 数据
|
||||
props: {
|
||||
chartSeries: {
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
xAxisData: {
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
dateData: {
|
||||
type: Object,
|
||||
default: () => {} // 默认空数组,避免报错
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 存储图表实例,方便后续操作
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initData();
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
// 监听 series 数据变化,实时更新图表
|
||||
chartSeries: {
|
||||
handler() {
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true // 深度监听数组内元素变化
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
this.myChart = echarts.init(chartDom);
|
||||
this.updateChart(); // 初始化时渲染图表
|
||||
|
||||
// 监听窗口缩放
|
||||
window.addEventListener('resize', () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
|
||||
// 组件销毁时清理
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
this.myChart && this.myChart.dispose();
|
||||
});
|
||||
},
|
||||
// 单独提取更新图表的方法,方便复用
|
||||
updateChart() {
|
||||
if (!this.myChart) return;
|
||||
console.log('this.chartSeries', this.chartSeries,this.xAxisData);
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: { backgroundColor: '#6a7985' }
|
||||
}
|
||||
},
|
||||
grid: { top: 35, bottom: 3, right: 15, left: 10, containLabel: true},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
onZero: false,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
// width: 38,
|
||||
overflow: 'break',
|
||||
formatter: (value) => {
|
||||
const dateParts = value.split('-'); // ["2025", "07", "01"]
|
||||
if (dateParts.length < 2) return value;
|
||||
|
||||
// 去掉月份前面的0,然后加上"月"
|
||||
const month = dateParts[1].replace(/^0+/, '');
|
||||
return `${month}月`;
|
||||
}
|
||||
},
|
||||
data: this.xAxisData
|
||||
}
|
||||
],
|
||||
yAxis: {
|
||||
// type: 'value',
|
||||
name: '元/㎡',
|
||||
// nameLocation:'center',
|
||||
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' },
|
||||
// min: () => 0,
|
||||
// max: (value) => Math.ceil(value.max),
|
||||
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 12 },
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }
|
||||
},
|
||||
series: this.chartSeries // 使用父组件传递的 series 数据
|
||||
};
|
||||
|
||||
this.myChart.setOption(option, true); // 第二个参数 true 表示替换现有配置
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="height: 184px; width: 100%;"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'Container',
|
||||
components: {},
|
||||
// 接收父组件传递的 series 数据
|
||||
props: {
|
||||
chartSeries: {
|
||||
type: Object,
|
||||
default: () => {{}} // 默认空数组,避免报错
|
||||
},
|
||||
xAxisData: {
|
||||
type: Array,
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
dateData: {
|
||||
type: Object,
|
||||
default: () => {} // 默认空数组,避免报错
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
myChart: null, // 存储图表实例,方便后续操作
|
||||
resizeHandler: null // 窗口resize事件处理器
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initData();
|
||||
});
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize();
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
watch: {
|
||||
// 监听 series 数据变化,实时更新图表
|
||||
chartSeries: {
|
||||
handler() {
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true // 深度监听数组内元素变化
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
this.myChart = echarts.init(chartDom);
|
||||
this.updateChart(); // 初始化时渲染图表
|
||||
},
|
||||
// 单独提取更新图表的方法,方便复用
|
||||
updateChart() {
|
||||
if (!this.myChart) return;
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: { backgroundColor: '#6a7985' }
|
||||
}
|
||||
},
|
||||
grid: { top: 35, bottom: 3, right: 15, left: 18, containLabel: true},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
onZero: false,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
// width: 38,
|
||||
overflow: 'break',
|
||||
formatter: (value) => {
|
||||
const dateParts = value.split('-'); // ["2025", "07", "01"]
|
||||
if (dateParts.length < 2) return value;
|
||||
|
||||
// 去掉月份前面的0,然后加上"月"
|
||||
const month = dateParts[1].replace(/^0+/, '');
|
||||
return `${month}月`;
|
||||
}
|
||||
},
|
||||
data: this.xAxisData
|
||||
}
|
||||
],
|
||||
yAxis: {
|
||||
name: this.chartSeries.unit,
|
||||
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' },
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 12 },
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }
|
||||
},
|
||||
series: this.chartSeries.series // 使用父组件传递的 series 数据
|
||||
};
|
||||
|
||||
this.myChart.setOption(option, true); // 第二个参数 true 表示替换现有配置
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
this.resizeHandler = null;
|
||||
}
|
||||
// 销毁图表,避免内存泄漏
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<div class="right">
|
||||
<!-- 环比额:计算差值并动态绑定颜色 -->
|
||||
<div class="number" :style="{ color: getColor(item.currentValue, item.targetValue) }">
|
||||
{{ item.hbe }}
|
||||
{{ item.thbe }}
|
||||
</div>
|
||||
<div class="title">环比额</div>
|
||||
</div>
|
||||
@@ -101,12 +101,12 @@ export default {
|
||||
// 遍历映射关系,转换数据
|
||||
return costMapping.map(mappingItem => {
|
||||
// 获取对应费用类型的数据,若不存在则使用默认值
|
||||
const costData = rawData[mappingItem.key] || { last: 0, this: 0, hbe: 0 };
|
||||
const costData = rawData[mappingItem.key] || { last: 0, this: 0, thbe: 0 };
|
||||
|
||||
return {
|
||||
name: mappingItem.name,
|
||||
path: mappingItem.path,
|
||||
hbe: costData.hbe,
|
||||
thbe: costData.thbe,
|
||||
targetValue: costData.last, // 上月值
|
||||
currentValue: costData.this // 本月值
|
||||
};
|
||||
|
||||
@@ -1,404 +1,412 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<!-- 传入点击切换的状态到Container组件 -->
|
||||
<Container name="财务重点指标" nameTwo="费用重点指标" icon="cockpitItemIcon" size="topBasic" @switchTab="handleTabSwitch" @tabChange='tabChange'>
|
||||
<div class="bottom-left-content" style="display: flex;gap: 9px;padding: 14px 16px;flex-direction: column;">
|
||||
<!-- 根据activeTab状态,切换显示采购/存货内容 -->
|
||||
<template v-if="activeTab === 'purchase'">
|
||||
<!-- 采购重点指标对应的内容 -->
|
||||
<coreBottomLeftItem :dateData="dateData" :finance="financeData"></coreBottomLeftItem>
|
||||
<div class="bottom"
|
||||
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
||||
<coreBottomBar :dateData="dateData" :line="finance.line"></coreBottomBar>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="activeTab === 'inventory'">
|
||||
<!-- 存货重点指标对应的内容 -->
|
||||
<costItem :dateData="dateData" :cost="costData" :currentTap='currentTap'></costItem>
|
||||
<div class="bottom"
|
||||
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
||||
<CostsBottomBar :line="cost.line" :dateData="dateData">
|
||||
</CostsBottomBar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 图表容器:根据状态控制显示(若需展示图表) -->
|
||||
<!-- <div id="productionStatusChart" style="height: 200px; margin-top: 12px;" v-if="showChart"></div> -->
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Container from './financeCostsContainer.vue'
|
||||
import * as echarts from 'echarts'
|
||||
import coreBottomLeftItem from './purchase-Item.vue'
|
||||
import coreBottomBar from './financeCostsBottomBar.vue'
|
||||
import costItem from './cost-Item.vue'
|
||||
import CostsBottomBar from './CostsBottomBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, coreBottomLeftItem, coreBottomBar, costItem, CostsBottomBar },
|
||||
props: {
|
||||
finance: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
cost: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
dateData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'purchase', // 激活的标签:purchase=采购,inventory=存货
|
||||
showChart: true, // 控制图表是否显示
|
||||
chart: null, // 图表实例
|
||||
financeData:{},
|
||||
costData:{},
|
||||
currentTap: 'month'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 切换标签时更新图表
|
||||
activeTab(newVal) {
|
||||
this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
productionOverviewVo: {
|
||||
handler() {
|
||||
this.updateChart()
|
||||
},
|
||||
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() {
|
||||
// 初始化图表
|
||||
this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 销毁图表,避免内存泄漏
|
||||
if (this.chart) {
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
}
|
||||
},
|
||||
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) {
|
||||
this.activeTab = tabType // tabType由Container组件传递:'purchase'或'inventory'
|
||||
this.showChart = true // 切换时默认显示图表(可根据需求调整)
|
||||
},
|
||||
// 更新图表内容
|
||||
updateChart() {
|
||||
const chartDom = document.getElementById('productionStatusChart')
|
||||
if (!chartDom) return
|
||||
|
||||
// 销毁已有图表实例
|
||||
if (this.chart) this.chart.dispose()
|
||||
this.chart = echarts.init(chartDom)
|
||||
|
||||
// 根据当前激活的标签,设置对应图表数据(示例:可根据实际需求修改)
|
||||
const data = this.activeTab === 'purchase'
|
||||
? [
|
||||
this.productionOverviewVo.purchaseInput || 0,
|
||||
this.productionOverviewVo.purchaseOutput || 0,
|
||||
this.productionOverviewVo.purchaseNg || 0,
|
||||
0, 0, 0, 0 // 补充默认值,避免图表报错
|
||||
]
|
||||
: [
|
||||
this.productionOverviewVo.inventoryInput || 0,
|
||||
this.productionOverviewVo.inventoryOutput || 0,
|
||||
this.productionOverviewVo.inventoryNg || 0,
|
||||
0, 0, 0, 0
|
||||
]
|
||||
|
||||
const option = {
|
||||
type: 'bar',
|
||||
grid: { left: 51, right: 40, top: 50, bottom: 45 },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
className: 'production-status-chart-tooltip'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
offset: 8,
|
||||
data: this.activeTab === 'purchase'
|
||||
? ['采购投入', '采购产出', '采购待判', '低价值', '报废', '在制', '实验片']
|
||||
: ['存货投入', '存货产出', '存货待判', '低价值', '报废', '在制', '实验片'],
|
||||
axisTick: { show: false },
|
||||
axisLine: { show: true, onZero: false, lineStyle: { color: '#00E8FF' } },
|
||||
axisLabel: {
|
||||
color: 'rgba(255,255,255,0.7)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
width: 38,
|
||||
overflow: 'break'
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '单位/片',
|
||||
nameTextStyle: { color: 'rgba(255,255,255,0.7)', fontSize: 14, align: 'left' },
|
||||
min: 0,
|
||||
max: (value) => Math.ceil(value.max),
|
||||
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: 'rgba(255,255,255,0.7)', fontSize: 12 },
|
||||
splitLine: { lineStyle: { color: 'RGBA(24, 88, 100, 0.6)', type: 'dashed' } },
|
||||
axisLine: { show: true, lineStyle: { color: '#00E8FF' } }
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pictorialBar',
|
||||
label: { show: true, position: 'top', distance: -3, color: '#89CDFF', fontSize: 11 },
|
||||
symbolSize: [20, 8],
|
||||
symbolOffset: [0, 5],
|
||||
z: 20,
|
||||
itemStyle: {
|
||||
borderColor: '#3588C7',
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'RGBA(22, 89, 98, 1)' },
|
||||
{ offset: 1, color: '#3588C7' }
|
||||
])
|
||||
},
|
||||
data: data
|
||||
},
|
||||
{
|
||||
type: 'bar',
|
||||
barWidth: 20,
|
||||
itemStyle: {
|
||||
borderWidth: 1,
|
||||
borderColor: '#3588C7',
|
||||
opacity: 0.8,
|
||||
color: {
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
type: 'linear',
|
||||
global: false,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(73,178,255,0)' },
|
||||
{ offset: 0.5, color: 'rgba(0, 232, 255, .5)' },
|
||||
{ offset: 1, color: 'rgba(0, 232, 255, 1)' }
|
||||
]
|
||||
}
|
||||
},
|
||||
tooltip: { show: false },
|
||||
data: data
|
||||
},
|
||||
{
|
||||
type: 'pictorialBar',
|
||||
symbolSize: [20, 8],
|
||||
symbolOffset: [0, -4],
|
||||
z: 12,
|
||||
symbolPosition: 'end',
|
||||
itemStyle: {
|
||||
borderColor: 'rgba(0, 232, 255, 1)',
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'RGBA(22, 89, 98, 1)' },
|
||||
{ offset: 1, color: '#3588C7' }
|
||||
])
|
||||
},
|
||||
tooltip: { show: false },
|
||||
data: data
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
this.chart.setOption(option)
|
||||
// 监听窗口 resize,自适应图表
|
||||
window.addEventListener('resize', this.chart.resize)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* 原有样式保留,新增内容容器样式 */
|
||||
.bottom-left-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 其他原有样式... */
|
||||
.scroll-container {
|
||||
max-height: 210px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 10px 0;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.proBarInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 27px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.slot {
|
||||
width: 21px;
|
||||
height: 23px;
|
||||
background: rgba(0, 106, 205, 0.22);
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #68B5FF;
|
||||
line-height: 23px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eq-name {
|
||||
margin-left: 8px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eqStatus {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.splitLine {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid #ADADAD;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.yield {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #00FFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.proBarWrapper {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
margin-top: 6px;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.proBarLine {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #ACACAC 100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.proBarLineTop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(53, 223, 247, 0) 0%, rgba(54, 220, 246, 0.92) 92%, #36F6E5 100%, #37ACF5 100%);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 57px;
|
||||
bottom: 42px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #00E8FF;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<!-- 传入点击切换的状态到Container组件 -->
|
||||
<Container name="财务重点指标" nameTwo="费用重点指标" icon="cockpitItemIcon" size="topBasic" @switchTab="handleTabSwitch" @tabChange='tabChange'>
|
||||
<div class="bottom-left-content" style="display: flex;gap: 0px;padding: 14px 16px;flex-direction: column;">
|
||||
<!-- 根据activeTab状态,切换显示采购/存货内容 -->
|
||||
<template v-if="activeTab === 'purchase'">
|
||||
<!-- 财务重点指标应的内容 -->
|
||||
<coreBottomLeftItem :dateData="dateData" :finance="financeData"></coreBottomLeftItem>
|
||||
<div class="bottom"
|
||||
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
||||
<coreBottomBar :dateData="dateData" :line="finance.line"></coreBottomBar>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="activeTab === 'inventory'">
|
||||
<!-- 费用重点指标对应的内容 -->
|
||||
<costItem :dateData="dateData" :cost="costData" :currentTap='currentTap'></costItem>
|
||||
<div class="bottom"
|
||||
style="display: flex; width: 100%;margin-top: 4px;background-color: rgba(249, 252, 255, 1);">
|
||||
<CostsBottomBar :line="cost.line" :dateData="dateData">
|
||||
</CostsBottomBar>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Container from './financeCostsContainer.vue'
|
||||
import * as echarts from 'echarts'
|
||||
import coreBottomLeftItem from './purchase-Item.vue'
|
||||
import coreBottomBar from './financeCostsBottomBar.vue'
|
||||
import costItem from './cost-Item.vue'
|
||||
import CostsBottomBar from './CostsBottomBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, coreBottomLeftItem, coreBottomBar, costItem, CostsBottomBar },
|
||||
props: {
|
||||
finance: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
cost: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
dateData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'purchase', // 激活的标签:purchase=采购,inventory=存货
|
||||
showChart: true, // 控制图表是否显示
|
||||
chart: null, // 图表实例
|
||||
financeData:{},
|
||||
costData:{},
|
||||
currentTap: 'month',
|
||||
resizeHandler: null // 窗口resize事件处理器
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 切换标签时更新图表
|
||||
activeTab(newVal) {
|
||||
this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
productionOverviewVo: {
|
||||
handler() {
|
||||
this.updateChart()
|
||||
},
|
||||
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() {
|
||||
// 初始化图表
|
||||
this.$nextTick(() => this.updateChart())
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.chart) {
|
||||
this.chart.resize()
|
||||
}
|
||||
}
|
||||
window.addEventListener('resize', this.resizeHandler)
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler)
|
||||
this.resizeHandler = null
|
||||
}
|
||||
// 销毁图表,避免内存泄漏
|
||||
if (this.chart) {
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
}
|
||||
},
|
||||
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) {
|
||||
this.activeTab = tabType // tabType由Container组件传递:'purchase'或'inventory'
|
||||
this.showChart = true // 切换时默认显示图表(可根据需求调整)
|
||||
},
|
||||
// 更新图表内容
|
||||
updateChart() {
|
||||
const chartDom = document.getElementById('productionStatusChart')
|
||||
if (!chartDom) return
|
||||
|
||||
// 销毁已有图表实例
|
||||
if (this.chart) this.chart.dispose()
|
||||
this.chart = echarts.init(chartDom)
|
||||
|
||||
// 根据当前激活的标签,设置对应图表数据(示例:可根据实际需求修改)
|
||||
const data = this.activeTab === 'purchase'
|
||||
? [
|
||||
this.productionOverviewVo.purchaseInput || 0,
|
||||
this.productionOverviewVo.purchaseOutput || 0,
|
||||
this.productionOverviewVo.purchaseNg || 0,
|
||||
0, 0, 0, 0 // 补充默认值,避免图表报错
|
||||
]
|
||||
: [
|
||||
this.productionOverviewVo.inventoryInput || 0,
|
||||
this.productionOverviewVo.inventoryOutput || 0,
|
||||
this.productionOverviewVo.inventoryNg || 0,
|
||||
0, 0, 0, 0
|
||||
]
|
||||
|
||||
const option = {
|
||||
type: 'bar',
|
||||
grid: { left: 51, right: 40, top: 50, bottom: 45 },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
className: 'production-status-chart-tooltip'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
offset: 8,
|
||||
data: this.activeTab === 'purchase'
|
||||
? ['采购投入', '采购产出', '采购待判', '低价值', '报废', '在制', '实验片']
|
||||
: ['存货投入', '存货产出', '存货待判', '低价值', '报废', '在制', '实验片'],
|
||||
axisTick: { show: false },
|
||||
axisLine: { show: true, onZero: false, lineStyle: { color: '#00E8FF' } },
|
||||
axisLabel: {
|
||||
color: 'rgba(255,255,255,0.7)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
width: 38,
|
||||
overflow: 'break'
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '单位/片',
|
||||
nameTextStyle: { color: 'rgba(255,255,255,0.7)', fontSize: 14, align: 'left' },
|
||||
min: 0,
|
||||
max: (value) => Math.ceil(value.max),
|
||||
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: 'rgba(255,255,255,0.7)', fontSize: 12 },
|
||||
splitLine: { lineStyle: { color: 'RGBA(24, 88, 100, 0.6)', type: 'dashed' } },
|
||||
axisLine: { show: true, lineStyle: { color: '#00E8FF' } }
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pictorialBar',
|
||||
label: { show: true, position: 'top', distance: -3, color: '#89CDFF', fontSize: 11 },
|
||||
symbolSize: [20, 8],
|
||||
symbolOffset: [0, 5],
|
||||
z: 20,
|
||||
itemStyle: {
|
||||
borderColor: '#3588C7',
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'RGBA(22, 89, 98, 1)' },
|
||||
{ offset: 1, color: '#3588C7' }
|
||||
])
|
||||
},
|
||||
data: data
|
||||
},
|
||||
{
|
||||
type: 'bar',
|
||||
barWidth: 20,
|
||||
itemStyle: {
|
||||
borderWidth: 1,
|
||||
borderColor: '#3588C7',
|
||||
opacity: 0.8,
|
||||
color: {
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
type: 'linear',
|
||||
global: false,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(73,178,255,0)' },
|
||||
{ offset: 0.5, color: 'rgba(0, 232, 255, .5)' },
|
||||
{ offset: 1, color: 'rgba(0, 232, 255, 1)' }
|
||||
]
|
||||
}
|
||||
},
|
||||
tooltip: { show: false },
|
||||
data: data
|
||||
},
|
||||
{
|
||||
type: 'pictorialBar',
|
||||
symbolSize: [20, 8],
|
||||
symbolOffset: [0, -4],
|
||||
z: 12,
|
||||
symbolPosition: 'end',
|
||||
itemStyle: {
|
||||
borderColor: 'rgba(0, 232, 255, 1)',
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'RGBA(22, 89, 98, 1)' },
|
||||
{ offset: 1, color: '#3588C7' }
|
||||
])
|
||||
},
|
||||
tooltip: { show: false },
|
||||
data: data
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
this.chart.setOption(option)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* 原有样式保留,新增内容容器样式 */
|
||||
.bottom-left-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 其他原有样式... */
|
||||
.scroll-container {
|
||||
max-height: 210px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 10px 0;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.proBarInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 27px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.slot {
|
||||
width: 21px;
|
||||
height: 23px;
|
||||
background: rgba(0, 106, 205, 0.22);
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #68B5FF;
|
||||
line-height: 23px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eq-name {
|
||||
margin-left: 8px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.eqStatus {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.splitLine {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid #ADADAD;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.yield {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #00FFFF;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.proBarInfoEqInfoRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.proBarWrapper {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
margin-top: 6px;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.proBarLine {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #ACACAC 100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.proBarLineTop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(65deg, rgba(53, 223, 247, 0) 0%, rgba(54, 220, 246, 0.92) 92%, #36F6E5 100%, #37ACF5 100%);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 57px;
|
||||
bottom: 42px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #00E8FF;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -27,11 +27,21 @@
|
||||
<div class="item-button" :class="{ active: activeButton === 3 }" @click="activeButton = 3">
|
||||
毛利率
|
||||
</div>
|
||||
<div class="button-line lineFour" v-if="activeButton !== 3 && activeButton !== 4"></div>
|
||||
|
||||
<div class="item-button" :class="{ active: activeButton === 4 }" @click="activeButton = 4">
|
||||
应收账款
|
||||
</div>
|
||||
<div class="button-line lineFive" v-if="activeButton !== 4 && activeButton !== 5"></div>
|
||||
|
||||
<div class="item-button" :class="{ active: activeButton === 5 }" @click="activeButton = 5">
|
||||
存货
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lineBottom" style="height: 210px; width: 100%">
|
||||
<div class="lineBottom" style="height: 184px; width: 100%">
|
||||
<!-- 传递当前选中的 series 数据和 xAxis 数据给子组件 -->
|
||||
<coreLineChart style="height: 210px; width: 680px" :chart-series="currentSeries" :x-axis-data="xAxisData"
|
||||
<coreLineChart style="height: 184px; width: 680px" :chart-series="currentSeries" :x-axis-data="xAxisData"
|
||||
:dateData="dateData" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,10 +69,12 @@ export default {
|
||||
activeButton: 0, // 初始激活第一个按钮(索引0)
|
||||
// 定义按钮与 line 数据中 key 的映射关系
|
||||
buttonToDataKey: [
|
||||
'营业收入',
|
||||
'经营性利润', // 注意:数据中的 key 是“经营收入”,按钮显示的是“经营性利润”
|
||||
'利润总额',
|
||||
'毛利率'
|
||||
{name:'营业收入',unit:'万元'},
|
||||
{name:'经营性利润',unit:'万元'},
|
||||
{name:'利润总额',unit:'万元'},
|
||||
{name:'毛利率',unit:'%'},
|
||||
{name:'应收账款',unit:'万元'},
|
||||
{name:'存货',unit:'万元'}
|
||||
]
|
||||
};
|
||||
},
|
||||
@@ -70,20 +82,19 @@ export default {
|
||||
// 根据当前激活的按钮,动态生成对应的 series 数据
|
||||
currentSeries() {
|
||||
|
||||
const dataKey = this.buttonToDataKey[this.activeButton];
|
||||
const dataKey = this.buttonToDataKey[this.activeButton].name;
|
||||
const chartData = this.line[dataKey];
|
||||
console.log('this.line[dataKey', this.buttonToDataKey[this.activeButton]);
|
||||
|
||||
if (!chartData) {
|
||||
return [];
|
||||
return {};
|
||||
}
|
||||
|
||||
// 提取目标和实际数据的值
|
||||
const targetDataValues = Object.values(chartData.target || {});
|
||||
const realDataValues = Object.values(chartData.real || {});
|
||||
console.log('realDataValues', realDataValues);
|
||||
|
||||
return [
|
||||
let obj = {
|
||||
unit: this.buttonToDataKey[this.activeButton].unit,
|
||||
series:[
|
||||
{
|
||||
name: '预算',
|
||||
type: 'line',
|
||||
@@ -116,11 +127,12 @@ export default {
|
||||
},
|
||||
data: realDataValues
|
||||
}
|
||||
];
|
||||
]}
|
||||
return obj;
|
||||
},
|
||||
// 提取 x 轴数据(日期)
|
||||
xAxisData() {
|
||||
const dataKey = this.buttonToDataKey[this.activeButton];
|
||||
const dataKey = this.buttonToDataKey[this.activeButton].name;
|
||||
const chartData = this.line[dataKey];
|
||||
// 使用 'target' 的键作为 x 轴,如果 'target' 不存在,则使用 'real' 的键
|
||||
if (chartData && chartData.target) {
|
||||
@@ -205,7 +217,7 @@ export default {
|
||||
display: flex;
|
||||
position: relative;
|
||||
gap: 2px;
|
||||
width: 252px;
|
||||
width: 350px;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
background: #ecf4fe;
|
||||
@@ -219,18 +231,27 @@ export default {
|
||||
}
|
||||
|
||||
.lineOne {
|
||||
top: 5px;
|
||||
top: 6px;
|
||||
left: 59px;
|
||||
}
|
||||
|
||||
.lineTwo {
|
||||
top: 5px;
|
||||
left: 134px;
|
||||
top: 6px;
|
||||
left: 131px;
|
||||
}
|
||||
|
||||
.lineThree {
|
||||
top: 5px;
|
||||
left: 193px;
|
||||
top: 6px;
|
||||
left: 190px;
|
||||
}
|
||||
.lineFour {
|
||||
top: 6px;
|
||||
left: 238px;
|
||||
}
|
||||
|
||||
.lineFive {
|
||||
top: 6px;
|
||||
left: 302px;
|
||||
}
|
||||
|
||||
.item-button {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<!-- 显示累计值,并绑定颜色类 -->
|
||||
<div class="accumulated-value" :class="injectData.status">
|
||||
{{ injectData.accumulated }} <!-- 显示累计数据 -->
|
||||
{{ injectData.total }} <!-- 显示累计数据 -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 214px;"></div>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 216px;"></div>
|
||||
</template>
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
@@ -8,23 +8,27 @@ export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 存储图表实例,避免重复创建
|
||||
myChart: null, // 存储图表实例,避免重复创建
|
||||
resizeHandler: null // 窗口resize事件处理器
|
||||
};
|
||||
},
|
||||
props: {
|
||||
lineData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
xData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize();
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
|
||||
// 新增:监听 chartData 变化
|
||||
@@ -32,7 +36,6 @@ export default {
|
||||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||
lineData: {
|
||||
handler() {
|
||||
console.log(this.lineData,'lineData');
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
@@ -52,8 +55,12 @@ export default {
|
||||
}
|
||||
|
||||
this.myChart = echarts.init(chartDom);
|
||||
const entries = Object.entries(this.lineData);
|
||||
entries.sort((item1, item2) => item2[1] - item1[1]);
|
||||
const sortedObj = Object.fromEntries(entries);
|
||||
let xData = Object.keys(sortedObj);
|
||||
let yData = Object.values(sortedObj);
|
||||
|
||||
console.log('linedaata',this.lineData)
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
@@ -86,7 +93,7 @@ export default {
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0]
|
||||
},
|
||||
data:this.xData
|
||||
data:xData
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
@@ -112,23 +119,47 @@ export default {
|
||||
splitNumber: 4
|
||||
},
|
||||
],
|
||||
series: []
|
||||
series: [
|
||||
{
|
||||
name: '实际',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 40,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top'
|
||||
},
|
||||
itemStyle: {
|
||||
color:{
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: yData
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
option && this.myChart.setOption(option);
|
||||
|
||||
// 窗口缩放适配和销毁逻辑保持不变
|
||||
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();
|
||||
});
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
this.resizeHandler = null;
|
||||
}
|
||||
// 销毁图表实例
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="指标填报日历" icon="cockpitItemIcon" size="calendarBg" topSize="calendarTitleBg">
|
||||
<Container name="填报总览" icon="cockpitItemIcon" size="calendarBg" topSize="calendarTitleBg">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<div class="kpi-content" style="padding: 14px 14px; display: flex;flex-direction: column; width: 100%;">
|
||||
<!-- 2. .top 保持 flex,无需固定高度,自动跟随子元素拉伸 -->
|
||||
<div class="bottom"
|
||||
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);height: 844px;padding: 26px 16px;">
|
||||
style="width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);height: 844px;padding: 26px 0px;">
|
||||
<!-- 动态生成12个月的容器:优化flex布局,缩小行间距 -->
|
||||
<div class="month-list"
|
||||
style="display: flex; gap: 16px; flex-wrap: wrap; align-content: flex-start; row-gap: 8px;">
|
||||
<div class="month-list">
|
||||
<!-- 循环生成12个月:通过判断当前月份索引,添加current类 -->
|
||||
<div class="monthItem" :class="{
|
||||
'has-data': month.haveData,
|
||||
@@ -91,20 +90,20 @@ export default {
|
||||
|
||||
// 基础月份样式
|
||||
.monthItem {
|
||||
width: 164px;
|
||||
height: 42px;
|
||||
width: 100px;
|
||||
height: 57px;
|
||||
border-radius: 4px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 20px;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
line-height: 42px;
|
||||
line-height: 57px;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
cursor: pointer; // 鼠标悬浮手型
|
||||
transition: all 0.2s ease; // 过渡效果,样式切换更平滑
|
||||
border: 2px solid transparent; // 透明边框,避免选中时布局偏移
|
||||
margin: 0; // 清除默认外边距,进一步缩小缝隙
|
||||
margin: 0 auto 10px; // 清除默认外边距,进一步缩小缝隙
|
||||
}
|
||||
|
||||
// 有数据的样式(背景色#D1E8FF)
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="填报月份">
|
||||
<el-date-picker v-model="form.month" type="month" placeholder="选择月" @change="handleMonthChange">
|
||||
<el-date-picker v-model="form.month" type="month" placeholder="选择月" :editable='false' :clearable='false' @change="handleMonthChange">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button style="background-color: #0B58FF;" type="primary" @click="onSubmit">查询</el-button>
|
||||
<el-button type="primary" plain size="medium" @click='downLoadExcel'>模板下载</el-button>
|
||||
<el-button type="primary" plain size="medium" @click='importExcel'>导入</el-button>
|
||||
<el-button type="primary" plain size="medium" @click='exportExcel'>导出</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@@ -64,8 +66,8 @@ font-style: normal;">指标详情</div>
|
||||
></base-table>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
|
||||
<el-upload ref="upload" :limit="1" accept=".xlsx, .xls" action="#" :disabled="upload.isUploading"
|
||||
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body @close="handleImportDialogClose">
|
||||
<el-upload ref="upload" :limit="1" accept=".xlsx, .xls" action="#" :disabled="upload.httpUploading"
|
||||
:on-change="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
@@ -75,9 +77,15 @@ font-style: normal;">指标详情</div>
|
||||
<div class="el-upload__tip" slot="tip">
|
||||
</div>
|
||||
</el-upload>
|
||||
<div v-if="upload.httpUploading" class="upload-progress-wrap">
|
||||
<div class="upload-progress-track">
|
||||
<div class="upload-progress-bar"></div>
|
||||
</div>
|
||||
<span class="upload-progress-text">正在上传</span>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
||||
<el-button @click="upload.open = false">取 消</el-button>
|
||||
<el-button type="primary" :loading="upload.httpUploading" :disabled="upload.httpUploading" @click="submitFileForm">确 定</el-button>
|
||||
<el-button :disabled="upload.httpUploading" @click="cancelBtn">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</Container>
|
||||
@@ -86,10 +94,11 @@ font-style: normal;">指标详情</div>
|
||||
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
import { getLevelStruc, getRealMonthPage, updateRealMonthData, getDictListData, } from '@/api/cockpit'
|
||||
import { getLevelStruc, getRealMonthPage, updateRealMonthData, getDictListData,importTemplateZB } from '@/api/cockpit'
|
||||
import inputArea from './inputArea.vue' // 导入输入组件
|
||||
import {getAccessToken, getTenantId} from '@/utils/auth'
|
||||
import axios from 'axios';
|
||||
import moment from 'moment'
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: {
|
||||
@@ -119,10 +128,10 @@ export default {
|
||||
open: false,
|
||||
// 弹出层标题
|
||||
title: "指标填报导入",
|
||||
// 是否禁用上传
|
||||
isUploading: false,
|
||||
fileList:[],
|
||||
currentFile:null
|
||||
currentFile:null,
|
||||
// HTTP 上传中(点击确定后 axios 上传,展示不确定进度条)
|
||||
httpUploading: false
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -169,7 +178,7 @@ export default {
|
||||
|
||||
// 基础表格列配置(只读模式使用)
|
||||
const baseTableProps = [
|
||||
// { prop: 'type', label: '指标类型', align: 'center' },
|
||||
{ prop: 'type', label: '指标类型', align: 'center' },
|
||||
{ prop: 'name', label: '指标名称', align: 'center' },
|
||||
{ prop: 'unitLabel', label: '单位', align: 'center' },
|
||||
{ prop: 'value', label: '实际值', align: 'center' },
|
||||
@@ -274,6 +283,8 @@ export default {
|
||||
}
|
||||
// 计算选中月份的起止时间戳
|
||||
this.setMonthTimeStamp(val);
|
||||
// 重新请求数据
|
||||
this.getDataPage();
|
||||
},
|
||||
getUnitLabel(unitCode) {
|
||||
// 若字典为空或无匹配编码,返回原编码或空字符串
|
||||
@@ -323,6 +334,8 @@ export default {
|
||||
|
||||
},
|
||||
handleLevelChange(id) {
|
||||
this.form.levelId = id;
|
||||
this.getDataPage()
|
||||
this.$emit('updateLevel', id)
|
||||
},
|
||||
|
||||
@@ -336,12 +349,41 @@ export default {
|
||||
},
|
||||
// 导入
|
||||
importExcel() {
|
||||
this.upload.httpUploading = false
|
||||
this.upload.open = true
|
||||
},
|
||||
downLoadExcel() {
|
||||
importTemplateZB({
|
||||
levelId:this.form.levelId,
|
||||
startTime: this.form.startTime,
|
||||
endTime: this.form.endTime,
|
||||
pageSize: 1000,
|
||||
template:1,
|
||||
pageNo: this.form.pageNo
|
||||
}).then(response => {
|
||||
let factoryName = this.levelLList.filter(item => item.id == this.form.levelId)[0].name;
|
||||
let fileName = factoryName+'_指标导入模板.xls';
|
||||
this.$download.excel(response, fileName);
|
||||
});
|
||||
},
|
||||
exportExcel() {
|
||||
importTemplateZB({
|
||||
levelId:this.form.levelId,
|
||||
startTime: this.form.startTime,
|
||||
endTime: this.form.endTime,
|
||||
pageSize: 1000,
|
||||
template:0,
|
||||
pageNo: this.form.pageNo
|
||||
}).then(response => {
|
||||
let mon = moment(this.form.endTime).format('YYYYMM');
|
||||
let factoryName = this.levelLList.filter(item => item.id == this.form.levelId)[0].name;
|
||||
let fileName = mon + factoryName + '月度指标填报表.xls';
|
||||
this.$download.excel(response, fileName);
|
||||
});
|
||||
},
|
||||
// 文件上传中处理
|
||||
handleFileUploadProgress(file, fileList) {
|
||||
console.log('文件上传中:',file, fileList)
|
||||
this.upload.isUploading = true;
|
||||
this.upload.fileList = fileList;
|
||||
this.upload.currentFile = file.raw;
|
||||
},
|
||||
@@ -349,10 +391,14 @@ export default {
|
||||
importTemplate() {},
|
||||
// 提交上传文件
|
||||
async submitFileForm() {
|
||||
if (!this.upload.currentFile) {
|
||||
return this.$message.error('请先选择要上传的文件!')
|
||||
}
|
||||
if (this.upload.httpUploading) {
|
||||
return
|
||||
}
|
||||
this.upload.httpUploading = true
|
||||
try {
|
||||
if (!this.upload.currentFile) {
|
||||
return this.$message.error('请先选择要上传的文件!')
|
||||
}
|
||||
const formData = new FormData()
|
||||
formData.append('file', this.upload.currentFile) // 文件字段
|
||||
formData.append('reportDate', this.form.endTime) // 时间维度字段
|
||||
@@ -366,7 +412,7 @@ export default {
|
||||
'Authorization': "Bearer " + getAccessToken(),
|
||||
'tenant-id': getTenantId(),
|
||||
},
|
||||
timeout: 30000
|
||||
timeout: 300000
|
||||
})
|
||||
// 4. 处理响应结果
|
||||
if (response.data.code === 0) {
|
||||
@@ -375,7 +421,6 @@ export default {
|
||||
this.upload.fileList = []
|
||||
this.upload.currentFile = null
|
||||
this.upload.open = false
|
||||
this.upload.isUploading = false
|
||||
this.$refs.upload.clearFiles();
|
||||
this.getDataPage();
|
||||
this.$emit('updateLeft')
|
||||
@@ -386,13 +431,66 @@ export default {
|
||||
// 5. 异常处理
|
||||
console.error('文件上传出错:', error)
|
||||
this.$message.error('上传失败!')
|
||||
} finally {
|
||||
this.upload.httpUploading = false
|
||||
}
|
||||
},
|
||||
cancelBtn() {
|
||||
if (this.upload.httpUploading) {
|
||||
return
|
||||
}
|
||||
this.upload.open = false
|
||||
},
|
||||
handleImportDialogClose() {
|
||||
this.upload.currentFile = null
|
||||
this.upload.fileList = []
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.upload) {
|
||||
this.$refs.upload.clearFiles()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.upload-progress-wrap {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.upload-progress-track {
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
background: rgba(11, 88, 255, 0.12);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.upload-progress-bar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 38%;
|
||||
border-radius: 4px;
|
||||
background: linear-gradient(90deg, #0b58ff, #5b9aff, #0b58ff);
|
||||
animation: upload-indeterminate-slide 1.35s ease-in-out infinite;
|
||||
}
|
||||
@keyframes upload-indeterminate-slide {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(320%);
|
||||
}
|
||||
}
|
||||
.upload-progress-text {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.55);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// 月份列表容器样式(保留原有配置,若无使用可忽略)
|
||||
.month-list {
|
||||
// 内联样式已优化行间距,此处可留空或补充
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
<div class="bottom" style="background-color: rgba(249, 252, 255, 1);">
|
||||
<div class="bottom" style="margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
||||
<div style='text-align: center;margin: 5px;font-size: 18px;font-weight: 400;color: #000;'>1200t/d</div>
|
||||
<heatBar :lineData="heatData['1200t']" :xData="['桐城','洛阳','江苏','秦皇岛']" :dateData="dateData" />
|
||||
<heatBar :lineData="heatData['1200t']"/>
|
||||
</div>
|
||||
<div style='text-align: center;margin: 5px;font-size: 18px;font-weight: 400;color: #000;'>650t/d</div>
|
||||
<heatBar :lineData="heatData['650t']" :xData="['宜兴','自贡','漳州']" :dateData="dateData" />
|
||||
<heatBar :lineData="heatData['650t']"/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -51,10 +51,6 @@ export default {
|
||||
type: Object,
|
||||
default: () => { } // 默认空数组,避免报错
|
||||
},
|
||||
heat: {
|
||||
type: Object,
|
||||
default: () => { }
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<span class="legend-text">未完成</span>
|
||||
</div>
|
||||
</div>
|
||||
<base-table style="height: 180px;" :page="1" :limit="10" :show-index="true" :beilv="1"
|
||||
<base-table style="height: 204px;" :page="1" :limit="10000" :show-index="true" :beilv="1"
|
||||
:tableConfig="tableProps" :table-data="tableData" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,18 +33,18 @@ export default {
|
||||
components: { Container, baseTable },
|
||||
props: {
|
||||
importantWork: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
tableProps: [
|
||||
{ prop: 'name', label: '攻坚指标', align: 'center' },
|
||||
{ prop: 'index', label: '攻坚指标', align: 'center',width:150 },
|
||||
{ prop: 'target', label: '攻坚预算', align: 'center' },
|
||||
{ prop: 'monthlyActual', label: '当月实际', align: 'center' },
|
||||
{ prop: 'accumulated', label: '累计', align: 'center', subcomponent: finishDiv },
|
||||
{ prop: 'real', label: '当月实际', align: 'center' },
|
||||
{ prop: 'total', label: '累计', align: 'center', subcomponent: finishDiv },
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -59,33 +59,13 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
transformData(rawData) {
|
||||
if (!rawData || typeof rawData !== 'object') return [];
|
||||
|
||||
const mapping = [
|
||||
{ key: 'jyxxjl', name: '经营现金流', unit: '万元' },
|
||||
{ key: 'yszk', name: '应收账款', unit: '万元' },
|
||||
{ key: 'ch', name: '存货', unit: '万元' },
|
||||
{ key: 'yysr', name: '营业收入', unit: '万元' },
|
||||
{ key: 'snysysk', name: '三年以上应收款', unit: '万元' },
|
||||
{ key: 'dzje', name: '非经营性资产处置到账金额', unit: '万元' },
|
||||
{ key: 'yfjftr', name: '研发经费投入', unit: '万元' },
|
||||
{ key: 'yfjftrqd', name: '研发经费投入强度', unit: '%' }
|
||||
];
|
||||
|
||||
return mapping.map((item, index) => {
|
||||
const data = rawData[item.key] || { monValue: 0, real: 0, target: 0 };
|
||||
const accumulated = data.real || 0;
|
||||
const target = data.target || 0;
|
||||
|
||||
return rawData.map((item, index) =>{
|
||||
return {
|
||||
...item,
|
||||
id: index + 1,
|
||||
name: item.name + '/' + item.unit,
|
||||
target: target,
|
||||
monthlyActual: data.monValue,
|
||||
accumulated: accumulated,
|
||||
status: accumulated > 0 && target > 0 && accumulated / target >= 1 ? 'done' : 'pending'
|
||||
status: (item.rate >= 100) ? 'done' : 'pending'
|
||||
};
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,17 @@
|
||||
|
||||
<!-- 右侧区域:全屏按钮 -->
|
||||
<div class="right-content">
|
||||
<el-dropdown trigger="click">
|
||||
<el-button type="text" class="logout-btn" :title="'退出'">
|
||||
<svg-icon style="color: #0B58FF;" icon-class="logout" />
|
||||
</el-button>
|
||||
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
|
||||
<div class="avatar-wrapper">
|
||||
<img :src="require(`../../../assets/images/choicepart/avatar.png`)" class="user-avatar">
|
||||
<span v-if="nickname" class="user-nickname">{{ nickname }}</span>
|
||||
<i class="el-icon-caret-bottom" />
|
||||
</div>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item @click.native='logout'>退出登录</el-dropdown-item>
|
||||
<el-dropdown-item @click.native='handleToggle'>切换账号</el-dropdown-item>
|
||||
<el-dropdown-item @click.native="logout">
|
||||
<span>退出登录</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item divided @click.native='handleToggle'>切换账号</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
<el-button type="text" class="return-btn" :title="'返回'" @click="handleReturn">
|
||||
@@ -39,6 +43,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import moment from 'moment'; // 引入moment
|
||||
import {getPath} from "@/utils/ruoyi";
|
||||
export default {
|
||||
@@ -64,6 +69,9 @@ export default {
|
||||
activeTime: 1, // 默认月维度(0=日,1=月,2=年)
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
...mapGetters(['nickname']),
|
||||
},
|
||||
watch: {
|
||||
activeTime(newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
@@ -346,12 +354,35 @@ export default {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 28px;
|
||||
padding: 0;
|
||||
}
|
||||
.avatar-container {
|
||||
margin-right: 30px;
|
||||
|
||||
.avatar-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.user-avatar {
|
||||
cursor: pointer;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.user-nickname{
|
||||
margin-left: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.el-icon-caret-bottom {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: -20px;
|
||||
top: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,176 +1,184 @@
|
||||
<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>
|
||||
<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, // 存储图表实例,避免重复创建
|
||||
resizeHandler: null // 窗口resize事件处理器
|
||||
};
|
||||
},
|
||||
props: {
|
||||
// 明确接收的props结构,增强可读性
|
||||
chartData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
series: [],
|
||||
allPlaceNames: []
|
||||
}),
|
||||
// 校验数据格式
|
||||
validator: (value) => {
|
||||
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize();
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
|
||||
// 新增:监听 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);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
this.resizeHandler = null;
|
||||
}
|
||||
// 销毁图表,避免内存泄漏
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,172 +1,180 @@
|
||||
<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>
|
||||
<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事件处理器
|
||||
};
|
||||
},
|
||||
props: {
|
||||
// 明确接收的props结构,增强可读性
|
||||
chartData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
series: [],
|
||||
allPlaceNames: []
|
||||
}),
|
||||
// 校验数据格式
|
||||
validator: (value) => {
|
||||
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize();
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
|
||||
// 新增:监听 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);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
this.resizeHandler = null;
|
||||
}
|
||||
// 销毁图表,避免内存泄漏
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -8,7 +8,8 @@ export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 存储图表实例,避免重复创建
|
||||
myChart: null, // 存储图表实例,避免重复创建
|
||||
resizeHandler: null // 窗口resize事件处理器
|
||||
};
|
||||
},
|
||||
props: {
|
||||
@@ -27,6 +28,13 @@ export default {
|
||||
this.$nextTick(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize();
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
|
||||
// 新增:监听 chartData 变化
|
||||
@@ -152,19 +160,19 @@ export default {
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
this.resizeHandler = null;
|
||||
}
|
||||
// 销毁图表实例
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -77,7 +77,7 @@ export default {
|
||||
completed: 1 // 销量超额达标
|
||||
},
|
||||
{
|
||||
name: "双镀面板",
|
||||
name: "双镀销量",
|
||||
targetValue: 0,
|
||||
value: 0,
|
||||
proportion: 0,
|
||||
@@ -113,7 +113,7 @@ export default {
|
||||
"单价": "unitPrice",
|
||||
"净价": "netPrice",
|
||||
"销量": "sales",
|
||||
"双镀面板": "panel",
|
||||
"双镀销量": "panel",
|
||||
"溢价产品销量": "premiumProduct"
|
||||
};
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<div class="coreItem">
|
||||
<!-- 单独渲染第一个item -->
|
||||
<div class="item" v-if="itemList.length > 0">
|
||||
<div class="unit">{{ itemList[0].unit }}</div>
|
||||
<div class="item" v-if="itemList.length > 0" v-for="(item, index) in itemList">
|
||||
<div class="unit">{{ item.unit }}</div>
|
||||
<div class="item-content">
|
||||
<div class="content-wrapper">
|
||||
<div class="left">
|
||||
<div class="number">{{ itemList[0].targetValue }}</div>
|
||||
<div class="number">{{ item.targetValue }}</div>
|
||||
<div class="title">预算值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="right">
|
||||
<div class="number" :style="{ color: getColor(itemList[0].currentValue, itemList[0].targetValue) }">
|
||||
{{ itemList[0].currentValue }}
|
||||
<div class="number" :style="{ color: getColor(item.currentValue, item.targetValue) }">
|
||||
{{ item.currentValue }}
|
||||
</div>
|
||||
<div class="title">实际值</div>
|
||||
</div>
|
||||
@@ -23,7 +23,7 @@
|
||||
<!-- 进度条颜色和宽度动态绑定 -->
|
||||
<div class="progress-bar" :style="{
|
||||
width: itemList[0].progress + '%',
|
||||
background: getColor(itemList[0].currentValue, itemList[0].targetValue)
|
||||
background: getColor(item.progress)
|
||||
}"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -32,67 +32,9 @@
|
||||
<div class="progress-percent">完成率</div>
|
||||
<!-- 百分比颜色动态绑定 -->
|
||||
<div class="progress-percent" :style="{
|
||||
color: getColor(itemList[0].currentValue, itemList[0].targetValue)
|
||||
color: getColor(item.progress)
|
||||
}">
|
||||
{{ itemList[0].progress }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 循环渲染剩余的item(从索引1开始) -->
|
||||
<div class="item groupData" style="display: flex;padding: 0;" v-for="(item, index) in itemList.slice(1)"
|
||||
:key="index">
|
||||
<!-- 左侧预算值/实际值部分(不变) -->
|
||||
<div class="left" style="display: flex;align-items: start;gap: 4px;padding: 12px 0 0 12px;">
|
||||
<div class="groupName">{{ item.unit }}</div>
|
||||
<div class="left-target">
|
||||
<div class="number">{{ item.targetValue }}</div>
|
||||
<div class="title">预算值</div>
|
||||
</div>
|
||||
<div class="left-real">
|
||||
<div class="number" :style="{ color: getColor(item.currentValue, item.targetValue) }">
|
||||
{{ item.currentValue }}
|
||||
</div>
|
||||
<div class="title">实际值</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cityLine"></div>
|
||||
<div class="right">
|
||||
<!-- 顶部完成率部分(不变) -->
|
||||
<div class="groupName" :class="{
|
||||
'bg-default': item.currentValue < item.targetValue,
|
||||
'bg-green': item.currentValue >= item.targetValue
|
||||
}" style="font-size: 12px;display: flex;align-items: center;justify-content: flex-end;">
|
||||
<div class="title">完成率</div>
|
||||
<div class="yield" style="font-size: 22px;margin-bottom: 4px;">
|
||||
{{ item.progress }}
|
||||
</div>
|
||||
<div class="unit">%</div>
|
||||
</div>
|
||||
|
||||
<!-- 动态渲染城市进度:循环 item.cities -->
|
||||
<div class="right-city" v-for="(city, cityIdx) in item.cities" :key="cityIdx"
|
||||
:style="{ marginTop: cityIdx > 0 ? '2px' : '0' }" @click="getTableData(city.num)" style="cursor: pointer;">
|
||||
<div class="city">{{ city.name }}</div> <!-- 动态城市名 -->
|
||||
<div class="city-progress-group">
|
||||
<div class="city-progress-container">
|
||||
<!-- 动态城市进度条(颜色按城市进度判断) -->
|
||||
<div class="city-progress-bar" :style="{
|
||||
width: city.progress + '%',
|
||||
background: getColor(city.completed, city.total) // 用城市已完成/总数判断颜色
|
||||
}"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="city-progress-yield" style="display: flex;justify-content: space-between;">
|
||||
<!-- 动态比值(已完成/总数) -->
|
||||
<div class="numerator" :style="{ color: getColor(city.completed, city.total) }">
|
||||
{{ city.completed }}/{{ city.total }}
|
||||
</div>
|
||||
<!-- 动态城市完成率 -->
|
||||
<div class="city-yield" :style="{ color: getColor(city.completed, city.total) }">
|
||||
{{ city.progress }}%
|
||||
</div>
|
||||
{{ item.progress }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,60 +50,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
progress: 90, // 进度值基础参数
|
||||
itemList: [
|
||||
// {
|
||||
// unit: "总进度",
|
||||
// targetValue: 16,
|
||||
// currentValue: 14.5,
|
||||
// progress: 90,
|
||||
// cities: [] // 总进度无需城市数据,留空
|
||||
// },
|
||||
// {
|
||||
// unit: "一组",
|
||||
// targetValue: 16,
|
||||
// currentValue: 17,
|
||||
// progress: 106,
|
||||
// cities: [
|
||||
// { name: "桐城", completed: 12, total: 13, progress: 92 },
|
||||
// { name: "自贡", completed: 15, total: 16, progress: 93 } // 新增城市示例
|
||||
// ]
|
||||
// },
|
||||
// {
|
||||
// unit: "二组",
|
||||
// targetValue: 16,
|
||||
// currentValue: 16,
|
||||
// progress: 100,
|
||||
// cities: [
|
||||
// { name: "蚌埠", completed: 10, total: 12, progress: 83 },
|
||||
// { name: "合肥", completed: 8, total: 10, progress: 80 }
|
||||
// ]
|
||||
// },
|
||||
// // 其他组同理,按需添加 cities 数据
|
||||
// {
|
||||
// unit: "三组",
|
||||
// targetValue: 16,
|
||||
// currentValue: 15.2,
|
||||
// progress: 85,
|
||||
// cities: [{ name: "宜兴", completed: 9, total: 11, progress: 81 }]
|
||||
// },
|
||||
// {
|
||||
// unit: "四组",
|
||||
// targetValue: 16,
|
||||
// currentValue: 18,
|
||||
// progress: 112,
|
||||
// cities: [
|
||||
// { name: "漳州", completed: 14, total: 15, progress: 93 },
|
||||
// { name: "洛阳", completed: 12, total: 14, progress: 85 }
|
||||
// ]
|
||||
// },
|
||||
// {
|
||||
// unit: "五组",
|
||||
// targetValue: 16,
|
||||
// currentValue: 14,
|
||||
// progress: 80,
|
||||
// cities: [{ name: "桐城", completed: 7, total: 9, progress: 77 }]
|
||||
// }
|
||||
]
|
||||
itemList: []
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
@@ -180,67 +69,46 @@ export default {
|
||||
targetValue: data.totalProgress.target,
|
||||
currentValue: data.totalProgress.real,
|
||||
progress: data.totalProgress.rate,
|
||||
cities: [] // 总进度无需城市数据,留空
|
||||
},
|
||||
{
|
||||
unit: "一组",
|
||||
targetValue: data.group1.target,
|
||||
currentValue: data.group1.real,
|
||||
progress: data.group1.rate,
|
||||
cities: [
|
||||
{ name: "桐城", completed: data[2].real, total: data[2].target, progress: data[2].rate,num:2 },
|
||||
{ name: "自贡", completed: data[3].real, total: data[3].target, progress: data[3].rate, num: 3 } // 新增城市示例
|
||||
]
|
||||
progress: data.group1.rate
|
||||
},
|
||||
{
|
||||
unit: "二组",
|
||||
targetValue: data.group2.target,
|
||||
currentValue: data.group2.real,
|
||||
progress: data.group2.rate,
|
||||
cities: [
|
||||
{ name: "蚌埠", completed: data[4].real, total: data[4].target, progress: data[4].rate, num: 4 },
|
||||
{ name: "合肥", completed: data[5].real, total: data[5].target, progress: data[5].rate, num: 5 }
|
||||
]
|
||||
progress: data.group2.rate
|
||||
},
|
||||
// 其他组同理,按需添加 cities 数据
|
||||
{
|
||||
unit: "三组",
|
||||
targetValue: data.group3.target,
|
||||
currentValue: data.group3.real,
|
||||
progress: data.group3.rate,
|
||||
cities: [{ name: "江苏凯盛", completed: data[6].real, total: data[6].target, progress: data[6].rate, num: 6 },
|
||||
{ name: "宜兴", completed: data[7].real, total: data[7].target, progress: data[7].rate, num: 7 }
|
||||
]
|
||||
progress: data.group3.rate
|
||||
},
|
||||
{
|
||||
unit: "四组",
|
||||
targetValue: data.group4.target,
|
||||
currentValue: data.group4.real,
|
||||
progress: data.group4.rate,
|
||||
cities: [
|
||||
{ name: "漳州", completed: data[8].real, total: data[8].target, progress: data[8].rate, num: 8 },
|
||||
{ name: "洛阳", completed: data[9].real, total: data[9].target, progress: data[9].rate, num: 9 }
|
||||
]
|
||||
progress: data.group4.rate
|
||||
},
|
||||
{
|
||||
unit: "五组",
|
||||
targetValue: data.group5.target,
|
||||
currentValue: data.group5.real,
|
||||
progress: data.group5.rate,
|
||||
cities: [{ name: "秦皇岛", completed: data[10].real, total: data[10].target, progress: data[10].rate, num: 10 },
|
||||
// { name: "秦皇岛", completed: 7, total: 9, progress: 77 }
|
||||
]
|
||||
progress: data.group5.rate
|
||||
}
|
||||
]
|
||||
},
|
||||
// 颜色判断核心方法:实际值≥预算值返回绿色,否则返回橙色
|
||||
getColor(currentValue, targetValue) {
|
||||
return currentValue >= targetValue
|
||||
getColor(progress) {
|
||||
return progress >= 100
|
||||
? "rgba(98, 213, 180, 1)"
|
||||
: "rgba(249, 164, 74, 1)";
|
||||
},
|
||||
getTableData(data) {
|
||||
console.log(data, 'data');
|
||||
this.$emit('handleShowTable',data)
|
||||
|
||||
}
|
||||
@@ -257,11 +125,12 @@ export default {
|
||||
.coreItem {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 220px;
|
||||
width: 227px;
|
||||
height: 122px;
|
||||
background: #f9fcff;
|
||||
padding: 12px;
|
||||
|
||||
@@ -24,7 +24,10 @@ export default {
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
return {
|
||||
myChart: null,
|
||||
resizeHandler: null
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
watch: {
|
||||
@@ -41,6 +44,13 @@ export default {
|
||||
this.$nextTick(() => {
|
||||
this.initChart(); // 只负责初始化图表实例
|
||||
});
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize();
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
@@ -154,7 +164,7 @@ export default {
|
||||
itemStyle: { color: 'rgba(39, 96, 255, 1)' }
|
||||
},
|
||||
{
|
||||
value: 735, name: '双镀面板',
|
||||
value: 735, name: '双镀销量',
|
||||
label: {
|
||||
normal: {
|
||||
align: 'left',
|
||||
@@ -266,21 +276,20 @@ export default {
|
||||
]
|
||||
};
|
||||
|
||||
option && myChart.setOption(option);
|
||||
|
||||
// 窗口缩放监听
|
||||
window.addEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
|
||||
// 组件销毁清理
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
myChart.dispose();
|
||||
});
|
||||
option && this.myChart.setOption(option);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
this.resizeHandler = null;
|
||||
}
|
||||
// 销毁图表实例
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,286 +1,294 @@
|
||||
<template>
|
||||
<div style="position: relative;">
|
||||
<div class="legend">
|
||||
<span class="legend-item-line">
|
||||
<span class="line target"></span>
|
||||
预算
|
||||
</span>
|
||||
<span class="legend-item-line">
|
||||
<span class="line real"></span>
|
||||
实际
|
||||
</span>
|
||||
</div>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="height: 219px; width: 100%;"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'Container',
|
||||
props: ["chartData",'dateData'],
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null, // 存储 echarts 实例
|
||||
};
|
||||
},
|
||||
// 关键:监听 chartData 变化
|
||||
watch: {
|
||||
chartData: {
|
||||
handler(newData) {
|
||||
this.updateChart(newData);
|
||||
},
|
||||
immediate: true, // 组件初始化时立即执行一次
|
||||
deep: true, // 深度监听对象内部变化
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initChart();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
// 初始化图表实例
|
||||
initChart() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
this.myChart = echarts.init(chartDom);
|
||||
|
||||
// 初始化时调用一次更新
|
||||
this.updateChart(this.chartData);
|
||||
|
||||
// 监听窗口缩放
|
||||
window.addEventListener('resize', () => {
|
||||
this.myChart?.resize();
|
||||
});
|
||||
},
|
||||
|
||||
// 核心:根据数据更新图表
|
||||
updateChart(data) {
|
||||
if (!this.myChart) {
|
||||
// 如果实例还未初始化,则等待 initChart 完成后再更新
|
||||
setTimeout(() => this.updateChart(data), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 处理数据,如果 data 无效则清空图表
|
||||
if (!data || typeof data !== 'object' || (!data.real && !data.target)) {
|
||||
this.myChart.setOption({
|
||||
xAxis: { data: [] },
|
||||
series: [{ data: [] }, { data: [] }]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 提取 X 轴数据(从 real 或 target 中取键名)
|
||||
const xAxisData = data.real ? Object.keys(data.real) : Object.keys(data.target);
|
||||
console.log('xAxisData', xAxisData);
|
||||
|
||||
// 3. 提取 "实际" 和 "目标" 系列的数据
|
||||
const realData = data.real ? Object.values(data.real) : [];
|
||||
const targetData = data.target ? Object.values(data.target) : [];
|
||||
|
||||
// 4. 准备 echarts 的 option 配置
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 35,
|
||||
bottom: 20,
|
||||
right: 13,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
onZero: false,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
// width: 38,
|
||||
overflow: 'break',
|
||||
formatter: (value) => {
|
||||
const dateParts = value.split('-'); // ["2025", "07", "01"]
|
||||
if (dateParts.length < 2) return value;
|
||||
|
||||
// 去掉月份前面的0,然后加上"月"
|
||||
const month = dateParts[1].replace(/^0+/, '');
|
||||
return `${month}月`;
|
||||
}
|
||||
},
|
||||
data: xAxisData
|
||||
}
|
||||
],
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '元/㎡',
|
||||
// nameLocation:'center',
|
||||
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' },
|
||||
min: 0,
|
||||
// max: function (value) { return Math.ceil(value.max * 1.1); }, // 增加一点余量
|
||||
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.15)',
|
||||
}
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '实际',
|
||||
type: 'line',
|
||||
// stack: 'Total', // 趋势图通常不需要堆叠
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
lineStyle: {
|
||||
color: 'rgba(255, 132, 0, 1)', // 加深颜色
|
||||
width: 2,
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(255, 132, 0, 1)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.3,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(255, 132, 0, .5)' },
|
||||
{ offset: 1, color: 'rgba(255, 132, 0, 0)' },
|
||||
]),
|
||||
},
|
||||
data: realData // 使用提取出的 "实际" 数据
|
||||
},
|
||||
{
|
||||
name: '预算',
|
||||
type: 'line',
|
||||
// stack: 'Total',
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
lineStyle: {
|
||||
color: 'rgba(98, 213, 180, 1)', // 加深颜色
|
||||
width: 2,
|
||||
// type: 'dashed' // 目标线使用虚线
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(98, 213, 180, 1)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.3,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(98, 213, 180, .5)' },
|
||||
{ offset: 1, color: 'rgba(98, 213, 180, 0)' },
|
||||
]),
|
||||
},
|
||||
data: targetData // 使用提取出的 "目标" 数据
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
// 5. 应用配置项更新图表
|
||||
this.myChart.setOption(option, true);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 组件销毁时清理
|
||||
window.removeEventListener('resize', () => {
|
||||
this.myChart?.resize();
|
||||
});
|
||||
this.myChart?.dispose();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
/* (你的样式代码保持不变) */
|
||||
.legend {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 0px;
|
||||
display: flex;
|
||||
/* 使用 flex 布局让两个图例项并排且对齐 */
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.legend-item-line {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
position: relative;
|
||||
padding-left: 24px;
|
||||
/* 为圆点和线条留出空间 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.line {
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
/* 线条从圆点右侧开始 */
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
/* 线条长度 */
|
||||
height: 2px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.target {
|
||||
background: rgba(98, 213, 180, 1);
|
||||
}
|
||||
|
||||
.real {
|
||||
background: rgba(255, 132, 0, 1);
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
background-color: rgba(255, 132, 0, 1);
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.legend-item-line:nth-child(1) {
|
||||
&::before {
|
||||
background-color: rgba(98, 213, 180, 1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div style="position: relative;">
|
||||
<div class="legend">
|
||||
<span class="legend-item-line">
|
||||
<span class="line target"></span>
|
||||
预算
|
||||
</span>
|
||||
<span class="legend-item-line">
|
||||
<span class="line real"></span>
|
||||
实际
|
||||
</span>
|
||||
</div>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="height: 219px; width: 100%;"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'Container',
|
||||
props: ["chartData",'dateData','unit'],
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null, // 存储 echarts 实例
|
||||
resizeHandler: null // 窗口resize事件处理器
|
||||
};
|
||||
},
|
||||
// 关键:监听 chartData 变化
|
||||
watch: {
|
||||
chartData: {
|
||||
handler(newData) {
|
||||
this.updateChart(newData);
|
||||
},
|
||||
immediate: true, // 组件初始化时立即执行一次
|
||||
deep: true, // 深度监听对象内部变化
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initChart();
|
||||
});
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize();
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
methods: {
|
||||
// 初始化图表实例
|
||||
initChart() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
this.myChart = echarts.init(chartDom);
|
||||
|
||||
// 初始化时调用一次更新
|
||||
this.updateChart(this.chartData);
|
||||
},
|
||||
|
||||
// 核心:根据数据更新图表
|
||||
updateChart(data) {
|
||||
if (!this.myChart) {
|
||||
// 如果实例还未初始化,则等待 initChart 完成后再更新
|
||||
setTimeout(() => this.updateChart(data), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 处理数据,如果 data 无效则清空图表
|
||||
if (!data || typeof data !== 'object' || (!data.real && !data.target)) {
|
||||
this.myChart.setOption({
|
||||
xAxis: { data: [] },
|
||||
series: [{ data: [] }, { data: [] }]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 提取 X 轴数据(从 real 或 target 中取键名)
|
||||
const xAxisData = data.real ? Object.keys(data.real) : Object.keys(data.target);
|
||||
console.log('xAxisData', xAxisData);
|
||||
|
||||
// 3. 提取 "实际" 和 "目标" 系列的数据
|
||||
const realData = data.real ? Object.values(data.real) : [];
|
||||
const targetData = data.target ? Object.values(data.target) : [];
|
||||
|
||||
// 4. 准备 echarts 的 option 配置
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 35,
|
||||
bottom: 20,
|
||||
right: 13,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
onZero: false,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
// width: 38,
|
||||
overflow: 'break',
|
||||
formatter: (value) => {
|
||||
const dateParts = value.split('-'); // ["2025", "07", "01"]
|
||||
if (dateParts.length < 2) return value;
|
||||
|
||||
// 去掉月份前面的0,然后加上"月"
|
||||
const month = dateParts[1].replace(/^0+/, '');
|
||||
return `${month}月`;
|
||||
}
|
||||
},
|
||||
data: xAxisData
|
||||
}
|
||||
],
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: this.unit,
|
||||
// nameLocation:'center',
|
||||
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' },
|
||||
min: 0,
|
||||
// max: function (value) { return Math.ceil(value.max * 1.1); }, // 增加一点余量
|
||||
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.15)',
|
||||
}
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '预算',
|
||||
type: 'line',
|
||||
// stack: 'Total',
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
lineStyle: {
|
||||
color: 'rgba(98, 213, 180, 1)', // 加深颜色
|
||||
width: 2,
|
||||
// type: 'dashed' // 目标线使用虚线
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(98, 213, 180, 1)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.3,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(98, 213, 180, .5)' },
|
||||
{ offset: 1, color: 'rgba(98, 213, 180, 0)' },
|
||||
]),
|
||||
},
|
||||
data: targetData // 使用提取出的 "目标" 数据
|
||||
},
|
||||
{
|
||||
name: '实际',
|
||||
type: 'line',
|
||||
// stack: 'Total', // 趋势图通常不需要堆叠
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
lineStyle: {
|
||||
color: 'rgba(255, 132, 0, 1)', // 加深颜色
|
||||
width: 2,
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(255, 132, 0, 1)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.3,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(255, 132, 0, .5)' },
|
||||
{ offset: 1, color: 'rgba(255, 132, 0, 0)' },
|
||||
]),
|
||||
},
|
||||
data: realData // 使用提取出的 "实际" 数据
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 5. 应用配置项更新图表
|
||||
this.myChart.setOption(option, true);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
this.resizeHandler = null;
|
||||
}
|
||||
// 销毁图表,避免内存泄漏
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
/* (你的样式代码保持不变) */
|
||||
.legend {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 0px;
|
||||
display: flex;
|
||||
/* 使用 flex 布局让两个图例项并排且对齐 */
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.legend-item-line {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
position: relative;
|
||||
padding-left: 24px;
|
||||
/* 为圆点和线条留出空间 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.line {
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
/* 线条从圆点右侧开始 */
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
/* 线条长度 */
|
||||
height: 2px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.target {
|
||||
background: rgba(98, 213, 180, 1);
|
||||
}
|
||||
|
||||
.real {
|
||||
background: rgba(255, 132, 0, 1);
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
background-color: rgba(255, 132, 0, 1);
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.legend-item-line:nth-child(1) {
|
||||
&::before {
|
||||
background-color: rgba(98, 213, 180, 1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,18 +14,28 @@
|
||||
<div class="button-line lineFour" v-if="activeButton !== 3 && activeButton !== 4"></div>
|
||||
<div class="item-button" style="width: 75px;" :class="{ active: activeButton === 4 }" @click="activeButton = 4">
|
||||
投入产出率</div>
|
||||
<div class="button-line lineFive" v-if="activeButton !== 4 && activeButton !== 5"></div>
|
||||
<div class="item-button" style="width: 45px;" :class="{ active: activeButton === 5 }" @click="activeButton = 5">
|
||||
折旧</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lineBottom" style="height: 219px; width: 100%" v-if="isLineDataReady">
|
||||
<!-- 核心改动:动态传递数据给子组件 -->
|
||||
<coreLineChart style="height: 219px; width: 100%" :chart-data="selectedChartData" :dateData="dateData" />
|
||||
<coreLineChart style="height: 219px; width: 100%" :chart-data="selectedChartData" :unit='unit' :dateData="dateData" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import coreLineChart from './productBar.vue';
|
||||
|
||||
const dataKeyMap = [
|
||||
{name:'制造成本',unit:'元/㎡'},
|
||||
{name:'原片成本',unit:'元/㎡'},
|
||||
{name:'加工成本',unit:'元/㎡'},
|
||||
{name:'原片成品率',unit:'%'},
|
||||
{name:'投入产出率',unit:'%'},
|
||||
{name:'折旧',unit:'万元'}
|
||||
];
|
||||
export default {
|
||||
name: "Container",
|
||||
components: { coreLineChart },
|
||||
@@ -33,6 +43,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
activeButton: 0, // 初始激活第一个按钮(索引0)
|
||||
dataKeyMap
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -42,22 +53,14 @@ export default {
|
||||
},
|
||||
// 核心改动:计算属性,根据activeButton动态返回选中的数据
|
||||
selectedChartData() {
|
||||
// 定义按钮索引与lineData中key的映射关系
|
||||
const dataKeyMap = [
|
||||
'制造成本',
|
||||
'原片成本',
|
||||
'加工成本',
|
||||
'原片成品率',
|
||||
'投入产出率'
|
||||
];
|
||||
|
||||
// 根据当前激活的按钮索引获取对应的数据key
|
||||
const selectedKey = dataKeyMap[this.activeButton];
|
||||
console.log(this.lineData[selectedKey]);
|
||||
|
||||
const selectedKey = this.dataKeyMap[this.activeButton].name;
|
||||
|
||||
// 从lineData中获取对应的数据,如果不存在则返回一个空对象以防止报错
|
||||
return this.lineData ? this.lineData[selectedKey] || {} : {};
|
||||
},
|
||||
unit() {
|
||||
return this.dataKeyMap[this.activeButton].unit;
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
@@ -73,7 +76,7 @@ export default {
|
||||
|
||||
.barTop {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
gap: 20px;
|
||||
|
||||
.title {
|
||||
height: 18px;
|
||||
@@ -91,7 +94,7 @@ export default {
|
||||
display: flex;
|
||||
position: relative;
|
||||
gap: 2px;
|
||||
width: 327px;
|
||||
width: 356px;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
background: #ecf4fe;
|
||||
@@ -105,23 +108,27 @@ export default {
|
||||
}
|
||||
|
||||
.lineOne {
|
||||
top: 5px;
|
||||
left: 57px;
|
||||
top: 6px;
|
||||
left: 55px;
|
||||
}
|
||||
|
||||
.lineTwo {
|
||||
top: 5px;
|
||||
left: 118px;
|
||||
top: 6px;
|
||||
left: 111px;
|
||||
}
|
||||
|
||||
.lineThree {
|
||||
top: 5px;
|
||||
left: 177px;
|
||||
top: 6px;
|
||||
left: 169px;
|
||||
}
|
||||
|
||||
.lineFour {
|
||||
top: 5px;
|
||||
left: 252px;
|
||||
top: 6px;
|
||||
left: 240px;
|
||||
}
|
||||
.lineFive {
|
||||
top: 6px;
|
||||
left: 314px;
|
||||
}
|
||||
|
||||
.item-button {
|
||||
|
||||
@@ -1,52 +1,96 @@
|
||||
<template>
|
||||
<div class="coreItem">
|
||||
<div class="item" @click="handleRoute(item.route)" v-for="(item, index) in itemList" :key="index">
|
||||
<div class="name">{{ item.name }}</div>
|
||||
<div class="item-content">
|
||||
<div class="content-wrapper">
|
||||
<div class="left">
|
||||
<div class="number" style="color: rgba(103, 103, 103, 0.79);">{{ item.targetValue }}</div>
|
||||
<div class="title" style="color: rgba(134, 134, 135, 1);">目标值</div>
|
||||
<div>
|
||||
<div class="coreItem">
|
||||
<div class="item" @click="handleRoute(item.route)" v-for="(item, index) in itemList" :key="index" v-if='index<4'>
|
||||
<div class="name">{{ item.name }}</div>
|
||||
<div class="item-content">
|
||||
<div class="content-wrapper">
|
||||
<div class="left">
|
||||
<div class="number" style="color: rgba(103, 103, 103, 0.79);">{{ item.targetValue }}</div>
|
||||
<div class="title" style="color: rgba(134, 134, 135, 1);">目标值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<!-- 实际值:根据 实际值≥目标值 动态绑定类名 -->
|
||||
<div class="right">
|
||||
<div class="number" :class="{
|
||||
'number-exceed': item.progress >= 100,
|
||||
'number-below': item.progress < 100
|
||||
}">
|
||||
{{ item.currentValue }}
|
||||
</div>
|
||||
<div class="title" style="color: rgba(134, 134, 135, 1);">实际值</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<!-- 实际值:根据 实际值≥目标值 动态绑定类名 -->
|
||||
<div class="right">
|
||||
<div class="number" :class="{
|
||||
'number-exceed': item.currentValue >= item.targetValue,
|
||||
'number-below': item.currentValue < item.targetValue
|
||||
}">
|
||||
{{ item.currentValue }}
|
||||
|
||||
<!-- 进度条:同步绑定类名 -->
|
||||
<div class="progress-group">
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" :style="{ width: item.progressWidth + '%' }" :class="{
|
||||
'bar-exceed': item.progress >= 100,
|
||||
'bar-below': item.progress < 100
|
||||
}"></div>
|
||||
</div>
|
||||
<div class="title" style="color: rgba(134, 134, 135, 1);">实际值</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
|
||||
<!-- 进度条:同步绑定类名 -->
|
||||
<div class="progress-group">
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" :style="{ width: item.progressWidth + '%' }" :class="{
|
||||
'bar-exceed': item.currentValue >= item.targetValue,
|
||||
'bar-below': item.currentValue < item.targetValue
|
||||
}"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 完成率:同步绑定类名 -->
|
||||
<div class="yield" style="display: flex;justify-content: space-between;">
|
||||
<div class="progress-percent" :class="{
|
||||
'percent-exceed': item.currentValue >= item.targetValue,
|
||||
'percent-below': item.currentValue < item.targetValue
|
||||
}">完成率</div>
|
||||
<div class="progress-percent" :class="{
|
||||
'percent-exceed': item.currentValue >= item.targetValue,
|
||||
'percent-below': item.currentValue < item.targetValue
|
||||
}">
|
||||
{{ item.progressDisplay }}
|
||||
<!-- 完成率:同步绑定类名 -->
|
||||
<div class="yield" style="display: flex;justify-content: space-between;">
|
||||
<div class="progress-percent" :class="{
|
||||
'percent-exceed': item.progress >= 100,
|
||||
'percent-below': item.progress < 100
|
||||
}">完成率</div>
|
||||
<div class="progress-percent" :class="{
|
||||
'percent-exceed': item.progress >= 100,
|
||||
'percent-below': item.progress < 100
|
||||
}">
|
||||
{{ item.progress }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="itemBottom">
|
||||
<div class="item" v-for="(item, index) in itemList" :key="index" @click="handleRoute(item.route)" v-if='index>=4'>
|
||||
<div class="unit">{{ item.name }}</div>
|
||||
<div class="item-content">
|
||||
<div class="content-wrapper">
|
||||
<div class="left">
|
||||
<div class="number">{{ item.targetValue }}</div>
|
||||
<div class="title">目标值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<!-- 实际值:根据与目标值的比较动态变色 -->
|
||||
<div class="right">
|
||||
<div class="number" :class="{
|
||||
'exceed-target': item.progress > 100,
|
||||
'below-target': item.progress < 100,
|
||||
'equal-target': item.progress == 100
|
||||
}">
|
||||
{{ item.currentValue }}
|
||||
</div>
|
||||
<div class="title">实际值</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 进度条和百分比:同步变色逻辑 -->
|
||||
<div class="progress-group">
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" :style="{ width: item.progress + '%' }" :class="{
|
||||
'exceed-pro-target': item.progress > 100,
|
||||
'below-pro-target': item.progress < 100,
|
||||
'equal-pro-target': item.progress == 100
|
||||
}"></div>
|
||||
</div>
|
||||
<div class="progress-percent" :class="{
|
||||
'exceed-target': item.progress > 100,
|
||||
'below-target': item.progress < 100,
|
||||
'equal-target': item.progress == 100
|
||||
}">
|
||||
{{ item.progress }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -160,6 +204,18 @@ export default {
|
||||
name: '毛利率·%',
|
||||
route: '/grossMargin/grossMargin',
|
||||
isPercentage: true // 需要加%符号
|
||||
},
|
||||
{
|
||||
key: 'accountsReceivable',
|
||||
name: '应收账款·万元',
|
||||
route: '/accountsReceivable/accountsReceivableIndex',
|
||||
isPercentage: false // 需要加%符号
|
||||
},
|
||||
{
|
||||
key: 'inventory',
|
||||
name: '存货·万元',
|
||||
route: '/inventoryAnalysis/inventoryAnalysisIndex',
|
||||
isPercentage: false // 需要加%符号
|
||||
}
|
||||
];
|
||||
|
||||
@@ -170,7 +226,7 @@ export default {
|
||||
// 额外兜底:避免data中的属性为undefined
|
||||
const target = data.target || 0;
|
||||
const real = data.real || 0;
|
||||
const rate = data.rate || '0%';
|
||||
const rate = data.rate || 0;
|
||||
|
||||
// 解析rate字符串
|
||||
const parsedRate = this.parseRateString(rate);
|
||||
@@ -178,24 +234,12 @@ export default {
|
||||
// 进度条宽度:限制在0-100之间
|
||||
const progressWidth = Math.min(Math.max(parsedRate.progressValue, 0), 100);
|
||||
|
||||
// 显示文本处理
|
||||
let progressDisplay;
|
||||
if (mappingItem.isPercentage) {
|
||||
// 对于需要加%的指标,确保有%符号
|
||||
progressDisplay = parsedRate.displayText.includes('%')
|
||||
? parsedRate.displayText
|
||||
: `${parsedRate.displayText}%`;
|
||||
} else {
|
||||
// 对于经营性利润和利润总额,直接使用原始rate字符串
|
||||
progressDisplay = parsedRate.displayText;
|
||||
}
|
||||
|
||||
return {
|
||||
name: mappingItem.name,
|
||||
targetValue: target,
|
||||
currentValue: real,
|
||||
progressWidth: progressWidth, // 用于进度条宽度
|
||||
progressDisplay: progressDisplay, // 用于显示文本
|
||||
progress: rate, // 用于显示文本
|
||||
route: mappingItem.route
|
||||
};
|
||||
});
|
||||
@@ -219,24 +263,152 @@ export default {
|
||||
.coreItem {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
// padding: 8px; // 避免边缘item hover阴影被截断
|
||||
}
|
||||
.item {
|
||||
width: 170px;
|
||||
height: 168px;
|
||||
background: #f9fcff;
|
||||
padding: 12px 0px 0px 12px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.item {
|
||||
width: 170px;
|
||||
height: 228px;
|
||||
&:hover {
|
||||
box-shadow: 0px 4px 12px 2px #B5CDE5;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.name {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
color: #000000;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: calc(100% - 26px);
|
||||
}
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
.line {
|
||||
width: 149px;
|
||||
height: 1px;
|
||||
background: linear-gradient(to left, rgba(255, 0, 0, 0), #cbcbcb);
|
||||
}
|
||||
.left,
|
||||
.right {
|
||||
margin-top: 0px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
width: 100%;
|
||||
}
|
||||
/* 实际值 - 基础样式(无颜色) */
|
||||
.number {
|
||||
height: 22px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
line-height: 22px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
/* 实际值 - 实际值≥目标值(绿色) */
|
||||
.number-exceed {
|
||||
color: rgba(54, 181, 138, 1) !important;
|
||||
}
|
||||
/* 实际值 - 实际值<目标值(黄色) */
|
||||
.number-below {
|
||||
color: rgba(249, 164, 74, 1) !important;
|
||||
}
|
||||
.title {
|
||||
height: 14px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #868687;
|
||||
line-height: 14px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
.progress-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.progress-container {
|
||||
width: 138px;
|
||||
height: 10px;
|
||||
background: #ECEFF7;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* 进度条 - 基础样式(无颜色) */
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
/* 进度条 - 实际值≥目标值(绿色) */
|
||||
.bar-exceed {
|
||||
background: rgba(98, 213, 180, 1) !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
/* 进度条 - 实际值<目标值(黄色) */
|
||||
.bar-below {
|
||||
background: rgba(249, 164, 74, 1) !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
/* 百分比 - 基础样式(无颜色) */
|
||||
.progress-percent {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
}
|
||||
/* 百分比 - 实际值≥目标值(绿色) */
|
||||
.percent-exceed {
|
||||
color: rgba(54, 181, 138, 1) !important;
|
||||
}
|
||||
/* 百分比 - 实际值<目标值(黄色) */
|
||||
.percent-below {
|
||||
color: rgba(249, 164, 74, 1) !important;
|
||||
}
|
||||
.yield {
|
||||
width: 138px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.itemBottom {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 5px;
|
||||
.item {
|
||||
width: 350px;
|
||||
height: 90px;
|
||||
background: #f9fcff;
|
||||
padding: 12px 0px 17px 12px;
|
||||
padding: 8px 8px 0px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer; // 提示可点击
|
||||
transition: all 0.3s ease; // 动画过渡
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 4px 12px 2px #B5CDE5;
|
||||
transform: translateY(-2px);
|
||||
transform: translateY(-2px); // 轻微上浮增强交互感
|
||||
}
|
||||
|
||||
.name {
|
||||
.unit {
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
@@ -246,57 +418,51 @@ export default {
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: calc(100% - 26px);
|
||||
height: calc(100% - 29px);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 149px;
|
||||
height: 1px;
|
||||
background: linear-gradient(to left, rgba(255, 0, 0, 0), #cbcbcb);
|
||||
width: 1px;
|
||||
height: 46px;
|
||||
background: linear-gradient(to bottom, rgba(255, 0, 0, 0), #cbcbcb);
|
||||
}
|
||||
|
||||
.left,
|
||||
.right {
|
||||
margin-top: 11px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 实际值 - 基础样式(无颜色) */
|
||||
.number {
|
||||
height: 22px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
color: rgba(103, 103, 103, 0.79);
|
||||
/* 默认颜色(等于目标值时) */
|
||||
line-height: 22px;
|
||||
text-align: left;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* 实际值 - 实际值≥目标值(绿色) */
|
||||
.number-exceed {
|
||||
color: rgba(54, 181, 138, 1) !important;
|
||||
}
|
||||
|
||||
/* 实际值 - 实际值<目标值(黄色) */
|
||||
.number-below {
|
||||
color: rgba(249, 164, 74, 1) !important;
|
||||
}
|
||||
|
||||
.title {
|
||||
height: 14px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
@@ -304,7 +470,7 @@ export default {
|
||||
font-size: 12px;
|
||||
color: #868687;
|
||||
line-height: 14px;
|
||||
text-align: left;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@@ -312,57 +478,83 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 138px;
|
||||
width: 280px;
|
||||
height: 10px;
|
||||
background: #ECEFF7;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 进度条 - 基础样式(无颜色) */
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: rgba(98, 213, 180, 1);
|
||||
/* 默认进度条颜色(等于目标值时) */
|
||||
border-radius: 8px;
|
||||
transition: width 0.5s ease;
|
||||
opacity: 0.7;
|
||||
transition: width 0.5s ease; // 进度条动画
|
||||
}
|
||||
|
||||
/* 进度条 - 实际值≥目标值(绿色) */
|
||||
.bar-exceed {
|
||||
background: rgba(98, 213, 180, 1) !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* 进度条 - 实际值<目标值(黄色) */
|
||||
.bar-below {
|
||||
background: rgba(249, 164, 74, 1) !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* 百分比 - 基础样式(无颜色) */
|
||||
.progress-percent {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #868687;
|
||||
/* 默认百分比颜色(等于目标值时) */
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 百分比 - 实际值≥目标值(绿色) */
|
||||
.percent-exceed {
|
||||
color: rgba(54, 181, 138, 1) !important;
|
||||
/* 实际值 > 目标值:绿色样式 */
|
||||
.exceed-target {
|
||||
color: rgba(98, 213, 180, 1) !important;
|
||||
/* 文字绿色 */
|
||||
// background: rgba(98, 213, 180, 1) !important;
|
||||
/* 进度条绿色 */
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* 百分比 - 实际值<目标值(黄色) */
|
||||
.percent-below {
|
||||
/* 实际值 < 目标值:黄色样式 */
|
||||
.below-target {
|
||||
color: rgba(249, 164, 74, 1) !important;
|
||||
/* 文字黄色 */
|
||||
// background: rgba(249, 164, 74, 1) !important;
|
||||
/* 进度条黄色 */
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.yield {
|
||||
width: 138px;
|
||||
margin-top: 3px;
|
||||
.exceed-pro-target {
|
||||
// color: rgba(98, 213, 180, 1) !important;
|
||||
/* 文字绿色 */
|
||||
background: rgba(98, 213, 180, 1) !important;
|
||||
/* 进度条绿色 */
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* 实际值 < 目标值:黄色样式 */
|
||||
.below-pro-target {
|
||||
// color: rgba(249, 164, 74, 1) !important;
|
||||
/* 文字黄色 */
|
||||
background: rgba(249, 164, 74, 1) !important;
|
||||
/* 进度条黄色 */
|
||||
opacity: 1 !important;
|
||||
}
|
||||
/* 实际值 = 目标值:默认灰色(可自定义) */
|
||||
.equal-target{
|
||||
color: rgba(98, 213, 180, 1) !important;
|
||||
/* 文字绿色 */
|
||||
// background: rgba(98, 213, 180, 1) !important;
|
||||
/* 进度条绿色 */
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.equal-pro-target {
|
||||
// color: rgba(98, 213, 180, 1) !important;
|
||||
/* 文字绿色 */
|
||||
background: rgba(98, 213, 180, 1) !important;
|
||||
/* 进度条绿色 */
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
<!-- 实际值:根据与目标值的比较动态变色 -->
|
||||
<div class="right">
|
||||
<div class="number" :class="{
|
||||
'exceed-target': item.currentValue > item.targetValue,
|
||||
'below-target': item.currentValue < item.targetValue,
|
||||
'equal-target': item.currentValue === item.targetValue
|
||||
'exceed-target': item.progress > 100,
|
||||
'below-target': item.progress < 100,
|
||||
'equal-target': item.progress === 100
|
||||
}">
|
||||
{{ item.currentValue }}
|
||||
</div>
|
||||
@@ -25,15 +25,15 @@
|
||||
<div class="progress-group">
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" :style="{ width: item.progress + '%' }" :class="{
|
||||
'exceed-pro-target': item.currentValue > item.targetValue,
|
||||
'below-pro-target': item.currentValue < item.targetValue,
|
||||
'equal-pro-target': item.currentValue === item.targetValue
|
||||
'exceed-pro-target': item.progress > 100,
|
||||
'below-pro-target': item.progress < 100,
|
||||
'equal-pro-target': item.progress === 100
|
||||
}"></div>
|
||||
</div>
|
||||
<div class="progress-percent" :class="{
|
||||
'exceed-target': item.currentValue > item.targetValue,
|
||||
'below-target': item.currentValue < item.targetValue,
|
||||
'equal-target': item.currentValue === item.targetValue
|
||||
'exceed-target': item.progress > 100,
|
||||
'below-target': item.progress < 100,
|
||||
'equal-target': item.progress === 100
|
||||
}">
|
||||
{{ item.progress }}%
|
||||
</div>
|
||||
@@ -77,7 +77,7 @@ export default {
|
||||
{ key: 'unitPrice', unit: '单价·元/㎡', path: '/unitPriceAnalysis/unitPriceAnalysis' },
|
||||
{ key: 'netPrice', unit: '净价·元/㎡', path: '/netPriceAnalysis/netPriceAnalysis' },
|
||||
{ key: 'sales', unit: '销量·万㎡', path: '/salesVolumeAnalysis/salesVolumeAnalysis' },
|
||||
{ key: 'panel', unit: '双镀面板·万㎡', path: '/salesVolumeAnalysis/salesVolumeAnalysis' }
|
||||
{ key: 'panel', unit: '双镀销量·万㎡', path: '/salesVolumeAnalysis/salesVolumeAnalysis' }
|
||||
];
|
||||
|
||||
// 遍历映射关系,转换数据
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="coreItem">
|
||||
<div class="item" :class="`item${index + 1}`" @click="handleItemClick(index)" v-for="(item, index) in itemList"
|
||||
<div class="item" @click="handleItemClick(index)" v-for="(item, index) in itemList"
|
||||
:key="index">
|
||||
<div class="unit">{{ item.unit }}</div>
|
||||
<div class="item-content">
|
||||
<div class="content-wrapper">
|
||||
<div class="left">
|
||||
<div class="left" v-if="item.unit !== '折旧·万元'">
|
||||
<div class="number">{{ item.target }}</div>
|
||||
<div class="title">预算值</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="line" v-if="item.unit !== '折旧·万元'"></div>
|
||||
<div class="right">
|
||||
<!-- 实际值颜色动态绑定 -->
|
||||
<div class="number" :style="{ color: getColor(index) }">
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="title">实际值</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-group">
|
||||
<div class="progress-group" v-if="item.unit !== '折旧·万元'">
|
||||
<div class="progress-container">
|
||||
<!-- 进度条样式动态绑定 -->
|
||||
<div class="progress-bar" :style="{
|
||||
@@ -88,12 +88,17 @@ export default {
|
||||
{
|
||||
key: 'rawYield',
|
||||
unit: '原片成品率·%',
|
||||
route: '/rawSheetYield/rawSheetYield' // 假设这个没有路由
|
||||
route: '/rawSheetYield/rawSheetYield'
|
||||
},
|
||||
{
|
||||
key: 'ioYield',
|
||||
unit: '投入产出率·%',
|
||||
route: '/inputOutputRatio/inputOutputRatio' // 假设这个没有路由
|
||||
route: '/inputOutputRatio/inputOutputRatio'
|
||||
},
|
||||
{
|
||||
key: 'depreciation',
|
||||
unit: '折旧·万元',
|
||||
route: '/depreciationAnalysis/depreciationAnalysisIndex'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -133,9 +138,9 @@ export default {
|
||||
const { actual, target, progress } = this.itemList[index];
|
||||
|
||||
// 新增条件:如果实际值、预算值和进度都为0,则显示绿色
|
||||
if (actual === 0 && target === 0 && progress === 0) {
|
||||
return "rgba(98, 213, 180, 1)"; // 绿色
|
||||
}
|
||||
// if (actual === 0 && target === 0 && progress === 0) {
|
||||
// return "rgba(98, 213, 180, 1)"; // 绿色
|
||||
// }
|
||||
|
||||
// 原有的通用判断逻辑
|
||||
return progress >= 100
|
||||
@@ -194,7 +199,7 @@ export default {
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 252px;
|
||||
width: 166px;
|
||||
height: 110px;
|
||||
background: #f9fcff;
|
||||
padding: 12px;
|
||||
@@ -300,10 +305,4 @@ export default {
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.item1,
|
||||
.item2,
|
||||
.item3 {
|
||||
width: 166px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,259 +0,0 @@
|
||||
<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 top-title="成本分析" :is-full-screen="isFullScreen" @screenfullChange="screenfullChange"
|
||||
@timeRangeChange="handleTimeChange" />
|
||||
<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: 530px 1078px;
|
||||
">
|
||||
<costOverview :costOverviews="costOverviews" />
|
||||
<costItemOverview :piecesCostViews="piecesCostViews" :processCostViews="processCostViews" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||
<div class="top-three" style="
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
grid-template-columns: 530px 530px 530px;
|
||||
">
|
||||
<overviewTrendChart :trendViews="trendViews" />
|
||||
<rawMaterialCost :trendViews="piecesCostTrendViews" />
|
||||
<processingCostTrendChart :trendViews="processCostTrendViews" />
|
||||
|
||||
|
||||
</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 costOverview from "./costComponents/costOverview.vue";
|
||||
import costItemOverview from "./costComponents/costItemOverview.vue";
|
||||
import { mapState } from "vuex";
|
||||
// import KFAP from "./costComponents/KFAP.vue";
|
||||
import overviewTrendChart from "./costComponents/overviewTrendChart.vue";
|
||||
import rawMaterialCost from "./costComponents/rawMaterialCost.vue";
|
||||
import processingCostTrendChart from "./costComponents/processingCostTrendChart.vue";
|
||||
|
||||
import { getCostAnalysisList } from '@/api/cockpit'
|
||||
|
||||
|
||||
// import coreBottomLeft from "./components/coreBottomLeft.vue";
|
||||
// import orderProgress from "./components/orderProgress.vue";
|
||||
// import keyWork from "./components/keyWork.vue";
|
||||
import moment from "moment";
|
||||
// import { getCostAnalysisList } from "../../api/cockpit";
|
||||
// import html2canvas from 'html2canvas'
|
||||
// import JsPDF from 'jspdf'
|
||||
export default {
|
||||
name: "DayReport",
|
||||
components: {
|
||||
ReportHeader,
|
||||
costOverview,
|
||||
costItemOverview,
|
||||
Sidebar,
|
||||
processingCostTrendChart,
|
||||
rawMaterialCost,
|
||||
overviewTrendChart
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
weekArr: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
|
||||
isFullScreen: false,
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
value: 100,
|
||||
costOverviews: [],
|
||||
piecesCostViews: [],
|
||||
processCostViews: [],
|
||||
trendViews: [],
|
||||
piecesCostTrendViews: [],
|
||||
processCostTrendViews:[],
|
||||
};
|
||||
},
|
||||
|
||||
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;
|
||||
})();
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getData(obj) {
|
||||
// obj.levelId = 1
|
||||
getCostAnalysisList({
|
||||
startTime: obj.startTime,
|
||||
endTime: obj.endTime,
|
||||
mode: obj.mode,
|
||||
}).then((res) => {
|
||||
this.costOverviews = res.data.costOverviews
|
||||
this.piecesCostViews = res.data.piecesCostViews
|
||||
this.processCostViews = res.data.processCostViews
|
||||
this.trendViews = res.data.trendViews
|
||||
this.piecesCostTrendViews = res.data.piecesCostTrendViews
|
||||
this.processCostTrendViews = res.data.processCostTrendViews
|
||||
|
||||
// this.profitTotalData = res.data.productAndSaleData.filter(item => {
|
||||
// return item.name === "利润总额" || item.name === "毛利率";
|
||||
// });
|
||||
// this.salesAndOutputData = res.data.productAndSaleData.filter(item => {
|
||||
// // 只保留name为“销量”或“产量”的项
|
||||
// return item.name === "销量" || item.name === "产量";
|
||||
// });
|
||||
// this.middleItemData = res.data.productAndSaleData.filter(item => {
|
||||
// return item.name === "营业收入" || item.name === "成本";
|
||||
// });
|
||||
// this.middleChartData = res.data.productFactors
|
||||
// this.bottomChartData = res.data.productFactors
|
||||
})
|
||||
},
|
||||
handleTimeChange(obj) {
|
||||
this.getData(obj)
|
||||
},
|
||||
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);
|
||||
},
|
||||
},
|
||||
};
|
||||
</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>
|
||||
@@ -9,7 +9,8 @@ export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 保存图表实例,方便更新
|
||||
myChart: null, // 保存图表实例,方便更新
|
||||
resizeHandler: null // 窗口resize事件处理器
|
||||
};
|
||||
},
|
||||
props: {
|
||||
@@ -35,6 +36,13 @@ export default {
|
||||
this.$nextTick(() => {
|
||||
this.initChart();
|
||||
});
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize();
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
methods: {
|
||||
initChart() {
|
||||
@@ -45,19 +53,18 @@ export default {
|
||||
}
|
||||
this.myChart = echarts.init(chartDom);
|
||||
this.updateChart(); // 初始化图表数据
|
||||
|
||||
// 窗口缩放适配
|
||||
window.addEventListener('resize', () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
|
||||
// 组件销毁清理
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
this.myChart && this.myChart.dispose();
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
this.resizeHandler = null;
|
||||
}
|
||||
// 销毁图表,避免内存泄漏
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
}
|
||||
},
|
||||
|
||||
updateChart() {
|
||||
|
||||
@@ -22,7 +22,8 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
myChart: null
|
||||
myChart: null,
|
||||
resizeHandler: null // 窗口resize事件处理器
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
@@ -38,6 +39,25 @@ export default {
|
||||
this.$nextTick(() => {
|
||||
this.initData();
|
||||
});
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize();
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
this.resizeHandler = null;
|
||||
}
|
||||
// 销毁图表,避免内存泄漏
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
@@ -259,19 +279,6 @@ export default {
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -30,14 +30,7 @@ export default {
|
||||
props: ["name", "size", "icon",'itemList'],
|
||||
data() {
|
||||
return {
|
||||
itemList: [
|
||||
// { unit: "营业收入·万元", targetValue: 16, currentValue: 14.5, progress: 90 },
|
||||
// { unit: "经营性利润·万元", targetValue: 16, currentValue: 15.2, progress: 85 },
|
||||
// { unit: "利润总额·万元", targetValue: 16, currentValue: 15.2, progress: 85 },
|
||||
// { unit: "毛利率·%", targetValue: 16, currentValue: 15.2, progress: 85 },
|
||||
// { unit: "销量·万㎡", targetValue: 20, currentValue: 16, progress: 80 },
|
||||
// { unit: "双镀面板·万㎡", targetValue: 15, currentValue: 13.8, progress: 92 }
|
||||
],
|
||||
itemList: [],
|
||||
// 拖拽相关状态
|
||||
isDragging: false, // 是否正在拖拽
|
||||
startX: 0, // 拖拽开始时的鼠标X坐标
|
||||
|
||||
@@ -1,169 +1,182 @@
|
||||
<template>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 500px;"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
props: {
|
||||
seriesData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
xData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: () => { }
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// 监听 xData 变化,触发图表更新
|
||||
xData: {
|
||||
handler() {
|
||||
this.$nextTick(() => this.initData());
|
||||
},
|
||||
deep: true // 深度监听数组内元素变化
|
||||
},
|
||||
// 监听 seriesData 变化,触发图表更新
|
||||
seriesData: {
|
||||
handler() {
|
||||
this.$nextTick(() => this.initData());
|
||||
},
|
||||
deep: true // 深度监听数组内元素变化
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initData();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
const myChart = echarts.init(chartDom);
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
},
|
||||
// 优化tooltip内容,区分各系列含义
|
||||
formatter: (params) => {
|
||||
let html = `${params[0].axisValue}<br/>`;
|
||||
params.forEach(item => {
|
||||
// 直接使用系列名,无需映射,仅保留单位判断
|
||||
html += `${item.marker} ${item.seriesName}: ${item.value}${item.seriesName === '完成率' ? '%' : '元'}<br/>`;
|
||||
});
|
||||
return html;
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 30,
|
||||
bottom: 30, // 增大底部间距,避免柱子与X轴标签重叠
|
||||
right: 70,
|
||||
left: 40,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: true, // 开启边界间隙,让柱子居中显示,不贴边
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0] // 标签向下偏移,避免与柱子底部重叠
|
||||
},
|
||||
data: this.xData
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
// 左侧Y轴:目标/达标/未达标(数量,单位“片”)
|
||||
{
|
||||
type: 'value',
|
||||
// name: this.yName,
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'right'
|
||||
},
|
||||
min: 0, // 最小值设0,确保柱子从X轴底部开始,不超过X轴
|
||||
max: (value) => Math.ceil(value.max * 1.1), // 最大值留10%余量,避免柱子顶满
|
||||
scale: false, // 关闭缩放,强制从0开始
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
formatter: '{value}'
|
||||
},
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
splitNumber: 4
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: this.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: this.seriesData,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
option && myChart.setOption(option);
|
||||
|
||||
// 窗口缩放适配
|
||||
window.addEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
|
||||
// 组件销毁清理
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
myChart.dispose();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 500px;"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null,
|
||||
resizeHandler: null // 窗口resize事件处理器
|
||||
};
|
||||
},
|
||||
props: {
|
||||
seriesData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
xData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: () => { }
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// 监听 xData 变化,触发图表更新
|
||||
xData: {
|
||||
handler() {
|
||||
this.$nextTick(() => this.initData());
|
||||
},
|
||||
deep: true // 深度监听数组内元素变化
|
||||
},
|
||||
// 监听 seriesData 变化,触发图表更新
|
||||
seriesData: {
|
||||
handler() {
|
||||
this.$nextTick(() => this.initData());
|
||||
},
|
||||
deep: true // 深度监听数组内元素变化
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initData();
|
||||
});
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize();
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
// 销毁已有图表实例
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
}
|
||||
this.myChart = echarts.init(chartDom);
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
},
|
||||
// 优化tooltip内容,区分各系列含义
|
||||
formatter: (params) => {
|
||||
let html = `${params[0].axisValue}<br/>`;
|
||||
params.forEach(item => {
|
||||
// 直接使用系列名,无需映射,仅保留单位判断
|
||||
html += `${item.marker} ${item.seriesName}: ${item.value}${item.seriesName === '完成率' ? '%' : '元'}<br/>`;
|
||||
});
|
||||
return html;
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 30,
|
||||
bottom: 30, // 增大底部间距,避免柱子与X轴标签重叠
|
||||
right: 70,
|
||||
left: 40,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: true, // 开启边界间隙,让柱子居中显示,不贴边
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0] // 标签向下偏移,避免与柱子底部重叠
|
||||
},
|
||||
data: this.xData
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
// 左侧Y轴:目标/达标/未达标(数量,单位“片”)
|
||||
{
|
||||
type: 'value',
|
||||
// name: this.yName,
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'right'
|
||||
},
|
||||
min: 0, // 最小值设0,确保柱子从X轴底部开始,不超过X轴
|
||||
max: (value) => Math.ceil(value.max * 1.1), // 最大值留10%余量,避免柱子顶满
|
||||
scale: false, // 关闭缩放,强制从0开始
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
formatter: '{value}'
|
||||
},
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
splitNumber: 4
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: this.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: this.seriesData,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
option && this.myChart.setOption(option);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
this.resizeHandler = null;
|
||||
}
|
||||
// 销毁图表,避免内存泄漏
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -53,7 +53,7 @@ export default {
|
||||
{ unit: "利润总额·万元", targetValue: 16, currentValue: 15.2, progress: 85 },
|
||||
{ unit: "毛利率·%", targetValue: 16, currentValue: 15.2, progress: 85 },
|
||||
{ unit: "销量·万㎡", targetValue: 20, currentValue: 16, progress: 80 },
|
||||
{ unit: "双镀面板·万㎡", targetValue: 15, currentValue: 13.8, progress: 92 }
|
||||
{ unit: "双镀销量·万㎡", targetValue: 15, currentValue: 13.8, progress: 92 }
|
||||
],
|
||||
// 拖拽相关状态
|
||||
isDragging: false, // 是否正在拖拽
|
||||
|
||||
@@ -56,7 +56,7 @@ export default {
|
||||
progress: 80
|
||||
},
|
||||
{
|
||||
unit: "双镀面板·万㎡",
|
||||
unit: "双镀销量·万㎡",
|
||||
targetValue: 15,
|
||||
currentValue: 13.8,
|
||||
progress: 92
|
||||
|
||||
@@ -1,280 +1,293 @@
|
||||
<template>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 280px;"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
props: {
|
||||
seriesData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
xData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: () => { }
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// 监听 xData 变化,触发图表更新
|
||||
xData: {
|
||||
handler() {
|
||||
this.$nextTick(() => this.initData());
|
||||
},
|
||||
deep: true // 深度监听数组内元素变化
|
||||
},
|
||||
// 监听 seriesData 变化,触发图表更新
|
||||
seriesData: {
|
||||
handler() {
|
||||
this.$nextTick(() => this.initData());
|
||||
},
|
||||
deep: true // 深度监听数组内元素变化
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initData();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
const myChart = echarts.init(chartDom);
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
},
|
||||
// 优化tooltip内容,区分各系列含义
|
||||
formatter: (params) => {
|
||||
let html = `${params[0].axisValue}<br/>`;
|
||||
params.forEach(item => {
|
||||
// 直接使用系列名,无需映射,仅保留单位判断
|
||||
html += `${item.marker} ${item.seriesName}: ${item.value}${'元'}<br/>`;
|
||||
});
|
||||
return html;
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 50,
|
||||
bottom: 30, // 增大底部间距,避免柱子与X轴标签重叠
|
||||
right: 70,
|
||||
left: 50,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: true, // 开启边界间隙,让柱子居中显示,不贴边
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0] // 标签向下偏移,避免与柱子底部重叠
|
||||
},
|
||||
data: this.xData // 绑定监听的 xData
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
// 左侧Y轴:目标/达标/未达标(数量,单位“片”)
|
||||
{
|
||||
type: 'value',
|
||||
name: '元',
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'right'
|
||||
},
|
||||
min: 0, // 最小值设0,确保柱子从X轴底部开始,不超过X轴
|
||||
max: (value) => Math.ceil(value.max * 1.1), // 最大值留10%余量,避免柱子顶满
|
||||
scale: false, // 关闭缩放,强制从0开始
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
formatter: '{value}'
|
||||
},
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
splitNumber: 4
|
||||
},
|
||||
// 右侧Y轴:完成率(百分比)
|
||||
// {
|
||||
// type: 'value',
|
||||
// // name: '%',
|
||||
// nameTextStyle: {
|
||||
// color: 'rgba(0, 0, 0, 0.45)',
|
||||
// fontSize: 12,
|
||||
// align: 'left'
|
||||
// },
|
||||
// min: 0,
|
||||
// max: 100, // 完成率最大100%,符合业务逻辑
|
||||
// axisTick: { show: false },
|
||||
// axisLabel: {
|
||||
// color: 'rgba(0, 0, 0, 0.45)',
|
||||
// fontSize: 12,
|
||||
// formatter: '{value}%'
|
||||
// },
|
||||
// splitLine: { show: false }, // 不重复显示分割线
|
||||
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
// splitNumber: 4
|
||||
// }
|
||||
],
|
||||
series: [
|
||||
// 1. 完成率:折线图(绑定右侧百分比Y轴)
|
||||
// {
|
||||
// name: '产销率',
|
||||
// type: 'line',
|
||||
// yAxisIndex: 1, // 绑定右侧Y轴
|
||||
// lineStyle: {
|
||||
// color: 'rgba(40, 138, 255, .5)',
|
||||
// width: 2 // 线条加粗,突出重点
|
||||
// },
|
||||
// itemStyle: {
|
||||
// color: 'rgba(40, 138, 255, 1)',
|
||||
// borderColor: 'rgba(40, 138, 255, 1)', // 数据点白色边框,增强辨识度
|
||||
// borderWidth: 2,
|
||||
// radius: 4 // 数据点圆角,更圆润
|
||||
// },
|
||||
// areaStyle: {
|
||||
// opacity: 0.2, // 降低面积透明度,不抢柱状图视觉焦点
|
||||
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
// { offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||
// { offset: 1, color: 'rgba(255, 132, 0, 0)' }
|
||||
// ])
|
||||
// },
|
||||
// data: [65, 78, 52, 85, 60, 95, 72], // 完成率数据(0-100)
|
||||
// symbol: 'circle', // 数据点为圆形
|
||||
// symbolSize: 6 // 数据点大小
|
||||
// },
|
||||
// 2. 目标:柱状图(绑定左侧数量Y轴)
|
||||
{
|
||||
name:this.name,
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 24,
|
||||
label: {
|
||||
show: true, // 开启显示
|
||||
position: 'top', // 标签位置,可选:'top'、'middle'、'bottom'
|
||||
// 也可以使用绝对像素值定位,例如 [10, '50%']
|
||||
// position: [10, '50%'],
|
||||
|
||||
// 标签内容格式化,这里直接显示数据值
|
||||
formatter: '{c}',
|
||||
|
||||
// 文字样式
|
||||
color: 'rgba(11, 88, 255, 1)', // 文字颜色
|
||||
fontSize: 14, // 文字大小
|
||||
// fontWeight: 'bold', // 文字粗细
|
||||
// fontFamily: 'Arial, sans-serif' // 字体
|
||||
},
|
||||
itemStyle: {
|
||||
// 移除多余的 normal 层级,直接配置 color 渐变
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: this.seriesData
|
||||
},
|
||||
// 3. 达标:柱状图(绑定左侧数量Y轴)
|
||||
// {
|
||||
// name: '产量',
|
||||
// type: 'bar',
|
||||
// yAxisIndex: 0,
|
||||
// barWidth: 24,
|
||||
// // 关键修复:label 直接放在 series 下,而非 itemStyle 内
|
||||
// itemStyle: {
|
||||
// // 移除多余的 normal 层级,直接配置 color 渐变
|
||||
// color: {
|
||||
// type: 'linear',
|
||||
// x: 0,
|
||||
// y: 0,
|
||||
// x2: 0,
|
||||
// y2: 1,
|
||||
// colorStops: [
|
||||
// { offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||
// { offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||
// ]
|
||||
// },
|
||||
// borderRadius: [4, 4, 0, 0],
|
||||
// borderWidth: 0
|
||||
// },
|
||||
// data: [130, 220, 95, 255, 132, 332] // 达标数据(小于目标)
|
||||
// },
|
||||
// 4. 未达标:柱状图(绑定左侧数量Y轴)
|
||||
// {
|
||||
// name: '未达标',
|
||||
// type: 'bar',
|
||||
// yAxisIndex: 0,
|
||||
// barWidth: 18,
|
||||
// itemStyle: {
|
||||
// color: 'rgba(249, 164, 74, 1)',
|
||||
// borderRadius: [4, 4, 0, 0],
|
||||
// borderWidth: 0
|
||||
// },
|
||||
// data: [70, 60, 85, 45, 88, 18, 78] // 未达标数据(目标-达标)
|
||||
// }
|
||||
],
|
||||
// 图例:区分各系列,点击可控制显示隐藏
|
||||
// legend: {
|
||||
// top: 0,
|
||||
// left: 'center',
|
||||
// itemWidth: 12,
|
||||
// itemHeight: 8,
|
||||
// textStyle: {
|
||||
// color: 'rgba(0, 0, 0, 0.45)',
|
||||
// fontSize: 12
|
||||
// },
|
||||
// data: ['完成率', '目标', '达标', '未达标']
|
||||
// }
|
||||
};
|
||||
|
||||
option && myChart.setOption(option);
|
||||
|
||||
// 窗口缩放适配
|
||||
window.addEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
|
||||
// 组件销毁清理
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
myChart.dispose();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 280px;"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null,
|
||||
resizeHandler: null // 窗口resize事件处理器
|
||||
};
|
||||
},
|
||||
props: {
|
||||
seriesData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
xData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: () => { }
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// 监听 xData 变化,触发图表更新
|
||||
xData: {
|
||||
handler() {
|
||||
this.$nextTick(() => this.initData());
|
||||
},
|
||||
deep: true // 深度监听数组内元素变化
|
||||
},
|
||||
// 监听 seriesData 变化,触发图表更新
|
||||
seriesData: {
|
||||
handler() {
|
||||
this.$nextTick(() => this.initData());
|
||||
},
|
||||
deep: true // 深度监听数组内元素变化
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initData();
|
||||
});
|
||||
// 注册窗口resize事件,使用稳定的引用以便后续移除
|
||||
this.resizeHandler = () => {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize();
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
// 销毁已有图表实例
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
}
|
||||
this.myChart = echarts.init(chartDom);
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
},
|
||||
// 优化tooltip内容,区分各系列含义
|
||||
formatter: (params) => {
|
||||
let html = `${params[0].axisValue}<br/>`;
|
||||
params.forEach(item => {
|
||||
// 直接使用系列名,无需映射,仅保留单位判断
|
||||
html += `${item.marker} ${item.seriesName}: ${item.value}${'元'}<br/>`;
|
||||
});
|
||||
return html;
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 50,
|
||||
bottom: 30, // 增大底部间距,避免柱子与X轴标签重叠
|
||||
right: 70,
|
||||
left: 50,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: true, // 开启边界间隙,让柱子居中显示,不贴边
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0] // 标签向下偏移,避免与柱子底部重叠
|
||||
},
|
||||
data: this.xData // 绑定监听的 xData
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
// 左侧Y轴:目标/达标/未达标(数量,单位“片”)
|
||||
{
|
||||
type: 'value',
|
||||
name: '元',
|
||||
nameTextStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
align: 'right'
|
||||
},
|
||||
min: 0, // 最小值设0,确保柱子从X轴底部开始,不超过X轴
|
||||
max: (value) => Math.ceil(value.max * 1.1), // 最大值留10%余量,避免柱子顶满
|
||||
scale: false, // 关闭缩放,强制从0开始
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: 12,
|
||||
formatter: '{value}'
|
||||
},
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
splitNumber: 4
|
||||
},
|
||||
// 右侧Y轴:完成率(百分比)
|
||||
// {
|
||||
// type: 'value',
|
||||
// // name: '%',
|
||||
// nameTextStyle: {
|
||||
// color: 'rgba(0, 0, 0, 0.45)',
|
||||
// fontSize: 12,
|
||||
// align: 'left'
|
||||
// },
|
||||
// min: 0,
|
||||
// max: 100, // 完成率最大100%,符合业务逻辑
|
||||
// axisTick: { show: false },
|
||||
// axisLabel: {
|
||||
// color: 'rgba(0, 0, 0, 0.45)',
|
||||
// fontSize: 12,
|
||||
// formatter: '{value}%'
|
||||
// },
|
||||
// splitLine: { show: false }, // 不重复显示分割线
|
||||
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
// splitNumber: 4
|
||||
// }
|
||||
],
|
||||
series: [
|
||||
// 1. 完成率:折线图(绑定右侧百分比Y轴)
|
||||
// {
|
||||
// name: '产销率',
|
||||
// type: 'line',
|
||||
// yAxisIndex: 1, // 绑定右侧Y轴
|
||||
// lineStyle: {
|
||||
// color: 'rgba(40, 138, 255, .5)',
|
||||
// width: 2 // 线条加粗,突出重点
|
||||
// },
|
||||
// itemStyle: {
|
||||
// color: 'rgba(40, 138, 255, 1)',
|
||||
// borderColor: 'rgba(40, 138, 255, 1)', // 数据点白色边框,增强辨识度
|
||||
// borderWidth: 2,
|
||||
// radius: 4 // 数据点圆角,更圆润
|
||||
// },
|
||||
// areaStyle: {
|
||||
// opacity: 0.2, // 降低面积透明度,不抢柱状图视觉焦点
|
||||
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
// { offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||
// { offset: 1, color: 'rgba(255, 132, 0, 0)' }
|
||||
// ])
|
||||
// },
|
||||
// data: [65, 78, 52, 85, 60, 95, 72], // 完成率数据(0-100)
|
||||
// symbol: 'circle', // 数据点为圆形
|
||||
// symbolSize: 6 // 数据点大小
|
||||
// },
|
||||
// 2. 目标:柱状图(绑定左侧数量Y轴)
|
||||
{
|
||||
name:this.name,
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 24,
|
||||
label: {
|
||||
show: true, // 开启显示
|
||||
position: 'top', // 标签位置,可选:'top'、'middle'、'bottom'
|
||||
// 也可以使用绝对像素值定位,例如 [10, '50%']
|
||||
// position: [10, '50%'],
|
||||
|
||||
// 标签内容格式化,这里直接显示数据值
|
||||
formatter: '{c}',
|
||||
|
||||
// 文字样式
|
||||
color: 'rgba(11, 88, 255, 1)', // 文字颜色
|
||||
fontSize: 14, // 文字大小
|
||||
// fontWeight: 'bold', // 文字粗细
|
||||
// fontFamily: 'Arial, sans-serif' // 字体
|
||||
},
|
||||
itemStyle: {
|
||||
// 移除多余的 normal 层级,直接配置 color 渐变
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: this.seriesData
|
||||
},
|
||||
// 3. 达标:柱状图(绑定左侧数量Y轴)
|
||||
// {
|
||||
// name: '产量',
|
||||
// type: 'bar',
|
||||
// yAxisIndex: 0,
|
||||
// barWidth: 24,
|
||||
// // 关键修复:label 直接放在 series 下,而非 itemStyle 内
|
||||
// itemStyle: {
|
||||
// // 移除多余的 normal 层级,直接配置 color 渐变
|
||||
// color: {
|
||||
// type: 'linear',
|
||||
// x: 0,
|
||||
// y: 0,
|
||||
// x2: 0,
|
||||
// y2: 1,
|
||||
// colorStops: [
|
||||
// { offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||
// { offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||
// ]
|
||||
// },
|
||||
// borderRadius: [4, 4, 0, 0],
|
||||
// borderWidth: 0
|
||||
// },
|
||||
// data: [130, 220, 95, 255, 132, 332] // 达标数据(小于目标)
|
||||
// },
|
||||
// 4. 未达标:柱状图(绑定左侧数量Y轴)
|
||||
// {
|
||||
// name: '未达标',
|
||||
// type: 'bar',
|
||||
// yAxisIndex: 0,
|
||||
// barWidth: 18,
|
||||
// itemStyle: {
|
||||
// color: 'rgba(249, 164, 74, 1)',
|
||||
// borderRadius: [4, 4, 0, 0],
|
||||
// borderWidth: 0
|
||||
// },
|
||||
// data: [70, 60, 85, 45, 88, 18, 78] // 未达标数据(目标-达标)
|
||||
// }
|
||||
],
|
||||
// 图例:区分各系列,点击可控制显示隐藏
|
||||
// legend: {
|
||||
// top: 0,
|
||||
// left: 'center',
|
||||
// itemWidth: 12,
|
||||
// itemHeight: 8,
|
||||
// textStyle: {
|
||||
// color: 'rgba(0, 0, 0, 0.45)',
|
||||
// fontSize: 12
|
||||
// },
|
||||
// data: ['完成率', '目标', '达标', '未达标']
|
||||
// }
|
||||
};
|
||||
|
||||
option && this.myChart.setOption(option);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除窗口resize事件监听器
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
this.resizeHandler = null;
|
||||
}
|
||||
// 销毁图表,避免内存泄漏
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<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"
|
||||
<ReportHeader :dateData="dateData" top-title="折旧分析" :is-full-screen="isFullScreen"
|
||||
@screenfullChange="screenfullChange" @timeRangeChange="handleTimeChange" />
|
||||
<div class="main-body" style="
|
||||
flex: 1;
|
||||
@@ -39,7 +39,7 @@ import { mapState } from "vuex";
|
||||
import operatingLineChart from "../depreciationAnalysisComponents/operatingLineChart";
|
||||
import operatingLineChartCumulative from "../depreciationAnalysisComponents/operatingLineChartCumulative.vue";
|
||||
|
||||
import { getSalesRevenueGroupData } from '@/api/cockpit'
|
||||
import { getDepreciationAnalysisData } from '@/api/cockpit'
|
||||
export default {
|
||||
name: "DepreciationAnalysis",
|
||||
components: {
|
||||
@@ -59,6 +59,7 @@ export default {
|
||||
selectDate:{},
|
||||
monthData: {},
|
||||
ytdData:{},
|
||||
dateData:{}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -127,12 +128,8 @@ export default {
|
||||
this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined
|
||||
},
|
||||
methods: {
|
||||
// sortChange(value) {
|
||||
// this.sort = value
|
||||
// this.getData()
|
||||
// },
|
||||
getData() {
|
||||
getSalesRevenueGroupData({
|
||||
getDepreciationAnalysisData({
|
||||
startTime: this.dateData.startTime,
|
||||
endTime: this.dateData.endTime,
|
||||
sort: this.sort,
|
||||
@@ -143,12 +140,6 @@ export default {
|
||||
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) {
|
||||
@@ -198,28 +189,7 @@ export default {
|
||||
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>
|
||||
|
||||
@@ -1,298 +0,0 @@
|
||||
<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>
|
||||
@@ -154,6 +154,18 @@ export default {
|
||||
},
|
||||
// 未使用的蒸汽仪表盘可注释/删除
|
||||
// getSteamGaugeOption(value) { ... }
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 销毁 ResizeObserver,避免内存泄漏
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect();
|
||||
this.resizeObserver = null;
|
||||
}
|
||||
// 销毁图表实例
|
||||
if (this.electricityChart) {
|
||||
this.electricityChart.dispose();
|
||||
this.electricityChart = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
<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> -->
|
||||
@@ -1,221 +0,0 @@
|
||||
<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>
|
||||
@@ -46,7 +46,6 @@
|
||||
|
||||
<script>
|
||||
import operatingLineBar from './operatingLineBarSale.vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: "Container",
|
||||
@@ -62,8 +61,6 @@ export default {
|
||||
profitOptions: [
|
||||
{ label: '实际值:高~低', value: 1 },
|
||||
{ label: '实际值:低~高', value: 2 },
|
||||
{ label: '完成率:高~低', value: 3 },
|
||||
{ label: '完成率:低~高', value: 4 },
|
||||
]
|
||||
};
|
||||
},
|
||||
@@ -95,12 +92,6 @@ export default {
|
||||
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;
|
||||
}
|
||||
@@ -123,51 +114,7 @@ export default {
|
||||
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 || []
|
||||
},
|
||||
// 实际(柱状图)
|
||||
series: [
|
||||
{
|
||||
name: '实际',
|
||||
type: 'bar',
|
||||
@@ -175,68 +122,7 @@ export default {
|
||||
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,
|
||||
}
|
||||
}
|
||||
position: 'top'
|
||||
},
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
|
||||
@@ -158,18 +158,6 @@ export default {
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -154,18 +154,6 @@ export default {
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ export default {
|
||||
return {
|
||||
myChart: null, // 存储图表实例
|
||||
resizeHandler: null, // 存储resize事件处理函数
|
||||
isMounted: false,
|
||||
// 核心:基地名称与序号的映射表(固定顺序)
|
||||
baseNameToIndexMap: {
|
||||
'宜兴': 7,
|
||||
@@ -35,6 +36,7 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.isMounted = true;
|
||||
this.$nextTick(() => {
|
||||
this.initChart(); // 初始化图表(只执行一次)
|
||||
this.updateChart(); // 更新图表数据
|
||||
@@ -43,11 +45,11 @@ export default {
|
||||
watch: {
|
||||
chartData: {
|
||||
handler() {
|
||||
if (!this.isMounted) return;
|
||||
console.log(this.chartData, 'chartData');
|
||||
this.updateChart(); // 仅更新数据,不重新创建实例
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
deep: true
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
@@ -59,7 +61,7 @@ export default {
|
||||
initChart() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
if (process.env.NODE_ENV === 'development') console.warn('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -94,18 +96,6 @@ export default {
|
||||
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处理函数(命名函数,方便移除)
|
||||
@@ -157,11 +147,6 @@ export default {
|
||||
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
|
||||
}
|
||||
@@ -185,23 +170,23 @@ export default {
|
||||
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
|
||||
}
|
||||
// {
|
||||
// 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
|
||||
};
|
||||
|
||||
@@ -8,7 +8,8 @@ export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 存储图表实例,避免重复创建
|
||||
myChart: null, // 存储图表实例,避免重复创建
|
||||
isMounted: false
|
||||
};
|
||||
},
|
||||
props: {
|
||||
@@ -17,13 +18,10 @@ export default {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
}),
|
||||
// 校验数据格式
|
||||
// validator: (value) => {
|
||||
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||
// }
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.isMounted = true;
|
||||
this.$nextTick(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
@@ -34,18 +32,18 @@ export default {
|
||||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||
chartData: {
|
||||
handler() {
|
||||
if (!this.isMounted) return;
|
||||
console.log(this.chartData,'chartData');
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
immediate: true // 初始化时立即执行
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateChart() {
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
if (process.env.NODE_ENV === 'development') console.warn('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -72,20 +70,10 @@ export default {
|
||||
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,
|
||||
bottom:5,
|
||||
right: 10,
|
||||
left: 25,
|
||||
containLabel: true
|
||||
@@ -154,18 +142,6 @@ export default {
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -147,18 +147,6 @@ export default {
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -20,10 +20,6 @@
|
||||
">
|
||||
集团情况
|
||||
</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="
|
||||
@@ -32,7 +28,6 @@
|
||||
width: 1220px;
|
||||
background-color: rgba(249, 252, 255, 1);
|
||||
">
|
||||
<!-- <top-item /> -->
|
||||
<operatingBar :dateData="dateData" :chartData="chartData" @sort-change="sortChange" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,23 +83,6 @@ export default {
|
||||
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
|
||||
*/
|
||||
@@ -118,7 +96,7 @@ getRateFlag(rate, real, target) {
|
||||
const groupReal = [this.groupData.real]; // 实际值数组
|
||||
const groupRate = [this.groupData.rate]; // 完成率数组
|
||||
// 新增:集团rate对应的flag
|
||||
const groupFlag = [this.getRateFlag(groupRate[0], groupReal[0], groupTarget[0])];
|
||||
const groupFlag = [this.groupData.rate];
|
||||
|
||||
console.log('集团数据数组:', {
|
||||
groupTarget,
|
||||
@@ -139,7 +117,7 @@ getRateFlag(rate, real, target) {
|
||||
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));
|
||||
const factoryFlags = this.factoryData.map(item => item.rate >= 100 ? 1 : 0);
|
||||
|
||||
// 3. 组装最终的chartData(供子组件使用)
|
||||
this.chartData = {
|
||||
|
||||
@@ -20,10 +20,6 @@
|
||||
">
|
||||
集团情况
|
||||
</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="
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
<script>
|
||||
import operatingLineBar from './operatingLineBarSaleGroup.vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: "Container",
|
||||
@@ -40,30 +39,6 @@ export default {
|
||||
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',
|
||||
@@ -104,88 +79,6 @@ export default {
|
||||
]
|
||||
};
|
||||
|
||||
// 毛利率场景数据
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
<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> -->
|
||||
@@ -1,226 +0,0 @@
|
||||
<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>
|
||||
@@ -1,217 +0,0 @@
|
||||
<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>
|
||||
@@ -16,7 +16,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns:1624px;
|
||||
">
|
||||
<operatingLineChart :dateData="dateData" :monData="monData" />
|
||||
<operatingLineChart :dateData="dateData" :monthData="monthData" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||
@@ -25,40 +25,27 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<operatingLineChartCumulative :dateData="dateData" :totalData="totalData" />
|
||||
<!-- <keyWork /> -->
|
||||
<operatingLineChartCumulative :dateData="dateData" :ytdData="ytdData" />
|
||||
</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";
|
||||
import { getElectricityCostAnalysisData } from '@/api/cockpit'
|
||||
export default {
|
||||
name: "DayReport",
|
||||
name: "electricityCostAnalysis",
|
||||
components: {
|
||||
ReportHeader,
|
||||
operatingLineChartCumulative,
|
||||
operatingLineChart,
|
||||
// premProdStatus,
|
||||
Sidebar,
|
||||
},
|
||||
data() {
|
||||
@@ -68,9 +55,11 @@ export default {
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
value: 100,
|
||||
sort:1,
|
||||
selectDate:{},
|
||||
monthData: {},
|
||||
ytdData:{},
|
||||
dateData: {},
|
||||
monData: [],
|
||||
totalData: [],
|
||||
};
|
||||
},
|
||||
|
||||
@@ -140,28 +129,18 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
getData() {
|
||||
getProfitAnalysisTotalList({
|
||||
getElectricityCostAnalysisData({
|
||||
startTime: this.dateData.startTime,
|
||||
endTime: this.dateData.endTime,
|
||||
analysisObject: [
|
||||
"利润总额"
|
||||
],
|
||||
levelId: 1,
|
||||
// timeDim: this.dateData.mode
|
||||
sort: this.sort,
|
||||
index: undefined,
|
||||
factory: undefined
|
||||
}).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 : {}
|
||||
this.monthData= res.data.month
|
||||
this.ytdData = res.data.ytd
|
||||
})
|
||||
},
|
||||
handleTimeChange(obj) {
|
||||
// console.log(obj, 'obj');
|
||||
this.dateData= obj
|
||||
this.getData()
|
||||
},
|
||||
@@ -208,41 +187,18 @@ export default {
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -52,14 +52,6 @@
|
||||
</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>
|
||||
@@ -69,22 +61,13 @@ 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 { getElectricityCostAnalysisFData } from '@/api/cockpit'
|
||||
import moment from "moment";
|
||||
// import html2canvas from 'html2canvas'
|
||||
// import JsPDF from 'jspdf'
|
||||
export default {
|
||||
name: "DayReport",
|
||||
name: "electricityCostAnalysisBase",
|
||||
components: {
|
||||
ReportHeader,
|
||||
changeBase,
|
||||
@@ -107,9 +90,8 @@ export default {
|
||||
monData: {},
|
||||
totalData: {},
|
||||
trend: [],
|
||||
relatedData: [],
|
||||
trendName: '利润总额',
|
||||
// cusProData: {},
|
||||
relatedData: {},
|
||||
trendName: '总电费'
|
||||
};
|
||||
},
|
||||
|
||||
@@ -199,45 +181,26 @@ export default {
|
||||
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),
|
||||
sort: 1,
|
||||
index: this.trendName,
|
||||
factory: 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
|
||||
getElectricityCostAnalysisFData(requestParams).then((res) => {
|
||||
this.monData = res.data.month
|
||||
this.totalData = res.data.ytd
|
||||
this.relatedData = {
|
||||
relatedMon: res.data.currentMonthData.filter(item => {
|
||||
return item.name !== "利润总额";
|
||||
}), // 兜底月度数据
|
||||
relatedTotal: res.data.totalMonthData.filter(item => {
|
||||
return item.name !== "利润总额";
|
||||
}) // 兜底累计数据
|
||||
relatedMon: res.data.monthAnalysis,
|
||||
relatedTotal: res.data.ytdAnalysis
|
||||
}
|
||||
this.trend = res.data.dataTrend
|
||||
this.trend = res.data.trend
|
||||
});
|
||||
},
|
||||
|
||||
handleTimeChange(obj) {
|
||||
this.month = obj.targetMonth
|
||||
this.dateData = {
|
||||
startTime: obj.startTime,
|
||||
endTime: obj.endTime,
|
||||
// mode: obj.mode,
|
||||
}
|
||||
|
||||
this.getData()
|
||||
@@ -294,35 +257,12 @@ export default {
|
||||
},
|
||||
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>
|
||||
|
||||
@@ -30,11 +30,8 @@ export default {
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 移除:原 chartData 定义,改为计算属性
|
||||
};
|
||||
return {};
|
||||
},
|
||||
// 移除:原 watch 监听配置,计算属性自动响应 trendData 变化
|
||||
computed: {
|
||||
/**
|
||||
* chartData 计算属性:自动响应 trendData 变化,格式化并提取各字段数组
|
||||
@@ -51,16 +48,14 @@ export default {
|
||||
|
||||
// 遍历传入的 trendData 数组(响应式依赖,变化时自动重算)
|
||||
this.trendData.forEach((item) => {
|
||||
// 1. 格式化时间并推入时间数组
|
||||
const yearMonth = this.formatTimeToYearMonth(item.time);
|
||||
timeArr.push(yearMonth);
|
||||
timeArr.push(item.title);
|
||||
|
||||
// 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);
|
||||
valueArr.push(item.real ?? 0);
|
||||
diffValueArr.push(item.diff ?? 0);
|
||||
targetValueArr.push(item.budget ?? 0);
|
||||
proportionArr.push(item.rate ?? 0);
|
||||
completedArr.push(item.rate>=100 ? 1 : 0);
|
||||
});
|
||||
|
||||
// 组装并返回格式化后的数据(结构与原一致)
|
||||
@@ -81,15 +76,6 @@ export default {
|
||||
* @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)
|
||||
},
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<div class="dropdown-options" v-if="isDropdownShow">
|
||||
<div class="dropdown-option" v-for="(item, index) in profitOptions" :key="index"
|
||||
@click.stop="selectProfit(item)">
|
||||
{{ item }}
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,23 +60,19 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
isDropdownShow: false,
|
||||
selectedProfit: '利润总额', // 选中的名称,初始为null
|
||||
profitOptions: [
|
||||
'利润总额',
|
||||
'销量',
|
||||
'单价',
|
||||
'制造成本',
|
||||
'管理费用',
|
||||
'销售费用',
|
||||
'财务费用',
|
||||
'非经营性利润',
|
||||
]
|
||||
selectedProfit: '总电费', // 选中的名称,初始为null
|
||||
profitOptions:[
|
||||
{name:'总电费',unit:'万元'},
|
||||
{name:'原片电费',unit:'万元'},
|
||||
{name:'加工电费',unit:'万元'},
|
||||
{name:'外围电费',unit:'万元'},
|
||||
{name:'发电量',unit:'度'},
|
||||
{name:'日均发电量',unit:'度'}
|
||||
],
|
||||
unit:'万元',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// profitOptions() {
|
||||
// return this.categoryData.map(item => item.name) || [];
|
||||
// },
|
||||
currentDataSource() {
|
||||
return this.chartData
|
||||
},
|
||||
@@ -92,6 +88,7 @@ export default {
|
||||
|
||||
const salesData = {
|
||||
allPlaceNames: this.locations,
|
||||
unit: this.unit,
|
||||
series: [
|
||||
// 1. 完成率(折线图)
|
||||
{
|
||||
@@ -146,74 +143,7 @@ export default {
|
||||
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
|
||||
}
|
||||
}
|
||||
show: false
|
||||
},
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
@@ -242,6 +172,81 @@ export default {
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.value || [] // 实际销量(万元)
|
||||
},
|
||||
// 4. 实际差值标签(独立scatter系列,zlevel=1确保标签在最上层)
|
||||
{
|
||||
name: '__实际差值标签',
|
||||
type: 'scatter',
|
||||
yAxisIndex: 0,
|
||||
zlevel: 1,
|
||||
symbolSize: 0,
|
||||
tooltip: {
|
||||
show: false
|
||||
},
|
||||
data: (data.value || []).map((value, index) => ({
|
||||
value: value,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
width: 68,
|
||||
height: 20,
|
||||
formatter: () => {
|
||||
const diff = data.diffValue || [];
|
||||
const flags = data.completed || [];
|
||||
const currentDiff = diff[index] || 0;
|
||||
const currentFlag = flags[index] || 0;
|
||||
if (currentFlag === 1) {
|
||||
return `{achieved|${currentDiff}}{text|差值}`;
|
||||
} else {
|
||||
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',
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
},
|
||||
unachieved: {
|
||||
width: 'auto',
|
||||
padding: [5, 0, 5, 10],
|
||||
align: 'center',
|
||||
color: '#F9A44A',
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -250,9 +255,10 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
selectProfit(item) {
|
||||
this.selectedProfit = item;
|
||||
this.selectedProfit = item.name;
|
||||
this.unit = item.unit;
|
||||
this.isDropdownShow = false;
|
||||
this.$emit('handleGetItemData', item)
|
||||
this.$emit('handleGetItemData', item.name)
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -154,6 +154,18 @@ export default {
|
||||
},
|
||||
// 未使用的蒸汽仪表盘可注释/删除
|
||||
// getSteamGaugeOption(value) { ... }
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 销毁 ResizeObserver,避免内存泄漏
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect();
|
||||
this.resizeObserver = null;
|
||||
}
|
||||
// 销毁图表实例
|
||||
if (this.electricityChart) {
|
||||
this.electricityChart.dispose();
|
||||
this.electricityChart = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -17,9 +17,6 @@
|
||||
<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给柱状图组件 -->
|
||||
@@ -65,13 +62,13 @@ export default {
|
||||
*/
|
||||
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,
|
||||
completeRate: this.monData.rate ? Number(this.monData.rate) : 0,
|
||||
diff: this.monData.diff,
|
||||
real: this.monData.real,
|
||||
target: this.monData.target,
|
||||
thb: this.monData.momRate,
|
||||
// ...rawData,
|
||||
flag: this.monData.completed // 新增flag字段
|
||||
flag: this.monData.rate >= 100 ? 1 : 0,
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -84,12 +81,7 @@ export default {
|
||||
return 0;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
/**
|
||||
* 判断完成率对应的flag值(<100为0,≥100为1)
|
||||
* @param {number} rate 完成率(原始值,如89代表89%)
|
||||
* @returns {0|1} flag值
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -123,6 +123,7 @@ export default {
|
||||
const data = this.currentDataSource;
|
||||
const salesData = {
|
||||
allPlaceNames: this.locations,
|
||||
unit:'万元',
|
||||
series: [
|
||||
// 完成率(折线图)
|
||||
{
|
||||
@@ -174,69 +175,7 @@ export default {
|
||||
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
|
||||
}
|
||||
}
|
||||
show: false
|
||||
},
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
@@ -264,6 +203,78 @@ export default {
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.reals || []
|
||||
},
|
||||
// 实际差值标签(独立scatter系列,zlevel=1确保标签在最上层)
|
||||
{
|
||||
name: '__实际差值标签',
|
||||
type: 'scatter',
|
||||
yAxisIndex: 0,
|
||||
zlevel: 1,
|
||||
symbolSize: 0,
|
||||
tooltip: {
|
||||
show: false
|
||||
},
|
||||
data: (data.reals || []).map((value, index) => ({
|
||||
value: value,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [32, 0],
|
||||
width: 100,
|
||||
height: 22,
|
||||
formatter: () => {
|
||||
const diff = data.diff || [];
|
||||
const flags = data.flags || [];
|
||||
const currentDiff = diff[index] || 0;
|
||||
const currentFlag = flags[index] || 0;
|
||||
if (currentFlag === 1) {
|
||||
return `{achieved|${currentDiff}}{text|差值}`;
|
||||
} else {
|
||||
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',
|
||||
fontSize: 14
|
||||
},
|
||||
unachieved: {
|
||||
width: 'auto',
|
||||
padding: [5, 0, 5, 10],
|
||||
align: 'center',
|
||||
color: '#F9A44A',
|
||||
fontSize: 14
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -158,18 +158,6 @@ export default {
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user