Compare commits

...

24 Commits

Author SHA1 Message Date
31f23d1d77 单项页面加条件 2026-04-30 14:21:25 +08:00
c1acba7196 改bug 2026-04-30 10:25:34 +08:00
eebb0804b0 1 2026-04-24 15:54:21 +08:00
59a1654cbd 制造成本分析部分字段 2026-04-24 14:33:54 +08:00
62340233e2 制造成本分析部分字段 2026-04-24 09:18:07 +08:00
8839116a9a 修改部分字段 2026-04-24 08:51:39 +08:00
f1116245fc 消除红色图表未找到的警告和删除views下多余的vue文件 2026-04-23 10:00:01 +08:00
a6eaf41099 修改字段 2026-04-22 16:05:55 +08:00
cfcb4f5068 处理监听图表的函数,确保及时移除&生产环境不打印log 2026-04-22 11:07:10 +08:00
845e5a8af3 修复复合澄清剂传参 2026-04-21 12:57:54 +08:00
f6aa736bff 制造成本分析bug修改 2026-04-21 09:40:57 +08:00
835d4efd5b 制造成本分析配合后端修改&添加顶部账号显示 2026-04-20 13:02:19 +08:00
8105e74122 月度概览加单位 2026-04-17 14:41:01 +08:00
d8abdfbfec bug修改 2026-04-14 15:27:06 +08:00
446078f558 修改bug 2026-04-14 13:54:05 +08:00
7135ab0e4b 达标函数删除,图表label置于最顶层 2026-04-14 10:17:38 +08:00
0d74e762ce 预算和指标填报上传样式修改 2026-04-10 16:00:39 +08:00
24102f0d0d 预算和指标填报上传样式修改 2026-04-10 10:47:58 +08:00
bdd73b8868 预算和指标填报 2026-04-10 08:41:39 +08:00
0c8bd440ae 制造成本分析接口+单位修改 2026-04-08 15:26:20 +08:00
ed0fd63474 应收账款等4个新增页面 2026-04-03 16:14:28 +08:00
9d91188b98 营业收入-全成本分析页面修改 2026-04-03 09:10:04 +08:00
f0ac88af3d 驾驶舱&基础信息配置 2026-04-01 15:04:37 +08:00
161d6a1bdf 运营驾驶舱对接 2026-03-31 15:13:13 +08:00
441 changed files with 15724 additions and 24758 deletions

View File

@@ -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'
# 路由懒加载

View File

@@ -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

View File

@@ -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().

View File

@@ -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",

View File

@@ -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
View 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,
});
}

View File

@@ -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,
});
}

View File

@@ -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>

View File

@@ -96,7 +96,7 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
"/salesVolumeAnalysis",
'/procurementGainAnalysis',
'/fullCostAnalysis',
// '/expenseAnalysis',
'/electricityCostAnalysis',
"/cost", // cost 根路由
"/cost/profitImpactAnalysis", // cost 子菜单(完整路径)
];

View File

@@ -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'
}
/**

View File

@@ -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, // 销量占比1100%
completed: res.data.productSaleData.saleCompleted
},
{
name: "产量",
targetValue: res.data.productSaleData.productTarget, // 产量目标2000
value: res.data.productSaleData.productValue, // 实际产量1000
proportion: res.data.productSaleData.productProportion, // 产量占比0.550%
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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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> -->

View File

@@ -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>

View File

@@ -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,
}
}
}
}))
}
]
};

View File

@@ -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();
});
}
},
};

View File

@@ -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();
});
}
},
};

View File

@@ -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处理函数命名函数方便移除

View File

@@ -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();
});
}
},
};

View File

@@ -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();
});
}
},
};

View File

@@ -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 = {

View File

@@ -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 = {

View File

@@ -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>

View File

@@ -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> -->

View File

@@ -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>

View File

@@ -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>

View File

@@ -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')

View File

@@ -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')

View File

@@ -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')

View File

@@ -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(() => {});
}

View File

@@ -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(() => {});
}

View File

@@ -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(() => {});
}

View File

@@ -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>

View File

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

View File

@@ -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;
}
}
}
}
/* 日期选择器样式保持不变 */

View File

@@ -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,

View File

@@ -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

View File

@@ -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>仅允许导入xlsxlsx格式文件</span>
<span>仅允许导入{{timeType==='month'?'月':'年'}}预算仅允许导入xlsxlsx格式文件</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 {
// 内联样式已优化行间距,此处可留空或补充

View File

@@ -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;
}
}
}
}
/* 自定义下拉框样式(替换原有日期选择器样式) */

View File

@@ -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)";
}

View File

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

View File

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

View File

@@ -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: {

View File

@@ -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>

View File

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

View File

@@ -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>

View File

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

View File

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

View File

@@ -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>

View File

@@ -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

View File

@@ -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 {
// 内联样式已优化行间距,此处可留空或补充

View File

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

View File

@@ -15,7 +15,7 @@
<span class="legend-text">未完成</span>
</div>
</div>
<base-table style="height: 180px;" :page="1" :limit="10" :show-index="true" :beilv="1"
<base-table style="height: 204px;" :page="1" :limit="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'
};
});
})
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"
};

View File

@@ -1,18 +1,18 @@
<template>
<div class="coreItem">
<!-- 单独渲染第一个item -->
<div class="item" v-if="itemList.length > 0">
<div class="unit">{{ itemList[0].unit }}</div>
<div class="item" v-if="itemList.length > 0" v-for="(item, index) in itemList">
<div class="unit">{{ item.unit }}</div>
<div class="item-content">
<div class="content-wrapper">
<div class="left">
<div class="number">{{ itemList[0].targetValue }}</div>
<div class="number">{{ item.targetValue }}</div>
<div class="title">预算值</div>
</div>
<div class="line"></div>
<div class="right">
<div class="number" :style="{ color: getColor(itemList[0].currentValue, itemList[0].targetValue) }">
{{ itemList[0].currentValue }}
<div class="number" :style="{ color: getColor(item.currentValue, item.targetValue) }">
{{ item.currentValue }}
</div>
<div class="title">实际值</div>
</div>
@@ -23,7 +23,7 @@
<!-- 进度条颜色和宽度动态绑定 -->
<div class="progress-bar" :style="{
width: itemList[0].progress + '%',
background: getColor(itemList[0].currentValue, itemList[0].targetValue)
background: getColor(item.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;

View File

@@ -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>

View File

@@ -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>

View File

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

View File

@@ -1,52 +1,96 @@
<template>
<div class="coreItem">
<div class="item" @click="handleRoute(item.route)" v-for="(item, index) in itemList" :key="index">
<div class="name">{{ item.name }}</div>
<div class="item-content">
<div class="content-wrapper">
<div class="left">
<div class="number" style="color: rgba(103, 103, 103, 0.79);">{{ item.targetValue }}</div>
<div class="title" style="color: rgba(134, 134, 135, 1);">目标值</div>
<div>
<div class="coreItem">
<div class="item" @click="handleRoute(item.route)" v-for="(item, index) in itemList" :key="index" v-if='index<4'>
<div class="name">{{ item.name }}</div>
<div class="item-content">
<div class="content-wrapper">
<div class="left">
<div class="number" style="color: rgba(103, 103, 103, 0.79);">{{ item.targetValue }}</div>
<div class="title" style="color: rgba(134, 134, 135, 1);">目标值</div>
</div>
<div class="line"></div>
<!-- 实际值根据 实际值目标值 动态绑定类名 -->
<div class="right">
<div class="number" :class="{
'number-exceed': item.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>

View File

@@ -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' }
];
// 遍历映射关系,转换数据

View File

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

View File

@@ -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>

View File

@@ -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() {

View File

@@ -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();
});
}
},
};

View File

@@ -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坐标

View File

@@ -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>

View File

@@ -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, // 是否正在拖拽

View File

@@ -56,7 +56,7 @@ export default {
progress: 80
},
{
unit: "双镀面板·万㎡",
unit: "双镀销量·万㎡",
targetValue: 15,
currentValue: 13.8,
progress: 92

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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> -->

View File

@@ -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>

View File

@@ -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) => {

View File

@@ -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();
});
}
},
};

View File

@@ -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();
});
}
},
};

View File

@@ -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
};

View File

@@ -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();
});
}
},
};

View File

@@ -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();
});
}
},
};

View File

@@ -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 = {

View File

@@ -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="

View File

@@ -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;
}

View File

@@ -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> -->

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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)
},

View File

@@ -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)
}
},
};

View File

@@ -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>

View File

@@ -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>

View File

@@ -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
}
}
}
}))
}
]
};

View File

@@ -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