Compare commits

..

29 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
05fe91618c 运营驾驶舱修改部分 2026-03-27 11:12:13 +08:00
b85ceb2542 merge B 2026-03-27 10:06:27 +08:00
9b0a768216 指标和预算填报删除指标类型 2026-03-27 08:36:02 +08:00
ee4fdbd45b 修改icon提示 2026-03-20 10:25:15 +08:00
5465a43bcc 去掉默认的账密 2026-03-19 09:13:27 +08:00
447 changed files with 16327 additions and 24733 deletions

View File

@@ -12,9 +12,11 @@ VUE_APP_TITLE = 洛玻集团驾驶舱
# 杨姗姗 # 杨姗姗
# VUE_APP_BASE_API = 'http://172.16.20.218:7070' # VUE_APP_BASE_API = 'http://172.16.20.218:7070'
# 小田 # 小田
VUE_APP_BASE_API = 'http://172.16.19.232:7070' # VUE_APP_BASE_API = 'http://172.16.19.232:7070'
# 测试 # 测试
# VUE_APP_BASE_API = 'http://192.168.0.35:8080' VUE_APP_BASE_API = 'http://192.168.0.35:8080'
# 闫阳
# VUE_APP_BASE_API = 'http://172.16.19.131:7070'
# 路由懒加载 # 路由懒加载

View File

@@ -1,4 +1,5 @@
# 生产环境配置 # 生产环境配置
NODE_ENV = production
ENV = 'production' ENV = 'production'
# 页面标题 # 页面标题
@@ -14,7 +15,7 @@ VUE_APP_BASE_API = ''
PUBLIC_PATH = '' PUBLIC_PATH = ''
# 二级部署路径 # 二级部署路径
VUE_APP_APP_NAME ='yudao-admin' # VUE_APP_APP_NAME ='yudao-admin'
# 多租户的开关 # 多租户的开关
VUE_APP_TENANT_ENABLE = true 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 = { module.exports = {
presets: [ presets: [
// https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
'@vue/cli-plugin-babel/preset' '@vue/cli-plugin-babel/preset'
], ],
plugins: plugins,
'env': { 'env': {
'development': { 'development': {
// babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require(). // 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", "code-brick-zj": "^1.1.1",
"core-js": "^3.26.0", "core-js": "^3.26.0",
"crypto-js": "^4.0.0", "crypto-js": "^4.0.0",
"diagram-js": "^15.12.0",
"echarts": "5.4.0", "echarts": "5.4.0",
"element-ui": "2.15.14", "element-ui": "2.15.14",
"file-saver": "2.0.5", "file-saver": "2.0.5",
@@ -65,6 +66,7 @@
"screenfull": "5.0.2", "screenfull": "5.0.2",
"sortablejs": "1.10.2", "sortablejs": "1.10.2",
"throttle-debounce": "2.1.0", "throttle-debounce": "2.1.0",
"video.js": "^8.23.7",
"vue": "2.7.14", "vue": "2.7.14",
"vue-count-to": "1.0.13", "vue-count-to": "1.0.13",
"vue-cropper": "0.5.8", "vue-cropper": "0.5.8",
@@ -83,6 +85,7 @@
"@vue/compiler-sfc": "^3.0.1", "@vue/compiler-sfc": "^3.0.1",
"@vue/eslint-config-prettier": "^5.0.0", "@vue/eslint-config-prettier": "^5.0.0",
"babel-eslint": "10.1.0", "babel-eslint": "10.1.0",
"babel-plugin-transform-remove-console": "^6.9.4",
"bpmn-js": "8.9.0", "bpmn-js": "8.9.0",
"bpmn-js-properties-panel": "0.46.0", "bpmn-js-properties-panel": "0.46.0",
"chalk": "4.1.0", "chalk": "4.1.0",

View File

@@ -5,6 +5,10 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit"> <meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <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"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= webpackConfig.name %></title> <title><%= webpackConfig.name %></title>
<!--[if lt IE 11]><script>window.location.href='html/ie.html';</script><![endif]--> <!--[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, 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) { export function getRealMonthPage(data) {
return request({ return request({
@@ -346,3 +364,38 @@ export function getDictListData(query) {
params: 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

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>切片</title> <title>全屏</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="01-运营驾驶舱" transform="translate(-1864.000000, -12.000000)" fill="#0B58FF"> <g id="01-运营驾驶舱" transform="translate(-1864.000000, -12.000000)" fill="#0B58FF">
<g id="icon/可视化/全屏" transform="translate(1864.000000, 12.000000)"> <g id="icon/可视化/全屏" transform="translate(1864.000000, 12.000000)">

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>展开备份</title> <title>展开菜单</title>
<g id="12-月修改-2-版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="12-月修改-2-版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="01-运营驾驶舱展开" transform="translate(-1818.000000, -12.000000)" fill="#0B58FF"> <g id="01-运营驾驶舱展开" transform="translate(-1818.000000, -12.000000)" fill="#0B58FF">
<g id="展开备份" transform="translate(1818.000000, 12.000000)"> <g id="展开菜单" transform="translate(1818.000000, 12.000000)">
<rect id="矩形" stroke="#0B58FF" opacity="0" x="0.5" y="0.5" width="31" height="31"></rect> <rect id="矩形" stroke="#0B58FF" opacity="0" x="0.5" y="0.5" width="31" height="31"></rect>
<path d="M26.9054416,1.26315789 L27.1627614,1.2715785 C27.589262,1.29963047 28.0033877,1.39768715 28.3969525,1.56449606 C28.853625,1.75530116 29.2618455,2.03033772 29.6157539,2.38424612 C29.967102,2.73559426 30.2434779,3.1465945 30.4352441,3.6024321 C30.6356746,4.07532525 30.7368421,4.57721075 30.7368421,5.09455843 L30.7368421,26.9054416 L30.7284215,27.1627614 C30.7003695,27.589262 30.6023129,28.0033877 30.4355039,28.3969525 C30.2446988,28.853625 29.9696623,29.2618455 29.6157539,29.6157539 C29.2644057,29.967102 28.8534055,30.2434779 28.3975679,30.4352441 C27.9246748,30.6356746 27.4227892,30.7368421 26.9054416,30.7368421 L5.09455843,30.7368421 L4.83723859,30.7284215 C4.41073802,30.7003695 3.99661229,30.6023129 3.60304751,30.4355039 C3.14637497,30.2446988 2.73815452,29.9696623 2.38424612,29.6157539 C2.03289798,29.2644057 1.75652209,28.8534055 1.56475593,28.3975679 C1.36432537,27.9246748 1.26315789,27.4227892 1.26315789,26.9054416 L1.26315789,5.09455843 L1.2715785,4.83723859 C1.29963047,4.41073802 1.39768715,3.99661229 1.56449606,3.60304751 C1.75530116,3.14637497 2.03033772,2.73815452 2.38424612,2.38424612 C2.73559426,2.03289798 3.1465945,1.75652209 3.6024321,1.56475593 C4.07532525,1.36432537 4.57721075,1.26315789 5.09455843,1.26315789 L26.9054416,1.26315789 Z M26.9054416,3.17216771 L5.09455843,3.17216771 L4.94438644,3.17795455 C3.95326514,3.25463661 3.17216771,4.08386243 3.17216771,5.09455843 L3.17216771,26.9054416 L3.17795455,27.0556136 C3.25463661,28.0467349 4.08386243,28.8278323 5.09455843,28.8278323 L26.9054416,28.8278323 L27.0556136,28.8220454 C28.0467349,28.7453634 28.8278323,27.9161376 28.8278323,26.9054416 L28.8278323,5.09455843 L28.8220454,4.94438644 C28.7453634,3.95326514 27.9161376,3.17216771 26.9054416,3.17216771 Z M29.2015182,9.76714413 L29.2015182,11.8615014 L2.12281432,11.8615014 L2.12281432,9.76714413 L29.2015182,9.76714413 Z" id="形状结合" fill-rule="nonzero" opacity="0.79078311"></path> <path d="M26.9054416,1.26315789 L27.1627614,1.2715785 C27.589262,1.29963047 28.0033877,1.39768715 28.3969525,1.56449606 C28.853625,1.75530116 29.2618455,2.03033772 29.6157539,2.38424612 C29.967102,2.73559426 30.2434779,3.1465945 30.4352441,3.6024321 C30.6356746,4.07532525 30.7368421,4.57721075 30.7368421,5.09455843 L30.7368421,26.9054416 L30.7284215,27.1627614 C30.7003695,27.589262 30.6023129,28.0033877 30.4355039,28.3969525 C30.2446988,28.853625 29.9696623,29.2618455 29.6157539,29.6157539 C29.2644057,29.967102 28.8534055,30.2434779 28.3975679,30.4352441 C27.9246748,30.6356746 27.4227892,30.7368421 26.9054416,30.7368421 L5.09455843,30.7368421 L4.83723859,30.7284215 C4.41073802,30.7003695 3.99661229,30.6023129 3.60304751,30.4355039 C3.14637497,30.2446988 2.73815452,29.9696623 2.38424612,29.6157539 C2.03289798,29.2644057 1.75652209,28.8534055 1.56475593,28.3975679 C1.36432537,27.9246748 1.26315789,27.4227892 1.26315789,26.9054416 L1.26315789,5.09455843 L1.2715785,4.83723859 C1.29963047,4.41073802 1.39768715,3.99661229 1.56449606,3.60304751 C1.75530116,3.14637497 2.03033772,2.73815452 2.38424612,2.38424612 C2.73559426,2.03289798 3.1465945,1.75652209 3.6024321,1.56475593 C4.07532525,1.36432537 4.57721075,1.26315789 5.09455843,1.26315789 L26.9054416,1.26315789 Z M26.9054416,3.17216771 L5.09455843,3.17216771 L4.94438644,3.17795455 C3.95326514,3.25463661 3.17216771,4.08386243 3.17216771,5.09455843 L3.17216771,26.9054416 L3.17795455,27.0556136 C3.25463661,28.0467349 4.08386243,28.8278323 5.09455843,28.8278323 L26.9054416,28.8278323 L27.0556136,28.8220454 C28.0467349,28.7453634 28.8278323,27.9161376 28.8278323,26.9054416 L28.8278323,5.09455843 L28.8220454,4.94438644 C28.7453634,3.95326514 27.9161376,3.17216771 26.9054416,3.17216771 Z M29.2015182,9.76714413 L29.2015182,11.8615014 L2.12281432,11.8615014 L2.12281432,9.76714413 L29.2015182,9.76714413 Z" id="形状结合" fill-rule="nonzero" opacity="0.79078311"></path>
</g> </g>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,6 +1,6 @@
<!--?xml version="1.0" encoding="UTF-8"?--> <!--?xml version="1.0" encoding="UTF-8"?-->
<svg width="100%" height="100%" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet"> <svg width="100%" height="100%" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet">
<title>icon/可视化/退出全屏</title> <title>退出全屏</title>
<g id="00首页" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="00首页" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(-1802.000000, -40.000000)" fill="#0B58FF" id="icon/可视化/退出全屏"> <g transform="translate(-1802.000000, -40.000000)" fill="#0B58FF" id="icon/可视化/退出全屏">
<g transform="translate(1802.000000, 40.000000)"> <g transform="translate(1802.000000, 40.000000)">

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -14,15 +14,16 @@
<i class="el-icon-caret-bottom" /> <i class="el-icon-caret-bottom" />
</div> </div>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<router-link to="/user/profile"> <!-- <router-link to="/user/profile">
<el-dropdown-item>个人中心</el-dropdown-item> <el-dropdown-item>个人中心</el-dropdown-item>
</router-link> </router-link> -->
<!-- <el-dropdown-item @click.native="setting = true"> <!-- <el-dropdown-item @click.native="setting = true">
<span>布局设置</span> <span>布局设置</span>
</el-dropdown-item> --> </el-dropdown-item> -->
<el-dropdown-item divided @click.native="logout"> <el-dropdown-item @click.native="logout">
<span>退出登录</span> <span>退出登录</span>
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item divided @click.native='handleToggle'>切换账号</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
</div> </div>
@@ -97,7 +98,12 @@ export default {
checkPermi(permissions) { checkPermi(permissions) {
return this.$auth.hasPermi(permissions) return this.$auth.hasPermi(permissions)
return true; return true;
} },
handleToggle() {
this.$store.dispatch('LogOut').then(() => {
location.href = getPath('/index');
})
},
} }
} }
</script> </script>

View File

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

View File

@@ -83,7 +83,10 @@ export const DICT_TYPE = {
PROMOTION_CONDITION_TYPE: 'promotion_condition_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 operatingLineChart from "../accountsReceivableComponents/operatingLineChart";
import operatingLineChartCumulative from "../accountsReceivableComponents/operatingLineChartCumulative.vue"; import operatingLineChartCumulative from "../accountsReceivableComponents/operatingLineChartCumulative.vue";
import { getSalesRevenueGroupData } from '@/api/cockpit' import { getAccountsReceivableData } from '@/api/cockpit'
export default { export default {
name: "AccountsReceivable", name: "AccountsReceivable",
components: { components: {
@@ -59,6 +59,7 @@ export default {
selectDate:{}, selectDate:{},
monthData: {}, monthData: {},
ytdData:{}, ytdData:{},
dateData: {},
}; };
}, },
@@ -127,28 +128,16 @@ export default {
this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined
}, },
methods: { methods: {
// sortChange(value) {
// this.sort = value
// this.getData()
// },
getData() { getData() {
getSalesRevenueGroupData({ getAccountsReceivableData({
startTime: this.dateData.startTime, startTime: this.dateData.startTime,
endTime: this.dateData.endTime, endTime: this.dateData.endTime,
sort: this.sort, sort: this.sort,
index: undefined, index: undefined,
factory: undefined factory: undefined
// timeDim: obj.mode
}).then((res) => { }).then((res) => {
console.log(res);
this.monthData= res.data.month this.monthData= res.data.month
this.ytdData = res.data.ytd 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) { handleTimeChange(obj) {
@@ -198,28 +187,7 @@ export default {
return false; return false;
} }
screenfull.toggle(this.$refs.dayReportB); 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> </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) { ... } // getSteamGaugeOption(value) { ... }
},
beforeDestroy() {
// 销毁 ResizeObserver避免内存泄漏
if (this.resizeObserver) {
this.resizeObserver.disconnect();
this.resizeObserver = null;
}
// 销毁图表实例
if (this.electricityChart) {
this.electricityChart.dispose();
this.electricityChart = null;
}
} }
} }
</script> </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, yAxisIndex: 0,
barWidth: 40, barWidth: 40,
label: { label: {
show: true, show: false
position: 'top',
offset: [32, 0],
width: 100,
height: 22,
formatter: (params) => {
const diff = data.diff || [];
const flags = data.flags || [];
const currentDiff = diff[params.dataIndex] || 0;
const currentFlag = flags[params.dataIndex] || 0;
const prefix = currentFlag === 1 ? '+' : '-';
// 根据标志位选择不同的样式类
if (currentFlag === 1) {
// 达标 - 使用 rate-achieved 样式
return `{achieved|${currentDiff}}{text|差值}`;
} else {
// 未达标 - 使用 rate-unachieved 样式
return `{unachieved|${currentDiff}}{text|差值}`;
}
},
backgroundColor: {
type: 'linear',
x: 0, y: 0, x2: 0, y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
{ offset: 0.2, color: '#ffffff' },
{ offset: 1, color: '#ffffff' }
]
},
shadowColor: 'rgba(191,203,215,0.5)',
shadowBlur: 2,
shadowOffsetX: 0,
shadowOffsetY: 2,
borderRadius: 4,
borderColor: '#BFCBD577',
borderWidth: 0,
lineHeight: 26,
rich: {
text: {
width: 'auto',
padding: [5, 10, 5, 0],
align: 'center',
color: '#464646',
fontSize: 14,
},
achieved: {
width: 'auto',
padding: [5, 0, 5, 10],
align: 'center',
color: '#76DABE', // 与达标的 offset: 1 颜色一致
fontSize: 14,
},
// 未达标样式
unachieved: {
width: 'auto',
padding: [5, 0, 5, 10],
align: 'center',
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
fontSize: 14,
}
}
}, },
itemStyle: { itemStyle: {
color: (params) => { color: (params) => {
@@ -264,6 +202,78 @@ export default {
borderWidth: 0 borderWidth: 0
}, },
data: data.reals || [] 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); 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); 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 { return {
myChart: null, // 存储图表实例 myChart: null, // 存储图表实例
resizeHandler: null, // 存储resize事件处理函数 resizeHandler: null, // 存储resize事件处理函数
isMounted: false,
// 核心:基地名称与序号的映射表(固定顺序) // 核心:基地名称与序号的映射表(固定顺序)
baseNameToIndexMap: { baseNameToIndexMap: {
'宜兴': 7, '宜兴': 7,
@@ -35,6 +36,7 @@ export default {
}, },
}, },
mounted() { mounted() {
this.isMounted = true;
this.$nextTick(() => { this.$nextTick(() => {
this.initChart(); // 初始化图表(只执行一次) this.initChart(); // 初始化图表(只执行一次)
this.updateChart(); // 更新图表数据 this.updateChart(); // 更新图表数据
@@ -43,11 +45,11 @@ export default {
watch: { watch: {
chartData: { chartData: {
handler() { handler() {
if (!this.isMounted) return;
console.log(this.chartData, 'chartData'); console.log(this.chartData, 'chartData');
this.updateChart(); // 仅更新数据,不重新创建实例 this.updateChart(); // 仅更新数据,不重新创建实例
}, },
deep: true, deep: true
immediate: true
}, },
}, },
beforeDestroy() { beforeDestroy() {
@@ -59,7 +61,7 @@ export default {
initChart() { initChart() {
const chartDom = this.$refs.cockpitEffChip; const chartDom = this.$refs.cockpitEffChip;
if (!chartDom) { if (!chartDom) {
console.error('图表容器未找到!'); if (process.env.NODE_ENV === 'development') console.warn('图表容器未找到!');
return; return;
} }
@@ -94,18 +96,6 @@ export default {
if (itemName === undefined) { if (itemName === undefined) {
return; return;
} }
// 路由跳转时携带序号(或名称+序号)
this.$router.push({
path: 'operatingRevenueBase',
query: { // 使用query传递参数推荐也可使用params
// baseName: itemName,
factory: baseIndex,
dateData: this.dateData
}
// 若仍需用base作为参数
// base: itemName,
// params: { baseIndex: baseIndex }
});
}); });
// 定义resize处理函数命名函数方便移除 // 定义resize处理函数命名函数方便移除

View File

@@ -8,7 +8,8 @@ export default {
components: {}, components: {},
data() { data() {
return { return {
myChart: null // 存储图表实例,避免重复创建 myChart: null, // 存储图表实例,避免重复创建
isMounted: false
}; };
}, },
props: { props: {
@@ -24,6 +25,7 @@ export default {
} }
}, },
mounted() { mounted() {
this.isMounted = true;
this.$nextTick(() => { this.$nextTick(() => {
this.updateChart(); this.updateChart();
}); });
@@ -34,18 +36,18 @@ export default {
// 深度监听数据变化,仅更新图表配置(不销毁实例) // 深度监听数据变化,仅更新图表配置(不销毁实例)
chartData: { chartData: {
handler() { handler() {
if (!this.isMounted) return;
console.log(this.chartData,'chartData'); console.log(this.chartData,'chartData');
this.updateChart(); this.updateChart();
}, },
deep: true, deep: true
immediate: true // 初始化时立即执行
} }
}, },
methods: { methods: {
updateChart() { updateChart() {
const chartDom = this.$refs.cockpitEffChip; const chartDom = this.$refs.cockpitEffChip;
if (!chartDom) { if (!chartDom) {
console.error('图表容器未找到!'); if (process.env.NODE_ENV === 'development') console.warn('图表容器未找到!');
return; return;
} }
@@ -154,18 +156,6 @@ export default {
}; };
option && this.myChart.setOption(option); 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); 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; width: 1220px;
background-color: rgba(249, 252, 255, 1); background-color: rgba(249, 252, 255, 1);
"> ">
<!-- <top-item /> -->
<operatingBar :dateData="dateData" :chartData="chartData" @sort-change="sortChange" /> <operatingBar :dateData="dateData" :chartData="chartData" @sort-change="sortChange" />
</div> </div>
</div> </div>
@@ -88,23 +87,6 @@ export default {
sortChange(value) { sortChange(value) {
this.$emit('sort-change', 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 * 核心处理函数:在所有数据都准备好后,才组装 chartData
*/ */
@@ -118,7 +100,7 @@ getRateFlag(rate, real, target) {
const groupReal = [this.groupData.real]; // 实际值数组 const groupReal = [this.groupData.real]; // 实际值数组
const groupRate = [this.groupData.rate]; // 完成率数组 const groupRate = [this.groupData.rate]; // 完成率数组
// 新增集团rate对应的flag // 新增集团rate对应的flag
const groupFlag = [this.getRateFlag(groupRate[0], groupReal[0], groupTarget[0])]; const groupFlag = [this.groupData.rate >= 100 ? 1 : 0];
console.log('集团数据数组:', { console.log('集团数据数组:', {
groupTarget, groupTarget,
@@ -139,7 +121,7 @@ getRateFlag(rate, real, target) {
const factoryRate = this.factoryData.map(item => item.rate || 0); const factoryRate = this.factoryData.map(item => item.rate || 0);
const factoryDiff = this.factoryData.map(item => item.diff || 0); const factoryDiff = this.factoryData.map(item => item.diff || 0);
// 新增每个工厂rate对应的flag数组 // 新增每个工厂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供子组件使用 // 3. 组装最终的chartData供子组件使用
this.chartData = { this.chartData = {

View File

@@ -32,7 +32,6 @@
width: 1220px; width: 1220px;
background-color: rgba(249, 252, 255, 1); background-color: rgba(249, 252, 255, 1);
"> ">
<!-- <top-item /> -->
<operatingBar :dateData="dateData" @sort-change="sortChange" :chartData="chartData" /> <operatingBar :dateData="dateData" @sort-change="sortChange" :chartData="chartData" />
</div> </div>
</div> </div>
@@ -83,23 +82,6 @@ export default {
sortChange(value) { sortChange(value) {
this.$emit('sort-change', 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 * 核心处理函数:在所有数据都准备好后,才组装 chartData
@@ -114,7 +96,7 @@ getRateFlag(rate, real, target) {
const groupReal = [this.groupData.real]; // 实际值数组 const groupReal = [this.groupData.real]; // 实际值数组
const groupRate = [this.groupData.rate]; // 完成率数组 const groupRate = [this.groupData.rate]; // 完成率数组
// 新增集团rate对应的flag // 新增集团rate对应的flag
const groupFlag = [this.getRateFlag(groupRate[0], groupReal[0], groupTarget[0])]; const groupFlag = [this.groupData.rate >= 100 ? 1 : 0];
console.log('集团数据数组:', { console.log('集团数据数组:', {
groupTarget, groupTarget,
@@ -135,7 +117,7 @@ getRateFlag(rate, real, target) {
const factoryRate = this.factoryData.map(item => item.rate || 0); const factoryRate = this.factoryData.map(item => item.rate || 0);
const factoryDiff = this.factoryData.map(item => item.diff || 0); const factoryDiff = this.factoryData.map(item => item.diff || 0);
// 新增每个工厂rate对应的flag数组 // 新增每个工厂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供子组件使用 // 3. 组装最终的chartData供子组件使用
this.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-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="所属基地" prop="daySpan"> <el-form-item label="所属基地" prop="factoryList">
<el-select v-model="form.daySpan" placeholder="请选择" style="width: 100%;"> <el-select v-model="form.factoryList" placeholder="请选择" style="width: 100%;" multiple>
<el-option label="" :value= '0' ></el-option> <el-option v-for='item in facList' :key='item.value' :label="item.name" :value= 'item.value' ></el-option>
<el-option label="是" :value= '1' ></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -23,7 +22,7 @@
</el-form> </el-form>
</template> </template>
<script> <script>
// import { getGroupClasses, updateGroupClasses, createGroupClasses, getCode } from '@/api/base/groupClasses' import { addCustomerConfig, updateCustomerConfig, getCustomerConfig } from '@/api/basicInfoConfig'
export default { export default {
name: 'CustomerInfoAdd', name: 'CustomerInfoAdd',
data() { data() {
@@ -32,14 +31,24 @@ export default {
id: '', id: '',
name: '', name: '',
code: '', code: '',
daySpan:'', factoryList:'',
}, },
isEdit: false, //是否是编辑 isEdit: false, //是否是编辑
rules: { rules: {
name: [{ required: true, message: '请输入客户名称', trigger: 'blur' }], name: [{ required: true, message: '请输入客户名称', trigger: 'blur' }],
code: [{ 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: { methods: {
@@ -47,11 +56,11 @@ export default {
if (id) { if (id) {
this.isEdit = true this.isEdit = true
this.form.id = id this.form.id = id
// getGroupClasses(id).then((res) => { getCustomerConfig({id}).then((res) => {
// if (res.code === 0) { if (res.code === 0) {
// this.form = res.data this.form = res.data
// } }
// }) })
} }
}, },
submitForm() { submitForm() {
@@ -59,14 +68,14 @@ export default {
if (valid) { if (valid) {
if (this.isEdit) { if (this.isEdit) {
//编辑 //编辑
updateGroupClasses({ ...this.form }).then((res) => { updateCustomerConfig({ ...this.form }).then((res) => {
if (res.code === 0) { if (res.code === 0) {
this.$modal.msgSuccess("操作成功"); this.$modal.msgSuccess("操作成功");
this.$emit('successSubmit') this.$emit('successSubmit')
} }
}) })
} else { } else {
createGroupClasses({ ...this.form }).then((res) => { addCustomerConfig({ ...this.form }).then((res) => {
if (res.code === 0) { if (res.code === 0) {
this.$modal.msgSuccess("操作成功"); this.$modal.msgSuccess("操作成功");
this.$emit('successSubmit') this.$emit('successSubmit')

View File

@@ -1,33 +1,39 @@
<template> <template>
<el-form ref="form" :rules="rules" label-width="110px" :model="form"> <el-form ref="form" :rules="rules" label-width="120px" :model="form">
<el-row> <el-row>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="重点工作" prop="name"> <el-form-item label="重点工作" prop="importantWork">
<el-input v-model="form.name"></el-input> <el-input v-model="form.importantWork"></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="单位" prop="daySpan"> <el-form-item label="单位" prop="unit">
<el-select v-model="form.daySpan" placeholder="请选择" style="width: 100%;"> <el-select v-model="form.unit" placeholder="请选择" style="width: 100%;">
<el-option label="" :value= '0' ></el-option> <el-option v-for='item in unitList' :key='item.value' :label="item.label" :value= 'item.value' ></el-option>
<el-option label="件" :value= '1' ></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="所属年份" prop="daySpan"> <el-form-item label="所属年份" prop="time">
<el-date-picker <el-date-picker
v-model="value3" v-model="form.time"
type="year" type="year"
placeholder="选择年"> placeholder="选择年"
style="width: 100%;">
</el-date-picker> </el-date-picker>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="累计值计算方式" prop="daySpan2"> <el-form-item label="累计值计算方式" prop="calculateMethod">
<el-select v-model="form.daySpan" placeholder="请选择" style="width: 100%;"> <el-select v-model="form.calculateMethod" placeholder="请选择" style="width: 100%;">
<el-option label="" :value= '0' ></el-option> <el-option v-for='item in calculateMethodList' :key='item.value' :label="item.label" :value= 'item.value' ></el-option>
<el-option label="日" :value= '1' ></el-option> </el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="数值目标属性" prop="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-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -35,24 +41,32 @@
</el-form> </el-form>
</template> </template>
<script> <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 { export default {
name: 'groupKeyAdd', name: 'groupKeyAdd',
data() { data() {
return { return {
form: { form: {
id: '', id: '',
name: '', importantWork: '',
code: '', unit: '',
daySpan:'', time:'',
calculateMethod:'',
targetProperty:'',
}, },
isEdit: false, //是否是编辑 isEdit: false, //是否是编辑
rules: { rules: {
name: [{ required: true, message: '请输入重点工作', trigger: 'blur' }], importantWork: [{ required: true, message: '请输入重点工作', trigger: 'blur' }],
daySpan: [{ required: true, message: '请选择单位', trigger: 'change' }], unit: [{ required: true, message: '请选择单位', trigger: 'change' }],
daySpan1: [{ required: true, message: '请选择所属年份', trigger: 'change' }], time: [{ required: true, message: '请选择所属年份', trigger: 'change' }],
daySpan2: [{ 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: { methods: {
@@ -60,26 +74,27 @@ export default {
if (id) { if (id) {
this.isEdit = true this.isEdit = true
this.form.id = id this.form.id = id
// getGroupClasses(id).then((res) => { getImportantWorkConfig({id}).then((res) => {
// if (res.code === 0) { if (res.code === 0) {
// this.form = res.data this.form = res.data
// } }
// }) })
} }
}, },
submitForm() { submitForm() {
this.$refs['form'].validate((valid) => { this.$refs['form'].validate((valid) => {
if (valid) { if (valid) {
this.form.time = moment(this.form.time).endOf('year').endOf('month').endOf('day').unix() * 1000
if (this.isEdit) { if (this.isEdit) {
//编辑 //编辑
updateGroupClasses({ ...this.form }).then((res) => { updateImportantWorkConfig({ ...this.form }).then((res) => {
if (res.code === 0) { if (res.code === 0) {
this.$modal.msgSuccess("操作成功"); this.$modal.msgSuccess("操作成功");
this.$emit('successSubmit') this.$emit('successSubmit')
} }
}) })
} else { } else {
createGroupClasses({ ...this.form }).then((res) => { addImportantWorkConfig({ ...this.form }).then((res) => {
if (res.code === 0) { if (res.code === 0) {
this.$modal.msgSuccess("操作成功"); this.$modal.msgSuccess("操作成功");
this.$emit('successSubmit') this.$emit('successSubmit')

View File

@@ -2,20 +2,19 @@
<el-form ref="form" :rules="rules" label-width="110px" :model="form"> <el-form ref="form" :rules="rules" label-width="110px" :model="form">
<el-row> <el-row>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="产品名称" prop="name"> <el-form-item label="产品名称" prop="product">
<el-input v-model="form.name"></el-input> <el-input v-model="form.product"></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="规格" prop="code"> <el-form-item label="规格" prop="spec">
<el-input v-model="form.code"></el-input> <el-input v-model="form.spec"></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="工艺" prop="daySpan"> <el-form-item label="工艺" prop="process">
<el-select v-model="form.daySpan" placeholder="请选择" style="width: 100%;"> <el-select v-model="form.process" placeholder="请选择" style="width: 100%;">
<el-option label="" :value= '0' ></el-option> <el-option v-for='item in processList' :key='item.value' :label="item.label" :value= 'item.value' ></el-option>
<el-option label="是" :value= '1' ></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -23,23 +22,23 @@
</el-form> </el-form>
</template> </template>
<script> <script>
// import { getGroupClasses, updateGroupClasses, createGroupClasses, getCode } from '@/api/base/groupClasses' import { addProductConfig, updateProductConfig, getProductConfig } from '@/api/basicInfoConfig'
import {getDictDatas } from '@/utils/dict'
export default { export default {
name: 'ProductInfoAdd', name: 'ProductInfoAdd',
data() { data() {
return { return {
form: { form: {
id: '', id: '',
name: '', product: '',
code: '', spec: '',
daySpan:'', process:'',
}, },
isEdit: false, //是否是编辑 isEdit: false, //是否是编辑
rules: { rules: {
name: [{ required: true, message: '请输入产品名称', trigger: 'blur' }], product: [{ required: true, message: '请输入产品名称', trigger: 'blur' }]
code: [{ required: true, message: '请输入产品编码', trigger: 'blur' }], },
daySpan: [{ required: true, message: '请选择工艺', trigger: 'change' }], processList:getDictDatas('process')
}
} }
}, },
methods: { methods: {
@@ -47,11 +46,13 @@ export default {
if (id) { if (id) {
this.isEdit = true this.isEdit = true
this.form.id = id this.form.id = id
// getGroupClasses(id).then((res) => { getProductConfig({id}).then((res) => {
// if (res.code === 0) { if (res.code === 0) {
// this.form = res.data this.form = res.data
// } }
// }) })
}else{
this.form.id = ''
} }
}, },
submitForm() { submitForm() {
@@ -59,14 +60,14 @@ export default {
if (valid) { if (valid) {
if (this.isEdit) { if (this.isEdit) {
//编辑 //编辑
updateGroupClasses({ ...this.form }).then((res) => { updateProductConfig({ ...this.form }).then((res) => {
if (res.code === 0) { if (res.code === 0) {
this.$modal.msgSuccess("操作成功"); this.$modal.msgSuccess("操作成功");
this.$emit('successSubmit') this.$emit('successSubmit')
} }
}) })
} else { } else {
createGroupClasses({ ...this.form }).then((res) => { addProductConfig({ ...this.form }).then((res) => {
if (res.code === 0) { if (res.code === 0) {
this.$modal.msgSuccess("操作成功"); this.$modal.msgSuccess("操作成功");
this.$emit('successSubmit') this.$emit('successSubmit')

View File

@@ -22,6 +22,7 @@
<script> <script>
import CustomerInfoAdd from './components/customerInfoAdd.vue'; import CustomerInfoAdd from './components/customerInfoAdd.vue';
import { getCustomerConfigPage,delCustomerConfig } from '@/api/basicInfoConfig';
const tableProps = [ const tableProps = [
{ {
prop: 'name', prop: 'name',
@@ -32,8 +33,9 @@ const tableProps = [
label: '客户编码' label: '客户编码'
}, },
{ {
prop: 'jd', prop: 'factoryNameList',
label: '所属基地' label: '所属基地',
filter: (val) => val ? val.join(',') :'-'
}, },
]; ];
export default { export default {
@@ -42,17 +44,33 @@ export default {
data () { data () {
return { return {
formConfig: [ 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', type: 'input',
label: '客户名称', label: '客户名称',
placeholder: '客户名称', placeholder: '客户名称',
param: 'cName' param: 'name'
}, },
{ {
type: 'input', type: 'input',
label: '客户编码', label: '客户编码',
placeholder: '客户编码', placeholder: '客户编码',
param: 'ccode' param: 'code'
}, },
{ {
type: 'button', type: 'button',
@@ -72,11 +90,14 @@ export default {
} }
], ],
listQuery:{ listQuery:{
factory:'',
name:'',
code:'',
pageSize:20, pageSize:20,
pageNo:1 pageNo:1
}, },
total:2, total:0,
tableData:[{name:'111',id:1}], tableData:[],
tableProps, tableProps,
tableBtn:[ tableBtn:[
{ {
@@ -92,13 +113,19 @@ export default {
centervisible: false, centervisible: false,
} }
}, },
mounted() {
this.getDataList();
},
methods: { methods: {
buttonClick(val) { buttonClick(val) {
console.log(val) console.log(val)
switch (val.btnName) { switch (val.btnName) {
case 'search': case 'search':
this.listQuery.name = val.name
this.listQuery.code = val.code
this.listQuery.factory = val.factory
this.listQuery.pageNo = 1; this.listQuery.pageNo = 1;
this.getList(); this.getDataList();
break; break;
case 'add': case 'add':
this.addOrEditTitle = '新增'; this.addOrEditTitle = '新增';
@@ -110,7 +137,15 @@ export default {
} }
}, },
getDataList() { 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) { handleClick(val) {
switch (val.type) { switch (val.type) {
@@ -135,14 +170,15 @@ export default {
}, },
successSubmit() { successSubmit() {
this.handleCancel(); this.handleCancel();
this.getList(); this.getDataList();
}, },
/** 删除按钮操作 */ /** 删除按钮操作 */
handleDelete(row) { handleDelete(row) {
this.$modal.confirm('确定删除客户"' + row.name + '吗?').then(function() { let _this = this;
deleteModel(row.id).then(response => { _this.$modal.confirm('确定删除客户"' + row.name + '吗?').then(function() {
that.getList(); delCustomerConfig({id:row.id}).then(response => {
that.$modal.msgSuccess("删除成功"); _this.$modal.msgSuccess("删除成功");
_this.getDataList();
}) })
}).catch(() => {}); }).catch(() => {});
} }

View File

@@ -22,22 +22,33 @@
<script> <script>
import GroupKeyAdd from './components/groupKeyAdd.vue'; import GroupKeyAdd from './components/groupKeyAdd.vue';
import { getImportantWorkConfigPage,delImportantWorkConfig } from '@/api/basicInfoConfig';
import { publicFormatter } from '@/utils/dict';
import moment from 'moment';
const tableProps = [ const tableProps = [
{ {
prop: 'name', prop: 'importantWork',
label: '重点工作' label: '重点工作'
}, },
{ {
prop: 'code1', prop: 'unit',
label: '单位' label: '单位',
filter: publicFormatter('lb_dw')
}, },
{ {
prop: 'code', prop: 'time',
label: '所属年份' label: '所属年份',
filter: (val) => val ? moment(val).format('YYYY') : '-'
}, },
{ {
prop: 'jd', prop: 'calculateMethod',
label: '累计值计算方式' label: '累计值计算方式',
filter: publicFormatter('important_work_method')
},
{
prop: 'targetProperty',
label: '数值目标属性',
filter: publicFormatter('target_property')
}, },
]; ];
export default { export default {
@@ -50,7 +61,7 @@ export default {
type: 'input', type: 'input',
label: '重点工作', label: '重点工作',
placeholder: '重点工作', placeholder: '重点工作',
param: 'cName' param: 'importantWork'
}, },
{ {
type: 'datePicker', type: 'datePicker',
@@ -59,7 +70,7 @@ export default {
format: 'yyyy', format: 'yyyy',
valueFormat: 'yyyy', valueFormat: 'yyyy',
placeholder: '所属年份', placeholder: '所属年份',
param: 'searchTime1', param: 'time',
width: 150 width: 150
}, },
{ {
@@ -80,11 +91,13 @@ export default {
} }
], ],
listQuery:{ listQuery:{
importantWork:'',
time:'',
pageSize:20, pageSize:20,
pageNo:1 pageNo:1
}, },
total:2, total:0,
tableData:[{name:'111'}], tableData:[],
tableProps, tableProps,
tableBtn:[ 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: { methods: {
buttonClick(val) { buttonClick(val) {
console.log(val)
switch (val.btnName) { switch (val.btnName) {
case 'search': 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.listQuery.pageNo = 1;
this.getList(); this.getDataList();
break; break;
case 'add': case 'add':
this.addOrEditTitle = '新增'; this.addOrEditTitle = '新增';
@@ -119,7 +138,15 @@ export default {
} }
}, },
getDataList() { 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) { handleClick(val) {
switch (val.type) { switch (val.type) {
@@ -144,14 +171,16 @@ export default {
}, },
successSubmit() { successSubmit() {
this.handleCancel(); this.handleCancel();
this.getList(); this.getDataList();
}, },
/** 删除按钮操作 */ /** 删除按钮操作 */
handleDelete(row) { handleDelete(row) {
this.$modal.confirm('确定删除重点工作"' + row.name + '吗?').then(function() { let _this = this;
deleteModel(row.id).then(response => { _this.$modal.confirm('确定删除重点工作"' + row.importantWork + '吗?').then(function() {
that.getList(); delImportantWorkConfig({id:row.id}).then(response => {
that.$modal.msgSuccess("删除成功"); _this.$modal.msgSuccess("删除成功");
_this.getDataList();
}) })
}).catch(() => {}); }).catch(() => {});
} }

View File

@@ -22,17 +22,20 @@
<script> <script>
import ProductInfoAdd from './components/productInfoAdd.vue'; import ProductInfoAdd from './components/productInfoAdd.vue';
import { getProductConfigPage,delProductConfig } from '@/api/basicInfoConfig';
import { publicFormatter } from '@/utils/dict';
const tableProps = [ const tableProps = [
{ {
prop: 'name', prop: 'product',
label: '产品名称' label: '产品名称'
}, },
{ {
prop: 'code', prop: 'process',
label: '工艺' label: '工艺',
filter: publicFormatter('process')
}, },
{ {
prop: 'jd', prop: 'spec',
label: '规格' label: '规格'
}, },
]; ];
@@ -46,7 +49,7 @@ export default {
type: 'input', type: 'input',
label: '产品名称', label: '产品名称',
placeholder: '产品名称', placeholder: '产品名称',
param: 'cName' param: 'product'
}, },
{ {
type: 'button', type: 'button',
@@ -66,11 +69,12 @@ export default {
} }
], ],
listQuery:{ listQuery:{
product:'',
pageSize:20, pageSize:20,
pageNo:1 pageNo:1
}, },
total:2, total:0,
tableData:[{name:'111'}], tableData:[],
tableProps, tableProps,
tableBtn:[ tableBtn:[
{ {
@@ -87,13 +91,16 @@ export default {
} }
}, },
mounted() {
this.getDataList();
},
methods: { methods: {
buttonClick(val) { buttonClick(val) {
console.log(val)
switch (val.btnName) { switch (val.btnName) {
case 'search': case 'search':
this.listQuery.product = val.product;
this.listQuery.pageNo = 1; this.listQuery.pageNo = 1;
this.getList(); this.getDataList();
break; break;
case 'add': case 'add':
this.addOrEditTitle = '新增'; this.addOrEditTitle = '新增';
@@ -105,7 +112,17 @@ export default {
} }
}, },
getDataList() { 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) { handleClick(val) {
switch (val.type) { switch (val.type) {
@@ -130,14 +147,15 @@ export default {
}, },
successSubmit() { successSubmit() {
this.handleCancel(); this.handleCancel();
this.getList(); this.getDataList();
}, },
/** 删除按钮操作 */ /** 删除按钮操作 */
handleDelete(row) { handleDelete(row) {
this.$modal.confirm('确定删除产品"' + row.name + '吗?').then(function() { let _this = this;
deleteModel(row.id).then(response => { _this.$modal.confirm('确定删除产品"' + row.product + '吗?').then(function() {
that.getList(); delProductConfig({id:row.id}).then(response => {
that.$modal.msgSuccess("删除成功"); _this.getDataList();
_this.$modal.msgSuccess("删除成功");
}) })
}).catch(() => {}); }).catch(() => {});
} }

View File

@@ -14,7 +14,7 @@
<div class="top-three" style=" <div class="top-three" style="
display: grid; display: grid;
gap: 12px; gap: 12px;
grid-template-columns:416px 1192px; grid-template-columns:186px 1422px;
"> ">
<indicatorCalendar :timeType="timeType" :calendarObj='calendarObj'/> <indicatorCalendar :timeType="timeType" :calendarObj='calendarObj'/>
<indicatorDetails :timeType="timeType" @updateLeft='getData' @updateLevel='getLevel'/> <indicatorDetails :timeType="timeType" @updateLeft='getData' @updateLevel='getLevel'/>
@@ -26,7 +26,6 @@
gap: 12px; gap: 12px;
grid-template-columns:416px 1192px; grid-template-columns:416px 1192px;
"> ">
</div> </div>
</div> --> </div> -->
<!-- <div class="centerImg" style=" <!-- <div class="centerImg" style="
@@ -39,7 +38,6 @@
"></div> --> "></div> -->
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import ReportHeader from "./components/budgetHeader.vue"; import ReportHeader from "./components/budgetHeader.vue";
@@ -47,12 +45,10 @@ import { Sidebar } from "../../layout/components";
import screenfull from "screenfull"; import screenfull from "screenfull";
import indicatorCalendar from "./components/budgetCalendar.vue"; import indicatorCalendar from "./components/budgetCalendar.vue";
import indicatorDetails from "./components/budgetDetails.vue"; import indicatorDetails from "./components/budgetDetails.vue";
// import premProdStatus from "./components/premProdStatus.vue"; // import premProdStatus from "./components/premProdStatus.vue";
import { mapState } from "vuex"; import { mapState } from "vuex";
// import operatingLineChart from "../operatingComponents/operatingLineChart"; // import operatingLineChart from "../operatingComponents/operatingLineChart";
// import operatingLineChartCumulative from "../operatingComponents/operatingLineChartCumulative.vue"; // import operatingLineChartCumulative from "../operatingComponents/operatingLineChartCumulative.vue";
import { getSalesRevenueGroupData } from '@/api/cockpit' import { getSalesRevenueGroupData } from '@/api/cockpit'
import moment from "moment"; import moment from "moment";
import {getCalendar, getCalendarYear} from '@/api/cockpit'; import {getCalendar, getCalendarYear} from '@/api/cockpit';
@@ -83,7 +79,6 @@ export default {
levelId: null levelId: null
}; };
}, },
created() { created() {
this.init(); this.init();
this.windowWidth(document.documentElement.clientWidth); this.windowWidth(document.documentElement.clientWidth);
@@ -203,7 +198,6 @@ export default {
// 全屏 // 全屏
screenfullChange() { screenfullChange() {
console.log("screenfull.enabled", screenfull.isEnabled); console.log("screenfull.enabled", screenfull.isEnabled);
if (!screenfull.isEnabled) { if (!screenfull.isEnabled) {
this.$message({ this.$message({
message: "you browser can not work", message: "you browser can not work",
@@ -249,12 +243,10 @@ export default {
.hideSidebar .fixed-header { .hideSidebar .fixed-header {
width: calc(100% - 54px); width: calc(100% - 54px);
} }
.sidebarHide .fixed-header { .sidebarHide .fixed-header {
width: calc(100%); width: calc(100%);
} }
.mobile .fixed-header { .mobile .fixed-header {
width: 100%; width: 100%;
} }
</style> </style>

View File

@@ -39,7 +39,7 @@ export default {
return { return {
// 图表样式配置项,可以抽离出来方便管理 // 图表样式配置项,可以抽离出来方便管理
chartConfig: { chartConfig: {
manageCost: { '管理费用': {
name: '管理费用', name: '管理费用',
lineColor: 'rgba(11, 88, 255, .5)', lineColor: 'rgba(11, 88, 255, .5)',
itemColor: 'rgba(11, 88, 255, .5)', itemColor: 'rgba(11, 88, 255, .5)',
@@ -49,7 +49,7 @@ export default {
{ offset: 1, color: 'rgba(18, 255, 245, 0)' }, { offset: 1, color: 'rgba(18, 255, 245, 0)' },
] ]
}, },
saleCost: { '销售费用': {
name: '销售费用', name: '销售费用',
lineColor: 'rgba(54, 181, 138, .5)', lineColor: 'rgba(54, 181, 138, .5)',
itemColor: 'rgba(54, 181, 138, .5)', itemColor: 'rgba(54, 181, 138, .5)',
@@ -59,7 +59,7 @@ export default {
{ offset: 1, color: 'rgba(18, 255, 245, 0)' }, { offset: 1, color: 'rgba(18, 255, 245, 0)' },
] ]
}, },
financeCost: { '财务费用': {
name: '财务费用', name: '财务费用',
lineColor: 'rgba(255, 132, 0, .5)', lineColor: 'rgba(255, 132, 0, .5)',
itemColor: 'rgba(255, 132, 0, .5)', itemColor: 'rgba(255, 132, 0, .5)',
@@ -78,7 +78,7 @@ export default {
// 从 cost.line 中获取任意一个有数据的键的 keys 作为 X 轴 // 从 cost.line 中获取任意一个有数据的键的 keys 作为 X 轴
const lineData = this.line || {}; const lineData = this.line || {};
const firstKey = Object.keys(lineData)[0]; const firstKey = Object.keys(lineData)[0];
return firstKey ? Object.keys(lineData[firstKey]) : []; return firstKey ? Object.keys(lineData[firstKey].real) : [];
}, },
// 动态生成 series 数据 // 动态生成 series 数据
chartSeries() { chartSeries() {
@@ -87,14 +87,17 @@ export default {
// 如果没有 X 轴数据,则返回空数组 // 如果没有 X 轴数据,则返回空数组
if (xAxisKeys.length === 0) { if (xAxisKeys.length === 0) {
return []; return {};
}
let obj = {
unit:'万元',
series:[]
} }
// 遍历配置项,生成 series // 遍历配置项,生成 series
return Object.keys(this.chartConfig).map(key => { obj.series = Object.keys(this.chartConfig).map(key => {
const config = this.chartConfig[key]; const config = this.chartConfig[key];
// 确保数据顺序和 X 轴一致 // 确保数据顺序和 X 轴一致
const dataValues = xAxisKeys.map(date => lineData[key] ? lineData[key][date] : 0); const dataValues = xAxisKeys.map(date => lineData[key] ? lineData[key].real[date] : 0);
return { return {
name: config.name, name: config.name,
@@ -114,6 +117,7 @@ export default {
data: dataValues data: dataValues
}; };
}); });
return obj;
} }
}, },
methods: {} methods: {}
@@ -126,6 +130,7 @@ export default {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 12px; padding: 12px;
margin: 7px 0;
.barTop { .barTop {
display: flex; display: flex;

View File

@@ -8,13 +8,17 @@
<!-- 右侧区域全屏按钮 --> <!-- 右侧区域全屏按钮 -->
<div class="right-content"> <div class="right-content">
<el-dropdown trigger="click"> <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
<el-button type="text" class="logout-btn" :title="'退出'"> <div class="avatar-wrapper">
<svg-icon style="color: #0B58FF;" icon-class="logout" /> <img :src="require(`../../../assets/images/choicepart/avatar.png`)" class="user-avatar">
</el-button> <span v-if="nickname" class="user-nickname">{{ nickname }}</span>
<i class="el-icon-caret-bottom" />
</div>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native='logout'>退出登录</el-dropdown-item> <el-dropdown-item @click.native="logout">
<el-dropdown-item @click.native='handleToggle'>切换账号</el-dropdown-item> <span>退出登录</span>
</el-dropdown-item>
<el-dropdown-item divided @click.native='handleToggle'>切换账号</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
<el-button type="text" class="screen-btn" @click="changeHomeSider"> <el-button type="text" class="screen-btn" @click="changeHomeSider">
@@ -44,6 +48,7 @@
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'
import moment from 'moment' import moment from 'moment'
import {getPath} from "@/utils/ruoyi"; import {getPath} from "@/utils/ruoyi";
export default { export default {
@@ -57,6 +62,9 @@ export default {
default: () => ({}) default: () => ({})
}, },
}, },
computed:{
...mapGetters(['nickname']),
},
data() { data() {
return { return {
currentTime: '', currentTime: '',
@@ -371,14 +379,35 @@ export default {
padding: 0; padding: 0;
margin: 0; margin: 0;
} }
.avatar-container {
margin-right: 30px;
.logout-btn { .avatar-wrapper {
width: 30px; display: flex;
height: 30px; justify-content: center;
font-size: 30px; align-items: center;
padding: 0; position: relative;
margin-top: 2px;
} .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> <template>
<div class="visual-base-table-container"> <div class="visual-base-table-container">
<el-table :max-height="maxHeight" ref="scroll_Table" @mouseenter.native="autoScroll(true)" <el-table :max-height="maxHeight" ref="scroll_Table" @mouseenter.native="autoScroll(true)"
@mouseleave.native="autoScroll(false)" v-loading="isLoading" @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" :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"> :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-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" <el-table-column v-for="item in renderTableHeadList" :key="item.prop" :show-overflow-tooltip="showOverflow" :width='item.width || ""'
v-bind="item"> v-bind="item">
<template slot-scope="scope"> <template slot-scope="scope">
@@ -39,7 +32,7 @@ export default {
maxHeight: { maxHeight: {
type: [Number, String], // 支持数字如300或字符串如'300px' type: [Number, String], // 支持数字如300或字符串如'300px'
required: false, required: false,
default: 200 // 原固定值,作为默认 fallback default: 200 // 原固定值,作为默认 fallback
}, },
tableData: { tableData: {
type: Array, type: Array,

View File

@@ -1,14 +1,13 @@
<template> <template>
<div style="flex: 1"> <div style="flex: 1">
<Container name="预算填报日历" icon="cockpitItemIcon" size="calendarBg" topSize="calendarTitleBg"> <Container name="填报总览" icon="cockpitItemIcon" size="calendarBg" topSize="calendarTitleBg">
<!-- 1. 移除 .kpi-content 的固定高度改为自适应 --> <!-- 1. 移除 .kpi-content 的固定高度改为自适应 -->
<div class="kpi-content" style="padding: 14px 14px; display: flex;flex-direction: column; width: 100%;"> <div class="kpi-content" style="padding: 14px 14px; display: flex;flex-direction: column; width: 100%;">
<!-- 2. .top 保持 flex无需固定高度自动跟随子元素拉伸 --> <!-- 2. .top 保持 flex无需固定高度自动跟随子元素拉伸 -->
<div class="bottom" <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布局缩小行间距 --> <!-- 动态生成12个月的容器优化flex布局缩小行间距 -->
<div class="month-list" <div class="month-list">
style="display: flex; gap: 16px; flex-wrap: wrap; align-content: flex-start; row-gap: 8px;">
<!-- 循环生成12个月通过判断当前月份索引添加current类 --> <!-- 循环生成12个月通过判断当前月份索引添加current类 -->
<div class="monthItem" :class="{ <div class="monthItem" :class="{
'has-data': calendar.haveData, 'has-data': calendar.haveData,
@@ -91,19 +90,19 @@ export default {
// 基础月份样式 // 基础月份样式
.monthItem { .monthItem {
width: 164px; width: 100px;
height: 42px; height: 57px;
border-radius: 4px; border-radius: 4px;
font-family: PingFangSC, PingFang SC; font-family: PingFangSC, PingFang SC;
font-weight: 400; font-weight: 400;
font-size: 20px; font-size: 20px;
color: rgba(0, 0, 0, 0.85); color: rgba(0, 0, 0, 0.85);
line-height: 42px; line-height: 57px;
text-align: center; text-align: center;
font-style: normal; font-style: normal;
transition: all 0.2s ease; // 过渡效果,样式切换更平滑 transition: all 0.2s ease; // 过渡效果,样式切换更平滑
border: 2px solid transparent; // 透明边框,避免选中时布局偏移 border: 2px solid transparent; // 透明边框,避免选中时布局偏移
margin: 0; // 清除默认外边距,进一步缩小缝隙 margin: 0 auto 10px; // 清除默认外边距,进一步缩小缝隙
} }
// 有数据的样式(背景色#D1E8FF // 有数据的样式(背景色#D1E8FF

View File

@@ -22,7 +22,9 @@
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button style="background-color: #0B58FF;" type="primary" @click="onSubmit">查询</el-button> <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='importExcel'>导入</el-button>
<el-button type="primary" plain size="medium" @click='exportExcel'>导出</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
@@ -73,24 +75,26 @@
:key="tableKey"></base-table> :key="tableKey"></base-table>
</div> </div>
</div> </div>
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body> <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.isUploading" <el-upload ref="upload" :limit="1" accept=".xlsx, .xls" action="#" :disabled="upload.httpUploading"
:on-change="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag> :on-change="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
<i class="el-icon-upload"></i> <i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div> <div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<div class="el-upload__tip text-center" slot="tip"> <div class="el-upload__tip text-center" slot="tip">
<span>仅允许导入xlsxlsx格式文件</span> <span>仅允许导入{{timeType==='month'?'月':'年'}}预算仅允许导入xlsxlsx格式文件</span>
</div> </div>
<div class="el-upload__tip" slot="tip"> <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> </div>
</el-upload> </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"> <div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileForm"> </el-button> <el-button type="primary" :loading="upload.httpUploading" :disabled="upload.httpUploading" @click="submitFileForm"> </el-button>
<el-button @click="upload.open = false"> </el-button> <el-button :disabled="upload.httpUploading" @click="cancelBtn"> </el-button>
</div> </div>
</el-dialog> </el-dialog>
</Container> </Container>
@@ -99,11 +103,12 @@
<script> <script>
import Container from './container.vue' 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 inputArea from './inputArea.vue' // 导入输入组件
import { publicFormatter } from '@/utils/dict'; import { publicFormatter } from '@/utils/dict';
import {getAccessToken, getTenantId} from '@/utils/auth' import {getAccessToken, getTenantId} from '@/utils/auth'
import axios from 'axios'; import axios from 'axios';
import moment from 'moment';
export default { export default {
name: 'ProductionStatus', name: 'ProductionStatus',
components: { components: {
@@ -132,11 +137,10 @@ export default {
open: false, open: false,
// 弹出层标题 // 弹出层标题
title: "预算填报导入", title: "预算填报导入",
// 是否禁用上传
isUploading: false,
fileList:[], fileList:[],
currentFile:null, currentFile:null,
timeDim: 2 // HTTP 上传中(点击确定后 axios 上传,展示不确定进度条)
httpUploading: false
}, },
getDataList: null, // 动态切换的查询接口 getDataList: null, // 动态切换的查询接口
updateData: null, // 动态切换的更新接口 updateData: null, // 动态切换的更新接口
@@ -359,6 +363,8 @@ export default {
}); });
}, },
handleLevelChange(id) { handleLevelChange(id) {
this.form.levelId = id;
this.getDataPage()
this.$emit('updateLevel', id) this.$emit('updateLevel', id)
}, },
// 表格单元格数据变更回调 // 表格单元格数据变更回调
@@ -455,15 +461,53 @@ export default {
} }
// 计算选中日期对应的时间戳 // 计算选中日期对应的时间戳
this.calculateTimeStamp(val); this.calculateTimeStamp(val);
// 重新请求数据
this.getDataPage();
}, },
// 导入 // 导入
importExcel() { importExcel() {
this.upload.httpUploading = false
this.upload.open = true 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) { handleFileUploadProgress(file, fileList) {
console.log('文件上传中:',file, fileList) console.log('文件上传中:',file, fileList)
this.upload.isUploading = true;
this.upload.fileList = fileList; this.upload.fileList = fileList;
this.upload.currentFile = file.raw; this.upload.currentFile = file.raw;
}, },
@@ -471,13 +515,17 @@ export default {
importTemplate() {}, importTemplate() {},
// 提交上传文件 // 提交上传文件
async submitFileForm() { async submitFileForm() {
if (!this.upload.currentFile) {
return this.$message.error('请先选择要上传的文件!')
}
if (this.upload.httpUploading) {
return
}
this.upload.httpUploading = true
try { try {
if (!this.upload.currentFile) {
return this.$message.error('请先选择要上传的文件!')
}
const formData = new FormData() const formData = new FormData()
formData.append('file', this.upload.currentFile) // 文件字段 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('reportDate', this.form.endTime) // 时间维度字段
formData.append('levelId', this.form.levelId) // 层级维度字段 formData.append('levelId', this.form.levelId) // 层级维度字段
const response = await axios({ const response = await axios({
@@ -489,17 +537,15 @@ export default {
'Authorization': "Bearer " + getAccessToken(), 'Authorization': "Bearer " + getAccessToken(),
'tenant-id': getTenantId(), 'tenant-id': getTenantId(),
}, },
timeout: 30000 timeout: 300000
}) })
// 4. 处理响应结果 // 4. 处理响应结果
if (response.data.code === 0) { if (response.data.code === 0) {
this.$message.success('文件上传成功!') this.$message.success('文件上传成功!')
// 重置表单 // 重置表单
this.upload.fileList = [] this.upload.fileList = []
this.upload.timeDim = 2
this.upload.currentFile = null this.upload.currentFile = null
this.upload.open = false this.upload.open = false
this.upload.isUploading = false
this.$refs.upload.clearFiles(); this.$refs.upload.clearFiles();
this.getDataPage(); this.getDataPage();
this.$emit('updateLeft') this.$emit('updateLeft')
@@ -510,13 +556,66 @@ export default {
// 5. 异常处理 // 5. 异常处理
console.error('文件上传出错:', error) console.error('文件上传出错:', error)
this.$message.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> </script>
<style lang='scss' scoped> <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 { .month-list {
// 内联样式已优化行间距,此处可留空或补充 // 内联样式已优化行间距,此处可留空或补充

View File

@@ -7,13 +7,17 @@
<!-- 右侧区域全屏按钮 --> <!-- 右侧区域全屏按钮 -->
<div class="right-content"> <div class="right-content">
<el-dropdown trigger="click"> <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
<el-button type="text" class="logout-btn" :title="'退出'"> <div class="avatar-wrapper">
<svg-icon style="color: #0B58FF;" icon-class="logout" /> <img :src="require(`../../../assets/images/choicepart/avatar.png`)" class="user-avatar">
</el-button> <span v-if="nickname" class="user-nickname">{{ nickname }}</span>
<i class="el-icon-caret-bottom" />
</div>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native='logout'>退出登录</el-dropdown-item> <el-dropdown-item @click.native="logout">
<el-dropdown-item @click.native='handleToggle'>切换账号</el-dropdown-item> <span>退出登录</span>
</el-dropdown-item>
<el-dropdown-item divided @click.native='handleToggle'>切换账号</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
<el-button type="text" class="return-btn" :title="'返回'" @click="handleReturn"> <el-button type="text" class="return-btn" :title="'返回'" @click="handleReturn">
@@ -43,6 +47,7 @@
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'
import moment from 'moment'; // 引入moment import moment from 'moment'; // 引入moment
import {getPath} from "@/utils/ruoyi"; import {getPath} from "@/utils/ruoyi";
export default { export default {
@@ -57,6 +62,9 @@ export default {
}, },
isBudget: { type: Boolean, default: false }, isBudget: { type: Boolean, default: false },
}, },
computed:{
...mapGetters(['nickname']),
},
data() { data() {
return { return {
currentTime: '', currentTime: '',
@@ -360,12 +368,35 @@ export default {
padding: 0; padding: 0;
} }
.logout-btn { .avatar-container {
width: 28px; margin-right: 30px;
height: 28px;
font-size: 28px; .avatar-wrapper {
padding: 0; 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>
<div class="line"></div> <div class="line"></div>
<div class="right"> <div class="right">
<div class="number" :style="{ color: getTargetColor(item.currentValue, item.targetValue) }"> <div class="number" :style="{ color: getTargetColor(item.progress) }">
{{ item.currentValue }} {{ item.currentValue }}
</div> </div>
<div class="title">实际值</div> <div class="title">实际值</div>
@@ -21,15 +21,15 @@
<div class="progress-container"> <div class="progress-container">
<div class="progress-bar" :style="{ <div class="progress-bar" :style="{
width: item.progress + '%', width: item.progress + '%',
background: getTargetColor(item.currentValue, item.targetValue) background: getTargetColor(item.progress)
}"></div> }"></div>
</div> </div>
</div> </div>
<div class="yield" style="display: flex;justify-content: space-between;"> <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>
<div class="progress-percent" :style="{ color: getTargetColor(item.currentValue, item.targetValue) }"> <div class="progress-percent" :style="{ color: getTargetColor(item.progress) }">
{{ item.progress }}% {{ item.progress }}%
</div> </div>
</div> </div>
@@ -95,8 +95,8 @@ export default {
}; };
}); });
}, },
getTargetColor(currentValue, targetValue) { getTargetColor(progress) {
return currentValue >= targetValue return progress >= 100
? "rgba(98, 213, 180, 1)" ? "rgba(98, 213, 180, 1)"
: "rgba(249, 164, 74, 1)"; : "rgba(249, 164, 74, 1)";
} }

View File

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

View File

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

View File

@@ -1,15 +1,14 @@
<template> <template>
<div style="flex: 1"> <div style="flex: 1">
<Container name="销售重点指标" icon="cockpitItemIcon" size="topBasic" topSize="basic"> <Container name="销售重点指标" icon="cockpitItemIcon" size="topBasic" topSize="basic" :isShowTab='true' @tabChange='tabChange'>
<!-- 1. 移除 .kpi-content 的固定高度改为自适应 --> <!-- 1. 移除 .kpi-content 的固定高度改为自适应 -->
<div class="kpi-content" style="padding: 14px 16px; display: flex;flex-direction: column; width: 100%;"> <div class="kpi-content" style="padding: 14px 16px; display: flex;flex-direction: column; width: 100%;">
<!-- 2. .top 保持 flex无需固定高度自动跟随子元素拉伸 --> <!-- 2. .top 保持 flex无需固定高度自动跟随子元素拉伸 -->
<div class="top" style="display: flex; width: 100%;"> <div class="top" style="display: flex; width: 100%;">
<top-item :sale="sale" :dateData="dateData" /> <top-item :sale="saleData" :dateData="dateData" />
</div> </div>
<div class="bottom" <div class="bottom"
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);"> style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
<!-- <top-item /> -->
<coreBottomBar :line="sale.line" :dateData="dateData" /> <coreBottomBar :line="sale.line" :dateData="dateData" />
</div> </div>
@@ -19,7 +18,6 @@
</template> </template>
<script> <script>
import Container from './container.vue' import Container from './container.vue'
// import * as echarts from 'echarts'
import topItem from './top-item.vue' import topItem from './top-item.vue'
import coreBottomBar from './coreBottomBar.vue' import coreBottomBar from './coreBottomBar.vue'
@@ -39,16 +37,37 @@ export default {
}, },
data() { data() {
return { return {
chart: null chart: null,
saleData:{},
currentTap:'month'
} }
}, },
watch: { watch: {
sale: {
handler(newVal) {
if(this.currentTap === 'month') {
this.saleData = this.sale.mon
}else{
this.saleData = this.sale.total
}
},
deep: true
}
}, },
mounted() { mounted() {
// 初始化图表(若需展示图表,需在模板中添加对应 DOM this.saleData = this.sale.month
// this.$nextTick(() => this.updateChart())
}, },
methods: { methods: {
tabChange(val) {
if(val === 'month') {
this.currentTap = 'month'
this.saleData = this.sale.mon
}else{
this.currentTap = 'total'
this.saleData = this.sale.total
}
console.log(this.saleData);
}
} }
} }
</script> </script>

View File

@@ -1,130 +1,131 @@
<template> <template>
<div ref="cockpitEffChip" id="coreLineChart" style="height: 219px; width: 100%;"></div> <div ref="cockpitEffChip" id="coreLineChart" style="height: 184px; width: 100%;"></div>
</template> </template>
<script> <script>
import * as echarts from 'echarts'; import * as echarts from 'echarts';
export default { export default {
name: 'Container', name: 'Container',
components: {}, components: {},
// 接收父组件传递的 series 数据 // 接收父组件传递的 series 数据
props: { props: {
chartSeries: { chartSeries: {
type: Array, type: Object,
default: () => [] // 默认空数组,避免报错 default: () => {{}} // 默认空数组,避免报错
}, },
xAxisData: { xAxisData: {
type: Array, type: Array,
default: () => [] // 默认空数组,避免报错 default: () => [] // 默认空数组,避免报错
}, },
dateData: { dateData: {
type: Object, type: Object,
default: () => {} // 默认空数组,避免报错 default: () => {} // 默认空数组,避免报错
}, },
}, },
data() { data() {
return { return {
myChart: null // 存储图表实例,方便后续操作 myChart: null, // 存储图表实例,方便后续操作
}; resizeHandler: null // 窗口resize事件处理器
}, };
mounted() { },
this.$nextTick(() => { mounted() {
this.initData(); this.$nextTick(() => {
}); this.initData();
}, });
watch: { // 注册窗口resize事件使用稳定的引用以便后续移除
// 监听 series 数据变化,实时更新图表 this.resizeHandler = () => {
chartSeries: { if (this.myChart) {
handler() { this.myChart.resize();
this.updateChart(); }
}, };
deep: true // 深度监听数组内元素变化 window.addEventListener('resize', this.resizeHandler);
} },
}, watch: {
methods: { // 监听 series 数据变化,实时更新图表
initData() { chartSeries: {
const chartDom = this.$refs.cockpitEffChip; handler() {
if (!chartDom) { this.updateChart();
console.error('图表容器未找到!'); },
return; deep: true // 深度监听数组内元素变化
} }
this.myChart = echarts.init(chartDom); },
this.updateChart(); // 初始化时渲染图表 methods: {
initData() {
// 监听窗口缩放 const chartDom = this.$refs.cockpitEffChip;
window.addEventListener('resize', () => { if (!chartDom) {
this.myChart && this.myChart.resize(); console.error('图表容器未找到!');
}); return;
}
// 组件销毁时清理 this.myChart = echarts.init(chartDom);
this.$once('hook:destroyed', () => { this.updateChart(); // 初始化时渲染图表
window.removeEventListener('resize', () => { },
this.myChart && this.myChart.resize(); // 单独提取更新图表的方法,方便复用
}); updateChart() {
this.myChart && this.myChart.dispose(); if (!this.myChart) return;
});
}, const option = {
// 单独提取更新图表的方法,方便复用 tooltip: {
updateChart() { trigger: 'axis',
if (!this.myChart) return; axisPointer: {
console.log('this.chartSeries', this.chartSeries,this.xAxisData); type: 'cross',
label: { backgroundColor: '#6a7985' }
const option = { }
tooltip: { },
trigger: 'axis', grid: { top: 35, bottom: 3, right: 15, left: 18, containLabel: true},
axisPointer: { xAxis: [
type: 'cross', {
label: { backgroundColor: '#6a7985' } type: 'category',
} boundaryGap: false,
}, axisTick: { show: false },
grid: { top: 35, bottom: 20, right: 25, left: 70 }, axisLine: {
xAxis: [ show: true,
{ onZero: false,
type: 'category', lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
boundaryGap: false, },
axisTick: { show: false }, axisLabel: {
axisLine: { color: 'rgba(0, 0, 0, 0.45)',
show: true, fontSize: 12,
onZero: false, interval: 0,
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } // width: 38,
}, overflow: 'break',
axisLabel: { formatter: (value) => {
color: 'rgba(0, 0, 0, 0.45)', const dateParts = value.split('-'); // ["2025", "07", "01"]
fontSize: 12, if (dateParts.length < 2) return value;
interval: 0,
// width: 38, // 去掉月份前面的0然后加上"月"
overflow: 'break', const month = dateParts[1].replace(/^0+/, '');
formatter: (value) => { return `${month}`;
const dateParts = value.split('-'); // ["2025", "07", "01"] }
if (dateParts.length < 2) return value; },
data: this.xAxisData
// 去掉月份前面的0然后加上"月" }
const month = dateParts[1].replace(/^0+/, ''); ],
return `${month}`; yAxis: {
} name: this.chartSeries.unit,
}, nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' },
data: this.xAxisData axisTick: { show: false },
} axisLabel: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 12 },
], splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
yAxis: { axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }
// type: 'value', },
name: '元/㎡', series: this.chartSeries.series // 使用父组件传递的 series 数据
// nameLocation:'center', };
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' },
// min: () => 0, this.myChart.setOption(option, true); // 第二个参数 true 表示替换现有配置
// max: (value) => Math.ceil(value.max), }
},
axisTick: { show: false }, beforeDestroy() {
axisLabel: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 12 }, // 移除窗口resize事件监听器
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }, if (this.resizeHandler) {
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } } window.removeEventListener('resize', this.resizeHandler);
}, this.resizeHandler = null;
series: this.chartSeries // 使用父组件传递的 series 数据 }
}; // 销毁图表,避免内存泄漏
if (this.myChart) {
this.myChart.setOption(option, true); // 第二个参数 true 表示替换现有配置 this.myChart.dispose();
} this.myChart = null;
} }
}; }
</script> };
</script>

View File

@@ -8,7 +8,7 @@
<div class="content-wrapper"> <div class="content-wrapper">
<div class="left"> <div class="left">
<div class="number">{{ item.targetValue }}</div> <div class="number">{{ item.targetValue }}</div>
<div class="title">上月</div> <div class="title">{{currentTap==='month'?'上月':'上年'}}</div>
</div> </div>
<div class="line"></div> <div class="line"></div>
<div class="right"> <div class="right">
@@ -16,7 +16,7 @@
<div class="number" :style="{ color: getColor(item.currentValue, item.targetValue) }"> <div class="number" :style="{ color: getColor(item.currentValue, item.targetValue) }">
{{ item.currentValue }} {{ item.currentValue }}
</div> </div>
<div class="title">本月</div> <div class="title">{{currentTap==='month'?'本月':'本年'}}</div>
</div> </div>
</div> </div>
<div class="line"></div> <div class="line"></div>
@@ -24,7 +24,7 @@
<div class="right"> <div class="right">
<!-- 环比额计算差值并动态绑定颜色 --> <!-- 环比额计算差值并动态绑定颜色 -->
<div class="number" :style="{ color: getColor(item.currentValue, item.targetValue) }"> <div class="number" :style="{ color: getColor(item.currentValue, item.targetValue) }">
{{ item.hbe }} {{ item.thbe }}
</div> </div>
<div class="title">环比额</div> <div class="title">环比额</div>
</div> </div>
@@ -46,6 +46,10 @@ export default {
type: Object, type: Object,
default: () => ({}) default: () => ({})
}, },
currentTap: {
type: String,
default: 'month'
}
}, },
data() { data() {
return { return {
@@ -97,12 +101,12 @@ export default {
// 遍历映射关系,转换数据 // 遍历映射关系,转换数据
return costMapping.map(mappingItem => { 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 { return {
name: mappingItem.name, name: mappingItem.name,
path: mappingItem.path, path: mappingItem.path,
hbe: costData.hbe, thbe: costData.thbe,
targetValue: costData.last, // 上月值 targetValue: costData.last, // 上月值
currentValue: costData.this // 本月值 currentValue: costData.this // 本月值
}; };

View File

@@ -1,369 +1,412 @@
<template> <template>
<div style="flex: 1"> <div style="flex: 1">
<!-- 传入点击切换的状态到Container组件 --> <!-- 传入点击切换的状态到Container组件 -->
<Container name="财务重点指标" nameTwo="费用重点指标" icon="cockpitItemIcon" size="topBasic" @switchTab="handleTabSwitch"> <Container name="财务重点指标" nameTwo="费用重点指标" icon="cockpitItemIcon" size="topBasic" @switchTab="handleTabSwitch" @tabChange='tabChange'>
<div class="bottom-left-content" style="display: flex;gap: 9px;padding: 14px 16px;flex-direction: column;"> <div class="bottom-left-content" style="display: flex;gap: 0px;padding: 14px 16px;flex-direction: column;">
<!-- 根据activeTab状态切换显示采购/存货内容 --> <!-- 根据activeTab状态切换显示采购/存货内容 -->
<template v-if="activeTab === 'purchase'"> <template v-if="activeTab === 'purchase'">
<!-- 采购重点指标应的内容 --> <!-- 财务重点指标应的内容 -->
<coreBottomLeftItem :dateData="dateData" :finance="finance"></coreBottomLeftItem> <coreBottomLeftItem :dateData="dateData" :finance="financeData"></coreBottomLeftItem>
<div class="bottom" <div class="bottom"
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);"> style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
<coreBottomBar :dateData="dateData" :line="finance.line"></coreBottomBar> <coreBottomBar :dateData="dateData" :line="finance.line"></coreBottomBar>
</div> </div>
</template> </template>
<template v-else-if="activeTab === 'inventory'"> <template v-else-if="activeTab === 'inventory'">
<!-- 存货重点指标对应的内容 --> <!-- 费用重点指标对应的内容 -->
<costItem :dateData="dateData" :cost="cost"></costItem> <costItem :dateData="dateData" :cost="costData" :currentTap='currentTap'></costItem>
<div class="bottom" <div class="bottom"
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);"> style="display: flex; width: 100%;margin-top: 4px;background-color: rgba(249, 252, 255, 1);">
<CostsBottomBar :line="cost.line" :dateData="dateData"> <CostsBottomBar :line="cost.line" :dateData="dateData">
</CostsBottomBar> </CostsBottomBar>
</div> </div>
</template> </template>
</div>
<!-- 图表容器根据状态控制显示若需展示图表 --> </Container>
<!-- <div id="productionStatusChart" style="height: 200px; margin-top: 12px;" v-if="showChart"></div> --> </div>
</div> </template>
</Container>
</div> <script>
</template> import Container from './financeCostsContainer.vue'
import * as echarts from 'echarts'
<script> import coreBottomLeftItem from './purchase-Item.vue'
import Container from './financeCostsContainer.vue' import coreBottomBar from './financeCostsBottomBar.vue'
import * as echarts from 'echarts' import costItem from './cost-Item.vue'
import coreBottomLeftItem from './purchase-Item.vue' import CostsBottomBar from './CostsBottomBar.vue'
import coreBottomBar from './financeCostsBottomBar.vue'
import costItem from './cost-Item.vue' export default {
import CostsBottomBar from './CostsBottomBar.vue' name: 'ProductionStatus',
components: { Container, coreBottomLeftItem, coreBottomBar, costItem, CostsBottomBar },
export default { props: {
name: 'ProductionStatus', finance: {
components: { Container, coreBottomLeftItem, coreBottomBar, costItem, CostsBottomBar }, type: Object,
props: { default: () => {}
finance: { },
type: Object, cost: {
default: () => {} type: Object,
}, default: () => ({})
cost: { },
type: Object, dateData: {
default: () => ({}) type: Object,
}, default: () => ({})
dateData: { }
type: Object, },
default: () => ({}) data() {
}, return {
}, activeTab: 'purchase', // 激活的标签purchase=采购inventory=存货
data() { showChart: true, // 控制图表是否显示
return { chart: null, // 图表实例
activeTab: 'purchase', // 激活的标签purchase=采购inventory=存货 financeData:{},
showChart: true, // 控制图表是否显示 costData:{},
chart: null // 图表实例 currentTap: 'month',
} resizeHandler: null // 窗口resize事件处理器
}, }
watch: { },
// 切换标签时更新图表 watch: {
activeTab(newVal) { // 切换标签时更新图表
this.$nextTick(() => this.updateChart()) activeTab(newVal) {
}, this.$nextTick(() => this.updateChart())
productionOverviewVo: { },
handler() { productionOverviewVo: {
this.updateChart() handler() {
}, this.updateChart()
deep: true },
} deep: true
}, },
mounted() { finance: {
// 初始化图表 handler() {
this.$nextTick(() => this.updateChart()) if(this.currentTap === 'month') {
}, this.financeData = this.finance.mon
beforeDestroy() { }else{
// 销毁图表,避免内存泄漏 this.financeData = this.finance.total
if (this.chart) { }
this.chart.dispose() },
this.chart = null deep: true
} },
}, cost: {
methods: { handler() {
// 处理标题点击切换标签 if(this.currentTap === 'month') {
handleTabSwitch(tabType) { this.costData = this.cost.mon
this.activeTab = tabType // tabType由Container组件传递'purchase'或'inventory' }else{
this.showChart = true // 切换时默认显示图表(可根据需求调整) this.costData = this.cost.total
}, }
// 更新图表内容 },
updateChart() { deep: true
const chartDom = document.getElementById('productionStatusChart') }
if (!chartDom) return
},
// 销毁已有图表实例 mounted() {
if (this.chart) this.chart.dispose() // 初始化图表
this.chart = echarts.init(chartDom) this.$nextTick(() => this.updateChart())
// 注册窗口resize事件使用稳定的引用以便后续移除
// 根据当前激活的标签,设置对应图表数据(示例:可根据实际需求修改) this.resizeHandler = () => {
const data = this.activeTab === 'purchase' if (this.chart) {
? [ this.chart.resize()
this.productionOverviewVo.purchaseInput || 0, }
this.productionOverviewVo.purchaseOutput || 0, }
this.productionOverviewVo.purchaseNg || 0, window.addEventListener('resize', this.resizeHandler)
0, 0, 0, 0 // 补充默认值,避免图表报错 },
] beforeDestroy() {
: [ // 移除窗口resize事件监听器
this.productionOverviewVo.inventoryInput || 0, if (this.resizeHandler) {
this.productionOverviewVo.inventoryOutput || 0, window.removeEventListener('resize', this.resizeHandler)
this.productionOverviewVo.inventoryNg || 0, this.resizeHandler = null
0, 0, 0, 0 }
] // 销毁图表,避免内存泄漏
if (this.chart) {
const option = { this.chart.dispose()
type: 'bar', this.chart = null
grid: { left: 51, right: 40, top: 50, bottom: 45 }, }
tooltip: { },
trigger: 'axis', methods: {
axisPointer: { type: 'shadow' }, tabChange(val) {
className: 'production-status-chart-tooltip' if(val === 'month') {
}, this.currentTap = 'month'
xAxis: { this.financeData = this.finance.mon
type: 'category', this.costData = this.cost.mon
offset: 8, }else{
data: this.activeTab === 'purchase' this.currentTap = 'total'
? ['采购投入', '采购产出', '采购待判', '低价值', '报废', '在制', '实验片'] this.financeData = this.finance.total
: ['存货投入', '存货产出', '存货待判', '低价值', '报废', '在制', '实验片'], this.costData = this.cost.total
axisTick: { show: false }, }
axisLine: { show: true, onZero: false, lineStyle: { color: '#00E8FF' } }, },
axisLabel: { // 处理标题点击切换标签
color: 'rgba(255,255,255,0.7)', handleTabSwitch(tabType) {
fontSize: 12, this.activeTab = tabType // tabType由Container组件传递'purchase'或'inventory'
interval: 0, this.showChart = true // 切换时默认显示图表(可根据需求调整)
width: 38, },
overflow: 'break' // 更新图表内容
} updateChart() {
}, const chartDom = document.getElementById('productionStatusChart')
yAxis: { if (!chartDom) return
type: 'value',
name: '单位/片', // 销毁已有图表实例
nameTextStyle: { color: 'rgba(255,255,255,0.7)', fontSize: 14, align: 'left' }, if (this.chart) this.chart.dispose()
min: 0, this.chart = echarts.init(chartDom)
max: (value) => Math.ceil(value.max),
// 根据当前激活的标签,设置对应图表数据(示例:可根据实际需求修改)
axisTick: { show: false }, const data = this.activeTab === 'purchase'
axisLabel: { color: 'rgba(255,255,255,0.7)', fontSize: 12 }, ? [
splitLine: { lineStyle: { color: 'RGBA(24, 88, 100, 0.6)', type: 'dashed' } }, this.productionOverviewVo.purchaseInput || 0,
axisLine: { show: true, lineStyle: { color: '#00E8FF' } } this.productionOverviewVo.purchaseOutput || 0,
}, this.productionOverviewVo.purchaseNg || 0,
series: [ 0, 0, 0, 0 // 补充默认值,避免图表报错
{ ]
type: 'pictorialBar', : [
label: { show: true, position: 'top', distance: -3, color: '#89CDFF', fontSize: 11 }, this.productionOverviewVo.inventoryInput || 0,
symbolSize: [20, 8], this.productionOverviewVo.inventoryOutput || 0,
symbolOffset: [0, 5], this.productionOverviewVo.inventoryNg || 0,
z: 20, 0, 0, 0, 0
itemStyle: { ]
borderColor: '#3588C7',
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ const option = {
{ offset: 0, color: 'RGBA(22, 89, 98, 1)' }, type: 'bar',
{ offset: 1, color: '#3588C7' } grid: { left: 51, right: 40, top: 50, bottom: 45 },
]) tooltip: {
}, trigger: 'axis',
data: data axisPointer: { type: 'shadow' },
}, className: 'production-status-chart-tooltip'
{ },
type: 'bar', xAxis: {
barWidth: 20, type: 'category',
itemStyle: { offset: 8,
borderWidth: 1, data: this.activeTab === 'purchase'
borderColor: '#3588C7', ? ['采购投入', '采购产出', '采购待判', '低价值', '报废', '在制', '实验片']
opacity: 0.8, : ['存货投入', '存货产出', '存货待判', '低价值', '报废', '在制', '实验片'],
color: { axisTick: { show: false },
x: 0, y: 0, x2: 0, y2: 1, axisLine: { show: true, onZero: false, lineStyle: { color: '#00E8FF' } },
type: 'linear', axisLabel: {
global: false, color: 'rgba(255,255,255,0.7)',
colorStops: [ fontSize: 12,
{ offset: 0, color: 'rgba(73,178,255,0)' }, interval: 0,
{ offset: 0.5, color: 'rgba(0, 232, 255, .5)' }, width: 38,
{ offset: 1, color: 'rgba(0, 232, 255, 1)' } overflow: 'break'
] }
} },
}, yAxis: {
tooltip: { show: false }, type: 'value',
data: data name: '单位/片',
}, nameTextStyle: { color: 'rgba(255,255,255,0.7)', fontSize: 14, align: 'left' },
{ min: 0,
type: 'pictorialBar', max: (value) => Math.ceil(value.max),
symbolSize: [20, 8],
symbolOffset: [0, -4], axisTick: { show: false },
z: 12, axisLabel: { color: 'rgba(255,255,255,0.7)', fontSize: 12 },
symbolPosition: 'end', splitLine: { lineStyle: { color: 'RGBA(24, 88, 100, 0.6)', type: 'dashed' } },
itemStyle: { axisLine: { show: true, lineStyle: { color: '#00E8FF' } }
borderColor: 'rgba(0, 232, 255, 1)', },
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ series: [
{ offset: 0, color: 'RGBA(22, 89, 98, 1)' }, {
{ offset: 1, color: '#3588C7' } type: 'pictorialBar',
]) label: { show: true, position: 'top', distance: -3, color: '#89CDFF', fontSize: 11 },
}, symbolSize: [20, 8],
tooltip: { show: false }, symbolOffset: [0, 5],
data: data z: 20,
} itemStyle: {
] borderColor: '#3588C7',
} color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'RGBA(22, 89, 98, 1)' },
this.chart.setOption(option) { offset: 1, color: '#3588C7' }
// 监听窗口 resize自适应图表 ])
window.addEventListener('resize', this.chart.resize) },
} data: data
} },
} {
</script> type: 'bar',
barWidth: 20,
<style lang='scss' scoped> itemStyle: {
/* 原有样式保留,新增内容容器样式 */ borderWidth: 1,
.bottom-left-content { borderColor: '#3588C7',
width: 100%; opacity: 0.8,
height: 100%; color: {
overflow: hidden; x: 0, y: 0, x2: 0, y2: 1,
} type: 'linear',
global: false,
/* 其他原有样式... */ colorStops: [
.scroll-container { { offset: 0, color: 'rgba(73,178,255,0)' },
max-height: 210px; { offset: 0.5, color: 'rgba(0, 232, 255, .5)' },
overflow-y: auto; { offset: 1, color: 'rgba(0, 232, 255, 1)' }
overflow-x: hidden; ]
padding: 10px 0; }
},
&::-webkit-scrollbar { tooltip: { show: false },
display: none; data: data
} },
{
scrollbar-width: none; type: 'pictorialBar',
-ms-overflow-style: none; symbolSize: [20, 8],
} symbolOffset: [0, -4],
z: 12,
.proBarInfo { symbolPosition: 'end',
display: flex; itemStyle: {
flex-direction: column; borderColor: 'rgba(0, 232, 255, 1)',
padding: 8px 27px; color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
margin-bottom: 10px; { offset: 0, color: 'RGBA(22, 89, 98, 1)' },
} { offset: 1, color: '#3588C7' }
])
.proBarInfoEqInfo { },
display: flex; tooltip: { show: false },
justify-content: space-between; data: data
align-items: center; }
} ]
}
.slot {
width: 21px; this.chart.setOption(option)
height: 23px; }
background: rgba(0, 106, 205, 0.22); }
backdrop-filter: blur(1.5px); }
font-family: PingFangSC, PingFang SC; </script>
font-weight: 400;
font-size: 16px; <style lang='scss' scoped>
color: #68B5FF; /* 原有样式保留,新增内容容器样式 */
line-height: 23px; .bottom-left-content {
text-align: center; width: 100%;
font-style: normal; height: 100%;
} overflow: hidden;
}
.eq-name {
margin-left: 8px; /* 其他原有样式... */
font-family: PingFangSC, PingFang SC; .scroll-container {
font-weight: 400; max-height: 210px;
font-size: 16px; overflow-y: auto;
color: #FFFFFF; overflow-x: hidden;
line-height: 18px; padding: 10px 0;
letter-spacing: 1px;
text-align: left; &::-webkit-scrollbar {
font-style: normal; display: none;
} }
.eqStatus { scrollbar-width: none;
font-family: PingFangSC, PingFang SC; -ms-overflow-style: none;
font-weight: 400; }
font-size: 16px;
color: #FFFFFF; .proBarInfo {
line-height: 18px; display: flex;
text-align: right; flex-direction: column;
font-style: normal; padding: 8px 27px;
} margin-bottom: 10px;
}
.splitLine {
width: 1px; .proBarInfoEqInfo {
height: 14px; display: flex;
border: 1px solid #ADADAD; justify-content: space-between;
margin: 0 8px; align-items: center;
} }
.yield { .slot {
height: 18px; width: 21px;
font-family: PingFangSC, PingFang SC; height: 23px;
font-weight: 400; background: rgba(0, 106, 205, 0.22);
font-size: 16px; backdrop-filter: blur(1.5px);
color: #00FFFF; font-family: PingFangSC, PingFang SC;
line-height: 18px; font-weight: 400;
text-align: right; font-size: 16px;
font-style: normal; color: #68B5FF;
} line-height: 23px;
text-align: center;
.proBarInfoEqInfoLeft { font-style: normal;
display: flex; }
align-items: center;
} .eq-name {
margin-left: 8px;
.proBarInfoEqInfoRight { font-family: PingFangSC, PingFang SC;
display: flex; font-weight: 400;
align-items: center; font-size: 16px;
} color: #FFFFFF;
line-height: 18px;
.proBarWrapper { letter-spacing: 1px;
position: relative; text-align: left;
height: 10px; font-style: normal;
margin-top: 6px; }
border-radius: 5px;
overflow: hidden; .eqStatus {
} font-family: PingFangSC, PingFang SC;
font-weight: 400;
.proBarLine { font-size: 16px;
width: 100%; color: #FFFFFF;
height: 100%; line-height: 18px;
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #ACACAC 100%); text-align: right;
opacity: 0.2; font-style: normal;
} }
.proBarLineTop { .splitLine {
position: absolute; width: 1px;
top: 0; height: 14px;
left: 0; border: 1px solid #ADADAD;
height: 100%; margin: 0 8px;
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; .yield {
} height: 18px;
font-family: PingFangSC, PingFang SC;
.chartImgBottom { font-weight: 400;
position: absolute; font-size: 16px;
bottom: 45px; color: #00FFFF;
left: 58px; line-height: 18px;
} text-align: right;
font-style: normal;
.line { }
display: inline-block;
position: absolute; .proBarInfoEqInfoLeft {
left: 57px; display: flex;
bottom: 42px; align-items: center;
width: 1px; }
height: 20px;
background-color: #00E8FF; .proBarInfoEqInfoRight {
} display: flex;
</style> align-items: center;
}
<style>
.production-status-chart-tooltip { .proBarWrapper {
background: #0a2b4f77 !important; position: relative;
border: none !important; height: 10px;
backdrop-filter: blur(12px); margin-top: 6px;
} border-radius: 5px;
overflow: hidden;
.production-status-chart-tooltip * { }
color: #fff !important;
} .proBarLine {
</style> 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 class="item-button" :class="{ active: activeButton === 3 }" @click="activeButton = 3">
毛利率 毛利率
</div> </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> </div>
<div class="lineBottom" style="height: 210px; width: 100%"> <div class="lineBottom" style="height: 184px; width: 100%">
<!-- 传递当前选中的 series 数据和 xAxis 数据给子组件 --> <!-- 传递当前选中的 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" /> :dateData="dateData" />
</div> </div>
</div> </div>
@@ -59,10 +69,12 @@ export default {
activeButton: 0, // 初始激活第一个按钮索引0 activeButton: 0, // 初始激活第一个按钮索引0
// 定义按钮与 line 数据中 key 的映射关系 // 定义按钮与 line 数据中 key 的映射关系
buttonToDataKey: [ buttonToDataKey: [
'营业收入', {name:'营业收入',unit:'万元'},
'经营性利润', // 注意:数据中的 key 是“经营收入”,按钮显示的是“经营性利润” {name:'经营性利润',unit:'万元'},
'利润总额', {name:'利润总额',unit:'万元'},
'毛利率' {name:'毛利率',unit:'%'},
{name:'应收账款',unit:'万元'},
{name:'存货',unit:'万元'}
] ]
}; };
}, },
@@ -70,20 +82,19 @@ export default {
// 根据当前激活的按钮,动态生成对应的 series 数据 // 根据当前激活的按钮,动态生成对应的 series 数据
currentSeries() { currentSeries() {
const dataKey = this.buttonToDataKey[this.activeButton]; const dataKey = this.buttonToDataKey[this.activeButton].name;
const chartData = this.line[dataKey]; const chartData = this.line[dataKey];
console.log('this.line[dataKey', this.buttonToDataKey[this.activeButton]);
if (!chartData) { if (!chartData) {
return []; return {};
} }
// 提取目标和实际数据的值 // 提取目标和实际数据的值
const targetDataValues = Object.values(chartData.target || {}); const targetDataValues = Object.values(chartData.target || {});
const realDataValues = Object.values(chartData.real || {}); const realDataValues = Object.values(chartData.real || {});
console.log('realDataValues', realDataValues); let obj = {
unit: this.buttonToDataKey[this.activeButton].unit,
return [ series:[
{ {
name: '预算', name: '预算',
type: 'line', type: 'line',
@@ -116,11 +127,12 @@ export default {
}, },
data: realDataValues data: realDataValues
} }
]; ]}
return obj;
}, },
// 提取 x 轴数据(日期) // 提取 x 轴数据(日期)
xAxisData() { xAxisData() {
const dataKey = this.buttonToDataKey[this.activeButton]; const dataKey = this.buttonToDataKey[this.activeButton].name;
const chartData = this.line[dataKey]; const chartData = this.line[dataKey];
// 使用 'target' 的键作为 x 轴,如果 'target' 不存在,则使用 'real' 的键 // 使用 'target' 的键作为 x 轴,如果 'target' 不存在,则使用 'real' 的键
if (chartData && chartData.target) { if (chartData && chartData.target) {
@@ -205,7 +217,7 @@ export default {
display: flex; display: flex;
position: relative; position: relative;
gap: 2px; gap: 2px;
width: 252px; width: 350px;
align-items: center; align-items: center;
height: 24px; height: 24px;
background: #ecf4fe; background: #ecf4fe;
@@ -219,18 +231,27 @@ export default {
} }
.lineOne { .lineOne {
top: 5px; top: 6px;
left: 59px; left: 59px;
} }
.lineTwo { .lineTwo {
top: 5px; top: 6px;
left: 134px; left: 131px;
} }
.lineThree { .lineThree {
top: 5px; top: 6px;
left: 193px; left: 190px;
}
.lineFour {
top: 6px;
left: 238px;
}
.lineFive {
top: 6px;
left: 302px;
} }
.item-button { .item-button {

View File

@@ -16,7 +16,17 @@
<span class="title-text">{{ nameTwo }}</span> <span class="title-text">{{ nameTwo }}</span>
</div> </div>
<span class="change-text" :class="{ 'change-text-right': isLeftTransparent }">点击切换</span> <!-- <span class="change-text" :class="{ 'change-text-right': isLeftTransparent }">点击切换</span> -->
<div class="tab-group">
<!-- 月度Tab点击切换状态动态绑定样式 -->
<div class="tab-item" :class="{ active: activeTab === 'month' }" @click="handleTabClick('month')">
月度
</div>
<!-- 累计Tab点击切换状态动态绑定样式 -->
<div class="tab-item" :class="{ active: activeTab === 'total' }" @click="handleTabClick('total')">
累计
</div>
</div>
</div> </div>
<div class="container-body"> <div class="container-body">
@@ -40,10 +50,18 @@ export default {
return { return {
// 初始状态左侧不透明1右侧透明0.3 // 初始状态左侧不透明1右侧透明0.3
isLeftTransparent: true, // 左侧透明度状态true=1false=0.3 isLeftTransparent: true, // 左侧透明度状态true=1false=0.3
isRightTransparent: false // 右侧透明度状态true=1false=0.3 isRightTransparent: false, // 右侧透明度状态true=1false=0.3
activeTab: 'month', // 默认激活的Tab为月度
}; };
}, },
methods: { methods: {
handleTabClick(tabType) {
this.activeTab = tabType;
// 向父组件派发Tab切换事件传递当前选中的Tab类型
this.$emit('tabChange', tabType);
// 可选:同时传递更详细的信息(如标签名)
// this.$emit('tabChange', { type: tabType, name: tabType === 'month' ? '月度' : '累计' });
},
// 点击左侧标题:左侧保持不透明,右侧变透明,并派发采购标签事件 // 点击左侧标题:左侧保持不透明,右侧变透明,并派发采购标签事件
handleLeftClick() { handleLeftClick() {
this.isLeftTransparent = true; // 左侧不透明 this.isLeftTransparent = true; // 左侧不透明
@@ -151,7 +169,7 @@ export default {
background: linear-gradient(90deg, #FFFFFF 0%, rgba(253, 255, 255, 0) 100%); background: linear-gradient(90deg, #FFFFFF 0%, rgba(253, 255, 255, 0) 100%);
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; left: 240px;
z-index: 10; z-index: 10;
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;
@@ -258,4 +276,41 @@ export default {
// text-align: left; // text-align: left;
// font-style: normal; // font-style: normal;
} }
.tab-group {
display: inline-flex;
position: absolute;
right: 5%;
top:20px;
z-index: 9999;
align-items: center;
border-radius: 24px;
overflow: hidden;
gap: 8px; // Tab之间的间距
}
// Tab基础样式统一
.tab-item {
padding: 0 24px;
width: 79px;
height: 24px;
line-height: 24px;
font-size: 12px;
cursor: pointer;
text-align: center;
border-radius: 12px;
transition: all 0.2s ease; // 样式切换动画
}
// 未激活的Tab样式原first-child样式
.tab-item:not(.active) {
background: #ECF4FE;
color: #0B58FF;
}
// 激活的Tab样式原last-child样式
.tab-item.active {
background: #3071FF;
color: #F9FCFF;
font-weight: bold;
}
</style> </style>

View File

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

View File

@@ -0,0 +1,165 @@
<template>
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 216px;"></div>
</template>
<script>
import * as echarts from 'echarts';
export default {
components: {},
data() {
return {
myChart: null, // 存储图表实例,避免重复创建
resizeHandler: null // 窗口resize事件处理器
};
},
props: {
lineData: {
type: Object,
default: () => ({}),
}
},
mounted() {
this.$nextTick(() => {
this.updateChart();
});
// 注册窗口resize事件使用稳定的引用以便后续移除
this.resizeHandler = () => {
if (this.myChart) {
this.myChart.resize();
}
};
window.addEventListener('resize', this.resizeHandler);
},
// 新增:监听 chartData 变化
watch: {
// 深度监听数据变化,仅更新图表配置(不销毁实例)
lineData: {
handler() {
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 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);
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
},
},
grid: {
top: 30,
bottom: 30,
right: 20,
left: 60,
},
xAxis: [
{
type: 'category',
boundaryGap: true,
axisTick: { show: false },
axisLine: {
show: true,
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
},
axisLabel: {
color: 'rgba(0, 0, 0, 0.45)',
fontSize: 12,
interval: 0,
padding: [5, 0, 0, 0]
},
data:xData
}
],
yAxis: [
// 左侧Y轴营业收入、成本单位万元
{
type: 'value',
name: 'kcal/kg',
nameTextStyle: {
color: 'rgba(0, 0, 0, 0.45)',
fontSize: 12,
align: 'right'
},
splitNumber: 4,
axisTick: { show: false },
axisLabel: {
color: 'rgba(0, 0, 0, 0.45)',
fontSize: 12,
formatter: '{value}'
},
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
splitNumber: 4
},
],
series: [
{
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);
}
},
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> <template>
<div style="flex: 1"> <div style="flex: 1">
<Container name="指标填报日历" icon="cockpitItemIcon" size="calendarBg" topSize="calendarTitleBg"> <Container name="填报总览" icon="cockpitItemIcon" size="calendarBg" topSize="calendarTitleBg">
<!-- 1. 移除 .kpi-content 的固定高度改为自适应 --> <!-- 1. 移除 .kpi-content 的固定高度改为自适应 -->
<div class="kpi-content" style="padding: 14px 14px; display: flex;flex-direction: column; width: 100%;"> <div class="kpi-content" style="padding: 14px 14px; display: flex;flex-direction: column; width: 100%;">
<!-- 2. .top 保持 flex无需固定高度自动跟随子元素拉伸 --> <!-- 2. .top 保持 flex无需固定高度自动跟随子元素拉伸 -->
<div class="bottom" <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布局缩小行间距 --> <!-- 动态生成12个月的容器优化flex布局缩小行间距 -->
<div class="month-list" <div class="month-list">
style="display: flex; gap: 16px; flex-wrap: wrap; align-content: flex-start; row-gap: 8px;">
<!-- 循环生成12个月通过判断当前月份索引添加current类 --> <!-- 循环生成12个月通过判断当前月份索引添加current类 -->
<div class="monthItem" :class="{ <div class="monthItem" :class="{
'has-data': month.haveData, 'has-data': month.haveData,
@@ -91,20 +90,20 @@ export default {
// 基础月份样式 // 基础月份样式
.monthItem { .monthItem {
width: 164px; width: 100px;
height: 42px; height: 57px;
border-radius: 4px; border-radius: 4px;
font-family: PingFangSC, PingFang SC; font-family: PingFangSC, PingFang SC;
font-weight: 400; font-weight: 400;
font-size: 20px; font-size: 20px;
color: rgba(0, 0, 0, 0.85); color: rgba(0, 0, 0, 0.85);
line-height: 42px; line-height: 57px;
text-align: center; text-align: center;
font-style: normal; font-style: normal;
cursor: pointer; // 鼠标悬浮手型 cursor: pointer; // 鼠标悬浮手型
transition: all 0.2s ease; // 过渡效果,样式切换更平滑 transition: all 0.2s ease; // 过渡效果,样式切换更平滑
border: 2px solid transparent; // 透明边框,避免选中时布局偏移 border: 2px solid transparent; // 透明边框,避免选中时布局偏移
margin: 0; // 清除默认外边距,进一步缩小缝隙 margin: 0 auto 10px; // 清除默认外边距,进一步缩小缝隙
} }
// 有数据的样式(背景色#D1E8FF // 有数据的样式(背景色#D1E8FF

View File

@@ -15,12 +15,14 @@
</el-form-item> </el-form-item>
<el-form-item label="填报月份"> <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-date-picker>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button style="background-color: #0B58FF;" type="primary" @click="onSubmit">查询</el-button> <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='importExcel'>导入</el-button>
<el-button type="primary" plain size="medium" @click='exportExcel'>导出</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
@@ -64,8 +66,8 @@ font-style: normal;">指标详情</div>
></base-table> ></base-table>
</div> </div>
</div> </div>
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body> <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.isUploading" <el-upload ref="upload" :limit="1" accept=".xlsx, .xls" action="#" :disabled="upload.httpUploading"
:on-change="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag> :on-change="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
<i class="el-icon-upload"></i> <i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div> <div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
@@ -75,9 +77,15 @@ font-style: normal;">指标详情</div>
<div class="el-upload__tip" slot="tip"> <div class="el-upload__tip" slot="tip">
</div> </div>
</el-upload> </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"> <div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileForm"> </el-button> <el-button type="primary" :loading="upload.httpUploading" :disabled="upload.httpUploading" @click="submitFileForm"> </el-button>
<el-button @click="upload.open = false"> </el-button> <el-button :disabled="upload.httpUploading" @click="cancelBtn"> </el-button>
</div> </div>
</el-dialog> </el-dialog>
</Container> </Container>
@@ -86,10 +94,11 @@ font-style: normal;">指标详情</div>
<script> <script>
import Container from './container.vue' 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 inputArea from './inputArea.vue' // 导入输入组件
import {getAccessToken, getTenantId} from '@/utils/auth' import {getAccessToken, getTenantId} from '@/utils/auth'
import axios from 'axios'; import axios from 'axios';
import moment from 'moment'
export default { export default {
name: 'ProductionStatus', name: 'ProductionStatus',
components: { components: {
@@ -119,10 +128,10 @@ export default {
open: false, open: false,
// 弹出层标题 // 弹出层标题
title: "指标填报导入", title: "指标填报导入",
// 是否禁用上传
isUploading: false,
fileList:[], fileList:[],
currentFile:null currentFile:null,
// HTTP 上传中(点击确定后 axios 上传,展示不确定进度条)
httpUploading: false
} }
} }
}, },
@@ -274,6 +283,8 @@ export default {
} }
// 计算选中月份的起止时间戳 // 计算选中月份的起止时间戳
this.setMonthTimeStamp(val); this.setMonthTimeStamp(val);
// 重新请求数据
this.getDataPage();
}, },
getUnitLabel(unitCode) { getUnitLabel(unitCode) {
// 若字典为空或无匹配编码,返回原编码或空字符串 // 若字典为空或无匹配编码,返回原编码或空字符串
@@ -323,6 +334,8 @@ export default {
}, },
handleLevelChange(id) { handleLevelChange(id) {
this.form.levelId = id;
this.getDataPage()
this.$emit('updateLevel', id) this.$emit('updateLevel', id)
}, },
@@ -336,12 +349,41 @@ export default {
}, },
// 导入 // 导入
importExcel() { importExcel() {
this.upload.httpUploading = false
this.upload.open = true 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) { handleFileUploadProgress(file, fileList) {
console.log('文件上传中:',file, fileList) console.log('文件上传中:',file, fileList)
this.upload.isUploading = true;
this.upload.fileList = fileList; this.upload.fileList = fileList;
this.upload.currentFile = file.raw; this.upload.currentFile = file.raw;
}, },
@@ -349,10 +391,14 @@ export default {
importTemplate() {}, importTemplate() {},
// 提交上传文件 // 提交上传文件
async submitFileForm() { async submitFileForm() {
if (!this.upload.currentFile) {
return this.$message.error('请先选择要上传的文件!')
}
if (this.upload.httpUploading) {
return
}
this.upload.httpUploading = true
try { try {
if (!this.upload.currentFile) {
return this.$message.error('请先选择要上传的文件!')
}
const formData = new FormData() const formData = new FormData()
formData.append('file', this.upload.currentFile) // 文件字段 formData.append('file', this.upload.currentFile) // 文件字段
formData.append('reportDate', this.form.endTime) // 时间维度字段 formData.append('reportDate', this.form.endTime) // 时间维度字段
@@ -366,7 +412,7 @@ export default {
'Authorization': "Bearer " + getAccessToken(), 'Authorization': "Bearer " + getAccessToken(),
'tenant-id': getTenantId(), 'tenant-id': getTenantId(),
}, },
timeout: 30000 timeout: 300000
}) })
// 4. 处理响应结果 // 4. 处理响应结果
if (response.data.code === 0) { if (response.data.code === 0) {
@@ -375,7 +421,6 @@ export default {
this.upload.fileList = [] this.upload.fileList = []
this.upload.currentFile = null this.upload.currentFile = null
this.upload.open = false this.upload.open = false
this.upload.isUploading = false
this.$refs.upload.clearFiles(); this.$refs.upload.clearFiles();
this.getDataPage(); this.getDataPage();
this.$emit('updateLeft') this.$emit('updateLeft')
@@ -386,13 +431,66 @@ export default {
// 5. 异常处理 // 5. 异常处理
console.error('文件上传出错:', error) console.error('文件上传出错:', error)
this.$message.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> </script>
<style lang='scss' scoped> <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 { .month-list {
// 内联样式已优化行间距,此处可留空或补充 // 内联样式已优化行间距,此处可留空或补充

View File

@@ -0,0 +1,316 @@
<template>
<div class="cockpitContainer" :class="['cockpitContainer__' + size]">
<div class="container-top">
<!-- 左侧标题点击切换到采购标签并更新透明度 -->
<div class="content-top-left title-wrapper" @click="handleLeftClick"
:style="{ opacity: isLeftTransparent ? 1 : 0.3 }">
<svg-icon class="title-icon" style="font-size: 32px; margin-left: 16px" :icon-class="icon" />
<span class="title-text">{{ name }}</span>
<!-- <span v-if="!isLeftTransparent" class="change-text">点击切换</span> -->
</div>
<!-- 右侧标题点击切换到存货标签并更新透明度 -->
<div class="content-top-right title-wrapper" v-if="nameTwo" @click="handleRightClick"
:style="{ opacity: isRightTransparent ? 1 : 0.3 }">
<svg-icon class="title-icon" style="font-size: 32px; margin-left: 16px" :icon-class="iconTwo || icon" />
<span class="title-text">{{ nameTwo }}</span>
</div>
<!-- <span class="change-text" :class="{ 'change-text-right': isLeftTransparent }">点击切换</span> -->
<div class="tab-group">
<!-- 月度Tab点击切换状态动态绑定样式 -->
<div class="tab-item" :class="{ active: activeTab === 'month' }" @click="handleTabClick('month')">
月度
</div>
<!-- 累计Tab点击切换状态动态绑定样式 -->
<div class="tab-item" :class="{ active: activeTab === 'total' }" @click="handleTabClick('total')">
累计
</div>
</div>
</div>
<div class="container-body">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'Container',
components: {},
props: {
name: { type: String, required: true },
nameTwo: { type: String, required: false },
size: { type: String, default: 'default' },
icon: { type: String, default: '' },
iconTwo: { type: String, default: '' }
},
data() {
return {
// 初始状态左侧不透明1右侧透明0.3
isLeftTransparent: true, // 左侧透明度状态true=1false=0.3
isRightTransparent: false, // 右侧透明度状态true=1false=0.3
activeTab: 'month', // 默认激活的Tab为月度
};
},
methods: {
handleTabClick(tabType) {
this.activeTab = tabType;
// 向父组件派发Tab切换事件传递当前选中的Tab类型
this.$emit('tabChange', tabType);
// 可选:同时传递更详细的信息(如标签名)
// this.$emit('tabChange', { type: tabType, name: tabType === 'month' ? '月度' : '累计' });
},
// 点击左侧标题:左侧保持不透明,右侧变透明,并派发采购标签事件
handleLeftClick() {
this.isLeftTransparent = true; // 左侧不透明
this.isRightTransparent = false; // 右侧透明
this.$emit('switchTab', 'product'); // 通知父组件切换到采购内容
},
// 点击右侧标题:右侧保持不透明,左侧变透明,并派发存货标签事件
handleRightClick() {
this.isLeftTransparent = false; // 左侧透明
this.isRightTransparent = true; // 右侧不透明
this.$emit('switchTab', 'heat'); // 通知父组件切换到存货内容
}
}
};
</script>
<style scoped lang="scss">
// 样式保持不变,确保透明度过渡生效
.cockpitContainer {
display: inline-block;
padding: 6px;
display: flex;
flex-direction: column;
position: relative;
.container-top {
position: relative;
height: 60px;
width: 100%;
}
.content-top-left {
width: 566px;
height: 60px;
background: linear-gradient(90deg, #FFFFFF 0%, rgba(253, 255, 255, 0) 100%);
position: relative;
overflow: hidden;
cursor: pointer;
z-index: 1;
transition: opacity 0.3s ease; // 透明度过渡动画
}
.title-wrapper {
display: flex;
align-items: center;
// margin-left: 10px;
/* 垂直居中关键属性 */
height: 100%;
/* 继承父容器高度,确保垂直居中范围 */
}
.title-icon {
font-size: 30px;
margin-right: 12px;
margin-top: 4px;
margin-left: 10px;
/* 图标和文字之间的间距 */
flex-shrink: 0;
/* 防止图标被压缩 */
}
.title-text {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 24px;
color: #000000;
letter-spacing: 3px;
text-align: left;
font-style: normal;
// 移除固定行高,避免影响垂直对齐
// line-height: 60px;
}
/* 左侧标题 - 左上角折现边框 */
.content-top-left::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 1px solid;
border-image: linear-gradient(277deg, rgba(255, 255, 255, 0), rgba(92, 140, 255, 1)) 1 1;
clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%, 0 20px);
z-index: 3;
}
/* 左侧标题 - 左上角折现细节 */
.content-top-left::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 30px;
height: 30px;
background: #E1f0fd;
border-top: 1px solid rgba(92, 140, 255, 1);
border-left: 1px solid rgba(92, 140, 255, 1);
transform: rotate(135deg) translate(-50%, -50%);
transform-origin: top left;
z-index: 3;
}
.content-top-right {
width: 368px;
height: 60px;
background: linear-gradient(90deg, #FFFFFF 0%, rgba(253, 255, 255, 0) 100%);
position: absolute;
top: 0;
left: 240px;
z-index: 10;
overflow: hidden;
cursor: pointer;
transition: opacity 0.3s ease; // 透明度过渡动画
}
/* 右侧标题 - 左上角折现边框 */
.content-top-right::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 1px solid;
border-image: linear-gradient(277deg, rgba(255, 255, 255, 0), rgba(92, 140, 255, 1)) 1 1;
clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%, 0 20px);
z-index: 12;
}
/* 右侧标题 - 左上角折现细节 */
.content-top-right::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 30px;
height: 30px;
background: #E1f0fd;
border-top: 1px solid rgba(92, 140, 255, 1);
border-left: 1px solid rgba(92, 140, 255, 1);
transform: rotate(135deg) translate(-50%, -50%);
transform-origin: top left;
z-index: 12;
}
.title-text {
margin-left: 6px;
height: 32px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 24px;
color: #000000;
// line-height: 60px;
letter-spacing: 3px;
text-align: left;
font-style: normal;
display: inline-block;
position: relative;
z-index: 1;
}
&__topBasic {
background: url(../../../assets/img/top-basic.png) no-repeat;
background-size: 100% 100%;
background-position: 0 0;
}
&__bottomBasic {
background: url(../../../assets/img/bottom-basic.png) no-repeat;
background-size: 100% 100%;
background-position: 0 0;
}
}
.container-body {
flex: 1;
}
.test-body {
padding: 20px;
color: #666;
}
.change-text{
position: absolute;
top: 26px;
left: 300px;
z-index: 999;
// width: 48px;
// height: 17px;
// margin-left: 80px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 12px;
color: #0B58FF;
line-height: 17px;
text-align: left;
font-style: normal;
}
.change-text-right {
// position: absolute;
// top: 26px;
left: auto; // 清除左侧定位
right: 30px;
// z-index: 999;
// // width: 48px;
// // height: 17px;
// // margin-left: 80px;
// font-family: PingFangSC, PingFang SC;
// font-weight: 400;
// font-size: 12px;
// color: #0B58FF;
// line-height: 17px;
// text-align: left;
// font-style: normal;
}
.tab-group {
display: inline-flex;
position: absolute;
right: 0px;
top:20px;
z-index: 9999;
align-items: center;
border-radius: 24px;
overflow: hidden;
gap: 8px; // Tab之间的间距
}
// Tab基础样式统一
.tab-item {
padding: 0 24px;
width: 79px;
height: 24px;
line-height: 24px;
font-size: 12px;
cursor: pointer;
text-align: center;
border-radius: 12px;
transition: all 0.2s ease; // 样式切换动画
}
// 未激活的Tab样式原first-child样式
.tab-item:not(.active) {
background: #ECF4FE;
color: #0B58FF;
}
// 激活的Tab样式原last-child样式
.tab-item.active {
background: #3071FF;
color: #F9FCFF;
font-weight: bold;
}
</style>

View File

@@ -1,33 +1,49 @@
<template> <template>
<div style="flex: 1"> <div style="flex: 1">
<Container name="生产重点指标" icon="cockpitItemIcon" size="topBasic" topSize="basic"> <Container name="生产重点指标" nameTwo="热耗" icon="cockpitItemIcon" size="topBasic" topSize="basic" @switchTab="handleTabSwitch" @tabChange='tabChange'>
<!-- 1. 移除 .kpi-content 的固定高度改为自适应 --> <!-- 1. 移除 .kpi-content 的固定高度改为自适应 -->
<div class="kpi-content" style="padding: 14px 16px; display: flex;flex-direction: column; width: 100%;"> <div class="kpi-content" style="padding: 14px 16px; display: flex;flex-direction: column; width: 100%;">
<!-- 2. .top 保持 flex无需固定高度自动跟随子元素拉伸 --> <!-- 2. .top 保持 flex无需固定高度自动跟随子元素拉伸 -->
<div class="top" style="display: flex; width: 100%;"> <template v-if="activeTab === 'product'">
<top-item :rawItemList='productData' :dateData="dateData" /> <div class="top" style="display: flex; width: 100%;">
</div> <top-item :rawItemList='productData' :dateData="dateData" />
<div class="bottom" style="display: flex;margin-top: 8px;background-color: rgba(249, 252, 255, 1);"> </div>
<!-- <top-item /> --> <div class="bottom" style="display: flex;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
<coreBottomBar :lineData="productData.line" :dateData="dateData" /> <!-- <top-item /> -->
<coreBottomBar :lineData="product.line" :dateData="dateData" />
</div> </div>
</template>
<template v-else-if="activeTab === 'heat'">
<div class="bottom" style="background-color: rgba(249, 252, 255, 1);">
<div class="bottom" style="margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
<div style='text-align: center;margin: 5px;font-size: 18px;font-weight: 400;color: #000;'>1200t/d</div>
<heatBar :lineData="heatData['1200t']"/>
</div>
<div style='text-align: center;margin: 5px;font-size: 18px;font-weight: 400;color: #000;'>650t/d</div>
<heatBar :lineData="heatData['650t']"/>
</div>
</template>
</div> </div>
</Container> </Container>
</div> </div>
</template> </template>
<script> <script>
import Container from './container.vue' import Container from './keyProductContainer.vue'
// import * as echarts from 'echarts' // import * as echarts from 'echarts'
import topItem from './top-product-item.vue' import topItem from './top-product-item.vue'
import coreBottomBar from './productBottomBar.vue' import coreBottomBar from './productBottomBar.vue'
import heatBar from './heatBarChart.vue'
export default { export default {
name: 'ProductionStatus', name: 'ProductionStatus',
components: { Container, topItem, coreBottomBar }, components: { Container, topItem, coreBottomBar, heatBar },
// mixins: [resize], // mixins: [resize],
props: { props: {
productData: { product: {
type: Object,
default: () => {} // 默认空数组,避免报错
},
heat: {
type: Object, type: Object,
default: () => {} // 默认空数组,避免报错 default: () => {} // 默认空数组,避免报错
}, },
@@ -38,10 +54,38 @@ export default {
}, },
data() { data() {
return { return {
chart: null chart: null,
activeTab: 'product',
currentTap: 'month',
productData:{},
heatData:{}
} }
}, },
watch: { watch: {
// 切换标签时更新图表
activeTab(newVal) {
// this.$nextTick(() => this.updateChart())
},
product: {
handler() {
if(this.currentTap === 'month') {
this.productData = this.product.mon
}else{
this.productData = this.product.total
}
},
deep: true
},
heat: {
handler() {
if(this.currentTap === 'month') {
this.heatData = this.heat.mon
}else{
this.heatData = this.heat.total
}
},
deep: true
},
}, },
mounted() { mounted() {
// 初始化图表(若需展示图表,需在模板中添加对应 DOM // 初始化图表(若需展示图表,需在模板中添加对应 DOM
@@ -49,6 +93,22 @@ export default {
}, },
methods: { methods: {
// 处理标题点击切换标签
handleTabSwitch(tabType) {
this.activeTab = tabType // tabType由Container组件传递'purchase'或'inventory'
this.showChart = true // 切换时默认显示图表(可根据需求调整)
},
tabChange(val) {
if(val === 'month') {
this.currentTap = 'month'
this.productData = this.product.mon
this.heatData = this.heat.mon
}else{
this.currentTap = 'total'
this.productData = this.product.total
this.heatData = this.heat.total
}
},
} }
} }
</script> </script>

View File

@@ -15,7 +15,7 @@
<span class="legend-text">未完成</span> <span class="legend-text">未完成</span>
</div> </div>
</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" /> :tableConfig="tableProps" :table-data="tableData" />
</div> </div>
</div> </div>
@@ -33,18 +33,18 @@ export default {
components: { Container, baseTable }, components: { Container, baseTable },
props: { props: {
importantWork: { importantWork: {
type: Object, type: Array,
default: () => ({}) default: () => ([])
} }
}, },
data() { data() {
return { return {
tableData: [], tableData: [],
tableProps: [ tableProps: [
{ prop: 'name', label: '攻坚指标', align: 'center' }, { prop: 'index', label: '攻坚指标', align: 'center',width:150 },
{ prop: 'target', label: '攻坚预算', align: 'center' }, { prop: 'target', label: '攻坚预算', align: 'center' },
{ prop: 'monthlyActual', label: '当月实际', align: 'center' }, { prop: 'real', label: '当月实际', align: 'center' },
{ prop: 'accumulated', label: '累计', align: 'center', subcomponent: finishDiv }, { prop: 'total', label: '累计', align: 'center', subcomponent: finishDiv },
] ]
} }
}, },
@@ -59,33 +59,13 @@ export default {
}, },
methods: { methods: {
transformData(rawData) { transformData(rawData) {
if (!rawData || typeof rawData !== 'object') return []; return rawData.map((item, index) =>{
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 { return {
...item,
id: index + 1, id: index + 1,
name: item.name + '/' + item.unit, status: (item.rate >= 100) ? 'done' : 'pending'
target: target,
monthlyActual: data.monValue,
accumulated: accumulated,
status: accumulated > 0 && target > 0 && accumulated / target >= 1 ? 'done' : 'pending'
}; };
}); })
} }
} }
} }

View File

@@ -7,13 +7,17 @@
<!-- 右侧区域全屏按钮 --> <!-- 右侧区域全屏按钮 -->
<div class="right-content"> <div class="right-content">
<el-dropdown trigger="click"> <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
<el-button type="text" class="logout-btn" :title="'退出'"> <div class="avatar-wrapper">
<svg-icon style="color: #0B58FF;" icon-class="logout" /> <img :src="require(`../../../assets/images/choicepart/avatar.png`)" class="user-avatar">
</el-button> <span v-if="nickname" class="user-nickname">{{ nickname }}</span>
<i class="el-icon-caret-bottom" />
</div>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native='logout'>退出登录</el-dropdown-item> <el-dropdown-item @click.native="logout">
<el-dropdown-item @click.native='handleToggle'>切换账号</el-dropdown-item> <span>退出登录</span>
</el-dropdown-item>
<el-dropdown-item divided @click.native='handleToggle'>切换账号</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
<el-button type="text" class="return-btn" :title="'返回'" @click="handleReturn"> <el-button type="text" class="return-btn" :title="'返回'" @click="handleReturn">
@@ -39,6 +43,7 @@
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'
import moment from 'moment'; // 引入moment import moment from 'moment'; // 引入moment
import {getPath} from "@/utils/ruoyi"; import {getPath} from "@/utils/ruoyi";
export default { export default {
@@ -64,6 +69,9 @@ export default {
activeTime: 1, // 默认月维度0=日1=月2=年) activeTime: 1, // 默认月维度0=日1=月2=年)
} }
}, },
computed:{
...mapGetters(['nickname']),
},
watch: { watch: {
activeTime(newVal, oldVal) { activeTime(newVal, oldVal) {
if (newVal !== oldVal) { if (newVal !== oldVal) {
@@ -346,12 +354,35 @@ export default {
padding: 0; padding: 0;
} }
.logout-btn { .avatar-container {
width: 28px; margin-right: 30px;
height: 28px;
font-size: 28px; .avatar-wrapper {
padding: 0; 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> <template>
<div ref="cockpitEffChipBottom" id="cockpitEffChipBottom" style="width: 100%; height: 400px;"></div> <div ref="cockpitEffChipBottom" id="cockpitEffChipBottom" style="width: 100%; height: 400px;"></div>
</template> </template>
<script> <script>
import * as echarts from 'echarts'; import * as echarts from 'echarts';
export default { export default {
components: {}, components: {},
data() { data() {
return { return {
myChart: null // 存储图表实例,避免重复创建 myChart: null, // 存储图表实例,避免重复创建
}; resizeHandler: null // 窗口resize事件处理器
}, };
props: { },
// 明确接收的props结构增强可读性 props: {
chartData: { // 明确接收的props结构增强可读性
type: Object, chartData: {
default: () => ({ type: Object,
series: [], default: () => ({
allPlaceNames: [] series: [],
}), allPlaceNames: []
// 校验数据格式 }),
validator: (value) => { // 校验数据格式
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames); validator: (value) => {
} return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
} }
}, }
mounted() { },
this.$nextTick(() => { mounted() {
this.updateChart(); this.$nextTick(() => {
}); this.updateChart();
}, });
// 注册窗口resize事件使用稳定的引用以便后续移除
// 新增:监听 chartData 变化 this.resizeHandler = () => {
watch: { if (this.myChart) {
// 深度监听数据变化,仅更新图表配置(不销毁实例) this.myChart.resize();
chartData: { }
handler() { };
console.log(this.chartData,'chartData'); window.addEventListener('resize', this.resizeHandler);
},
this.updateChart();
}, // 新增:监听 chartData 变化
deep: true, watch: {
immediate: true // 初始化时立即执行 // 深度监听数据变化,仅更新图表配置(不销毁实例)
} chartData: {
}, handler() {
methods: { console.log(this.chartData,'chartData');
updateChart() {
const chartDom = this.$refs.cockpitEffChipBottom; this.updateChart();
if (!chartDom) { },
console.error('图表容器未找到!'); deep: true,
return; immediate: true // 初始化时立即执行
} }
},
if (this.myChart) { methods: {
this.myChart.dispose(); updateChart() {
} const chartDom = this.$refs.cockpitEffChipBottom;
if (!chartDom) {
this.myChart = echarts.init(chartDom); console.error('图表容器未找到!');
const { allPlaceNames, series } = this.chartData || {}; return;
}
// 处理空数据
const xData = allPlaceNames || []; if (this.myChart) {
const chartSeries = series || []; // 父组件传递的 series this.myChart.dispose();
}
const option = {
tooltip: { this.myChart = echarts.init(chartDom);
trigger: 'axis', const { allPlaceNames, series } = this.chartData || {};
axisPointer: {
type: 'cross', // 处理空数据
label: { const xData = allPlaceNames || [];
backgroundColor: '#6a7985' const chartSeries = series || []; // 父组件传递的 series
}
}, const option = {
formatter: (params) => { tooltip: {
let html = `${params[0].axisValue}<br/>`; trigger: 'axis',
params.forEach(item => { axisPointer: {
const unit = item.seriesName === '完成率' ? '%' : ( type: 'cross',
['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元' label: {
); backgroundColor: '#6a7985'
html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`; }
}); },
return html; formatter: (params) => {
} let html = `${params[0].axisValue}<br/>`;
}, params.forEach(item => {
grid: { const unit = item.seriesName === '完成率' ? '%' : (
top: 30, ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
bottom: 30, );
right: 20, html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
left: 60, });
}, return html;
xAxis: [ }
{ },
type: 'category', grid: {
boundaryGap: true, top: 30,
axisTick: { show: false }, bottom: 30,
axisLine: { right: 20,
show: true, left: 60,
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
}, xAxis: [
axisLabel: { {
color: 'rgba(0, 0, 0, 0.45)', type: 'category',
fontSize: 12, boundaryGap: true,
interval: 0, axisTick: { show: false },
padding: [5, 0, 0, 0] axisLine: {
}, show: true,
data: xData lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
} },
], axisLabel: {
yAxis: [ color: 'rgba(0, 0, 0, 0.45)',
// 左侧Y轴营业收入、成本单位万元 fontSize: 12,
{ interval: 0,
type: 'value', padding: [5, 0, 0, 0]
splitNumber: 4, },
name: '万元', data: xData
nameTextStyle: { }
color: 'rgba(0, 0, 0, 0.45)', ],
fontSize: 12, yAxis: [
align: 'right' // 左侧Y轴营业收入、成本单位万元
}, {
// min: 0, type: 'value',
// max: (value) => Math.ceil((value.max || 0) * 1.1), splitNumber: 4,
name: '万元',
axisTick: { show: false }, nameTextStyle: {
axisLabel: { color: 'rgba(0, 0, 0, 0.45)',
color: 'rgba(0, 0, 0, 0.45)', fontSize: 12,
fontSize: 12, align: 'right'
formatter: '{value}' },
}, // min: 0,
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }, // max: (value) => Math.ceil((value.max || 0) * 1.1),
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
splitNumber: 4 axisTick: { show: false },
}, axisLabel: {
// 右侧Y轴利润占比百分比 color: 'rgba(0, 0, 0, 0.45)',
{ fontSize: 12,
type: 'value', formatter: '{value}'
nameTextStyle: { },
color: 'rgba(0, 0, 0, 0.45)', splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
fontSize: 12, axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
align: 'left' splitNumber: 4
}, },
// min: 0, // 右侧Y轴利润占比百分比
// max: 100, {
scale:true, type: 'value',
splitNumber: 4, nameTextStyle: {
axisTick: { show: false }, color: 'rgba(0, 0, 0, 0.45)',
axisLabel: { fontSize: 12,
color: 'rgba(0, 0, 0, 0.45)', align: 'left'
fontSize: 12, },
formatter: '{value}%' // min: 0,
}, // max: 100,
splitLine: { show: false }, scale:true,
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }, splitNumber: 4,
splitNumber: 4 axisTick: { show: false },
} axisLabel: {
], color: 'rgba(0, 0, 0, 0.45)',
series: chartSeries // 直接使用父组件传递的 series fontSize: 12,
}; formatter: '{value}%'
},
option && this.myChart.setOption(option); splitLine: { show: false },
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
// 窗口缩放适配和销毁逻辑保持不变 splitNumber: 4
window.addEventListener('resize', () => { }
this.myChart && this.myChart.resize(); ],
}); series: chartSeries // 直接使用父组件传递的 series
};
this.$once('hook:destroyed', () => {
window.removeEventListener('resize', () => { option && this.myChart.setOption(option);
this.myChart && this.myChart.resize(); }
}); },
this.myChart && this.myChart.dispose(); beforeDestroy() {
}); // 移除窗口resize事件监听器
} if (this.resizeHandler) {
}, window.removeEventListener('resize', this.resizeHandler);
}; this.resizeHandler = null;
</script> }
// 销毁图表,避免内存泄漏
if (this.myChart) {
this.myChart.dispose();
this.myChart = null;
}
}
};
</script>

View File

@@ -1,172 +1,180 @@
<template> <template>
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div> <div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
</template> </template>
<script> <script>
import * as echarts from 'echarts'; import * as echarts from 'echarts';
export default { export default {
components: {}, components: {},
data() { data() {
return { return {
myChart: null // 存储图表实例,避免重复创建 myChart: null, // 存储图表实例,避免重复创建
}; resizeHandler: null // 窗口resize事件处理器
}, };
props: { },
// 明确接收的props结构增强可读性 props: {
chartData: { // 明确接收的props结构增强可读性
type: Object, chartData: {
default: () => ({ type: Object,
series: [], default: () => ({
allPlaceNames: [] series: [],
}), allPlaceNames: []
// 校验数据格式 }),
validator: (value) => { // 校验数据格式
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames); validator: (value) => {
} return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
} }
}, }
mounted() { },
this.$nextTick(() => { mounted() {
this.updateChart(); this.$nextTick(() => {
}); this.updateChart();
}, });
// 注册窗口resize事件使用稳定的引用以便后续移除
// 新增:监听 chartData 变化 this.resizeHandler = () => {
watch: { if (this.myChart) {
// 深度监听数据变化,仅更新图表配置(不销毁实例) this.myChart.resize();
chartData: { }
handler() { };
console.log(this.chartData,'chartData'); window.addEventListener('resize', this.resizeHandler);
},
this.updateChart();
}, // 新增:监听 chartData 变化
deep: true, watch: {
immediate: true // 初始化时立即执行 // 深度监听数据变化,仅更新图表配置(不销毁实例)
} chartData: {
}, handler() {
methods: { console.log(this.chartData,'chartData');
updateChart() {
const chartDom = this.$refs.cockpitEffChip; this.updateChart();
if (!chartDom) { },
console.error('图表容器未找到!'); deep: true,
return; immediate: true // 初始化时立即执行
} }
},
if (this.myChart) { methods: {
this.myChart.dispose(); updateChart() {
} const chartDom = this.$refs.cockpitEffChip;
if (!chartDom) {
this.myChart = echarts.init(chartDom); console.error('图表容器未找到!');
const { allPlaceNames, series } = this.chartData || {}; return;
}
// 处理空数据
const xData = allPlaceNames || []; if (this.myChart) {
const chartSeries = series || []; // 父组件传递的 series this.myChart.dispose();
}
const option = {
tooltip: { this.myChart = echarts.init(chartDom);
trigger: 'axis', const { allPlaceNames, series } = this.chartData || {};
axisPointer: {
type: 'cross', // 处理空数据
label: { const xData = allPlaceNames || [];
backgroundColor: '#6a7985' const chartSeries = series || []; // 父组件传递的 series
}
}, const option = {
// formatter: (params) => { tooltip: {
// let html = `${params[0].axisValue}<br/>`; trigger: 'axis',
// params.forEach(item => { axisPointer: {
// const unit = item.seriesName === '完成率' ? '%' : ( type: 'cross',
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元' label: {
// ); backgroundColor: '#6a7985'
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`; }
// }); },
// return html; // formatter: (params) => {
// } // let html = `${params[0].axisValue}<br/>`;
}, // params.forEach(item => {
grid: { // const unit = item.seriesName === '完成率' ? '%' : (
top: 30, // ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
bottom: 30, // );
right: 20, // html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
left: 60, // });
}, // return html;
xAxis: [ // }
{ },
type: 'category', grid: {
boundaryGap: true, top: 30,
axisTick: { show: false }, bottom: 30,
axisLine: { right: 20,
show: true, left: 60,
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
}, xAxis: [
axisLabel: { {
color: 'rgba(0, 0, 0, 0.45)', type: 'category',
fontSize: 12, boundaryGap: true,
interval: 0, axisTick: { show: false },
padding: [5, 0, 0, 0] axisLine: {
}, show: true,
data: xData lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
} },
], axisLabel: {
yAxis: [ color: 'rgba(0, 0, 0, 0.45)',
// 左侧Y轴营业收入、成本单位万元 fontSize: 12,
{ interval: 0,
type: 'value', padding: [5, 0, 0, 0]
name: '万元', },
nameTextStyle: { data: xData
color: 'rgba(0, 0, 0, 0.45)', }
fontSize: 12, ],
align: 'right' yAxis: [
}, // 左侧Y轴营业收入、成本单位万元
{
splitNumber: 4, type: 'value',
axisTick: { show: false }, name: '万元',
axisLabel: { nameTextStyle: {
color: 'rgba(0, 0, 0, 0.45)', color: 'rgba(0, 0, 0, 0.45)',
fontSize: 12, fontSize: 12,
formatter: '{value}' align: 'right'
}, },
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }, splitNumber: 4,
splitNumber: 4 axisTick: { show: false },
}, axisLabel: {
// 右侧Y轴利润占比百分比 color: 'rgba(0, 0, 0, 0.45)',
// { fontSize: 12,
// type: 'value', formatter: '{value}'
// nameTextStyle: { },
// color: 'rgba(0, 0, 0, 0.45)', splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
// fontSize: 12, axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
// align: 'left' splitNumber: 4
// }, },
// min: 0, // 右侧Y轴利润占比百分比
// max: 100, // {
// axisTick: { show: false }, // type: 'value',
// axisLabel: { // nameTextStyle: {
// color: 'rgba(0, 0, 0, 0.45)', // color: 'rgba(0, 0, 0, 0.45)',
// fontSize: 12, // fontSize: 12,
// formatter: '{value}%' // align: 'left'
// }, // },
// splitLine: { show: false }, // min: 0,
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }, // max: 100,
// splitNumber: 4 // axisTick: { show: false },
// } // axisLabel: {
], // color: 'rgba(0, 0, 0, 0.45)',
series: chartSeries // 直接使用父组件传递的 series // fontSize: 12,
}; // formatter: '{value}%'
// },
option && this.myChart.setOption(option); // splitLine: { show: false },
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
// 窗口缩放适配和销毁逻辑保持不变 // splitNumber: 4
window.addEventListener('resize', () => { // }
this.myChart && this.myChart.resize(); ],
}); series: chartSeries // 直接使用父组件传递的 series
};
this.$once('hook:destroyed', () => {
window.removeEventListener('resize', () => { option && this.myChart.setOption(option);
this.myChart && this.myChart.resize(); }
}); },
this.myChart && this.myChart.dispose(); beforeDestroy() {
}); // 移除窗口resize事件监听器
} if (this.resizeHandler) {
}, window.removeEventListener('resize', this.resizeHandler);
}; this.resizeHandler = null;
</script> }
// 销毁图表,避免内存泄漏
if (this.myChart) {
this.myChart.dispose();
this.myChart = null;
}
}
};
</script>

View File

@@ -8,7 +8,8 @@ export default {
components: {}, components: {},
data() { data() {
return { return {
myChart: null // 存储图表实例,避免重复创建 myChart: null, // 存储图表实例,避免重复创建
resizeHandler: null // 窗口resize事件处理器
}; };
}, },
props: { props: {
@@ -27,6 +28,13 @@ export default {
this.$nextTick(() => { this.$nextTick(() => {
this.updateChart(); this.updateChart();
}); });
// 注册窗口resize事件使用稳定的引用以便后续移除
this.resizeHandler = () => {
if (this.myChart) {
this.myChart.resize();
}
};
window.addEventListener('resize', this.resizeHandler);
}, },
// 新增:监听 chartData 变化 // 新增:监听 chartData 变化
@@ -152,19 +160,19 @@ export default {
}; };
option && this.myChart.setOption(option); 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> </script>

View File

@@ -77,7 +77,7 @@ export default {
completed: 1 // 销量超额达标 completed: 1 // 销量超额达标
}, },
{ {
name: "双镀面板", name: "双镀销量",
targetValue: 0, targetValue: 0,
value: 0, value: 0,
proportion: 0, proportion: 0,
@@ -113,7 +113,7 @@ export default {
"单价": "unitPrice", "单价": "unitPrice",
"净价": "netPrice", "净价": "netPrice",
"销量": "sales", "销量": "sales",
"双镀面板": "panel", "双镀销量": "panel",
"溢价产品销量": "premiumProduct" "溢价产品销量": "premiumProduct"
}; };

View File

@@ -1,18 +1,18 @@
<template> <template>
<div class="coreItem"> <div class="coreItem">
<!-- 单独渲染第一个item --> <!-- 单独渲染第一个item -->
<div class="item" v-if="itemList.length > 0"> <div class="item" v-if="itemList.length > 0" v-for="(item, index) in itemList">
<div class="unit">{{ itemList[0].unit }}</div> <div class="unit">{{ item.unit }}</div>
<div class="item-content"> <div class="item-content">
<div class="content-wrapper"> <div class="content-wrapper">
<div class="left"> <div class="left">
<div class="number">{{ itemList[0].targetValue }}</div> <div class="number">{{ item.targetValue }}</div>
<div class="title">预算值</div> <div class="title">预算值</div>
</div> </div>
<div class="line"></div> <div class="line"></div>
<div class="right"> <div class="right">
<div class="number" :style="{ color: getColor(itemList[0].currentValue, itemList[0].targetValue) }"> <div class="number" :style="{ color: getColor(item.currentValue, item.targetValue) }">
{{ itemList[0].currentValue }} {{ item.currentValue }}
</div> </div>
<div class="title">实际值</div> <div class="title">实际值</div>
</div> </div>
@@ -23,7 +23,7 @@
<!-- 进度条颜色和宽度动态绑定 --> <!-- 进度条颜色和宽度动态绑定 -->
<div class="progress-bar" :style="{ <div class="progress-bar" :style="{
width: itemList[0].progress + '%', width: itemList[0].progress + '%',
background: getColor(itemList[0].currentValue, itemList[0].targetValue) background: getColor(item.progress)
}"></div> }"></div>
</div> </div>
</div> </div>
@@ -32,67 +32,9 @@
<div class="progress-percent">完成率</div> <div class="progress-percent">完成率</div>
<!-- 百分比颜色动态绑定 --> <!-- 百分比颜色动态绑定 -->
<div class="progress-percent" :style="{ <div class="progress-percent" :style="{
color: getColor(itemList[0].currentValue, itemList[0].targetValue) color: getColor(item.progress)
}"> }">
{{ itemList[0].progress }}% {{ item.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>
</div> </div>
</div> </div>
</div> </div>
@@ -108,60 +50,7 @@ export default {
data() { data() {
return { return {
progress: 90, // 进度值基础参数 progress: 90, // 进度值基础参数
itemList: [ 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 }]
// }
]
}; };
}, },
watch: { watch: {
@@ -180,67 +69,46 @@ export default {
targetValue: data.totalProgress.target, targetValue: data.totalProgress.target,
currentValue: data.totalProgress.real, currentValue: data.totalProgress.real,
progress: data.totalProgress.rate, progress: data.totalProgress.rate,
cities: [] // 总进度无需城市数据,留空
}, },
{ {
unit: "一组", unit: "一组",
targetValue: data.group1.target, targetValue: data.group1.target,
currentValue: data.group1.real, currentValue: data.group1.real,
progress: data.group1.rate, 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 } // 新增城市示例
]
}, },
{ {
unit: "二组", unit: "二组",
targetValue: data.group2.target, targetValue: data.group2.target,
currentValue: data.group2.real, currentValue: data.group2.real,
progress: data.group2.rate, 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 }
]
}, },
// 其他组同理,按需添加 cities 数据
{ {
unit: "三组", unit: "三组",
targetValue: data.group3.target, targetValue: data.group3.target,
currentValue: data.group3.real, currentValue: data.group3.real,
progress: data.group3.rate, 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 }
]
}, },
{ {
unit: "四组", unit: "四组",
targetValue: data.group4.target, targetValue: data.group4.target,
currentValue: data.group4.real, currentValue: data.group4.real,
progress: data.group4.rate, 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 }
]
}, },
{ {
unit: "五组", unit: "五组",
targetValue: data.group5.target, targetValue: data.group5.target,
currentValue: data.group5.real, currentValue: data.group5.real,
progress: data.group5.rate, 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 }
]
} }
] ]
}, },
// 颜色判断核心方法:实际值≥预算值返回绿色,否则返回橙色 // 颜色判断核心方法:实际值≥预算值返回绿色,否则返回橙色
getColor(currentValue, targetValue) { getColor(progress) {
return currentValue >= targetValue return progress >= 100
? "rgba(98, 213, 180, 1)" ? "rgba(98, 213, 180, 1)"
: "rgba(249, 164, 74, 1)"; : "rgba(249, 164, 74, 1)";
}, },
getTableData(data) { getTableData(data) {
console.log(data, 'data');
this.$emit('handleShowTable',data) this.$emit('handleShowTable',data)
} }
@@ -257,11 +125,12 @@ export default {
.coreItem { .coreItem {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-between;
gap: 8px; gap: 8px;
} }
.item { .item {
width: 220px; width: 227px;
height: 122px; height: 122px;
background: #f9fcff; background: #f9fcff;
padding: 12px; padding: 12px;

View File

@@ -24,7 +24,10 @@ export default {
}, },
}, },
data() { data() {
return {}; return {
myChart: null,
resizeHandler: null
};
}, },
computed: {}, computed: {},
watch: { watch: {
@@ -41,6 +44,13 @@ export default {
this.$nextTick(() => { this.$nextTick(() => {
this.initChart(); // 只负责初始化图表实例 this.initChart(); // 只负责初始化图表实例
}); });
// 注册窗口resize事件使用稳定的引用以便后续移除
this.resizeHandler = () => {
if (this.myChart) {
this.myChart.resize();
}
};
window.addEventListener('resize', this.resizeHandler);
}, },
methods: { methods: {
initData() { initData() {
@@ -154,7 +164,7 @@ export default {
itemStyle: { color: 'rgba(39, 96, 255, 1)' } itemStyle: { color: 'rgba(39, 96, 255, 1)' }
}, },
{ {
value: 735, name: '双镀面板', value: 735, name: '双镀销量',
label: { label: {
normal: { normal: {
align: 'left', align: 'left',
@@ -266,21 +276,20 @@ export default {
] ]
}; };
option && myChart.setOption(option); option && this.myChart.setOption(option);
// 窗口缩放监听
window.addEventListener('resize', () => {
myChart.resize();
});
// 组件销毁清理
this.$once('hook:destroyed', () => {
window.removeEventListener('resize', () => {
myChart.resize();
});
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> </script>

View File

@@ -1,286 +1,294 @@
<template> <template>
<div style="position: relative;"> <div style="position: relative;">
<div class="legend"> <div class="legend">
<span class="legend-item-line"> <span class="legend-item-line">
<span class="line target"></span> <span class="line target"></span>
预算 预算
</span> </span>
<span class="legend-item-line"> <span class="legend-item-line">
<span class="line real"></span> <span class="line real"></span>
实际 实际
</span> </span>
</div> </div>
<div ref="cockpitEffChip" id="coreLineChart" style="height: 219px; width: 100%;"></div> <div ref="cockpitEffChip" id="coreLineChart" style="height: 219px; width: 100%;"></div>
</div> </div>
</template> </template>
<script> <script>
import * as echarts from 'echarts'; import * as echarts from 'echarts';
export default { export default {
name: 'Container', name: 'Container',
props: ["chartData",'dateData'], props: ["chartData",'dateData','unit'],
components: {}, components: {},
data() { data() {
return { return {
myChart: null, // 存储 echarts 实例 myChart: null, // 存储 echarts 实例
}; resizeHandler: null // 窗口resize事件处理器
}, };
// 关键:监听 chartData 变化 },
watch: { // 关键:监听 chartData 变化
chartData: { watch: {
handler(newData) { chartData: {
this.updateChart(newData); handler(newData) {
}, this.updateChart(newData);
immediate: true, // 组件初始化时立即执行一次 },
deep: true, // 深度监听对象内部变化 immediate: true, // 组件初始化时立即执行一次
} deep: true, // 深度监听对象内部变化
}, }
mounted() { },
this.$nextTick(() => { mounted() {
this.initChart(); this.$nextTick(() => {
}); this.initChart();
}, });
methods: { // 注册窗口resize事件使用稳定的引用以便后续移除
// 初始化图表实例 this.resizeHandler = () => {
initChart() { if (this.myChart) {
const chartDom = this.$refs.cockpitEffChip; this.myChart.resize();
if (!chartDom) { }
console.error('图表容器未找到!'); };
return; window.addEventListener('resize', this.resizeHandler);
} },
this.myChart = echarts.init(chartDom); methods: {
// 初始化图表实例
// 初始化时调用一次更新 initChart() {
this.updateChart(this.chartData); const chartDom = this.$refs.cockpitEffChip;
if (!chartDom) {
// 监听窗口缩放 console.error('图表容器未找到!');
window.addEventListener('resize', () => { return;
this.myChart?.resize(); }
}); this.myChart = echarts.init(chartDom);
},
// 初始化时调用一次更新
// 核心:根据数据更新图表 this.updateChart(this.chartData);
updateChart(data) { },
if (!this.myChart) {
// 如果实例还未初始化,则等待 initChart 完成后再更新 // 核心:根据数据更新图表
setTimeout(() => this.updateChart(data), 0); updateChart(data) {
return; if (!this.myChart) {
} // 如果实例还未初始化,则等待 initChart 完成后再更新
setTimeout(() => this.updateChart(data), 0);
// 1. 处理数据,如果 data 无效则清空图表 return;
if (!data || typeof data !== 'object' || (!data.real && !data.target)) { }
this.myChart.setOption({
xAxis: { data: [] }, // 1. 处理数据,如果 data 无效则清空图表
series: [{ data: [] }, { data: [] }] if (!data || typeof data !== 'object' || (!data.real && !data.target)) {
}); this.myChart.setOption({
return; xAxis: { data: [] },
} series: [{ data: [] }, { data: [] }]
});
// 2. 提取 X 轴数据(从 real 或 target 中取键名) return;
const xAxisData = data.real ? Object.keys(data.real) : Object.keys(data.target); }
console.log('xAxisData', xAxisData);
// 2. 提取 X 轴数据(从 real 或 target 中取键名)
// 3. 提取 "实际" 和 "目标" 系列的数据 const xAxisData = data.real ? Object.keys(data.real) : Object.keys(data.target);
const realData = data.real ? Object.values(data.real) : []; console.log('xAxisData', xAxisData);
const targetData = data.target ? Object.values(data.target) : [];
// 3. 提取 "实际" 和 "目标" 系列的数据
// 4. 准备 echarts 的 option 配置 const realData = data.real ? Object.values(data.real) : [];
const option = { const targetData = data.target ? Object.values(data.target) : [];
tooltip: {
trigger: 'axis', // 4. 准备 echarts 的 option 配置
axisPointer: { const option = {
type: 'cross', tooltip: {
label: { trigger: 'axis',
backgroundColor: '#6a7985' axisPointer: {
} type: 'cross',
} label: {
}, backgroundColor: '#6a7985'
grid: { }
top: 35, }
bottom: 20, },
right: 13, grid: {
}, top: 35,
xAxis: [ bottom: 20,
{ right: 13,
type: 'category', },
boundaryGap: false, xAxis: [
axisTick: { show: false }, {
axisLine: { type: 'category',
show: true, boundaryGap: false,
onZero: false, axisTick: { show: false },
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } axisLine: {
}, show: true,
axisLabel: { onZero: false,
color: 'rgba(0, 0, 0, 0.45)', lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
fontSize: 12, },
interval: 0, axisLabel: {
// width: 38, color: 'rgba(0, 0, 0, 0.45)',
overflow: 'break', fontSize: 12,
formatter: (value) => { interval: 0,
const dateParts = value.split('-'); // ["2025", "07", "01"] // width: 38,
if (dateParts.length < 2) return value; overflow: 'break',
formatter: (value) => {
// 去掉月份前面的0然后加上"月" const dateParts = value.split('-'); // ["2025", "07", "01"]
const month = dateParts[1].replace(/^0+/, ''); if (dateParts.length < 2) return value;
return `${month}`;
} // 去掉月份前面的0然后加上"月"
}, const month = dateParts[1].replace(/^0+/, '');
data: xAxisData return `${month}`;
} }
], },
yAxis: { data: xAxisData
type: 'value', }
name: '元/㎡', ],
// nameLocation:'center', yAxis: {
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' }, type: 'value',
min: 0, name: this.unit,
// max: function (value) { return Math.ceil(value.max * 1.1); }, // 增加一点余量 // nameLocation:'center',
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' },
axisTick: { show: false }, min: 0,
axisLabel: { // max: function (value) { return Math.ceil(value.max * 1.1); }, // 增加一点余量
color: 'rgba(0, 0, 0, 0.45)',
fontSize: 12 axisTick: { show: false },
}, axisLabel: {
splitLine: { color: 'rgba(0, 0, 0, 0.45)',
lineStyle: { fontSize: 12
color: 'rgba(0, 0, 0, 0.15)', },
} splitLine: {
}, lineStyle: {
axisLine: { color: 'rgba(0, 0, 0, 0.15)',
show: true, }
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
} axisLine: {
}, show: true,
series: [ lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
{ }
name: '实际', },
type: 'line', series: [
// stack: 'Total', // 趋势图通常不需要堆叠 {
symbol: 'circle', name: '预算',
symbolSize: 8, type: 'line',
lineStyle: { // stack: 'Total',
color: 'rgba(255, 132, 0, 1)', // 加深颜色 symbol: 'circle',
width: 2, symbolSize: 8,
}, lineStyle: {
itemStyle: { color: 'rgba(98, 213, 180, 1)', // 加深颜色
color: 'rgba(255, 132, 0, 1)', width: 2,
borderColor: '#fff', // type: 'dashed' // 目标线使用虚线
borderWidth: 2, },
}, itemStyle: {
areaStyle: { color: 'rgba(98, 213, 180, 1)',
opacity: 0.3, borderColor: '#fff',
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ borderWidth: 2,
{ offset: 0, color: 'rgba(255, 132, 0, .5)' }, },
{ offset: 1, color: 'rgba(255, 132, 0, 0)' }, areaStyle: {
]), opacity: 0.3,
}, color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
data: realData // 使用提取出的 "实际" 数据 { offset: 0, color: 'rgba(98, 213, 180, .5)' },
}, { offset: 1, color: 'rgba(98, 213, 180, 0)' },
{ ]),
name: '预算', },
type: 'line', data: targetData // 使用提取出的 "目标" 数据
// stack: 'Total', },
symbol: 'circle', {
symbolSize: 8, name: '实际',
lineStyle: { type: 'line',
color: 'rgba(98, 213, 180, 1)', // 加深颜色 // stack: 'Total', // 趋势图通常不需要堆叠
width: 2, symbol: 'circle',
// type: 'dashed' // 目标线使用虚线 symbolSize: 8,
}, lineStyle: {
itemStyle: { color: 'rgba(255, 132, 0, 1)', // 加深颜色
color: 'rgba(98, 213, 180, 1)', width: 2,
borderColor: '#fff', },
borderWidth: 2, itemStyle: {
}, color: 'rgba(255, 132, 0, 1)',
areaStyle: { borderColor: '#fff',
opacity: 0.3, borderWidth: 2,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ },
{ offset: 0, color: 'rgba(98, 213, 180, .5)' }, areaStyle: {
{ offset: 1, color: 'rgba(98, 213, 180, 0)' }, opacity: 0.3,
]), color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
}, { offset: 0, color: 'rgba(255, 132, 0, .5)' },
data: targetData // 使用提取出的 "目标" 数据 { offset: 1, color: 'rgba(255, 132, 0, 0)' },
}, ]),
] },
}; data: realData // 使用提取出的 "实际" 数据
}
// 5. 应用配置项更新图表 ]
this.myChart.setOption(option, true); };
}
}, // 5. 应用配置项更新图表
beforeDestroy() { this.myChart.setOption(option, true);
// 组件销毁时清理 }
window.removeEventListener('resize', () => { },
this.myChart?.resize(); beforeDestroy() {
}); // 移除窗口resize事件监听器
this.myChart?.dispose(); if (this.resizeHandler) {
} window.removeEventListener('resize', this.resizeHandler);
}; this.resizeHandler = null;
</script> }
<style lang="scss" scoped> // 销毁图表,避免内存泄漏
/* (你的样式代码保持不变) */ if (this.myChart) {
.legend { this.myChart.dispose();
position: absolute; this.myChart = null;
right: 12px; }
top: 0px; }
display: flex; };
/* 使用 flex 布局让两个图例项并排且对齐 */ </script>
gap: 5px; <style lang="scss" scoped>
} /* (你的样式代码保持不变) */
.legend {
.legend-item-line { position: absolute;
font-family: PingFangSC, PingFang SC; right: 12px;
font-weight: 400; top: 0px;
font-size: 14px; display: flex;
color: rgba(0, 0, 0, 0.8); /* 使用 flex 布局让两个图例项并排且对齐 */
text-align: left; gap: 5px;
font-style: normal; }
position: relative;
padding-left: 24px; .legend-item-line {
/* 为圆点和线条留出空间 */ font-family: PingFangSC, PingFang SC;
display: flex; font-weight: 400;
align-items: center; font-size: 14px;
color: rgba(0, 0, 0, 0.8);
.line { text-align: left;
position: absolute; font-style: normal;
left: 6px; position: relative;
/* 线条从圆点右侧开始 */ padding-left: 24px;
top: 50%; /* 为圆点和线条留出空间 */
transform: translateY(-50%); display: flex;
display: inline-block; align-items: center;
width: 16px;
/* 线条长度 */ .line {
height: 2px; position: absolute;
margin-right: 4px; left: 6px;
} /* 线条从圆点右侧开始 */
top: 50%;
.target { transform: translateY(-50%);
background: rgba(98, 213, 180, 1); display: inline-block;
} width: 16px;
/* 线条长度 */
.real { height: 2px;
background: rgba(255, 132, 0, 1); margin-right: 4px;
} }
&::before { .target {
content: ""; background: rgba(98, 213, 180, 1);
display: inline-block; }
width: 6px;
height: 6px; .real {
border-radius: 50%; background: rgba(255, 132, 0, 1);
margin-right: 8px; }
background-color: rgba(255, 132, 0, 1);
position: absolute; &::before {
left: 10px; content: "";
top: 50%; display: inline-block;
transform: translateY(-50%); width: 6px;
} height: 6px;
} border-radius: 50%;
margin-right: 8px;
.legend-item-line:nth-child(1) { background-color: rgba(255, 132, 0, 1);
&::before { position: absolute;
background-color: rgba(98, 213, 180, 1); left: 10px;
} top: 50%;
} transform: translateY(-50%);
</style> }
}
.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="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 class="item-button" style="width: 75px;" :class="{ active: activeButton === 4 }" @click="activeButton = 4">
投入产出率</div> 投入产出率</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> </div>
<div class="lineBottom" style="height: 219px; width: 100%" v-if="isLineDataReady"> <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>
</div> </div>
</template> </template>
<script> <script>
import coreLineChart from './productBar.vue'; import coreLineChart from './productBar.vue';
const dataKeyMap = [
{name:'制造成本',unit:'元/㎡'},
{name:'原片成本',unit:'元/㎡'},
{name:'加工成本',unit:'元/㎡'},
{name:'原片成品率',unit:'%'},
{name:'投入产出率',unit:'%'},
{name:'折旧',unit:'万元'}
];
export default { export default {
name: "Container", name: "Container",
components: { coreLineChart }, components: { coreLineChart },
@@ -33,6 +43,7 @@ export default {
data() { data() {
return { return {
activeButton: 0, // 初始激活第一个按钮索引0 activeButton: 0, // 初始激活第一个按钮索引0
dataKeyMap
}; };
}, },
computed: { computed: {
@@ -42,22 +53,14 @@ export default {
}, },
// 核心改动计算属性根据activeButton动态返回选中的数据 // 核心改动计算属性根据activeButton动态返回选中的数据
selectedChartData() { selectedChartData() {
// 定义按钮索引与lineData中key的映射关系
const dataKeyMap = [
'制造成本',
'原片成本',
'加工成本',
'原片成品率',
'投入产出率'
];
// 根据当前激活的按钮索引获取对应的数据key // 根据当前激活的按钮索引获取对应的数据key
const selectedKey = dataKeyMap[this.activeButton]; const selectedKey = this.dataKeyMap[this.activeButton].name;
console.log(this.lineData[selectedKey]);
// 从lineData中获取对应的数据如果不存在则返回一个空对象以防止报错 // 从lineData中获取对应的数据如果不存在则返回一个空对象以防止报错
return this.lineData ? this.lineData[selectedKey] || {} : {}; return this.lineData ? this.lineData[selectedKey] || {} : {};
},
unit() {
return this.dataKeyMap[this.activeButton].unit;
} }
}, },
methods: {}, methods: {},
@@ -73,7 +76,7 @@ export default {
.barTop { .barTop {
display: flex; display: flex;
gap: 40px; gap: 20px;
.title { .title {
height: 18px; height: 18px;
@@ -91,7 +94,7 @@ export default {
display: flex; display: flex;
position: relative; position: relative;
gap: 2px; gap: 2px;
width: 327px; width: 356px;
align-items: center; align-items: center;
height: 24px; height: 24px;
background: #ecf4fe; background: #ecf4fe;
@@ -105,23 +108,27 @@ export default {
} }
.lineOne { .lineOne {
top: 5px; top: 6px;
left: 57px; left: 55px;
} }
.lineTwo { .lineTwo {
top: 5px; top: 6px;
left: 118px; left: 111px;
} }
.lineThree { .lineThree {
top: 5px; top: 6px;
left: 177px; left: 169px;
} }
.lineFour { .lineFour {
top: 5px; top: 6px;
left: 252px; left: 240px;
}
.lineFive {
top: 6px;
left: 314px;
} }
.item-button { .item-button {

View File

@@ -1,52 +1,96 @@
<template> <template>
<div class="coreItem"> <div>
<div class="item" @click="handleRoute(item.route)" v-for="(item, index) in itemList" :key="index"> <div class="coreItem">
<div class="name">{{ item.name }}</div> <div class="item" @click="handleRoute(item.route)" v-for="(item, index) in itemList" :key="index" v-if='index<4'>
<div class="item-content"> <div class="name">{{ item.name }}</div>
<div class="content-wrapper"> <div class="item-content">
<div class="left"> <div class="content-wrapper">
<div class="number" style="color: rgba(103, 103, 103, 0.79);">{{ item.targetValue }}</div> <div class="left">
<div class="title" style="color: rgba(134, 134, 135, 1);">目标值</div> <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>
<div class="line"></div> <div class="line"></div>
<!-- 实际值根据 实际值目标值 动态绑定类名 -->
<div class="right"> <!-- 进度条同步绑定类名 -->
<div class="number" :class="{ <div class="progress-group">
'number-exceed': item.currentValue >= item.targetValue, <div class="progress-container">
'number-below': item.currentValue < item.targetValue <div class="progress-bar" :style="{ width: item.progressWidth + '%' }" :class="{
}"> 'bar-exceed': item.progress >= 100,
{{ item.currentValue }} 'bar-below': item.progress < 100
}"></div>
</div> </div>
<div class="title" style="color: rgba(134, 134, 135, 1);">实际值</div>
</div> </div>
</div>
<div class="line"></div>
<!-- 进度条同步绑定类名 --> <!-- 完成率同步绑定类名 -->
<div class="progress-group"> <div class="yield" style="display: flex;justify-content: space-between;">
<div class="progress-container"> <div class="progress-percent" :class="{
<div class="progress-bar" :style="{ width: item.progressWidth + '%' }" :class="{ 'percent-exceed': item.progress >= 100,
'bar-exceed': item.currentValue >= item.targetValue, 'percent-below': item.progress < 100
'bar-below': item.currentValue < item.targetValue }">完成率</div>
}"></div> <div class="progress-percent" :class="{
</div> 'percent-exceed': item.progress >= 100,
</div> 'percent-below': item.progress < 100
}">
<!-- 完成率同步绑定类名 --> {{ item.progress }}%
<div class="yield" style="display: flex;justify-content: space-between;"> </div>
<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> </div>
</div> </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> </div>
</template> </template>
@@ -160,6 +204,18 @@ export default {
name: '毛利率·%', name: '毛利率·%',
route: '/grossMargin/grossMargin', route: '/grossMargin/grossMargin',
isPercentage: true // 需要加%符号 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 // 额外兜底避免data中的属性为undefined
const target = data.target || 0; const target = data.target || 0;
const real = data.real || 0; const real = data.real || 0;
const rate = data.rate || '0%'; const rate = data.rate || 0;
// 解析rate字符串 // 解析rate字符串
const parsedRate = this.parseRateString(rate); const parsedRate = this.parseRateString(rate);
@@ -178,24 +234,12 @@ export default {
// 进度条宽度限制在0-100之间 // 进度条宽度限制在0-100之间
const progressWidth = Math.min(Math.max(parsedRate.progressValue, 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 { return {
name: mappingItem.name, name: mappingItem.name,
targetValue: target, targetValue: target,
currentValue: real, currentValue: real,
progressWidth: progressWidth, // 用于进度条宽度 progressWidth: progressWidth, // 用于进度条宽度
progressDisplay: progressDisplay, // 用于显示文本 progress: rate, // 用于显示文本
route: mappingItem.route route: mappingItem.route
}; };
}); });
@@ -219,24 +263,152 @@ export default {
.coreItem { .coreItem {
display: flex; display: flex;
gap: 8px; 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 { &:hover {
width: 170px; box-shadow: 0px 4px 12px 2px #B5CDE5;
height: 228px; 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; background: #f9fcff;
padding: 12px 0px 17px 12px; padding: 8px 8px 0px;
box-sizing: border-box; box-sizing: border-box;
cursor: pointer; cursor: pointer; // 提示可点击
transition: all 0.3s ease; transition: all 0.3s ease; // 动画过渡
&:hover { &:hover {
box-shadow: 0px 4px 12px 2px #B5CDE5; box-shadow: 0px 4px 12px 2px #B5CDE5;
transform: translateY(-2px); transform: translateY(-2px); // 轻微上浮增强交互感
} }
.name { .unit {
height: 18px; height: 18px;
font-family: PingFangSC, PingFang SC; font-family: PingFangSC, PingFang SC;
font-weight: 400; font-weight: 400;
@@ -246,57 +418,51 @@ export default {
letter-spacing: 1px; letter-spacing: 1px;
text-align: left; text-align: left;
font-style: normal; font-style: normal;
margin-bottom: 2px;
} }
.item-content { .item-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
height: calc(100% - 26px); height: calc(100% - 29px);
} }
.content-wrapper { .content-wrapper {
display: flex; display: flex;
flex-direction: column; align-items: center;
gap: 10px; justify-content: space-around;
flex: 1;
} }
.line { .line {
width: 149px; width: 1px;
height: 1px; height: 46px;
background: linear-gradient(to left, rgba(255, 0, 0, 0), #cbcbcb); background: linear-gradient(to bottom, rgba(255, 0, 0, 0), #cbcbcb);
} }
.left, .left,
.right { .right {
margin-top: 11px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center;
align-items: center;
gap: 2px; gap: 2px;
width: 100%; flex: 1;
} }
/* 实际值 - 基础样式(无颜色) */
.number { .number {
height: 22px; height: 22px;
font-family: PingFangSC, PingFang SC; font-family: PingFangSC, PingFang SC;
font-weight: 600; font-weight: 600;
font-size: 24px; font-size: 24px;
color: rgba(103, 103, 103, 0.79);
/* 默认颜色(等于目标值时) */
line-height: 22px; line-height: 22px;
text-align: left; text-align: center;
font-style: normal; font-style: normal;
} }
/* 实际值 - 实际值≥目标值(绿色) */
.number-exceed {
color: rgba(54, 181, 138, 1) !important;
}
/* 实际值 - 实际值<目标值(黄色) */
.number-below {
color: rgba(249, 164, 74, 1) !important;
}
.title { .title {
height: 14px; height: 14px;
font-family: PingFangSC, PingFang SC; font-family: PingFangSC, PingFang SC;
@@ -304,7 +470,7 @@ export default {
font-size: 12px; font-size: 12px;
color: #868687; color: #868687;
line-height: 14px; line-height: 14px;
text-align: left; text-align: center;
font-style: normal; font-style: normal;
} }
@@ -312,57 +478,83 @@ export default {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
margin-top: 15px;
} }
.progress-container { .progress-container {
width: 138px; width: 280px;
height: 10px; height: 10px;
background: #ECEFF7; background: #ECEFF7;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
} }
/* 进度条 - 基础样式(无颜色) */
.progress-bar { .progress-bar {
height: 100%; height: 100%;
background: rgba(98, 213, 180, 1);
/* 默认进度条颜色(等于目标值时) */
border-radius: 8px; 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 { .progress-percent {
font-family: PingFangSC, PingFang SC; font-family: PingFangSC, PingFang SC;
font-weight: 400; font-weight: 400;
font-size: 12px; font-size: 12px;
color: #868687;
/* 默认百分比颜色(等于目标值时) */
line-height: 1; line-height: 1;
} }
/* 百分比 - 实际值≥目标值绿色 */ /* 实际值 > 目标值绿色样式 */
.percent-exceed { .exceed-target {
color: rgba(54, 181, 138, 1) !important; 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; color: rgba(249, 164, 74, 1) !important;
/* 文字黄色 */
// background: rgba(249, 164, 74, 1) !important;
/* 进度条黄色 */
opacity: 1 !important;
} }
.yield { .exceed-pro-target {
width: 138px; // color: rgba(98, 213, 180, 1) !important;
margin-top: 3px; /* 文字绿色 */
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> </style>

View File

@@ -12,9 +12,9 @@
<!-- 实际值根据与目标值的比较动态变色 --> <!-- 实际值根据与目标值的比较动态变色 -->
<div class="right"> <div class="right">
<div class="number" :class="{ <div class="number" :class="{
'exceed-target': item.currentValue > item.targetValue, 'exceed-target': item.progress > 100,
'below-target': item.currentValue < item.targetValue, 'below-target': item.progress < 100,
'equal-target': item.currentValue === item.targetValue 'equal-target': item.progress === 100
}"> }">
{{ item.currentValue }} {{ item.currentValue }}
</div> </div>
@@ -25,15 +25,15 @@
<div class="progress-group"> <div class="progress-group">
<div class="progress-container"> <div class="progress-container">
<div class="progress-bar" :style="{ width: item.progress + '%' }" :class="{ <div class="progress-bar" :style="{ width: item.progress + '%' }" :class="{
'exceed-pro-target': item.currentValue > item.targetValue, 'exceed-pro-target': item.progress > 100,
'below-pro-target': item.currentValue < item.targetValue, 'below-pro-target': item.progress < 100,
'equal-pro-target': item.currentValue === item.targetValue 'equal-pro-target': item.progress === 100
}"></div> }"></div>
</div> </div>
<div class="progress-percent" :class="{ <div class="progress-percent" :class="{
'exceed-target': item.currentValue > item.targetValue, 'exceed-target': item.progress > 100,
'below-target': item.currentValue < item.targetValue, 'below-target': item.progress < 100,
'equal-target': item.currentValue === item.targetValue 'equal-target': item.progress === 100
}"> }">
{{ item.progress }}% {{ item.progress }}%
</div> </div>
@@ -77,7 +77,7 @@ export default {
{ key: 'unitPrice', unit: '单价·元/㎡', path: '/unitPriceAnalysis/unitPriceAnalysis' }, { key: 'unitPrice', unit: '单价·元/㎡', path: '/unitPriceAnalysis/unitPriceAnalysis' },
{ key: 'netPrice', unit: '净价·元/㎡', path: '/netPriceAnalysis/netPriceAnalysis' }, { key: 'netPrice', unit: '净价·元/㎡', path: '/netPriceAnalysis/netPriceAnalysis' },
{ key: 'sales', unit: '销量·万㎡', path: '/salesVolumeAnalysis/salesVolumeAnalysis' }, { 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> <template>
<div class="coreItem"> <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"> :key="index">
<div class="unit">{{ item.unit }}</div> <div class="unit">{{ item.unit }}</div>
<div class="item-content"> <div class="item-content">
<div class="content-wrapper"> <div class="content-wrapper">
<div class="left"> <div class="left" v-if="item.unit !== '折旧·万元'">
<div class="number">{{ item.target }}</div> <div class="number">{{ item.target }}</div>
<div class="title">预算值</div> <div class="title">预算值</div>
</div> </div>
<div class="line"></div> <div class="line" v-if="item.unit !== '折旧·万元'"></div>
<div class="right"> <div class="right">
<!-- 实际值颜色动态绑定 --> <!-- 实际值颜色动态绑定 -->
<div class="number" :style="{ color: getColor(index) }"> <div class="number" :style="{ color: getColor(index) }">
@@ -18,7 +18,7 @@
<div class="title">实际值</div> <div class="title">实际值</div>
</div> </div>
</div> </div>
<div class="progress-group"> <div class="progress-group" v-if="item.unit !== '折旧·万元'">
<div class="progress-container"> <div class="progress-container">
<!-- 进度条样式动态绑定 --> <!-- 进度条样式动态绑定 -->
<div class="progress-bar" :style="{ <div class="progress-bar" :style="{
@@ -88,12 +88,17 @@ export default {
{ {
key: 'rawYield', key: 'rawYield',
unit: '原片成品率·%', unit: '原片成品率·%',
route: '/rawSheetYield/rawSheetYield' // 假设这个没有路由 route: '/rawSheetYield/rawSheetYield'
}, },
{ {
key: 'ioYield', key: 'ioYield',
unit: '投入产出率·%', 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]; const { actual, target, progress } = this.itemList[index];
// 新增条件如果实际值、预算值和进度都为0则显示绿色 // 新增条件如果实际值、预算值和进度都为0则显示绿色
if (actual === 0 && target === 0 && progress === 0) { // if (actual === 0 && target === 0 && progress === 0) {
return "rgba(98, 213, 180, 1)"; // 绿色 // return "rgba(98, 213, 180, 1)"; // 绿色
} // }
// 原有的通用判断逻辑 // 原有的通用判断逻辑
return progress >= 100 return progress >= 100
@@ -194,7 +199,7 @@ export default {
} }
.item { .item {
width: 252px; width: 166px;
height: 110px; height: 110px;
background: #f9fcff; background: #f9fcff;
padding: 12px; padding: 12px;
@@ -300,10 +305,4 @@ export default {
line-height: 1; line-height: 1;
} }
} }
.item1,
.item2,
.item3 {
width: 166px;
}
</style> </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: {}, components: {},
data() { data() {
return { return {
myChart: null // 保存图表实例,方便更新 myChart: null, // 保存图表实例,方便更新
resizeHandler: null // 窗口resize事件处理器
}; };
}, },
props: { props: {
@@ -35,6 +36,13 @@ export default {
this.$nextTick(() => { this.$nextTick(() => {
this.initChart(); this.initChart();
}); });
// 注册窗口resize事件使用稳定的引用以便后续移除
this.resizeHandler = () => {
if (this.myChart) {
this.myChart.resize();
}
};
window.addEventListener('resize', this.resizeHandler);
}, },
methods: { methods: {
initChart() { initChart() {
@@ -45,19 +53,18 @@ export default {
} }
this.myChart = echarts.init(chartDom); this.myChart = echarts.init(chartDom);
this.updateChart(); // 初始化图表数据 this.updateChart(); // 初始化图表数据
},
// 窗口缩放适配 beforeDestroy() {
window.addEventListener('resize', () => { // 移除窗口resize事件监听器
this.myChart && this.myChart.resize(); if (this.resizeHandler) {
}); window.removeEventListener('resize', this.resizeHandler);
this.resizeHandler = null;
// 组件销毁清理 }
this.$once('hook:destroyed', () => { // 销毁图表,避免内存泄漏
window.removeEventListener('resize', () => { if (this.myChart) {
this.myChart && this.myChart.resize(); this.myChart.dispose();
}); this.myChart = null;
this.myChart && this.myChart.dispose(); }
});
}, },
updateChart() { updateChart() {

View File

@@ -22,7 +22,8 @@ export default {
}, },
data() { data() {
return { return {
myChart: null myChart: null,
resizeHandler: null // 窗口resize事件处理器
}; };
}, },
watch: { watch: {
@@ -38,6 +39,25 @@ export default {
this.$nextTick(() => { this.$nextTick(() => {
this.initData(); 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: { methods: {
initData() { initData() {
@@ -259,19 +279,6 @@ export default {
}; };
this.myChart.setOption(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

@@ -30,14 +30,7 @@ export default {
props: ["name", "size", "icon",'itemList'], props: ["name", "size", "icon",'itemList'],
data() { data() {
return { return {
itemList: [ itemList: [],
// { unit: "营业收入·万元", targetValue: 16, currentValue: 14.5, progress: 90 },
// { unit: "经营性利润·万元", targetValue: 16, currentValue: 15.2, progress: 85 },
// { unit: "利润总额·万元", targetValue: 16, currentValue: 15.2, progress: 85 },
// { unit: "毛利率·%", targetValue: 16, currentValue: 15.2, progress: 85 },
// { unit: "销量·万㎡", targetValue: 20, currentValue: 16, progress: 80 },
// { unit: "双镀面板·万㎡", targetValue: 15, currentValue: 13.8, progress: 92 }
],
// 拖拽相关状态 // 拖拽相关状态
isDragging: false, // 是否正在拖拽 isDragging: false, // 是否正在拖拽
startX: 0, // 拖拽开始时的鼠标X坐标 startX: 0, // 拖拽开始时的鼠标X坐标

View File

@@ -1,169 +1,182 @@
<template> <template>
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 500px;"></div> <div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 500px;"></div>
</template> </template>
<script> <script>
import * as echarts from 'echarts'; import * as echarts from 'echarts';
export default { export default {
components: {}, components: {},
data() { data() {
return {}; return {
}, myChart: null,
props: { resizeHandler: null // 窗口resize事件处理器
seriesData: { };
type: Array, },
default: () => [] props: {
}, seriesData: {
xData: { type: Array,
type: Array, default: () => []
default: () => [] },
}, xData: {
name: { type: Array,
type: String, default: () => []
default: () => { } },
}, name: {
}, type: String,
watch: { default: () => { }
// 监听 xData 变化,触发图表更新 },
xData: { },
handler() { watch: {
this.$nextTick(() => this.initData()); // 监听 xData 变化,触发图表更新
}, xData: {
deep: true // 深度监听数组内元素变化 handler() {
}, this.$nextTick(() => this.initData());
// 监听 seriesData 变化,触发图表更新 },
seriesData: { deep: true // 深度监听数组内元素变化
handler() { },
this.$nextTick(() => this.initData()); // 监听 seriesData 变化,触发图表更新
}, seriesData: {
deep: true // 深度监听数组内元素变化 handler() {
} this.$nextTick(() => this.initData());
}, },
mounted() { deep: true // 深度监听数组内元素变化
this.$nextTick(() => { }
this.initData(); },
}); mounted() {
}, this.$nextTick(() => {
methods: { this.initData();
initData() { });
const chartDom = this.$refs.cockpitEffChip; // 注册窗口resize事件使用稳定的引用以便后续移除
if (!chartDom) { this.resizeHandler = () => {
console.error('图表容器未找到!'); if (this.myChart) {
return; this.myChart.resize();
} }
const myChart = echarts.init(chartDom); };
const option = { window.addEventListener('resize', this.resizeHandler);
tooltip: { },
trigger: 'axis', methods: {
axisPointer: { initData() {
type: 'cross', const chartDom = this.$refs.cockpitEffChip;
label: { if (!chartDom) {
backgroundColor: '#6a7985' console.error('图表容器未找到!');
} return;
}, }
// 优化tooltip内容区分各系列含义 // 销毁已有图表实例
formatter: (params) => { if (this.myChart) {
let html = `${params[0].axisValue}<br/>`; this.myChart.dispose();
params.forEach(item => { }
// 直接使用系列名,无需映射,仅保留单位判断 this.myChart = echarts.init(chartDom);
html += `${item.marker} ${item.seriesName}: ${item.value}${item.seriesName === '完成率' ? '%' : '元'}<br/>`; const option = {
}); tooltip: {
return html; trigger: 'axis',
} axisPointer: {
}, type: 'cross',
grid: { label: {
top: 30, backgroundColor: '#6a7985'
bottom: 30, // 增大底部间距避免柱子与X轴标签重叠 }
right: 70, },
left: 40, // 优化tooltip内容区分各系列含义
}, formatter: (params) => {
xAxis: [ let html = `${params[0].axisValue}<br/>`;
{ params.forEach(item => {
type: 'category', // 直接使用系列名,无需映射,仅保留单位判断
boundaryGap: true, // 开启边界间隙,让柱子居中显示,不贴边 html += `${item.marker} ${item.seriesName}: ${item.value}${item.seriesName === '完成率' ? '%' : '元'}<br/>`;
axisTick: { show: false }, });
axisLine: { return html;
show: true, }
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
}, grid: {
axisLabel: { top: 30,
color: 'rgba(0, 0, 0, 0.45)', bottom: 30, // 增大底部间距避免柱子与X轴标签重叠
fontSize: 12, right: 70,
interval: 0, left: 40,
padding: [5, 0, 0, 0] // 标签向下偏移,避免与柱子底部重叠 },
}, xAxis: [
data: this.xData {
} type: 'category',
], boundaryGap: true, // 开启边界间隙,让柱子居中显示,不贴边
yAxis: [ axisTick: { show: false },
// 左侧Y轴目标/达标/未达标(数量,单位“片”) axisLine: {
{ show: true,
type: 'value', lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
// name: this.yName, },
nameTextStyle: { axisLabel: {
color: 'rgba(0, 0, 0, 0.45)', color: 'rgba(0, 0, 0, 0.45)',
fontSize: 12, fontSize: 12,
align: 'right' interval: 0,
}, padding: [5, 0, 0, 0] // 标签向下偏移,避免与柱子底部重叠
min: 0, // 最小值设0确保柱子从X轴底部开始不超过X轴 },
max: (value) => Math.ceil(value.max * 1.1), // 最大值留10%余量,避免柱子顶满 data: this.xData
scale: false, // 关闭缩放强制从0开始 }
axisTick: { show: false }, ],
axisLabel: { yAxis: [
color: 'rgba(0, 0, 0, 0.45)', // 左侧Y轴目标/达标/未达标(数量,单位“片”)
fontSize: 12, {
formatter: '{value}' type: 'value',
}, // name: this.yName,
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }, nameTextStyle: {
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }, color: 'rgba(0, 0, 0, 0.45)',
splitNumber: 4 fontSize: 12,
}, align: 'right'
], },
series: [ min: 0, // 最小值设0确保柱子从X轴底部开始不超过X轴
{ max: (value) => Math.ceil(value.max * 1.1), // 最大值留10%余量,避免柱子顶满
name: this.name, scale: false, // 关闭缩放强制从0开始
type: 'line', axisTick: { show: false },
// yAxisIndex: 1, axisLabel: {
lineStyle: { color: 'rgba(0, 0, 0, 0.45)',
color: 'rgba(40, 138, 255, .5)', fontSize: 12,
width: 2 formatter: '{value}'
}, },
itemStyle: { splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
color: 'rgba(40, 138, 255, 1)', axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
borderColor: 'rgba(40, 138, 255, 1)', splitNumber: 4
borderWidth: 2, },
radius: 4 ],
}, series: [
areaStyle: { {
opacity: 0.2, name: this.name,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ type: 'line',
{ offset: 0, color: 'rgba(40, 138, 255, .9)' }, // yAxisIndex: 1,
{ offset: 1, color: 'rgba(40, 138, 255, 0)' } lineStyle: {
]) color: 'rgba(40, 138, 255, .5)',
}, width: 2
data: this.seriesData, },
symbol: 'circle', itemStyle: {
symbolSize: 6 color: 'rgba(40, 138, 255, 1)',
}, borderColor: 'rgba(40, 138, 255, 1)',
], borderWidth: 2,
}; radius: 4
},
option && myChart.setOption(option); areaStyle: {
opacity: 0.2,
// 窗口缩放适配 color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
window.addEventListener('resize', () => { { offset: 0, color: 'rgba(40, 138, 255, .9)' },
myChart.resize(); { offset: 1, color: 'rgba(40, 138, 255, 0)' }
}); ])
},
// 组件销毁清理 data: this.seriesData,
this.$once('hook:destroyed', () => { symbol: 'circle',
window.removeEventListener('resize', () => { symbolSize: 6
myChart.resize(); },
}); ],
myChart.dispose(); };
});
} option && this.myChart.setOption(option);
}, }
}; },
</script> 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: 16, currentValue: 15.2, progress: 85 }, { unit: "毛利率·%", targetValue: 16, currentValue: 15.2, progress: 85 },
{ unit: "销量·万㎡", targetValue: 20, currentValue: 16, progress: 80 }, { unit: "销量·万㎡", targetValue: 20, currentValue: 16, progress: 80 },
{ unit: "双镀面板·万㎡", targetValue: 15, currentValue: 13.8, progress: 92 } { unit: "双镀销量·万㎡", targetValue: 15, currentValue: 13.8, progress: 92 }
], ],
// 拖拽相关状态 // 拖拽相关状态
isDragging: false, // 是否正在拖拽 isDragging: false, // 是否正在拖拽

View File

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

View File

@@ -1,280 +1,293 @@
<template> <template>
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 280px;"></div> <div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 280px;"></div>
</template> </template>
<script> <script>
import * as echarts from 'echarts'; import * as echarts from 'echarts';
export default { export default {
components: {}, components: {},
data() { data() {
return {}; return {
}, myChart: null,
props: { resizeHandler: null // 窗口resize事件处理器
seriesData: { };
type: Array, },
default: () => [] props: {
}, seriesData: {
xData: { type: Array,
type: Array, default: () => []
default: () => [] },
}, xData: {
name: { type: Array,
type: String, default: () => []
default: () => { } },
}, name: {
}, type: String,
watch: { default: () => { }
// 监听 xData 变化,触发图表更新 },
xData: { },
handler() { watch: {
this.$nextTick(() => this.initData()); // 监听 xData 变化,触发图表更新
}, xData: {
deep: true // 深度监听数组内元素变化 handler() {
}, this.$nextTick(() => this.initData());
// 监听 seriesData 变化,触发图表更新 },
seriesData: { deep: true // 深度监听数组内元素变化
handler() { },
this.$nextTick(() => this.initData()); // 监听 seriesData 变化,触发图表更新
}, seriesData: {
deep: true // 深度监听数组内元素变化 handler() {
} this.$nextTick(() => this.initData());
}, },
mounted() { deep: true // 深度监听数组内元素变化
this.$nextTick(() => { }
this.initData(); },
}); mounted() {
}, this.$nextTick(() => {
methods: { this.initData();
initData() { });
const chartDom = this.$refs.cockpitEffChip; // 注册窗口resize事件使用稳定的引用以便后续移除
if (!chartDom) { this.resizeHandler = () => {
console.error('图表容器未找到!'); if (this.myChart) {
return; this.myChart.resize();
} }
const myChart = echarts.init(chartDom); };
const option = { window.addEventListener('resize', this.resizeHandler);
tooltip: { },
trigger: 'axis', methods: {
axisPointer: { initData() {
type: 'cross', const chartDom = this.$refs.cockpitEffChip;
label: { if (!chartDom) {
backgroundColor: '#6a7985' console.error('图表容器未找到!');
} return;
}, }
// 优化tooltip内容区分各系列含义 // 销毁已有图表实例
formatter: (params) => { if (this.myChart) {
let html = `${params[0].axisValue}<br/>`; this.myChart.dispose();
params.forEach(item => { }
// 直接使用系列名,无需映射,仅保留单位判断 this.myChart = echarts.init(chartDom);
html += `${item.marker} ${item.seriesName}: ${item.value}${'元'}<br/>`; const option = {
}); tooltip: {
return html; trigger: 'axis',
} axisPointer: {
}, type: 'cross',
grid: { label: {
top: 50, backgroundColor: '#6a7985'
bottom: 30, // 增大底部间距避免柱子与X轴标签重叠 }
right: 70, },
left: 50, // 优化tooltip内容区分各系列含义
}, formatter: (params) => {
xAxis: [ let html = `${params[0].axisValue}<br/>`;
{ params.forEach(item => {
type: 'category', // 直接使用系列名,无需映射,仅保留单位判断
boundaryGap: true, // 开启边界间隙,让柱子居中显示,不贴边 html += `${item.marker} ${item.seriesName}: ${item.value}${'元'}<br/>`;
axisTick: { show: false }, });
axisLine: { return html;
show: true, }
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
}, grid: {
axisLabel: { top: 50,
color: 'rgba(0, 0, 0, 0.45)', bottom: 30, // 增大底部间距避免柱子与X轴标签重叠
fontSize: 12, right: 70,
interval: 0, left: 50,
padding: [5, 0, 0, 0] // 标签向下偏移,避免与柱子底部重叠 },
}, xAxis: [
data: this.xData // 绑定监听的 xData {
} type: 'category',
], boundaryGap: true, // 开启边界间隙,让柱子居中显示,不贴边
yAxis: [ axisTick: { show: false },
// 左侧Y轴目标/达标/未达标(数量,单位“片”) axisLine: {
{ show: true,
type: 'value', lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
name: '元', },
nameTextStyle: { axisLabel: {
color: 'rgba(0, 0, 0, 0.45)', color: 'rgba(0, 0, 0, 0.45)',
fontSize: 12, fontSize: 12,
align: 'right' interval: 0,
}, padding: [5, 0, 0, 0] // 标签向下偏移,避免与柱子底部重叠
min: 0, // 最小值设0确保柱子从X轴底部开始不超过X轴 },
max: (value) => Math.ceil(value.max * 1.1), // 最大值留10%余量,避免柱子顶满 data: this.xData // 绑定监听的 xData
scale: false, // 关闭缩放强制从0开始 }
axisTick: { show: false }, ],
axisLabel: { yAxis: [
color: 'rgba(0, 0, 0, 0.45)', // 左侧Y轴目标/达标/未达标(数量,单位“片”)
fontSize: 12, {
formatter: '{value}' type: 'value',
}, name: '元',
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }, nameTextStyle: {
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }, color: 'rgba(0, 0, 0, 0.45)',
splitNumber: 4 fontSize: 12,
}, align: 'right'
// 右侧Y轴完成率百分比 },
// { min: 0, // 最小值设0确保柱子从X轴底部开始不超过X轴
// type: 'value', max: (value) => Math.ceil(value.max * 1.1), // 最大值留10%余量,避免柱子顶满
// // name: '%', scale: false, // 关闭缩放强制从0开始
// nameTextStyle: { axisTick: { show: false },
// color: 'rgba(0, 0, 0, 0.45)', axisLabel: {
// fontSize: 12, color: 'rgba(0, 0, 0, 0.45)',
// align: 'left' fontSize: 12,
// }, formatter: '{value}'
// min: 0, },
// max: 100, // 完成率最大100%,符合业务逻辑 splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
// axisTick: { show: false }, axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
// axisLabel: { splitNumber: 4
// color: 'rgba(0, 0, 0, 0.45)', },
// fontSize: 12, // 右侧Y轴完成率百分比
// formatter: '{value}%' // {
// }, // type: 'value',
// splitLine: { show: false }, // 不重复显示分割线 // // name: '%',
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }, // nameTextStyle: {
// splitNumber: 4 // color: 'rgba(0, 0, 0, 0.45)',
// } // fontSize: 12,
], // align: 'left'
series: [ // },
// 1. 完成率折线图绑定右侧百分比Y轴 // min: 0,
// { // max: 100, // 完成率最大100%,符合业务逻辑
// name: '产销率', // axisTick: { show: false },
// type: 'line', // axisLabel: {
// yAxisIndex: 1, // 绑定右侧Y轴 // color: 'rgba(0, 0, 0, 0.45)',
// lineStyle: { // fontSize: 12,
// color: 'rgba(40, 138, 255, .5)', // formatter: '{value}%'
// width: 2 // 线条加粗,突出重点 // },
// }, // splitLine: { show: false }, // 不重复显示分割线
// itemStyle: { // axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
// color: 'rgba(40, 138, 255, 1)', // splitNumber: 4
// borderColor: 'rgba(40, 138, 255, 1)', // 数据点白色边框,增强辨识度 // }
// borderWidth: 2, ],
// radius: 4 // 数据点圆角,更圆润 series: [
// }, // 1. 完成率折线图绑定右侧百分比Y轴
// areaStyle: { // {
// opacity: 0.2, // 降低面积透明度,不抢柱状图视觉焦点 // name: '产销率',
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ // type: 'line',
// { offset: 0, color: 'rgba(40, 138, 255, .9)' }, // yAxisIndex: 1, // 绑定右侧Y轴
// { offset: 1, color: 'rgba(255, 132, 0, 0)' } // lineStyle: {
// ]) // color: 'rgba(40, 138, 255, .5)',
// }, // width: 2 // 线条加粗,突出重点
// data: [65, 78, 52, 85, 60, 95, 72], // 完成率数据0-100 // },
// symbol: 'circle', // 数据点为圆形 // itemStyle: {
// symbolSize: 6 // 数据点大小 // color: 'rgba(40, 138, 255, 1)',
// }, // borderColor: 'rgba(40, 138, 255, 1)', // 数据点白色边框,增强辨识度
// 2. 目标柱状图绑定左侧数量Y轴 // borderWidth: 2,
{ // radius: 4 // 数据点圆角,更圆润
name:this.name, // },
type: 'bar', // areaStyle: {
yAxisIndex: 0, // opacity: 0.2, // 降低面积透明度,不抢柱状图视觉焦点
barWidth: 24, // color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
label: { // { offset: 0, color: 'rgba(40, 138, 255, .9)' },
show: true, // 开启显示 // { offset: 1, color: 'rgba(255, 132, 0, 0)' }
position: 'top', // 标签位置,可选:'top'、'middle'、'bottom' // ])
// 也可以使用绝对像素值定位,例如 [10, '50%'] // },
// position: [10, '50%'], // data: [65, 78, 52, 85, 60, 95, 72], // 完成率数据0-100
// symbol: 'circle', // 数据点为圆形
// 标签内容格式化,这里直接显示数据值 // symbolSize: 6 // 数据点大小
formatter: '{c}', // },
// 2. 目标柱状图绑定左侧数量Y轴
// 文字样式 {
color: 'rgba(11, 88, 255, 1)', // 文字颜色 name:this.name,
fontSize: 14, // 文字大小 type: 'bar',
// fontWeight: 'bold', // 文字粗细 yAxisIndex: 0,
// fontFamily: 'Arial, sans-serif' // 字体 barWidth: 24,
}, label: {
itemStyle: { show: true, // 开启显示
// 移除多余的 normal 层级,直接配置 color 渐变 position: 'top', // 标签位置,可选:'top'、'middle'、'bottom'
color: { // 也可以使用绝对像素值定位,例如 [10, '50%']
type: 'linear', // position: [10, '50%'],
x: 0,
y: 0, // 标签内容格式化,这里直接显示数据值
x2: 0, formatter: '{c}',
y2: 1,
colorStops: [ // 文字样式
{ offset: 0, color: 'rgba(130, 204, 255, 1)' }, color: 'rgba(11, 88, 255, 1)', // 文字颜色
{ offset: 1, color: 'rgba(75, 157, 255, 1)' } fontSize: 14, // 文字大小
] // fontWeight: 'bold', // 文字粗细
}, // fontFamily: 'Arial, sans-serif' // 字体
borderRadius: [4, 4, 0, 0], },
borderWidth: 0 itemStyle: {
}, // 移除多余的 normal 层级,直接配置 color 渐变
data: this.seriesData color: {
}, type: 'linear',
// 3. 达标柱状图绑定左侧数量Y轴 x: 0,
// { y: 0,
// name: '产量', x2: 0,
// type: 'bar', y2: 1,
// yAxisIndex: 0, colorStops: [
// barWidth: 24, { offset: 0, color: 'rgba(130, 204, 255, 1)' },
// // 关键修复label 直接放在 series 下,而非 itemStyle 内 { offset: 1, color: 'rgba(75, 157, 255, 1)' }
// itemStyle: { ]
// // 移除多余的 normal 层级,直接配置 color 渐变 },
// color: { borderRadius: [4, 4, 0, 0],
// type: 'linear', borderWidth: 0
// x: 0, },
// y: 0, data: this.seriesData
// x2: 0, },
// y2: 1, // 3. 达标柱状图绑定左侧数量Y轴
// colorStops: [ // {
// { offset: 0, color: 'rgba(174, 239, 224, 1)' }, // name: '产量',
// { offset: 1, color: 'rgba(118, 218, 190, 1)' } // type: 'bar',
// ] // yAxisIndex: 0,
// }, // barWidth: 24,
// borderRadius: [4, 4, 0, 0], // // 关键修复label 直接放在 series 下,而非 itemStyle 内
// borderWidth: 0 // itemStyle: {
// }, // // 移除多余的 normal 层级,直接配置 color 渐变
// data: [130, 220, 95, 255, 132, 332] // 达标数据(小于目标) // color: {
// }, // type: 'linear',
// 4. 未达标柱状图绑定左侧数量Y轴 // x: 0,
// { // y: 0,
// name: '未达标', // x2: 0,
// type: 'bar', // y2: 1,
// yAxisIndex: 0, // colorStops: [
// barWidth: 18, // { offset: 0, color: 'rgba(174, 239, 224, 1)' },
// itemStyle: { // { offset: 1, color: 'rgba(118, 218, 190, 1)' }
// color: 'rgba(249, 164, 74, 1)', // ]
// borderRadius: [4, 4, 0, 0], // },
// borderWidth: 0 // borderRadius: [4, 4, 0, 0],
// }, // borderWidth: 0
// data: [70, 60, 85, 45, 88, 18, 78] // 未达标数据(目标-达标) // },
// } // data: [130, 220, 95, 255, 132, 332] // 达标数据(小于目标)
], // },
// 图例:区分各系列,点击可控制显示隐藏 // 4. 未达标柱状图绑定左侧数量Y轴
// legend: { // {
// top: 0, // name: '未达标',
// left: 'center', // type: 'bar',
// itemWidth: 12, // yAxisIndex: 0,
// itemHeight: 8, // barWidth: 18,
// textStyle: { // itemStyle: {
// color: 'rgba(0, 0, 0, 0.45)', // color: 'rgba(249, 164, 74, 1)',
// fontSize: 12 // borderRadius: [4, 4, 0, 0],
// }, // borderWidth: 0
// data: ['完成率', '目标', '达标', '未达标'] // },
// } // data: [70, 60, 85, 45, 88, 18, 78] // 未达标数据(目标-达标)
}; // }
],
option && myChart.setOption(option); // 图例:区分各系列,点击可控制显示隐藏
// legend: {
// 窗口缩放适配 // top: 0,
window.addEventListener('resize', () => { // left: 'center',
myChart.resize(); // itemWidth: 12,
}); // itemHeight: 8,
// textStyle: {
// 组件销毁清理 // color: 'rgba(0, 0, 0, 0.45)',
this.$once('hook:destroyed', () => { // fontSize: 12
window.removeEventListener('resize', () => { // },
myChart.resize(); // data: ['完成率', '目标', '达标', '未达标']
}); // }
myChart.dispose(); };
});
} option && this.myChart.setOption(option);
}, }
}; },
</script> 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 id="dayReport" class="dayReport" :style="styles">
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" /> <div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
<sidebar v-if="!sidebar.hide" class="sidebar-container" /> <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" /> @screenfullChange="screenfullChange" @timeRangeChange="handleTimeChange" />
<div class="main-body" style=" <div class="main-body" style="
flex: 1; flex: 1;
@@ -39,7 +39,7 @@ import { mapState } from "vuex";
import operatingLineChart from "../depreciationAnalysisComponents/operatingLineChart"; import operatingLineChart from "../depreciationAnalysisComponents/operatingLineChart";
import operatingLineChartCumulative from "../depreciationAnalysisComponents/operatingLineChartCumulative.vue"; import operatingLineChartCumulative from "../depreciationAnalysisComponents/operatingLineChartCumulative.vue";
import { getSalesRevenueGroupData } from '@/api/cockpit' import { getDepreciationAnalysisData } from '@/api/cockpit'
export default { export default {
name: "DepreciationAnalysis", name: "DepreciationAnalysis",
components: { components: {
@@ -59,6 +59,7 @@ export default {
selectDate:{}, selectDate:{},
monthData: {}, monthData: {},
ytdData:{}, ytdData:{},
dateData:{}
}; };
}, },
@@ -127,12 +128,8 @@ export default {
this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined
}, },
methods: { methods: {
// sortChange(value) {
// this.sort = value
// this.getData()
// },
getData() { getData() {
getSalesRevenueGroupData({ getDepreciationAnalysisData({
startTime: this.dateData.startTime, startTime: this.dateData.startTime,
endTime: this.dateData.endTime, endTime: this.dateData.endTime,
sort: this.sort, sort: this.sort,
@@ -143,12 +140,6 @@ export default {
console.log(res); console.log(res);
this.monthData= res.data.month this.monthData= res.data.month
this.ytdData = res.data.ytd 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) { handleTimeChange(obj) {
@@ -198,28 +189,7 @@ export default {
return false; return false;
} }
screenfull.toggle(this.$refs.dayReportB); 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> </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) { ... } // getSteamGaugeOption(value) { ... }
},
beforeDestroy() {
// 销毁 ResizeObserver避免内存泄漏
if (this.resizeObserver) {
this.resizeObserver.disconnect();
this.resizeObserver = null;
}
// 销毁图表实例
if (this.electricityChart) {
this.electricityChart.dispose();
this.electricityChart = null;
}
} }
} }
</script> </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> <script>
import operatingLineBar from './operatingLineBarSale.vue'; import operatingLineBar from './operatingLineBarSale.vue';
import * as echarts from 'echarts';
export default { export default {
name: "Container", name: "Container",
@@ -62,8 +61,6 @@ export default {
profitOptions: [ profitOptions: [
{ label: '实际值:高~低', value: 1 }, { label: '实际值:高~低', value: 1 },
{ label: '实际值:低~高', value: 2 }, { label: '实际值:低~高', value: 2 },
{ label: '完成率:高~低', value: 3 },
{ label: '完成率:低~高', value: 4 },
] ]
}; };
}, },
@@ -95,12 +92,6 @@ export default {
case 2: // 实际值:低~高 case 2: // 实际值:低~高
dataWithIndex.sort((a, b) => a.real - b.real); dataWithIndex.sort((a, b) => a.real - b.real);
break; break;
case 3: // 预算值:高~低
dataWithIndex.sort((a, b) => b.rate - a.rate);
break;
case 4: // 预算值:低~高
dataWithIndex.sort((a, b) => a.rate - b.rate);
break;
default: default:
return factory; return factory;
} }
@@ -123,51 +114,7 @@ export default {
const data = this.currentDataSource; const data = this.currentDataSource;
const salesData = { const salesData = {
allPlaceNames: this.locations, allPlaceNames: this.locations,
series: [ series: [
// 完成率(折线图)
{
name: '完成率',
type: 'line',
yAxisIndex: 1,
lineStyle: { color: 'rgba(40, 138, 255, .5)', width: 2 },
itemStyle: {
color: 'rgba(40, 138, 255, 1)',
borderColor: 'rgba(40, 138, 255, 1)',
borderWidth: 2,
radius: 4
},
areaStyle: {
opacity: 0.2,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
])
},
data: data.rates || [],
symbol: 'circle',
symbolSize: 6
},
// 目标(柱状图)
{
name: '预算',
type: 'bar',
yAxisIndex: 0,
barWidth: 40,
itemStyle: {
color: {
type: 'linear',
x: 0, y: 0, x2: 0, y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
]
},
borderRadius: [4, 4, 0, 0],
borderWidth: 0
},
data: data.targets || []
},
// 实际(柱状图)
{ {
name: '实际', name: '实际',
type: 'bar', type: 'bar',
@@ -175,68 +122,7 @@ export default {
barWidth: 40, barWidth: 40,
label: { label: {
show: true, show: true,
position: 'top', position: 'top'
offset: [32, 0],
width: 100,
height: 22,
formatter: (params) => {
const diff = data.diff || [];
const flags = data.flags || [];
const currentDiff = diff[params.dataIndex] || 0;
const currentFlag = flags[params.dataIndex] || 0;
const prefix = currentFlag === 1 ? '+' : '-';
// 根据标志位选择不同的样式类
if (currentFlag === 1) {
// 达标 - 使用 rate-achieved 样式
return `{achieved|${currentDiff}}{text|差值}`;
} else {
// 未达标 - 使用 rate-unachieved 样式
return `{unachieved|${currentDiff}}{text|差值}`;
}
},
backgroundColor: {
type: 'linear',
x: 0, y: 0, x2: 0, y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
{ offset: 0.2, color: '#ffffff' },
{ offset: 1, color: '#ffffff' }
]
},
shadowColor: 'rgba(191,203,215,0.5)',
shadowBlur: 2,
shadowOffsetX: 0,
shadowOffsetY: 2,
borderRadius: 4,
borderColor: '#BFCBD577',
borderWidth: 0,
lineHeight: 26,
rich: {
text: {
width: 'auto',
padding: [5, 10, 5, 0],
align: 'center',
color: '#464646',
fontSize: 14,
},
achieved: {
width: 'auto',
padding: [5, 0, 5, 10],
align: 'center',
color: '#76DABE', // 与达标的 offset: 1 颜色一致
fontSize: 14,
},
// 未达标样式
unachieved: {
width: 'auto',
padding: [5, 0, 5, 10],
align: 'center',
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
fontSize: 14,
}
}
}, },
itemStyle: { itemStyle: {
color: (params) => { color: (params) => {

View File

@@ -158,18 +158,6 @@ export default {
}; };
option && this.myChart.setOption(option); 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); 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 { return {
myChart: null, // 存储图表实例 myChart: null, // 存储图表实例
resizeHandler: null, // 存储resize事件处理函数 resizeHandler: null, // 存储resize事件处理函数
isMounted: false,
// 核心:基地名称与序号的映射表(固定顺序) // 核心:基地名称与序号的映射表(固定顺序)
baseNameToIndexMap: { baseNameToIndexMap: {
'宜兴': 7, '宜兴': 7,
@@ -35,6 +36,7 @@ export default {
}, },
}, },
mounted() { mounted() {
this.isMounted = true;
this.$nextTick(() => { this.$nextTick(() => {
this.initChart(); // 初始化图表(只执行一次) this.initChart(); // 初始化图表(只执行一次)
this.updateChart(); // 更新图表数据 this.updateChart(); // 更新图表数据
@@ -43,11 +45,11 @@ export default {
watch: { watch: {
chartData: { chartData: {
handler() { handler() {
if (!this.isMounted) return;
console.log(this.chartData, 'chartData'); console.log(this.chartData, 'chartData');
this.updateChart(); // 仅更新数据,不重新创建实例 this.updateChart(); // 仅更新数据,不重新创建实例
}, },
deep: true, deep: true
immediate: true
}, },
}, },
beforeDestroy() { beforeDestroy() {
@@ -59,7 +61,7 @@ export default {
initChart() { initChart() {
const chartDom = this.$refs.cockpitEffChip; const chartDom = this.$refs.cockpitEffChip;
if (!chartDom) { if (!chartDom) {
console.error('图表容器未找到!'); if (process.env.NODE_ENV === 'development') console.warn('图表容器未找到!');
return; return;
} }
@@ -94,18 +96,6 @@ export default {
if (itemName === undefined) { if (itemName === undefined) {
return; return;
} }
// 路由跳转时携带序号(或名称+序号)
this.$router.push({
path: 'operatingRevenueBase',
query: { // 使用query传递参数推荐也可使用params
// baseName: itemName,
factory: baseIndex,
dateData: this.dateData
}
// 若仍需用base作为参数
// base: itemName,
// params: { baseIndex: baseIndex }
});
}); });
// 定义resize处理函数命名函数方便移除 // 定义resize处理函数命名函数方便移除
@@ -157,11 +147,6 @@ export default {
fontSize: 12, fontSize: 12,
interval: 0, interval: 0,
padding: [5, 0, 0, 0], padding: [5, 0, 0, 0],
// 可选X轴标签显示“序号+名称”如“1 宜兴”)
// formatter: (value) => {
// const index = this.baseNameToIndexMap[value] || '';
// return index ? `${index} ${value}` : value;
// }
}, },
data: xData data: xData
} }
@@ -185,23 +170,23 @@ export default {
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }, splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } } axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }
}, },
{ // {
type: 'value', // type: 'value',
nameTextStyle: { // nameTextStyle: {
color: 'rgba(0, 0, 0, 0.45)', // color: 'rgba(0, 0, 0, 0.45)',
fontSize: 12, // fontSize: 12,
align: 'left' // align: 'left'
}, // },
axisTick: { show: false }, // axisTick: { show: false },
axisLabel: { // axisLabel: {
color: 'rgba(0, 0, 0, 0.45)', // color: 'rgba(0, 0, 0, 0.45)',
fontSize: 12, // fontSize: 12,
formatter: '{value}%' // formatter: '{value}%'
}, // },
splitLine: { show: false }, // splitLine: { show: false },
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }, // axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
splitNumber: 4 // splitNumber: 4
} // }
], ],
series: chartSeries series: chartSeries
}; };

View File

@@ -8,7 +8,8 @@ export default {
components: {}, components: {},
data() { data() {
return { return {
myChart: null // 存储图表实例,避免重复创建 myChart: null, // 存储图表实例,避免重复创建
isMounted: false
}; };
}, },
props: { props: {
@@ -17,13 +18,10 @@ export default {
type: Object, type: Object,
default: () => ({ default: () => ({
}), }),
// 校验数据格式
// validator: (value) => {
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
// }
} }
}, },
mounted() { mounted() {
this.isMounted = true;
this.$nextTick(() => { this.$nextTick(() => {
this.updateChart(); this.updateChart();
}); });
@@ -34,18 +32,18 @@ export default {
// 深度监听数据变化,仅更新图表配置(不销毁实例) // 深度监听数据变化,仅更新图表配置(不销毁实例)
chartData: { chartData: {
handler() { handler() {
if (!this.isMounted) return;
console.log(this.chartData,'chartData'); console.log(this.chartData,'chartData');
this.updateChart(); this.updateChart();
}, },
deep: true, deep: true
immediate: true // 初始化时立即执行
} }
}, },
methods: { methods: {
updateChart() { updateChart() {
const chartDom = this.$refs.cockpitEffChip; const chartDom = this.$refs.cockpitEffChip;
if (!chartDom) { if (!chartDom) {
console.error('图表容器未找到!'); if (process.env.NODE_ENV === 'development') console.warn('图表容器未找到!');
return; return;
} }
@@ -72,20 +70,10 @@ export default {
backgroundColor: '#6a7985' 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: { grid: {
top: 30, top: 30,
bottom:20, bottom:5,
right: 10, right: 10,
left: 25, left: 25,
containLabel: true containLabel: true
@@ -154,18 +142,6 @@ export default {
}; };
option && this.myChart.setOption(option); 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); 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>
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
<span>完成率:<span style='color: #0B58FF;'>{{chartData.group.rate[0]}}%</span></span>
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.group.flags>0?'#30B590':'#FF9423'}" >{{chartData.group.diff[0]}}</span></span>
</div>
<operatingTopBar :chartData="chartData" /> <operatingTopBar :chartData="chartData" />
</div> </div>
<div class="right" style=" <div class="right" style="
@@ -32,7 +28,6 @@
width: 1220px; width: 1220px;
background-color: rgba(249, 252, 255, 1); background-color: rgba(249, 252, 255, 1);
"> ">
<!-- <top-item /> -->
<operatingBar :dateData="dateData" :chartData="chartData" @sort-change="sortChange" /> <operatingBar :dateData="dateData" :chartData="chartData" @sort-change="sortChange" />
</div> </div>
</div> </div>
@@ -88,23 +83,6 @@ export default {
sortChange(value) { sortChange(value) {
this.$emit('sort-change', 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 * 核心处理函数:在所有数据都准备好后,才组装 chartData
*/ */
@@ -118,7 +96,7 @@ getRateFlag(rate, real, target) {
const groupReal = [this.groupData.real]; // 实际值数组 const groupReal = [this.groupData.real]; // 实际值数组
const groupRate = [this.groupData.rate]; // 完成率数组 const groupRate = [this.groupData.rate]; // 完成率数组
// 新增集团rate对应的flag // 新增集团rate对应的flag
const groupFlag = [this.getRateFlag(groupRate[0], groupReal[0], groupTarget[0])]; const groupFlag = [this.groupData.rate];
console.log('集团数据数组:', { console.log('集团数据数组:', {
groupTarget, groupTarget,
@@ -139,7 +117,7 @@ getRateFlag(rate, real, target) {
const factoryRate = this.factoryData.map(item => item.rate || 0); const factoryRate = this.factoryData.map(item => item.rate || 0);
const factoryDiff = this.factoryData.map(item => item.diff || 0); const factoryDiff = this.factoryData.map(item => item.diff || 0);
// 新增每个工厂rate对应的flag数组 // 新增每个工厂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供子组件使用 // 3. 组装最终的chartData供子组件使用
this.chartData = { this.chartData = {

View File

@@ -20,10 +20,6 @@
"> ">
集团情况 集团情况
</div> </div>
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
<span>完成率:<span style='color: #0B58FF;'>{{chartData.group.rate[0]}}%</span></span>
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.group.flags>0?'#30B590':'#FF9423'}" >{{chartData.group.diff[0]}}</span></span>
</div>
<operatingTopBar :chartData="chartData" /> <operatingTopBar :chartData="chartData" />
</div> </div>
<div class="right" style=" <div class="right" style="

View File

@@ -8,7 +8,6 @@
<script> <script>
import operatingLineBar from './operatingLineBarSaleGroup.vue'; import operatingLineBar from './operatingLineBarSaleGroup.vue';
import * as echarts from 'echarts';
export default { export default {
name: "Container", name: "Container",
@@ -40,30 +39,6 @@ export default {
const salesData = { const salesData = {
allPlaceNames: this.locations, allPlaceNames: this.locations,
series: [ 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: '实际', name: '实际',
type: 'bar', 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; 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; gap: 12px;
grid-template-columns:1624px; grid-template-columns:1624px;
"> ">
<operatingLineChart :dateData="dateData" :monData="monData" /> <operatingLineChart :dateData="dateData" :monthData="monthData" />
</div> </div>
</div> </div>
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;"> <div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
@@ -25,40 +25,27 @@
gap: 12px; gap: 12px;
grid-template-columns: 1624px; grid-template-columns: 1624px;
"> ">
<operatingLineChartCumulative :dateData="dateData" :totalData="totalData" /> <operatingLineChartCumulative :dateData="dateData" :ytdData="ytdData" />
<!-- <keyWork /> -->
</div> </div>
</div> </div>
</div> </div>
<!-- <div class="centerImg" style="
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1; /* 确保在 backp 之上、内容之下 */
"></div> -->
</div> </div>
</template> </template>
<script> <script>
import ReportHeader from "../components/noRouterHeader.vue"; import ReportHeader from "../components/noRouterHeader.vue";
import { Sidebar } from "../../../layout/components"; import { Sidebar } from "../../../layout/components";
import screenfull from "screenfull"; import screenfull from "screenfull";
// import operatingSalesRevenue from "./operatingComponents/operatingSalesRevenue";
// import premProdStatus from "./components/premProdStatus.vue";
import { mapState } from "vuex"; import { mapState } from "vuex";
import operatingLineChart from "../electricityCostAnalysisComponents/operatingLineChart"; import operatingLineChart from "../electricityCostAnalysisComponents/operatingLineChart";
import operatingLineChartCumulative from "../electricityCostAnalysisComponents/operatingLineChartCumulative.vue"; import operatingLineChartCumulative from "../electricityCostAnalysisComponents/operatingLineChartCumulative.vue";
import { getProfitAnalysisTotalList } from '@/api/cockpit' import { getElectricityCostAnalysisData } from '@/api/cockpit'
import moment from "moment";
export default { export default {
name: "DayReport", name: "electricityCostAnalysis",
components: { components: {
ReportHeader, ReportHeader,
operatingLineChartCumulative, operatingLineChartCumulative,
operatingLineChart, operatingLineChart,
// premProdStatus,
Sidebar, Sidebar,
}, },
data() { data() {
@@ -68,9 +55,11 @@ export default {
timer: null, timer: null,
beilv: 1, beilv: 1,
value: 100, value: 100,
sort:1,
selectDate:{},
monthData: {},
ytdData:{},
dateData: {}, dateData: {},
monData: [],
totalData: [],
}; };
}, },
@@ -140,28 +129,18 @@ export default {
}, },
methods: { methods: {
getData() { getData() {
getProfitAnalysisTotalList({ getElectricityCostAnalysisData({
startTime: this.dateData.startTime, startTime: this.dateData.startTime,
endTime: this.dateData.endTime, endTime: this.dateData.endTime,
analysisObject: [ sort: this.sort,
"利润总额" index: undefined,
], factory: undefined
levelId: 1,
// timeDim: this.dateData.mode
}).then((res) => { }).then((res) => {
console.log(res); this.monthData= res.data.month
this.monData = res.data.currentMonthData this.ytdData = res.data.ytd
this.totalData = res.data.totalMonthData
// this.totalData = res.data.totalData
// this.saleData = res.data.SaleData
// this.premiumProduct = res.data.premiumProduct
// this.salesTrendMap = res.data.salesTrendMap
// this.grossMarginTrendMap = res.data.grossMarginTrendMap
// this.salesProportion = res.data.salesProportion ? res.data.salesProportion : {}
}) })
}, },
handleTimeChange(obj) { handleTimeChange(obj) {
// console.log(obj, 'obj');
this.dateData= obj this.dateData= obj
this.getData() this.getData()
}, },
@@ -208,41 +187,18 @@ export default {
} }
screenfull.toggle(this.$refs.dayReportB); 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> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "~@/assets/styles/mixin.scss"; @import "~@/assets/styles/mixin.scss";
@import "~@/assets/styles/variables.scss"; @import "~@/assets/styles/variables.scss";
.dayReport { .dayReport {
width: 1920px; width: 1920px;
height: 1080px; height: 1080px;
background: url("../../../assets/img/backp.png") no-repeat; background: url("../../../assets/img/backp.png") no-repeat;
background-size: cover; background-size: cover;
} }
.hideSidebar .fixed-header { .hideSidebar .fixed-header {
width: calc(100% - 54px); width: calc(100% - 54px);
} }

View File

@@ -52,14 +52,6 @@
</div> </div>
</div> </div>
</div> </div>
<!-- <div class="centerImg" style="
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1; /* 确保在 backp 之上、内容之下 */
"></div> -->
</div> </div>
</template> </template>
<script> <script>
@@ -69,22 +61,13 @@ import screenfull from "screenfull";
import changeBase from "../components/changeBase.vue"; import changeBase from "../components/changeBase.vue";
import monthlyOverview from "../electricityCostAnalysisComponents/monthlyOverview.vue"; import monthlyOverview from "../electricityCostAnalysisComponents/monthlyOverview.vue";
import totalOverview from "../electricityCostAnalysisComponents/totalOverview.vue"; import totalOverview from "../electricityCostAnalysisComponents/totalOverview.vue";
// import totalOverview from "../operatingComponents/totalOverview.vue";
import relatedIndicatorsAnalysis from "../electricityCostAnalysisComponents/relatedIndicatorsAnalysis.vue"; import relatedIndicatorsAnalysis from "../electricityCostAnalysisComponents/relatedIndicatorsAnalysis.vue";
import dataTrend from "../electricityCostAnalysisComponents/dataTrend.vue"; import dataTrend from "../electricityCostAnalysisComponents/dataTrend.vue";
import { mapState } from "vuex"; import { mapState } from "vuex";
import { getProfitAnalysisTotalList } from '@/api/cockpit' import { getElectricityCostAnalysisFData } from '@/api/cockpit'
// import PSDO from "./components/PSDO.vue";
// import psiLineChart from "./components/psiLineChart.vue";
// import coreBottomLeft from "./components/coreBottomLeft.vue";
// import orderProgress from "./components/orderProgress.vue";
// import keyWork from "./components/keyWork.vue";
import moment from "moment"; import moment from "moment";
// import html2canvas from 'html2canvas'
// import JsPDF from 'jspdf'
export default { export default {
name: "DayReport", name: "electricityCostAnalysisBase",
components: { components: {
ReportHeader, ReportHeader,
changeBase, changeBase,
@@ -107,9 +90,8 @@ export default {
monData: {}, monData: {},
totalData: {}, totalData: {},
trend: [], trend: [],
relatedData: [], relatedData: {},
trendName: '利润总额', trendName: '总电费'
// cusProData: {},
}; };
}, },
@@ -199,45 +181,26 @@ export default {
const requestParams = { const requestParams = {
startTime: this.dateData.startTime, startTime: this.dateData.startTime,
endTime: this.dateData.endTime, endTime: this.dateData.endTime,
// index: this.index, sort: 1,
// sort: 1, index: this.trendName,
trendName: this.trendName, factory: this.factory
analysisObject: [
"利润总额",
],
// paramList: ['制造成本', '财务费用', '销售费用', '管理费用', '运费'],
levelId: this.factory,
// baseId: Number(this.factory),
}; };
// 调用接口 // 调用接口
getProfitAnalysisTotalList(requestParams).then((res) => { getElectricityCostAnalysisFData(requestParams).then((res) => {
this.monData = res.data.currentMonthData.find(item => { this.monData = res.data.month
return item.name === "利润总额"; this.totalData = res.data.ytd
});
console.log('this.monData', this.monData);
this.totalData = res.data.totalMonthData.find(item => {
return item.name === "利润总额";
});
// this.relatedMon = res.data.relatedMon
this.relatedData = { this.relatedData = {
relatedMon: res.data.currentMonthData.filter(item => { relatedMon: res.data.monthAnalysis,
return item.name !== "利润总额"; relatedTotal: res.data.ytdAnalysis
}), // 兜底月度数据
relatedTotal: res.data.totalMonthData.filter(item => {
return item.name !== "利润总额";
}) // 兜底累计数据
} }
this.trend = res.data.dataTrend this.trend = res.data.trend
}); });
}, },
handleTimeChange(obj) { handleTimeChange(obj) {
this.month = obj.targetMonth this.month = obj.targetMonth
this.dateData = { this.dateData = {
startTime: obj.startTime, startTime: obj.startTime,
endTime: obj.endTime, endTime: obj.endTime,
// mode: obj.mode,
} }
this.getData() this.getData()
@@ -294,35 +257,12 @@ export default {
}, },
changeDate(val) { changeDate(val) {
this.date = val; this.date = val;
// this.weekDay = this.weekArr[moment(this.date).format('e')]
// this.getData()
if (this.date === moment().format("yyyy-MM-DD")) { if (this.date === moment().format("yyyy-MM-DD")) {
this.loopTime(); this.loopTime();
} else { } else {
clearInterval(this.timer); 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> </script>

View File

@@ -30,11 +30,8 @@ export default {
}, },
}, },
data() { data() {
return { return {};
// 移除:原 chartData 定义,改为计算属性
};
}, },
// 移除:原 watch 监听配置,计算属性自动响应 trendData 变化
computed: { computed: {
/** /**
* chartData 计算属性:自动响应 trendData 变化,格式化并提取各字段数组 * chartData 计算属性:自动响应 trendData 变化,格式化并提取各字段数组
@@ -51,16 +48,14 @@ export default {
// 遍历传入的 trendData 数组(响应式依赖,变化时自动重算) // 遍历传入的 trendData 数组(响应式依赖,变化时自动重算)
this.trendData.forEach((item) => { this.trendData.forEach((item) => {
// 1. 格式化时间并推入时间数组 timeArr.push(item.title);
const yearMonth = this.formatTimeToYearMonth(item.time);
timeArr.push(yearMonth);
// 2. 提取其他字段兜底为0防止null/undefined影响图表渲染 // 2. 提取其他字段兜底为0防止null/undefined影响图表渲染
valueArr.push(item.value ?? 0); valueArr.push(item.real ?? 0);
diffValueArr.push(item.diffValue ?? 0); diffValueArr.push(item.diff ?? 0);
targetValueArr.push(item.targetValue ?? 0); targetValueArr.push(item.budget ?? 0);
proportionArr.push(item.proportion ?? 0); proportionArr.push(item.rate ?? 0);
completedArr.push(item.completed ?? 0); completedArr.push(item.rate>=100 ? 1 : 0);
}); });
// 组装并返回格式化后的数据(结构与原一致) // 组装并返回格式化后的数据(结构与原一致)
@@ -81,15 +76,6 @@ export default {
* @param {Number} timestamp 13位毫秒级时间戳 * @param {Number} timestamp 13位毫秒级时间戳
* @returns {String} 格式化后的年月字符串2025-10 * @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) { getData(value) {
this.$emit('getData', value) this.$emit('getData', value)
}, },

Some files were not shown because too many files have changed in this diff Show More