Compare commits
17 Commits
projects/l
...
projects/l
| Author | SHA1 | Date | |
|---|---|---|---|
| 8105e74122 | |||
| d8abdfbfec | |||
| 446078f558 | |||
| 7135ab0e4b | |||
| 0d74e762ce | |||
| 24102f0d0d | |||
| bdd73b8868 | |||
| 0c8bd440ae | |||
| ed0fd63474 | |||
| 9d91188b98 | |||
| f0ac88af3d | |||
| 161d6a1bdf | |||
| 05fe91618c | |||
| b85ceb2542 | |||
| e770dc4fed | |||
| 4f7466bb29 | |||
| bb66f97b95 |
2
.env.dev
2
.env.dev
@@ -15,6 +15,8 @@ VUE_APP_TITLE = 洛玻集团驾驶舱
|
|||||||
# 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'
|
||||||
|
|
||||||
|
|
||||||
# 路由懒加载
|
# 路由懒加载
|
||||||
|
|||||||
@@ -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
109
src/api/basicInfoConfig.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
// 产品信息
|
||||||
|
export function getProductConfigPage(data) {
|
||||||
|
return request({
|
||||||
|
url: "/lb/product-config/page",
|
||||||
|
method: "get",
|
||||||
|
params: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function getProductConfig(data) {
|
||||||
|
return request({
|
||||||
|
url: "/lb/product-config/get",
|
||||||
|
method: "get",
|
||||||
|
params: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function addProductConfig(data) {
|
||||||
|
return request({
|
||||||
|
url: "/lb/product-config/create",
|
||||||
|
method: "post",
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function updateProductConfig(data) {
|
||||||
|
return request({
|
||||||
|
url: "/lb/product-config/update",
|
||||||
|
method: "put",
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function delProductConfig(data) {
|
||||||
|
return request({
|
||||||
|
url: "/lb/product-config/delete",
|
||||||
|
method: "delete",
|
||||||
|
params: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 集团重点工作
|
||||||
|
export function getImportantWorkConfigPage(data) {
|
||||||
|
return request({
|
||||||
|
url: "/lb/important-work-config/page",
|
||||||
|
method: "get",
|
||||||
|
params: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function getImportantWorkConfig(data) {
|
||||||
|
return request({
|
||||||
|
url: "/lb/important-work-config/get",
|
||||||
|
method: "get",
|
||||||
|
params: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function addImportantWorkConfig(data) {
|
||||||
|
return request({
|
||||||
|
url: "/lb/important-work-config/create",
|
||||||
|
method: "post",
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function updateImportantWorkConfig(data) {
|
||||||
|
return request({
|
||||||
|
url: "/lb/important-work-config/update",
|
||||||
|
method: "put",
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function delImportantWorkConfig(data) {
|
||||||
|
return request({
|
||||||
|
url: "/lb/important-work-config/delete",
|
||||||
|
method: "delete",
|
||||||
|
params: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 客户信息配置
|
||||||
|
export function getCustomerConfigPage(data) {
|
||||||
|
return request({
|
||||||
|
url: "/lb/customer-config/page",
|
||||||
|
method: "get",
|
||||||
|
params: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function getCustomerConfig(data) {
|
||||||
|
return request({
|
||||||
|
url: "/lb/customer-config/get",
|
||||||
|
method: "get",
|
||||||
|
params: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function addCustomerConfig(data) {
|
||||||
|
return request({
|
||||||
|
url: "/lb/customer-config/create",
|
||||||
|
method: "post",
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function updateCustomerConfig(data) {
|
||||||
|
return request({
|
||||||
|
url: "/lb/customer-config/update",
|
||||||
|
method: "put",
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function delCustomerConfig(data) {
|
||||||
|
return request({
|
||||||
|
url: "/lb/customer-config/delete",
|
||||||
|
method: "delete",
|
||||||
|
params: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -316,6 +316,24 @@ export function updateTargetYearData(data) {
|
|||||||
data: data,
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
24
src/assets/icons/svg/turn-data.svg
Normal file
24
src/assets/icons/svg/turn-data.svg
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>切换数据</title>
|
||||||
|
<g id="页面" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="icon和插图" transform="translate(-339.000000, -683.000000)" fill-rule="nonzero">
|
||||||
|
<g id="检测数据" transform="translate(339.000000, 683.000000)">
|
||||||
|
<rect id="矩形" fill="#000000" opacity="0" x="0" y="0" width="32" height="32"></rect>
|
||||||
|
<g id="编组-5" transform="translate(1.358796, 1.358796)">
|
||||||
|
<path d="M14.6412037,0 C6.56329821,0 0,6.56329821 0,14.6412037 C0,22.7191092 6.56329821,29.2824074 14.6412037,29.2824074 C22.7191092,29.2824074 29.2824074,22.7191092 29.2824074,14.6412037 C29.2824074,6.56329821 22.7191092,0 14.6412037,0 Z" id="形状" fill="#ECF5FE"></path>
|
||||||
|
<g id="烧瓶,实验,化学,科学" transform="translate(4.362930, 5.001581)">
|
||||||
|
<rect id="矩形" fill="#000000" opacity="0" x="1.95940783" y="1.320757" width="16.6377315" height="16.6377315"></rect>
|
||||||
|
<g>
|
||||||
|
<rect id="矩形" fill="#000000" opacity="0" x="2.83308197" y="2.32185598" width="14.8903832" height="14.8903832"></rect>
|
||||||
|
<g id="还原画布" fill="#3E6AF7">
|
||||||
|
<rect id="矩形" opacity="0" x="1.23578458" y="0.724558589" width="18.084978" height="18.084978"></rect>
|
||||||
|
<path d="M17.6932207,6.74823569 L13.2296336,6.74823569 C13.0215873,6.74823569 12.8513571,6.54528408 12.8513571,6.29724746 L12.8513571,1.97232623 C12.8513571,1.72428962 13.0215542,1.521338 13.2296171,1.521338 L17.6932207,1.521338 C17.901267,1.521338 18.0714972,1.72428962 18.0714972,1.97232623 L18.0714972,6.29722771 C18.0714972,6.54526433 17.9031719,6.74823569 17.6932207,6.74823569 L17.6932207,6.74823569 Z M6.87068348,17.3028361 L2.40704671,17.3028361 C2.19900041,17.3028361 2.02877016,17.0998845 2.02877016,16.8518479 L2.02877016,12.5269266 C2.02877016,12.27889 2.19900041,12.0759384 2.40704671,12.0759384 L6.87068348,12.0759384 C7.07872978,12.0759384 7.24896003,12.2788703 7.24896003,12.5269266 L7.24896003,16.8518479 C7.24896003,17.1021358 7.07872978,17.3028361 6.87068348,17.3028361 Z M20.4661601,11.5941637 L19.1062728,9.97893922 C19.0135961,9.86923455 18.8717403,9.82573697 18.7412476,9.85222312 C18.680722,9.86356959 18.6239731,9.89194406 18.574794,9.93355332 L16.8858257,11.3558546 C16.7269585,11.4901405 16.7061539,11.7284496 16.8404398,11.8892217 C16.9160885,11.9781052 17.0220165,12.0235076 17.1298163,12.0235076 C17.2149231,12.0235076 17.3019183,11.9951332 17.3738069,11.9346076 L18.4537759,11.0267571 C18.4518711,11.0418803 18.4518711,11.0551316 18.4518711,11.0702547 C18.4518711,13.3493556 17.7350555,15.2804262 16.3789447,16.6535483 C15.0077109,18.0437151 13.0596123,18.7775586 10.7445837,18.7775586 C10.5365374,18.7775586 10.3663072,18.9477723 10.3663072,19.1558352 C10.3663072,19.3638981 10.5365208,19.5340952 10.7445837,19.5340952 C13.2657703,19.5340952 15.3992222,18.7227146 16.9160885,17.1850271 C17.6556136,16.4360439 18.2287016,15.5319866 18.6183247,14.4993084 C18.9890314,13.5157926 19.1857312,12.4301421 19.2065193,11.2707477 L19.8912003,12.0840332 C19.9668656,12.1729167 20.072777,12.2183192 20.1805768,12.2183192 C20.2657002,12.2183192 20.3526954,12.1899447 20.4245674,12.1294191 C20.5815463,11.9932449 20.602351,11.7549358 20.4661767,11.5941637 L20.4661601,11.5941637 Z M9.98803061,0 C8.67730576,0 7.47441384,0.202364785 6.41524944,0.603334282 C5.37500136,0.996733941 4.47470417,1.57928016 3.74086058,2.33204005 C2.34880553,3.76191116 1.58658748,5.7724236 1.52796679,8.16311747 L0.622004647,7.3990111 C0.461232559,7.2647086 0.222923469,7.28551323 0.0886375303,7.444397 C-0.0456484081,7.6032642 -0.0248437777,7.84347818 0.134023428,7.97776411 L1.82300826,9.40006538 C1.88255245,9.4508878 1.95677081,9.48136671 2.03484776,9.48706054 C2.04619423,9.48706054 2.05565239,9.48896542 2.06699887,9.48896542 C2.17481522,9.48896542 2.28261501,9.44357953 2.35637537,9.35466292 L3.71626275,7.73945502 C3.85054869,7.58057125 3.82974406,7.34037384 3.67087685,7.20608791 C3.51200965,7.07180197 3.27179567,7.0926066 3.13750974,7.2514738 L2.28263157,8.26714062 C2.32044763,6.0334422 3.00890532,4.16667386 4.28178097,2.86163053 C4.93808755,2.18640764 5.74570807,1.66627531 6.681933,1.3125966 C7.65597399,0.943778206 8.76809412,0.758424851 9.98803061,0.758424851 C10.1960769,0.758424851 10.3663072,0.588211171 10.3663072,0.380164867 C10.3663072,0.172118563 10.1979652,0 9.98803061,0 Z" id="形状" stroke="#3E6AF7" stroke-width="0.7"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.8 KiB |
@@ -8,11 +8,8 @@ import { isRelogin } from '@/utils/request'
|
|||||||
|
|
||||||
NProgress.configure({ showSpinner: false })
|
NProgress.configure({ showSpinner: false })
|
||||||
|
|
||||||
/**
|
// 从菜单树中查找第一个可见叶子菜单路径(按接口返回顺序)
|
||||||
* 从菜单树中查找 jumpFlag===1 的默认跳转路径
|
function findFirstLeafPathFromMenus(menus) {
|
||||||
* 若打标在父节点则取其下第一个可见叶子路径;若在叶子则直接返回该路径
|
|
||||||
*/
|
|
||||||
function findDefaultPathFromMenus(menus) {
|
|
||||||
if (!Array.isArray(menus) || menus.length === 0) return null
|
if (!Array.isArray(menus) || menus.length === 0) return null
|
||||||
|
|
||||||
function dfs(list, parentPath = '') {
|
function dfs(list, parentPath = '') {
|
||||||
@@ -23,16 +20,11 @@ function findDefaultPathFromMenus(menus) {
|
|||||||
? rawPath
|
? rawPath
|
||||||
: `${parentPath}/${rawPath}`.replace(/\/+/g, '/')
|
: `${parentPath}/${rawPath}`.replace(/\/+/g, '/')
|
||||||
|
|
||||||
if (item.jumpFlag === 1) {
|
|
||||||
if (item.children && item.children.length > 0) {
|
|
||||||
const childPath = dfs(item.children, currentPath)
|
|
||||||
return childPath != null ? childPath : currentPath
|
|
||||||
}
|
|
||||||
return currentPath
|
|
||||||
}
|
|
||||||
if (item.children && item.children.length > 0) {
|
if (item.children && item.children.length > 0) {
|
||||||
const found = dfs(item.children, currentPath)
|
const found = dfs(item.children, currentPath)
|
||||||
if (found != null) return found
|
if (found != null) return found
|
||||||
|
} else {
|
||||||
|
return currentPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
@@ -40,6 +32,24 @@ function findDefaultPathFromMenus(menus) {
|
|||||||
return dfs(menus)
|
return dfs(menus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findFirstLeafPathFromRoutes(routes) {
|
||||||
|
if (!Array.isArray(routes) || routes.length === 0) return null
|
||||||
|
const stack = [...routes]
|
||||||
|
while (stack.length) {
|
||||||
|
const route = stack.shift()
|
||||||
|
if (!route || route.hidden === true) continue
|
||||||
|
if (Array.isArray(route.children) && route.children.length > 0) {
|
||||||
|
stack.unshift(...route.children)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const p = route.path || ''
|
||||||
|
if (typeof p === 'string' && p.startsWith('/') && p !== '/404') {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// 增加三方登陆 update by 芋艿
|
// 增加三方登陆 update by 芋艿
|
||||||
const whiteList = ['/login', '/social-login', '/auth-redirect', '/bind', '/register', '/oauthLogin/gitee']
|
const whiteList = ['/login', '/social-login', '/auth-redirect', '/bind', '/register', '/oauthLogin/gitee']
|
||||||
|
|
||||||
@@ -65,11 +75,16 @@ router.beforeEach((to, from, next) => {
|
|||||||
store.dispatch('GenerateRoutes', userInfo.menus).then(accessRoutes => {
|
store.dispatch('GenerateRoutes', userInfo.menus).then(accessRoutes => {
|
||||||
// 根据 roles 权限生成可访问的路由表
|
// 根据 roles 权限生成可访问的路由表
|
||||||
router.addRoutes(accessRoutes) // 动态添加可访问路由表
|
router.addRoutes(accessRoutes) // 动态添加可访问路由表
|
||||||
const defaultPath = findDefaultPathFromMenus(userInfo.menus) || '/'
|
const menuDefaultPath = findFirstLeafPathFromMenus(userInfo.menus)
|
||||||
|
const routeFallbackPath = findFirstLeafPathFromRoutes(accessRoutes)
|
||||||
|
const defaultPath = menuDefaultPath || routeFallbackPath || '/'
|
||||||
store.dispatch('SetDefaultPath', defaultPath)
|
store.dispatch('SetDefaultPath', defaultPath)
|
||||||
// 仅当目标为根路径 '/' 时跳默认页;否则保持当前路径(含刷新场景),避免刷新被误判为“未匹配”而跳到默认页
|
// 仅当目标为根路径 '/' 时跳默认页;否则保持当前路径(含刷新场景),避免刷新被误判为“未匹配”而跳到默认页
|
||||||
if (to.path === '/') {
|
if (to.path === '/') {
|
||||||
next({ path: defaultPath, replace: true })
|
const matched = router.match(defaultPath)
|
||||||
|
const hasMatch = matched && Array.isArray(matched.matched) && matched.matched.length > 0
|
||||||
|
const safePath = hasMatch ? defaultPath : (routeFallbackPath || '/')
|
||||||
|
next({ path: safePath, replace: true })
|
||||||
} else {
|
} else {
|
||||||
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成,当前页刷新时保留 to 的路径
|
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成,当前页刷新时保留 to 的路径
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 子菜单(完整路径)
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
<div class="bullshit__info">
|
<div class="bullshit__info">
|
||||||
对不起,您正在寻找的页面不存在。尝试检查URL的错误,然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容。
|
对不起,您正在寻找的页面不存在。尝试检查URL的错误,然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容。
|
||||||
</div>
|
</div>
|
||||||
<router-link to="/" class="bullshit__return-home">
|
<router-link to="/login" class="bullshit__return-home">
|
||||||
返回首页
|
返回登录页
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
214
src/views/home/accountsReceivable/accountsReceivable.vue
Normal file
214
src/views/home/accountsReceivable/accountsReceivable.vue
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
<template>
|
||||||
|
<div id="dayReport" class="dayReport" :style="styles">
|
||||||
|
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||||
|
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||||
|
<ReportHeader :dateData="dateData" top-title="应收账款" :is-full-screen="isFullScreen"
|
||||||
|
@screenfullChange="screenfullChange" @timeRangeChange="handleTimeChange" />
|
||||||
|
<div class="main-body" style="
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
padding: 0px 16px 0 272px;
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div class="top" style="margin-top: -20px; display: flex; gap: 16px">
|
||||||
|
<div class="top-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns:1624px;
|
||||||
|
">
|
||||||
|
<operatingLineChart :dateData="dateData" :monthData="monthData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||||
|
<div class="left-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns: 1624px;
|
||||||
|
">
|
||||||
|
<operatingLineChartCumulative :dateData="dateData" :ytdData="ytdData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import ReportHeader from "../components/noRouterHeader.vue";
|
||||||
|
import { Sidebar } from "../../../layout/components";
|
||||||
|
import screenfull from "screenfull";
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
import operatingLineChart from "../accountsReceivableComponents/operatingLineChart";
|
||||||
|
import operatingLineChartCumulative from "../accountsReceivableComponents/operatingLineChartCumulative.vue";
|
||||||
|
|
||||||
|
import { getAccountsReceivableData } from '@/api/cockpit'
|
||||||
|
export default {
|
||||||
|
name: "AccountsReceivable",
|
||||||
|
components: {
|
||||||
|
ReportHeader,
|
||||||
|
operatingLineChartCumulative,
|
||||||
|
operatingLineChart,
|
||||||
|
Sidebar,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
weekArr: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
|
||||||
|
isFullScreen: false,
|
||||||
|
timer: null,
|
||||||
|
beilv: 1,
|
||||||
|
value: 100,
|
||||||
|
sort:1,
|
||||||
|
selectDate:{},
|
||||||
|
monthData: {},
|
||||||
|
ytdData:{},
|
||||||
|
dateData: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.init();
|
||||||
|
this.windowWidth(document.documentElement.clientWidth);
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
theme: (state) => state.settings.theme,
|
||||||
|
sideTheme: (state) => state.settings.sideTheme,
|
||||||
|
sidebar: (state) => state.app.sidebar,
|
||||||
|
device: (state) => state.app.device,
|
||||||
|
needTagsView: (state) => state.settings.tagsView,
|
||||||
|
fixedHeader: (state) => state.settings.fixedHeader,
|
||||||
|
}),
|
||||||
|
classObj() {
|
||||||
|
return {
|
||||||
|
hideSidebar: !this.sidebar.opened,
|
||||||
|
openSidebar: this.sidebar.opened,
|
||||||
|
withoutAnimation: this.sidebar.withoutAnimation,
|
||||||
|
mobile: this.device === "mobile",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
variables() {
|
||||||
|
return variables;
|
||||||
|
},
|
||||||
|
// ...mapGetters(['sidebar']),
|
||||||
|
styles() {
|
||||||
|
const v = Math.floor(this.value * this.beilv * 100) / 10000;
|
||||||
|
return {
|
||||||
|
transform: `scale(${v})`,
|
||||||
|
transformOrigin: "left top",
|
||||||
|
// overflow: hidden;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
clientWidth(val) {
|
||||||
|
if (!this.timer) {
|
||||||
|
this.clientWidth = val;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
this.timer = true;
|
||||||
|
let _this = this;
|
||||||
|
setTimeout(function () {
|
||||||
|
_this.timer = false;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
// 这里可以添加修改时的方法
|
||||||
|
this.windowWidth(val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.destroy();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const _this = this;
|
||||||
|
_this.beilv = document.documentElement.clientWidth / 1920;
|
||||||
|
window.onresize = () => {
|
||||||
|
return (() => {
|
||||||
|
_this.clientWidth = `${document.documentElement.clientWidth}`;
|
||||||
|
this.beilv = _this.clientWidth / 1920;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getData() {
|
||||||
|
getAccountsReceivableData({
|
||||||
|
startTime: this.dateData.startTime,
|
||||||
|
endTime: this.dateData.endTime,
|
||||||
|
sort: this.sort,
|
||||||
|
index: undefined,
|
||||||
|
factory: undefined
|
||||||
|
}).then((res) => {
|
||||||
|
this.monthData= res.data.month
|
||||||
|
this.ytdData = res.data.ytd
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleTimeChange(obj) {
|
||||||
|
console.log(obj, 'obj');
|
||||||
|
this.dateData= obj
|
||||||
|
this.getData()
|
||||||
|
},
|
||||||
|
handleClickOutside() {
|
||||||
|
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
||||||
|
},
|
||||||
|
windowWidth(value) {
|
||||||
|
this.clientWidth = value;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
},
|
||||||
|
change() {
|
||||||
|
this.isFullScreen = screenfull.isFullscreen;
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.on("change", this.change);
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.off("change", this.change);
|
||||||
|
},
|
||||||
|
// 全屏
|
||||||
|
screenfullChange() {
|
||||||
|
console.log("screenfull.enabled", screenfull.isEnabled);
|
||||||
|
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.toggle(this.$refs.dayReportB);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "~@/assets/styles/mixin.scss";
|
||||||
|
@import "~@/assets/styles/variables.scss";
|
||||||
|
.dayReport {
|
||||||
|
width: 1920px;
|
||||||
|
height: 1080px;
|
||||||
|
background: url("../../../assets/img/backp.png") no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
.hideSidebar .fixed-header {
|
||||||
|
width: calc(100% - 54px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarHide .fixed-header {
|
||||||
|
width: calc(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile .fixed-header {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
419
src/views/home/accountsReceivableComponents/Header.vue
Normal file
419
src/views/home/accountsReceivableComponents/Header.vue
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
<template>
|
||||||
|
<header class="report-header">
|
||||||
|
<!-- 左侧区域:logo + 标题 -->
|
||||||
|
<div class="left-content">
|
||||||
|
<img style="height: 36px;" src="../../../assets/img/cnbm.png" alt="benmaLogo" >
|
||||||
|
<div class="top-title">{{ topTitle }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="center-content">
|
||||||
|
<!-- 循环 pageRoutes,不再硬编码文字 -->
|
||||||
|
<div class="item" v-for="(page, index) in pageRoutes" :key="index" @click="goToPage(page.path, index)">
|
||||||
|
<span class="item-text">{{ page.text }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- :class="{ 'no-skew': activeIndex === index }
|
||||||
|
" -->
|
||||||
|
<!-- 右侧区域:全屏按钮 -->
|
||||||
|
<div class="right-content">
|
||||||
|
<el-button type="text" class="screen-btn" :title="isFullScreen ? '退出全屏' : '全屏'" @click="changeFullScreen">
|
||||||
|
<svg-icon style="color: #0B58FF;" v-if="isFullScreen" icon-class="unFullScreenView" />
|
||||||
|
<svg-icon style="color: #0B58FF;" v-else icon-class="fullScreenView" />
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 时间选择区域:日/月/年按钮 + label + 日期选择器 -->
|
||||||
|
<div class="timeType">
|
||||||
|
<!-- <div class="item" v-for="(item, index) in timeTypes" :key="index" @click="activeTime = index"
|
||||||
|
:class="{ 'no-skew': activeTime === index }">
|
||||||
|
<span class="item-text">{{ item.text }}</span>
|
||||||
|
</div> -->
|
||||||
|
<div class="dateP">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">月份选择</span>
|
||||||
|
</div>
|
||||||
|
<el-date-picker v-model="date" :type="getPickerType" :placeholder="getPickerPlaceholder"
|
||||||
|
class="custom-date-picker" style="width: 132px;height: 29px;" @change="emitTimeRange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment'
|
||||||
|
export default {
|
||||||
|
name: 'Header',
|
||||||
|
props: {
|
||||||
|
isFullScreen: { type: Boolean, default: false },
|
||||||
|
topTitle: { type: String, default: '' }
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentTime: '',
|
||||||
|
timeTimer: null,
|
||||||
|
date: undefined,
|
||||||
|
activeIndex: -1,
|
||||||
|
activeTime: 1, // 0=日,1=月,2=年(默认选中“日”)
|
||||||
|
pageRoutes: [
|
||||||
|
{ text: '营业收入', path: '/operatingRevenue/operatingRevenueIndex' },
|
||||||
|
{ text: '利润分析', path: '/profitAnalysis' },
|
||||||
|
{ text: '产销率库存分析', path: '/PSIAnal' },
|
||||||
|
{ text: '成本分析', path: '/cost/cost' },
|
||||||
|
{ text: '驾驶舱报表', path: '/cockpit' }
|
||||||
|
],
|
||||||
|
// 定义时间类型配置:text=按钮文字,pickerType=选择器类型,placeholder=占位符
|
||||||
|
timeTypes: [
|
||||||
|
{ text: '日', pickerType: 'date', placeholder: '选择日期' },
|
||||||
|
{ text: '月', pickerType: 'month', placeholder: '选择月份' },
|
||||||
|
{ text: '年', pickerType: 'year', placeholder: '选择年份' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 动态获取日期选择器类型
|
||||||
|
getPickerType() {
|
||||||
|
return this.timeTypes[this.activeTime].pickerType;
|
||||||
|
},
|
||||||
|
// 动态获取日期选择器占位符
|
||||||
|
getPickerPlaceholder() {
|
||||||
|
return this.timeTypes[this.activeTime].placeholder;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goToPage(path, index) {
|
||||||
|
// 1. 跳转到对应路由
|
||||||
|
this.$router.push(path);
|
||||||
|
// 2. 更新activeIndex,让当前点击项高亮
|
||||||
|
this.activeIndex = index;
|
||||||
|
},
|
||||||
|
changeFullScreen() { this.$emit('screenfullChange') },
|
||||||
|
padZero(num) { return num < 10 ? '0' + num : num },
|
||||||
|
/**
|
||||||
|
* 核心方法1:根据维度计算时间区间(首次进入时基于赋值的当月日期,计算“当月第一天0点→次月第一天0点”)
|
||||||
|
* @returns {Object} 包含 start(开始时间)、end(结束时间)、dimension(维度)的区间对象
|
||||||
|
*/
|
||||||
|
calculateTimeRange() {
|
||||||
|
let startTime = 0;
|
||||||
|
let endTime = 0;
|
||||||
|
const mode = this.activeTime + 1; // 1=日,2=月,3=年
|
||||||
|
const defaultMoment = moment(); // 默认当前时间
|
||||||
|
|
||||||
|
const targetMoment = this.date
|
||||||
|
? moment(this.date, this.getPickerType === 'date' ? 'YYYY-MM-DD' : (this.getPickerType === 'month' ? 'YYYY-MM' : 'YYYY'))
|
||||||
|
: defaultMoment;
|
||||||
|
|
||||||
|
if (!targetMoment.isValid()) {
|
||||||
|
console.error('无效日期:', this.date);
|
||||||
|
return { startTime, endTime, mode };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 日维度:00:00:00 → 23:59:59(无毫秒)
|
||||||
|
if (this.activeTime === 0) {
|
||||||
|
startTime = targetMoment.startOf('day').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('day').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 月维度:当月1日00:00:00 → 当月最后一天23:59:59(无毫秒)
|
||||||
|
else if (this.activeTime === 1) {
|
||||||
|
startTime = targetMoment.startOf('month').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('month').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 年维度:当年1月1日00:00:00 → 当年最后一天23:59:59(无毫秒)
|
||||||
|
else if (this.activeTime === 2) {
|
||||||
|
startTime = targetMoment.startOf('year').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('year').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调试输出:验证是否去掉毫秒
|
||||||
|
console.log('时间范围计算结果:', {
|
||||||
|
mode,
|
||||||
|
startTime: moment(startTime * 1000).format('YYYY-MM-DD HH:mm:ss'), // 格式:2025-11-30 00:00:00
|
||||||
|
endTime: moment(endTime * 1000).format('YYYY-MM-DD HH:mm:ss'), // 格式:2025-11-30 23:59:59(无毫秒)
|
||||||
|
startTimeStamp: startTime, // 秒级时间戳(如:1764422400)
|
||||||
|
endTimeStamp: endTime // 秒级时间戳(如:1764508799)
|
||||||
|
});
|
||||||
|
|
||||||
|
return { startTime, endTime, mode };
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 核心方法2:传递时间区间给父组件(首次进入时触发,传递“当月第一天0点→次月第一天0点”)
|
||||||
|
*/
|
||||||
|
emitTimeRange() {
|
||||||
|
const timeRange = this.calculateTimeRange();
|
||||||
|
this.$emit('timeRangeChange', timeRange);
|
||||||
|
// 调试用:查看首次传递的区间(如{start: "2025-10-01T00:00:00", end: "2025-11-01T00:00:00", dimension: "月"})
|
||||||
|
console.log('当前时间区间:', timeRange);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 维度切换时:清空选择的日期,并传递当前维度的默认区间
|
||||||
|
activeTime(newVal, oldVal) {
|
||||||
|
if (newVal !== oldVal) {
|
||||||
|
this.date = undefined;
|
||||||
|
// this.emitTimeRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 核心逻辑:首次进入页面,计算当月默认日期并赋值给选择器,同时传递区间
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = this.padZero(now.getMonth() + 1); // 月份从0开始,+1后补零(如1月→01)
|
||||||
|
// 赋值当月默认日期(格式:YYYY-MM,适配month类型选择器)
|
||||||
|
this.date = `${year}-${month}`;
|
||||||
|
// 确保选择器渲染完成后,传递“当月第一天0点→次月第一天0点”的区间
|
||||||
|
this.$nextTick(() => this.emitTimeRange());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
/* 原有样式不变,仅补充label文字的倾斜抵消样式 */
|
||||||
|
@font-face {
|
||||||
|
font-family: "YouSheBiaoTiHei";
|
||||||
|
src: url('../../../assets/fonts/YouSheBiaoTiHe.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-header {
|
||||||
|
height: 117px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
background: url('../../../assets/img/topTitle.png') no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: 0 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
/* 确保timeType绝对定位生效 */
|
||||||
|
|
||||||
|
.left-content {
|
||||||
|
margin-top: 11px;
|
||||||
|
margin-left: 44px;
|
||||||
|
height: 55px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-title {
|
||||||
|
height: 55px;
|
||||||
|
font-family: "YouSheBiaoTiHei", sans-serif;
|
||||||
|
font-size: 42px;
|
||||||
|
color: #1E1651;
|
||||||
|
line-height: 55px;
|
||||||
|
letter-spacing: 6px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 18px;
|
||||||
|
margin-left: 70px;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
width: 180px;
|
||||||
|
height: 50px;
|
||||||
|
background: #E1EEFC;
|
||||||
|
transform: skew(-20deg);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #1E1651;
|
||||||
|
line-height: 50px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0px 13px 16px 0px rgba(179, 217, 255, 0.43),
|
||||||
|
0px 2px 4px 0px rgba(92, 140, 255, 0.25),
|
||||||
|
inset 0px -43px 13px 0px rgba(255, 255, 255, 0.51);
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(20deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.no-skew {
|
||||||
|
background: none !important;
|
||||||
|
transform: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
color: #1E1651;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 垂直居中,避免元素高低错位 */
|
||||||
|
top: 42px;
|
||||||
|
right:10px;
|
||||||
|
margin-top: 18px;
|
||||||
|
gap: 0;
|
||||||
|
/* 清除间隙,让按钮与选择器紧密连接 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item {
|
||||||
|
width: 40px;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(236, 244, 254, 1);
|
||||||
|
transform: skew(-20deg);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(11, 88, 255, 1);
|
||||||
|
line-height: 28px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
/* 选中按钮与未选中按钮倾斜角度统一,避免切换时跳动 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item .item-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(20deg);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item.no-skew {
|
||||||
|
background: rgba(11, 88, 255, 1);
|
||||||
|
color: rgba(249, 252, 255, 1);
|
||||||
|
transform: skew(-20deg) !important;
|
||||||
|
/* 统一倾斜角度,修复原30deg的错位 */
|
||||||
|
box-shadow: 0 2px 8px rgba(11, 88, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item.no-skew .item-text {
|
||||||
|
transform: skew(20deg) !important;
|
||||||
|
/* 同步统一文字倾斜角度 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateP {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateP .label {
|
||||||
|
width: 165px;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(236, 244, 254, 1);
|
||||||
|
transform: skew(-25deg);
|
||||||
|
/* 与按钮倾斜角度统一(原30deg改为25deg,避免视觉错位) */
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 28px;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 补充:label文字抵消倾斜(原代码遗漏,导致文字倾斜) */
|
||||||
|
.dateP .label-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(25deg);
|
||||||
|
/* 与label倾斜角度相反,确保文字正立 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-right: 4px;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-time {
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 24px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screen-btn {
|
||||||
|
width: 26px;
|
||||||
|
margin-left: 300px;
|
||||||
|
color: #00fff0;
|
||||||
|
font-size: 26px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 日期选择器样式保持不变 */
|
||||||
|
::v-deep .custom-date-picker {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
width: 165px !important;
|
||||||
|
height: 28px !important;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 !important;
|
||||||
|
|
||||||
|
/* 1. 调整输入框文字:确保行高与输入框高度一致,垂直居中 */
|
||||||
|
.el-input__inner {
|
||||||
|
height: 28px !important;
|
||||||
|
width: 165px !important;
|
||||||
|
text-align: center;
|
||||||
|
padding-left: 15px !important;
|
||||||
|
padding-right: 32px !important;
|
||||||
|
/* 给图标留空间,避免文字被遮挡 */
|
||||||
|
font-size: 14px !important;
|
||||||
|
line-height: 28px !important;
|
||||||
|
/* 行高=输入框高度,文字垂直居中 */
|
||||||
|
color: rgba(237, 245, 253, 1) !important;
|
||||||
|
vertical-align: middle !important;
|
||||||
|
/* 强制文字垂直对齐 */
|
||||||
|
clip-path: polygon(18px 0, 100% 0, 100% 100%, 0 100%);
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
background-color: rgba(11, 88, 255, 1) !important;
|
||||||
|
border-left: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. 调整图标容器:让图标与文字在同一水平线上 */
|
||||||
|
.el-input__prefix {
|
||||||
|
left: auto !important;
|
||||||
|
right: 8px !important;
|
||||||
|
top: 50% !important;
|
||||||
|
/* 从40%改为50%,基于输入框垂直居中 */
|
||||||
|
transform: translateY(-50%) !important;
|
||||||
|
/* 向上偏移自身50%,精准居中 */
|
||||||
|
display: inline-flex !important;
|
||||||
|
/* 让容器内图标垂直居中 */
|
||||||
|
align-items: center !important;
|
||||||
|
/* 图标在容器内垂直居中 */
|
||||||
|
height: 28px !important;
|
||||||
|
/* 容器高度=输入框高度,避免偏移 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. 调整图标本身:确保图标大小和对齐方式 */
|
||||||
|
.el-input__icon {
|
||||||
|
color: #ffffff !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
line-height: 28px !important;
|
||||||
|
/* 图标行高=输入框高度,与文字对齐 */
|
||||||
|
vertical-align: middle !important;
|
||||||
|
/* 强制图标垂直对齐 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. 图标伪类:确保颜色和对齐继承 */
|
||||||
|
.el-icon-date::before {
|
||||||
|
color: #ffffff !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
line-height: inherit !important;
|
||||||
|
/* 继承父级行高,避免错位 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
282
src/views/home/accountsReceivableComponents/container.vue
Normal file
282
src/views/home/accountsReceivableComponents/container.vue
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
<template>
|
||||||
|
<div class="cockpitContainer" :class="['cockpitContainer__' + size]">
|
||||||
|
<div class="content-top" :class="['content-top__' + topSize]">
|
||||||
|
<!-- 使用 flex 容器包裹图标和文字,实现垂直居中 -->
|
||||||
|
<div class="title-wrapper">
|
||||||
|
<svg-icon class="title-icon" :icon-class="icon" />
|
||||||
|
<span class="title-text">
|
||||||
|
{{ name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cockpitContainer-body">
|
||||||
|
<slot>
|
||||||
|
<div class="test-body">something test....</div>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Container',
|
||||||
|
components: {},
|
||||||
|
// eslint-disable-next-line vue/require-prop-types
|
||||||
|
props: ['name', 'size', 'icon', 'topSize'],
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.cockpitContainer {
|
||||||
|
display: inline-block;
|
||||||
|
// width: 100%;
|
||||||
|
// height: 100%;
|
||||||
|
padding: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.content-top {
|
||||||
|
height: 60px;
|
||||||
|
|
||||||
|
.title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 10px;
|
||||||
|
/* 垂直居中关键属性 */
|
||||||
|
height: 100%;
|
||||||
|
/* 继承父容器高度,确保垂直居中范围 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
font-size: 30px;
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
/* 图标和文字之间的间距 */
|
||||||
|
flex-shrink: 0;
|
||||||
|
/* 防止图标被压缩 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 移除固定行高,避免影响垂直对齐
|
||||||
|
// line-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// width: 547px;
|
||||||
|
// background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
&__basic {
|
||||||
|
// width: 547px;
|
||||||
|
background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__middle {
|
||||||
|
background: url(../../../assets/img/topTileMiddle.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__large {
|
||||||
|
background: url(../../../assets/img/topTitleLargeBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__KFAPTopTitle {
|
||||||
|
background: url(../../../assets/img/KFAPTopTitle.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiTopTitleBasic {
|
||||||
|
background: url(../../../assets/img/psiTopTitleBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__rawTopTitleLarge {
|
||||||
|
background: url(../../../assets/img/rawTopTitleLarge.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__topBasic {
|
||||||
|
background: url(../../../assets/img/top-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bottomBasic {
|
||||||
|
background: url(../../../assets/img/bottom-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingBasic {
|
||||||
|
background: url(../../../assets/img/operating-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingLarge {
|
||||||
|
background: url(../../../assets/img/operating-large.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__profitTopBasic {
|
||||||
|
background: url(../../../assets/img/profitTopBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__profitMiddleBasic {
|
||||||
|
background: url(../../../assets/img/profitMiddleBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiBasicBg {
|
||||||
|
background: url(../../../assets/img/psiBasicBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiMiddleBg {
|
||||||
|
background: url(../../../assets/img/psiMiddleBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingRevenueBg {
|
||||||
|
background: url(../../../assets/img/operatingRevenueBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__costBasicBg {
|
||||||
|
background: url(../../../assets/img/costBasicBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__rawTopBg {
|
||||||
|
background: url(../../../assets/img/rawTopBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__opLargeBg {
|
||||||
|
background: url(../../../assets/img/opLargeBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// &__left {
|
||||||
|
// background: url(../../../../../../../assets/img/left.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__energyConsumption {
|
||||||
|
// background: url(../../../../../../../assets/img/energyConsumption.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__left2 {
|
||||||
|
// background: url(../../assets/left_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__left3 {
|
||||||
|
// background: url(../../assets/left_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__mid2 {
|
||||||
|
// background: url(../../assets/mid_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__mid3 {
|
||||||
|
// background: url(../../assets/mid_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right1 {
|
||||||
|
// background: url(../../assets/right_1.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right2 {
|
||||||
|
// background: url(../../assets/right_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right3 {
|
||||||
|
// background: url(../../assets/right_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekRight2 {
|
||||||
|
// background: url(../../assets/week_right_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekMidTop {
|
||||||
|
// background: url(../../assets/week-mid-top.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekMidMid {
|
||||||
|
// background: url(../../assets/week-mid-mid.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: ' ';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
// background: inherit;
|
||||||
|
/* 设置模糊,不用 filter */
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-body {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
226
src/views/home/accountsReceivableComponents/electricityGauge.vue
Normal file
226
src/views/home/accountsReceivableComponents/electricityGauge.vue
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="chartBox" style="width: 100%; height: 108px; position: relative;">
|
||||||
|
<div :id="id" style="width: 100%; height:100%;"></div>
|
||||||
|
<div class="bottomTip">
|
||||||
|
<div class="precent">
|
||||||
|
<span class="precentNum">{{ detailData.rate || 0 }}% </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'EnergyConsumption',
|
||||||
|
// components: { Container },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
detailData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
// electricComu: 0,
|
||||||
|
// steamComu: 20, // 调整为符合max范围的数值(0-8)
|
||||||
|
// // electricity: [120, 150, 130, 180, 160, 200, 190],
|
||||||
|
// // steam: [80, 95, 85, 110, 100, 120, 115],
|
||||||
|
// // dates: ['1日', '2日', '3日', '4日', '5日', '6日', '7日']
|
||||||
|
})
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
default: () => ('monthG')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// electricityChart: null,
|
||||||
|
// steamChart: null,
|
||||||
|
// specialTicks: [2, 4, 6, 8], // 统一的刻度显示
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
detailData: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true, // 初始化时立即执行
|
||||||
|
handler() {
|
||||||
|
this.updateGauges()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initGauges()
|
||||||
|
// window.addEventListener('resize', this.handleResize)
|
||||||
|
this.observeContainerResize()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
observeContainerResize() {
|
||||||
|
// 修复:获取正确的容器(组件内的.gauge-container)
|
||||||
|
const container = this.$el.querySelector('.gauge-container')
|
||||||
|
if (container && window.ResizeObserver) {
|
||||||
|
this.resizeObserver = new ResizeObserver(entries => {
|
||||||
|
if (this.electricityChart) {
|
||||||
|
this.electricityChart.resize() // 直接触发resize,无需防抖
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.resizeObserver.observe(container)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initGauges() {
|
||||||
|
// console.log('this.id',this.id);
|
||||||
|
|
||||||
|
// 初始化电气图表实例
|
||||||
|
const electricityDom = document.getElementById(this.id)
|
||||||
|
if (electricityDom) {
|
||||||
|
// 修复:正确创建并存储图表实例
|
||||||
|
this.electricityChart = echarts.init(electricityDom)
|
||||||
|
// 首次更新数据
|
||||||
|
this.updateGauges()
|
||||||
|
}
|
||||||
|
// 蒸汽图表若未使用,可注释/删除
|
||||||
|
// const steamDom = document.getElementById('steamGauge')
|
||||||
|
// if (steamDom) {
|
||||||
|
// this.steamChart = echarts.init(steamDom)
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
updateGauges() {
|
||||||
|
// 修复:先判断实例是否存在,再更新配置
|
||||||
|
if (!this.electricityChart) return
|
||||||
|
|
||||||
|
// 修复:兜底获取rate值,确保数值有效
|
||||||
|
const rate = Number(this.detailData?.rate) || 0
|
||||||
|
console.log('当前rate值:', rate); // 调试:确认rate值正确
|
||||||
|
|
||||||
|
// 关键:第二个参数传true,清空原有配置,强制更新
|
||||||
|
this.electricityChart.setOption(this.getElectricityGaugeOption(rate), true)
|
||||||
|
},
|
||||||
|
// 用电量仪表盘配置(保留原有样式,优化数值范围)
|
||||||
|
getElectricityGaugeOption(value) {
|
||||||
|
const electricityGradient = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: '#0B58FF' },
|
||||||
|
{ offset: 1, color: '#32FFCD' }
|
||||||
|
])
|
||||||
|
|
||||||
|
return {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '月度',
|
||||||
|
type: 'gauge',
|
||||||
|
radius: '95', // 修复:添加%,避免数值错误
|
||||||
|
center: ['50%', '90%'],
|
||||||
|
startAngle: 180,
|
||||||
|
endAngle: 0,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
splitNumber: 4,
|
||||||
|
label: { show: false },
|
||||||
|
progress: {
|
||||||
|
show: true,
|
||||||
|
overlap: false,
|
||||||
|
roundCap: true,
|
||||||
|
clip: false,
|
||||||
|
width: 14,
|
||||||
|
itemStyle: { color: electricityGradient }
|
||||||
|
},
|
||||||
|
pointer: {
|
||||||
|
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
|
||||||
|
length: '75%',
|
||||||
|
width: 16,
|
||||||
|
itemStyle: { color: '#288AFF' },
|
||||||
|
offsetCenter: [0, '10%']
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
roundCap: true,
|
||||||
|
lineStyle: { width: 12, color: [[1, '#E6EBF7']] }
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
length: 10,
|
||||||
|
lineStyle: { width: 5, color: '#D6DAE5' },
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
splitNumber: 2,
|
||||||
|
length: 6,
|
||||||
|
lineStyle: { width: 2, color: '#D6DAE5' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
detail: { show: false },
|
||||||
|
data: [{ value: value, unit: '' }] // 确保数值正确传入
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 未使用的蒸汽仪表盘可注释/删除
|
||||||
|
// getSteamGaugeOption(value) { ... }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
.chartBox {
|
||||||
|
.bottomTip {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.precent {
|
||||||
|
line-height: 3px;
|
||||||
|
|
||||||
|
&::after,
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 54px;
|
||||||
|
height: 5px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
margin-right: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用电量线条颜色
|
||||||
|
.precent::before {
|
||||||
|
|
||||||
|
background: linear-gradient(90deg, rgba(40, 138, 255, 0) 0%, rgba(12, 125, 254, 0.4) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ::after:从 透明 到 rgba(12, 125, 254, 0.4)(90度渐变,左到右) */
|
||||||
|
.precent::after {
|
||||||
|
background: linear-gradient(90deg, rgba(12, 125, 254, 0.4) 0%, rgba(40, 138, 255, 0) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蒸汽线条颜色
|
||||||
|
// .steam-precent::after,
|
||||||
|
// .steam-precent::before {
|
||||||
|
// background: linear-gradient(90deg, rgba(11, 168, 255, 0.26) 0%, rgba(54, 239, 230, 1) 100%);
|
||||||
|
// }
|
||||||
|
|
||||||
|
.precentNum {
|
||||||
|
display: inline-block;
|
||||||
|
// width: 52px;
|
||||||
|
height: 22px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蒸汽数字颜色
|
||||||
|
.steam-num {
|
||||||
|
color: rgba(54, 239, 230, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
513
src/views/home/accountsReceivableComponents/operatingBar.vue
Normal file
513
src/views/home/accountsReceivableComponents/operatingBar.vue
Normal file
@@ -0,0 +1,513 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<div class="header-row">
|
||||||
|
<div class="base-title">各基地情况</div>
|
||||||
|
<div class="barTop">
|
||||||
|
<div class="right-container">
|
||||||
|
<div class="legend">
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon line yield"></span>完成率
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square target"></span>预算
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square achieved"></span>实际·达标
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square unachieved"></span>实际·未达标
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<div class="item-button category-btn">
|
||||||
|
<span class="item-text">展示顺序</span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-container">
|
||||||
|
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||||
|
<span class="item-text profit-text">{{ selectedSort || '请选择' }}</span>
|
||||||
|
<span class="dropdown-arrow" :class="{ 'rotate': isDropdownShow }"></span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-options" v-if="isDropdownShow">
|
||||||
|
<div class="dropdown-option" v-for="(item, index) in profitOptions" :key="index"
|
||||||
|
@click.stop="selectProfit(item)">
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||||
|
<operatingLineBar :dateData="dateData" :chartData="chartD" style="height: 100%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSale.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData", 'dateData'],
|
||||||
|
emits: ['sort-change'], // 声明事件(Vue3 推荐)
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
isDropdownShow: false,
|
||||||
|
selectedSort: '实际值:高~低', // 选中的label
|
||||||
|
selectedSortValue: 1, // 选中的value,用于排序逻辑
|
||||||
|
profitOptions: [
|
||||||
|
{ label: '实际值:高~低', value: 1 },
|
||||||
|
{ label: '实际值:低~高', value: 2 },
|
||||||
|
{ label: '完成率:高~低', value: 3 },
|
||||||
|
{ label: '完成率:低~高', value: 4 },
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 排序后的数据源(核心:根据selectedSortValue重新排序)
|
||||||
|
currentDataSource() {
|
||||||
|
if (!this.chartData?.factory) return {};
|
||||||
|
|
||||||
|
// 深拷贝原始数据,避免修改原数据
|
||||||
|
const factory = JSON.parse(JSON.stringify(this.chartData.factory));
|
||||||
|
if (!factory.locations.length || !this.selectedSortValue) return factory;
|
||||||
|
|
||||||
|
// 构建带索引的数组,方便同步所有字段排序
|
||||||
|
const dataWithIndex = factory.locations.map((name, index) => ({
|
||||||
|
index,
|
||||||
|
name,
|
||||||
|
real: factory.reals[index],
|
||||||
|
target: factory.targets[index],
|
||||||
|
rate: factory.rates[index],
|
||||||
|
diff: factory.diff[index],
|
||||||
|
flag: factory.flags[index]
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 根据选中的排序规则排序
|
||||||
|
switch (this.selectedSortValue) {
|
||||||
|
case 1: // 实际值:高~低
|
||||||
|
dataWithIndex.sort((a, b) => b.real - a.real);
|
||||||
|
break;
|
||||||
|
case 2: // 实际值:低~高
|
||||||
|
dataWithIndex.sort((a, b) => a.real - b.real);
|
||||||
|
break;
|
||||||
|
case 3: // 预算值:高~低
|
||||||
|
dataWithIndex.sort((a, b) => b.rate - a.rate);
|
||||||
|
break;
|
||||||
|
case 4: // 预算值:低~高
|
||||||
|
dataWithIndex.sort((a, b) => a.rate - b.rate);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步更新所有数组
|
||||||
|
factory.locations = dataWithIndex.map(item => item.name);
|
||||||
|
factory.reals = dataWithIndex.map(item => item.real);
|
||||||
|
factory.targets = dataWithIndex.map(item => item.target);
|
||||||
|
factory.rates = dataWithIndex.map(item => item.rate);
|
||||||
|
factory.diff = dataWithIndex.map(item => item.diff);
|
||||||
|
factory.flags = dataWithIndex.map(item => item.flag);
|
||||||
|
|
||||||
|
return factory;
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
return this.currentDataSource.locations || [];
|
||||||
|
},
|
||||||
|
// 最终传递给图表的排序后数据
|
||||||
|
chartD() {
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
// 完成率(折线图)
|
||||||
|
{
|
||||||
|
name: '完成率',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
lineStyle: { color: 'rgba(40, 138, 255, .5)', width: 2 },
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderColor: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
radius: 4
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.2,
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||||
|
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: data.rates || [],
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 40,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.targets || []
|
||||||
|
},
|
||||||
|
// 实际(柱状图)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 40,
|
||||||
|
label: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const safeFlag = data.flags || [];
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals || []
|
||||||
|
},
|
||||||
|
// 实际差值标签(独立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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
return salesData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectProfit(item) {
|
||||||
|
// 更新选中的label和value
|
||||||
|
this.selectedSort = item.label;
|
||||||
|
this.selectedSortValue = item.value;
|
||||||
|
this.isDropdownShow = false;
|
||||||
|
// 向父组件传递排序事件(可选,保持原有逻辑)
|
||||||
|
this.$emit('sort-change', item.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 监听父组件传入的chartData变化,重置选中状态(可选)
|
||||||
|
watch: {
|
||||||
|
'chartData.factory': {
|
||||||
|
handler() {
|
||||||
|
// 若需要切换数据源后重置排序,可取消注释
|
||||||
|
// this.selectedSort = null;
|
||||||
|
// this.selectedSortValue = null;
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.header-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0 0 0 16px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
width: auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
margin-right: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.dropdown-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
fontSize: 12px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-btn {
|
||||||
|
width: 75px;
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-bottom-left-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-btn {
|
||||||
|
width: 123px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 18px 0 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #3071ff;
|
||||||
|
color: rgba(249, 252, 255, .8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-text {
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 6px solid currentColor;
|
||||||
|
border-top: 4px solid transparent;
|
||||||
|
border-bottom: 4px solid transparent;
|
||||||
|
border-right: 4px solid transparent;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
|
||||||
|
&.rotate {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-options {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
right: 0;
|
||||||
|
margin-top: 2px;
|
||||||
|
width: 123px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.dropdown-option {
|
||||||
|
padding: 6px 12px;
|
||||||
|
fontSize: 12px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
color: #3071ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChipBottom" id="cockpitEffChipBottom" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
series: [],
|
||||||
|
allPlaceNames: []
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
validator: (value) => {
|
||||||
|
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChipBottom;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatter: (params) => {
|
||||||
|
let html = `${params[0].axisValue}<br/>`;
|
||||||
|
params.forEach(item => {
|
||||||
|
const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
);
|
||||||
|
html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 60,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
splitNumber: 4,
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: (value) => Math.ceil((value.max || 0) * 1.1),
|
||||||
|
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
scale:true,
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
172
src/views/home/accountsReceivableComponents/operatingLineBar.vue
Normal file
172
src/views/home/accountsReceivableComponents/operatingLineBar.vue
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
series: [],
|
||||||
|
allPlaceNames: []
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
validator: (value) => {
|
||||||
|
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 60,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
// {
|
||||||
|
// type: 'value',
|
||||||
|
// nameTextStyle: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// align: 'left'
|
||||||
|
// },
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
// axisTick: { show: false },
|
||||||
|
// axisLabel: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// formatter: '{value}%'
|
||||||
|
// },
|
||||||
|
// splitLine: { show: false },
|
||||||
|
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
// splitNumber: 4
|
||||||
|
// }
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null, // 存储图表实例
|
||||||
|
resizeHandler: null, // 存储resize事件处理函数
|
||||||
|
// 核心:基地名称与序号的映射表(固定顺序)
|
||||||
|
baseNameToIndexMap: {
|
||||||
|
'宜兴': 7,
|
||||||
|
'漳州': 8,
|
||||||
|
'自贡': 3,
|
||||||
|
'桐城': 2,
|
||||||
|
'洛阳': 9,
|
||||||
|
'合肥': 5,
|
||||||
|
'宿迁': 6,
|
||||||
|
'秦皇岛': 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.initChart(); // 初始化图表(只执行一次)
|
||||||
|
this.updateChart(); // 更新图表数据
|
||||||
|
});
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
this.updateChart(); // 仅更新数据,不重新创建实例
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 组件销毁时清理资源
|
||||||
|
this.destroyChart();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 初始化图表(只在mounted中执行一次)
|
||||||
|
initChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只创建一次图表实例
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
// 绑定点击事件(只绑定一次,永久生效)
|
||||||
|
this.myChart.getZr().on('click', (params) => {
|
||||||
|
console.log('params', params);
|
||||||
|
|
||||||
|
// 提取点击的基地名称
|
||||||
|
// const itemName = params.name;
|
||||||
|
let itemName = undefined
|
||||||
|
// 根据映射表获取对应的序号(未匹配到则返回0或其他默认值)
|
||||||
|
let pointInPixel = [params.offsetX, params.offsetY];
|
||||||
|
if (this.myChart.containPixel('grid', pointInPixel)) {
|
||||||
|
let pointInGrid = this.myChart.convertFromPixel({
|
||||||
|
seriesIndex: 0
|
||||||
|
}, pointInPixel);
|
||||||
|
let xIndex = pointInGrid[0]; //索引
|
||||||
|
let handleIndex = Number(xIndex);
|
||||||
|
let seriesObj = this.myChart.getOption(); //图表object对象
|
||||||
|
var op = this.myChart.getOption();
|
||||||
|
//获得图表中点击的列
|
||||||
|
itemName = op.xAxis[0].data[handleIndex]; //获取点击的列名
|
||||||
|
console.log(itemName, 'monthmonthmonth');
|
||||||
|
console.log(handleIndex, seriesObj);
|
||||||
|
};
|
||||||
|
const baseIndex = this.baseNameToIndexMap[itemName] || 0;
|
||||||
|
|
||||||
|
console.log(`你点击了【${itemName}】(序号:${baseIndex})`);
|
||||||
|
if (itemName === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定义resize处理函数(命名函数,方便移除)
|
||||||
|
this.resizeHandler = () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
};
|
||||||
|
// 绑定resize事件(只绑定一次)
|
||||||
|
window.addEventListener('resize', this.resizeHandler);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新图表数据(数据变化时执行)
|
||||||
|
updateChart() {
|
||||||
|
if (!this.myChart) {
|
||||||
|
return; // 实例未初始化则返回
|
||||||
|
}
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || [];
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 5,
|
||||||
|
right: 20,
|
||||||
|
left: 25,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0],
|
||||||
|
// 可选:X轴标签显示“序号+名称”(如“1 宜兴”)
|
||||||
|
// formatter: (value) => {
|
||||||
|
// const index = this.baseNameToIndexMap[value] || '';
|
||||||
|
// return index ? `${index} ${value}` : value;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries
|
||||||
|
};
|
||||||
|
|
||||||
|
// 只更新配置,不重新创建实例
|
||||||
|
this.myChart.setOption(option, true); // 第二个参数true表示清空原有配置,避免数据残留
|
||||||
|
},
|
||||||
|
|
||||||
|
// 销毁图表资源
|
||||||
|
destroyChart() {
|
||||||
|
// 移除resize事件
|
||||||
|
if (this.resizeHandler) {
|
||||||
|
window.removeEventListener('resize', this.resizeHandler);
|
||||||
|
}
|
||||||
|
// 销毁图表实例
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
this.myChart = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 380px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
console.log('chartData', this.chartData);
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
console.log('xData', xData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom:20,
|
||||||
|
right: 10,
|
||||||
|
left: 25,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
//
|
||||||
|
splitNumber: 4,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 200px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
refName: {
|
||||||
|
type: String,
|
||||||
|
default: () => 'cockpitEffChip',
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs[this.refName];
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
console.log('chartData', this.chartData);
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
console.log('xData', xData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
// axisPointer: {
|
||||||
|
// type: 'cross',
|
||||||
|
// label: {
|
||||||
|
// backgroundColor: '#6a7985'
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 20,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 5,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
// name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true, // 显示Y轴轴线(关键)
|
||||||
|
lineStyle: {
|
||||||
|
color: '#E5E6EB', // 轴线颜色(浅灰色,可自定义)
|
||||||
|
width: 1, // 轴线宽度
|
||||||
|
type: 'solid' // 实线(可选:dashed虚线、dotted点线)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 2,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
},
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,331 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="当月数据对比" icon="cockpitItemIcon" size="operatingLarge" topSize="large">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="left" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 348px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
">
|
||||||
|
集团情况
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{chartData.group.rate[0]}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.group.flags>0?'#30B590':'#FF9423'}" >{{chartData.group.diff[0]}}</span></span>
|
||||||
|
</div>
|
||||||
|
<operatingTopBar :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
<div class="right" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 1220px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<operatingBar :dateData="dateData" :chartData="chartData" @sort-change="sortChange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import operatingBar from "./operatingBar.vue";
|
||||||
|
import operatingTopBar from "./operatingTopBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, operatingBar, operatingTopBar },
|
||||||
|
props: {
|
||||||
|
monthData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
group: {
|
||||||
|
rate: 0,
|
||||||
|
diff: 0,
|
||||||
|
real: 0,
|
||||||
|
target: 0
|
||||||
|
},
|
||||||
|
factory: []
|
||||||
|
}),
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: null, // 初始化 chartData 为 null
|
||||||
|
groupData: {}, // 集团数据
|
||||||
|
factoryData: [] // 工厂数据
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
monthData: {
|
||||||
|
handler() {
|
||||||
|
this.processChartData();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
sortChange(value) {
|
||||||
|
this.$emit('sort-change', value);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 核心处理函数:在所有数据都准备好后,才组装 chartData
|
||||||
|
*/
|
||||||
|
processChartData() {
|
||||||
|
// 1. 处理集团数据 - 提取各字段到对应数组
|
||||||
|
this.groupData = this.monthData.group || { rate: 0, diff: 0, real: 0, target: 0 };
|
||||||
|
|
||||||
|
// 集团各维度数据数组(单条数据,对应凯盛新能)
|
||||||
|
const groupTarget = [this.groupData.target]; // 预算值数组
|
||||||
|
const groupDiff = [this.groupData.diff]; // 差值数组
|
||||||
|
const groupReal = [this.groupData.real]; // 实际值数组
|
||||||
|
const groupRate = [this.groupData.rate]; // 完成率数组
|
||||||
|
// 新增:集团rate对应的flag
|
||||||
|
const groupFlag = [this.groupData.rate >= 100 ? 1 : 0];
|
||||||
|
|
||||||
|
console.log('集团数据数组:', {
|
||||||
|
groupTarget,
|
||||||
|
groupDiff,
|
||||||
|
groupReal,
|
||||||
|
groupRate,
|
||||||
|
groupFlag,
|
||||||
|
rawGroupData: this.groupData
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 处理工厂数据 - 提取每个工厂的对应字段到数组
|
||||||
|
this.factoryData = this.monthData.factory || [];
|
||||||
|
// 提取工厂名称数组
|
||||||
|
const factoryNames = this.factoryData.map(item => item.title || '');
|
||||||
|
// 提取工厂各维度数据数组
|
||||||
|
const factoryBudget = this.factoryData.map(item => item.budget || 0);
|
||||||
|
const factoryReal = this.factoryData.map(item => item.real || 0);
|
||||||
|
const factoryRate = this.factoryData.map(item => item.rate || 0);
|
||||||
|
const factoryDiff = this.factoryData.map(item => item.diff || 0);
|
||||||
|
// 新增:每个工厂rate对应的flag数组
|
||||||
|
const factoryFlags = this.factoryData.map(item => item.rate >= 100 ? 1 : 0);
|
||||||
|
|
||||||
|
// 3. 组装最终的chartData(供子组件使用)
|
||||||
|
this.chartData = {
|
||||||
|
// 集团数据(对应凯盛新能)
|
||||||
|
group: {
|
||||||
|
locations: ['凯盛新能'], // 集团名称
|
||||||
|
targets: groupTarget, // 集团预算值数组
|
||||||
|
diff: groupDiff, // 集团差值数组
|
||||||
|
reals: groupReal, // 集团实际值数组
|
||||||
|
rate: groupRate, // 集团完成率数组
|
||||||
|
flags: groupFlag // 新增:集团rate对应的flag
|
||||||
|
},
|
||||||
|
// 工厂数据
|
||||||
|
factory: {
|
||||||
|
locations: factoryNames, // 工厂名称数组
|
||||||
|
targets: factoryBudget, // 工厂预算数组
|
||||||
|
reals: factoryReal, // 工厂实际值数组
|
||||||
|
rates: factoryRate, // 工厂完成率数组
|
||||||
|
diff: factoryDiff, // 工厂差值数组
|
||||||
|
flags: factoryFlags // 新增:工厂rate对应的flags数组
|
||||||
|
},
|
||||||
|
// 原始数据备份(方便后续使用)
|
||||||
|
rawData: {
|
||||||
|
group: this.groupData,
|
||||||
|
factory: this.factoryData
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('最终处理后的图表数据:', this.chartData);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||||
|
.scroll-container {
|
||||||
|
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||||
|
max-height: 210px;
|
||||||
|
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||||
|
overflow-y: auto;
|
||||||
|
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||||
|
/* Chrome/Safari */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* IE/Edge */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||||
|
.proBarInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px 27px;
|
||||||
|
/* 调整内边距,优化排版 */
|
||||||
|
margin-bottom: 10px;
|
||||||
|
/* 设备项之间的垂直间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 原有样式保留,优化细节 */
|
||||||
|
.proBarInfoEqInfo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
/* 垂直居中,避免序号/文字错位 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
width: 21px;
|
||||||
|
height: 23px;
|
||||||
|
background: rgba(0, 106, 205, 0.22);
|
||||||
|
backdrop-filter: blur(1.5px);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #68b5ff;
|
||||||
|
line-height: 23px;
|
||||||
|
/* 垂直居中文字 */
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eq-name {
|
||||||
|
margin-left: 8px;
|
||||||
|
/* 增加与序号的间距 */
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eqStatus {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitLine {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
border: 1px solid #adadad;
|
||||||
|
margin: 0 8px;
|
||||||
|
/* 优化分割线间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #00ffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoLeft {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 序号和设备名垂直居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoRight {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 状态/分割线/百分比垂直居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarWrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
/* 进度条与上方信息的间距 */
|
||||||
|
border-radius: 5px;
|
||||||
|
/* 进度条圆角,优化视觉 */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLine {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #acacac 100%);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLineTop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg,
|
||||||
|
rgba(53, 223, 247, 0) 0%,
|
||||||
|
rgba(54, 220, 246, 0.92) 92%,
|
||||||
|
#36f6e5 100%,
|
||||||
|
#37acf5 100%);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
/* 进度变化时添加过渡动画,更流畅 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图表相关样式保留 */
|
||||||
|
.chartImgBottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 45px;
|
||||||
|
left: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
left: 57px;
|
||||||
|
bottom: 42px;
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #00e8ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,329 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="累计数据对比" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="left" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 348px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
">
|
||||||
|
集团情况
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{chartData.group.rate[0]}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.group.flags>0?'#30B590':'#FF9423'}" >{{chartData.group.diff[0]}}</span></span>
|
||||||
|
</div>
|
||||||
|
<operatingTopBar :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
<div class="right" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 1220px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<operatingBar :dateData="dateData" @sort-change="sortChange" :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import operatingBar from "./operatingBar.vue";
|
||||||
|
import operatingTopBar from "./operatingTopBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, operatingBar, operatingTopBar },
|
||||||
|
props: {
|
||||||
|
salesTrendMap: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
ytdData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: null, // 初始化 chartData 为 null
|
||||||
|
groupData: {}, // 集团数据
|
||||||
|
factoryData: [] // 工厂数据
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
ytdData: {
|
||||||
|
handler() {
|
||||||
|
this.processChartData();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
sortChange(value) {
|
||||||
|
this.$emit('sort-change', value);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心处理函数:在所有数据都准备好后,才组装 chartData
|
||||||
|
*/
|
||||||
|
processChartData() {
|
||||||
|
// 1. 处理集团数据 - 提取各字段到对应数组
|
||||||
|
this.groupData = this.ytdData.group || { rate: 0, diff: 0, real: 0, target: 0 };
|
||||||
|
|
||||||
|
// 集团各维度数据数组(单条数据,对应凯盛新能)
|
||||||
|
const groupTarget = [this.groupData.target]; // 预算值数组
|
||||||
|
const groupDiff = [this.groupData.diff]; // 差值数组
|
||||||
|
const groupReal = [this.groupData.real]; // 实际值数组
|
||||||
|
const groupRate = [this.groupData.rate]; // 完成率数组
|
||||||
|
// 新增:集团rate对应的flag
|
||||||
|
const groupFlag = [this.groupData.rate >= 100 ? 1 : 0];
|
||||||
|
|
||||||
|
console.log('集团数据数组:', {
|
||||||
|
groupTarget,
|
||||||
|
groupDiff,
|
||||||
|
groupReal,
|
||||||
|
groupRate,
|
||||||
|
groupFlag,
|
||||||
|
rawGroupData: this.groupData
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 处理工厂数据 - 提取每个工厂的对应字段到数组
|
||||||
|
this.factoryData = this.ytdData.factory || [];
|
||||||
|
// 提取工厂名称数组
|
||||||
|
const factoryNames = this.factoryData.map(item => item.title || '');
|
||||||
|
// 提取工厂各维度数据数组
|
||||||
|
const factoryBudget = this.factoryData.map(item => item.budget || 0);
|
||||||
|
const factoryReal = this.factoryData.map(item => item.real || 0);
|
||||||
|
const factoryRate = this.factoryData.map(item => item.rate || 0);
|
||||||
|
const factoryDiff = this.factoryData.map(item => item.diff || 0);
|
||||||
|
// 新增:每个工厂rate对应的flag数组
|
||||||
|
const factoryFlags = this.factoryData.map(item => item.rate >= 100 ? 1 : 0);
|
||||||
|
|
||||||
|
// 3. 组装最终的chartData(供子组件使用)
|
||||||
|
this.chartData = {
|
||||||
|
// 集团数据(对应凯盛新能)
|
||||||
|
group: {
|
||||||
|
locations: ['凯盛新能'], // 集团名称
|
||||||
|
targets: groupTarget, // 集团预算值数组
|
||||||
|
diff: groupDiff, // 集团差值数组
|
||||||
|
reals: groupReal, // 集团实际值数组
|
||||||
|
rate: groupRate, // 集团完成率数组
|
||||||
|
flags: groupFlag // 新增:集团rate对应的flag
|
||||||
|
},
|
||||||
|
// 工厂数据
|
||||||
|
factory: {
|
||||||
|
locations: factoryNames, // 工厂名称数组
|
||||||
|
targets: factoryBudget, // 工厂预算数组
|
||||||
|
reals: factoryReal, // 工厂实际值数组
|
||||||
|
rates: factoryRate, // 工厂完成率数组
|
||||||
|
diff: factoryDiff, // 工厂差值数组
|
||||||
|
flags: factoryFlags // 新增:工厂rate对应的flags数组
|
||||||
|
},
|
||||||
|
// 原始数据备份(方便后续使用)
|
||||||
|
rawData: {
|
||||||
|
group: this.groupData,
|
||||||
|
factory: this.factoryData
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('最终处理后的图表数据:', this.chartData);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||||
|
.scroll-container {
|
||||||
|
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||||
|
max-height: 210px;
|
||||||
|
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||||
|
overflow-y: auto;
|
||||||
|
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||||
|
/* Chrome/Safari */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* IE/Edge */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||||
|
.proBarInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px 27px;
|
||||||
|
/* 调整内边距,优化排版 */
|
||||||
|
margin-bottom: 10px;
|
||||||
|
/* 设备项之间的垂直间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 原有样式保留,优化细节 */
|
||||||
|
.proBarInfoEqInfo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
/* 垂直居中,避免序号/文字错位 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
width: 21px;
|
||||||
|
height: 23px;
|
||||||
|
background: rgba(0, 106, 205, 0.22);
|
||||||
|
backdrop-filter: blur(1.5px);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #68b5ff;
|
||||||
|
line-height: 23px;
|
||||||
|
/* 垂直居中文字 */
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eq-name {
|
||||||
|
margin-left: 8px;
|
||||||
|
/* 增加与序号的间距 */
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eqStatus {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitLine {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
border: 1px solid #adadad;
|
||||||
|
margin: 0 8px;
|
||||||
|
/* 优化分割线间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #00ffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoLeft {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 序号和设备名垂直居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoRight {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 状态/分割线/百分比垂直居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarWrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
/* 进度条与上方信息的间距 */
|
||||||
|
border-radius: 5px;
|
||||||
|
/* 进度条圆角,优化视觉 */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLine {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #acacac 100%);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLineTop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(
|
||||||
|
65deg,
|
||||||
|
rgba(53, 223, 247, 0) 0%,
|
||||||
|
rgba(54, 220, 246, 0.92) 92%,
|
||||||
|
#36f6e5 100%,
|
||||||
|
#37acf5 100%
|
||||||
|
);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
/* 进度变化时添加过渡动画,更流畅 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图表相关样式保留 */
|
||||||
|
.chartImgBottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 45px;
|
||||||
|
left: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
left: 57px;
|
||||||
|
bottom: 42px;
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #00e8ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
337
src/views/home/accountsReceivableComponents/operatingTopBar.vue
Normal file
337
src/views/home/accountsReceivableComponents/operatingTopBar.vue
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<div class="lineBottom" style="height: 320px; width: 100%">
|
||||||
|
<operatingLineBar :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSaleGroup.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentDataSource() {
|
||||||
|
console.log('yyyy', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData.group
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
console.log('this.chartData', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData.group.locations
|
||||||
|
},
|
||||||
|
// 根据按钮切换生成对应的 chartData
|
||||||
|
chartD() {
|
||||||
|
// 销量场景数据
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
const diff = this.currentDataSource.diff[0]
|
||||||
|
const rate = this.currentDataSource.rate[0]
|
||||||
|
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.targets // 目标销量(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图,含达标状态)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||||
|
const safeFlag = data.flags;
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals // 实际销量(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 毛利率场景数据
|
||||||
|
const grossProfitData = {
|
||||||
|
series: [
|
||||||
|
// 1. 完成率(折线图)
|
||||||
|
{
|
||||||
|
name: '完成率',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
lineStyle: { color: 'rgba(40, 138, 255, .5)', width: 2 },
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderColor: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
radius: 4
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.2,
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||||
|
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: [106.7, 96.9, 106.5, 106.1, 93.8, 105.9], // 毛利率完成率(%)
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 2. 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: [30, 32, 31, 33, 32, 34] // 目标毛利率(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const safeFlag = [1, 0, 1, 1, 0, 1]; // 达标状态
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: [32, 31, 33, 35, 30, 36] // 实际毛利率(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据按钮状态返回对应数据
|
||||||
|
return this.activeButton === 0 ? salesData : grossProfitData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end; // 标题左、右侧容器右,整体两端对齐
|
||||||
|
align-items: center; // 垂直居中,避免上下错位
|
||||||
|
gap: 16px; // 标题与右侧容器的最小间距,防止拥挤
|
||||||
|
width: 100%; // 确保占满父容器,实现两端对齐
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 标题固定在左侧,不挤压右侧空间
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; // 图例和按钮组垂直居中
|
||||||
|
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||||
|
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 图例:在右侧容器内横向排列
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px; // 图例项之间间距,避免重叠
|
||||||
|
align-items: center;
|
||||||
|
// 移除原margin-left,避免位置偏移
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap; // 防止图例文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图例颜色
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
width: 283px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
border-radius: 12px;
|
||||||
|
// 移除原margin-right,由右侧容器统一控制
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0b58ff;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 8px; // 确保文字间距生效
|
||||||
|
padding-left: 8px; // 抵消letter-spacing导致的文字左偏
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button.active {
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
background: #3071ff;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<template>
|
||||||
|
<el-form ref="form" :rules="rules" label-width="110px" :model="form">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="客户名称" prop="name">
|
||||||
|
<el-input v-model="form.name"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="客户编码" prop="code">
|
||||||
|
<el-input v-model="form.code"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="所属基地" prop="factoryList">
|
||||||
|
<el-select v-model="form.factoryList" placeholder="请选择" style="width: 100%;" multiple>
|
||||||
|
<el-option v-for='item in facList' :key='item.value' :label="item.name" :value= 'item.value' ></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { addCustomerConfig, updateCustomerConfig, getCustomerConfig } from '@/api/basicInfoConfig'
|
||||||
|
export default {
|
||||||
|
name: 'CustomerInfoAdd',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
code: '',
|
||||||
|
factoryList:'',
|
||||||
|
},
|
||||||
|
isEdit: false, //是否是编辑
|
||||||
|
rules: {
|
||||||
|
name: [{ required: true, message: '请输入客户名称', trigger: 'blur' }],
|
||||||
|
code: [{ required: true, message: '请输入客户编码', trigger: 'blur' }],
|
||||||
|
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: {
|
||||||
|
init(id) {
|
||||||
|
if (id) {
|
||||||
|
this.isEdit = true
|
||||||
|
this.form.id = id
|
||||||
|
getCustomerConfig({id}).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.form = res.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitForm() {
|
||||||
|
this.$refs['form'].validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
if (this.isEdit) {
|
||||||
|
//编辑
|
||||||
|
updateCustomerConfig({ ...this.form }).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$modal.msgSuccess("操作成功");
|
||||||
|
this.$emit('successSubmit')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
addCustomerConfig({ ...this.form }).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$modal.msgSuccess("操作成功");
|
||||||
|
this.$emit('successSubmit')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
formClear() {
|
||||||
|
this.$refs.form.resetFields()
|
||||||
|
this.isEdit = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
115
src/views/home/basicInfoConfiguration/components/groupKeyAdd.vue
Normal file
115
src/views/home/basicInfoConfiguration/components/groupKeyAdd.vue
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<template>
|
||||||
|
<el-form ref="form" :rules="rules" label-width="120px" :model="form">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="重点工作" prop="importantWork">
|
||||||
|
<el-input v-model="form.importantWork"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="单位" prop="unit">
|
||||||
|
<el-select v-model="form.unit" placeholder="请选择" style="width: 100%;">
|
||||||
|
<el-option v-for='item in unitList' :key='item.value' :label="item.label" :value= 'item.value' ></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="所属年份" prop="time">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="form.time"
|
||||||
|
type="year"
|
||||||
|
placeholder="选择年"
|
||||||
|
style="width: 100%;">
|
||||||
|
</el-date-picker>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="累计值计算方式" prop="calculateMethod">
|
||||||
|
<el-select v-model="form.calculateMethod" placeholder="请选择" style="width: 100%;">
|
||||||
|
<el-option v-for='item in calculateMethodList' :key='item.value' :label="item.label" :value= 'item.value' ></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="数值目标属性" prop="targetProperty">
|
||||||
|
<el-select v-model="form.targetProperty" placeholder="请选择" style="width: 100%;">
|
||||||
|
<el-option v-for='item in targetPropertyList' :key='item.value' :label="item.label" :value= 'item.value' ></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { addImportantWorkConfig, updateImportantWorkConfig, getImportantWorkConfig } from '@/api/basicInfoConfig'
|
||||||
|
import {getDictDatas } from '@/utils/dict'
|
||||||
|
import moment from 'moment';
|
||||||
|
export default {
|
||||||
|
name: 'groupKeyAdd',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: {
|
||||||
|
id: '',
|
||||||
|
importantWork: '',
|
||||||
|
unit: '',
|
||||||
|
time:'',
|
||||||
|
calculateMethod:'',
|
||||||
|
targetProperty:'',
|
||||||
|
},
|
||||||
|
isEdit: false, //是否是编辑
|
||||||
|
rules: {
|
||||||
|
importantWork: [{ required: true, message: '请输入重点工作', trigger: 'blur' }],
|
||||||
|
unit: [{ required: true, message: '请选择单位', trigger: 'change' }],
|
||||||
|
time: [{ required: true, message: '请选择所属年份', trigger: 'change' }],
|
||||||
|
calculateMethod: [{ required: true, message: '请选择累计值计算方式', trigger: 'change' }],
|
||||||
|
targetProperty: [{ required: true, message: '请选择数值目标属性', trigger: 'change' }],
|
||||||
|
},
|
||||||
|
unitList:getDictDatas('lb_dw'),
|
||||||
|
calculateMethodList:getDictDatas('important_work_method'),
|
||||||
|
targetPropertyList:getDictDatas('target_property')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
init(id) {
|
||||||
|
if (id) {
|
||||||
|
this.isEdit = true
|
||||||
|
this.form.id = id
|
||||||
|
getImportantWorkConfig({id}).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.form = res.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitForm() {
|
||||||
|
this.$refs['form'].validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
this.form.time = moment(this.form.time).endOf('year').endOf('month').endOf('day').unix() * 1000
|
||||||
|
if (this.isEdit) {
|
||||||
|
//编辑
|
||||||
|
updateImportantWorkConfig({ ...this.form }).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$modal.msgSuccess("操作成功");
|
||||||
|
this.$emit('successSubmit')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
addImportantWorkConfig({ ...this.form }).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$modal.msgSuccess("操作成功");
|
||||||
|
this.$emit('successSubmit')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
formClear() {
|
||||||
|
this.$refs.form.resetFields()
|
||||||
|
this.isEdit = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<template>
|
||||||
|
<el-form ref="form" :rules="rules" label-width="110px" :model="form">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="产品名称" prop="product">
|
||||||
|
<el-input v-model="form.product"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="规格" prop="spec">
|
||||||
|
<el-input v-model="form.spec"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="工艺" prop="process">
|
||||||
|
<el-select v-model="form.process" placeholder="请选择" style="width: 100%;">
|
||||||
|
<el-option v-for='item in processList' :key='item.value' :label="item.label" :value= 'item.value' ></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { addProductConfig, updateProductConfig, getProductConfig } from '@/api/basicInfoConfig'
|
||||||
|
import {getDictDatas } from '@/utils/dict'
|
||||||
|
export default {
|
||||||
|
name: 'ProductInfoAdd',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: {
|
||||||
|
id: '',
|
||||||
|
product: '',
|
||||||
|
spec: '',
|
||||||
|
process:'',
|
||||||
|
},
|
||||||
|
isEdit: false, //是否是编辑
|
||||||
|
rules: {
|
||||||
|
product: [{ required: true, message: '请输入产品名称', trigger: 'blur' }]
|
||||||
|
},
|
||||||
|
processList:getDictDatas('process')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
init(id) {
|
||||||
|
if (id) {
|
||||||
|
this.isEdit = true
|
||||||
|
this.form.id = id
|
||||||
|
getProductConfig({id}).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.form = res.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
this.form.id = ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitForm() {
|
||||||
|
this.$refs['form'].validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
if (this.isEdit) {
|
||||||
|
//编辑
|
||||||
|
updateProductConfig({ ...this.form }).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$modal.msgSuccess("操作成功");
|
||||||
|
this.$emit('successSubmit')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
addProductConfig({ ...this.form }).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$modal.msgSuccess("操作成功");
|
||||||
|
this.$emit('successSubmit')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
formClear() {
|
||||||
|
this.$refs.form.resetFields()
|
||||||
|
this.isEdit = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<search-bar :formConfigs="formConfig" ref="searchBarForm" @headBtnClick="buttonClick" />
|
||||||
|
<base-table v-if="tableData.length" class="right-aside" :table-props="tableProps"
|
||||||
|
:page="listQuery.pageNo" :limit="listQuery.pageSize" :table-data="tableData">
|
||||||
|
<method-btn v-if="tableBtn.length" slot="handleBtn" :width="200" label="操作" :method-list="tableBtn"
|
||||||
|
@clickBtn="handleClick" />
|
||||||
|
</base-table>
|
||||||
|
<pagination :limit.sync="listQuery.pageSize" :page.sync="listQuery.pageNo" :total="total" @pagination="getDataList" :background="true" />
|
||||||
|
<!-- 新增 -->
|
||||||
|
<base-dialog
|
||||||
|
:dialogTitle="addOrEditTitle"
|
||||||
|
:dialogVisible="centervisible"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@confirm="handleConfirm"
|
||||||
|
:before-close="handleCancel"
|
||||||
|
width="50%">
|
||||||
|
<customer-info-add ref="customerList" @successSubmit="successSubmit" />
|
||||||
|
</base-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import CustomerInfoAdd from './components/customerInfoAdd.vue';
|
||||||
|
import { getCustomerConfigPage,delCustomerConfig } from '@/api/basicInfoConfig';
|
||||||
|
const tableProps = [
|
||||||
|
{
|
||||||
|
prop: 'name',
|
||||||
|
label: '客户名称'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'code',
|
||||||
|
label: '客户编码'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'factoryNameList',
|
||||||
|
label: '所属基地',
|
||||||
|
filter: (val) => val ? val.join(',') :'-'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export default {
|
||||||
|
name: 'CustomerInfoConfiguration',
|
||||||
|
components:{CustomerInfoAdd},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
formConfig: [
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
label: '所属基地',
|
||||||
|
selectOptions: [
|
||||||
|
{ id: 2, name: '桐城' },
|
||||||
|
{ id: 3, name: '自贡' },
|
||||||
|
{ id: 5, name: '合肥' },
|
||||||
|
{ id: 6, name: '宿迁' },
|
||||||
|
{ id: 7, name: '宜兴' },
|
||||||
|
{ id: 8, name: '漳州' },
|
||||||
|
{ id: 9, name: '洛阳' },
|
||||||
|
{ id: 10, name: '秦皇岛' },
|
||||||
|
],
|
||||||
|
param: 'factory',
|
||||||
|
onchange: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '客户名称',
|
||||||
|
placeholder: '客户名称',
|
||||||
|
param: 'name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '客户编码',
|
||||||
|
placeholder: '客户编码',
|
||||||
|
param: 'code'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
btnName: '查询',
|
||||||
|
name: 'search',
|
||||||
|
color: 'primary',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
btnName: '新增',
|
||||||
|
name: 'add',
|
||||||
|
color: 'success',
|
||||||
|
plain: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
listQuery:{
|
||||||
|
factory:'',
|
||||||
|
name:'',
|
||||||
|
code:'',
|
||||||
|
pageSize:20,
|
||||||
|
pageNo:1
|
||||||
|
},
|
||||||
|
total:0,
|
||||||
|
tableData:[],
|
||||||
|
tableProps,
|
||||||
|
tableBtn:[
|
||||||
|
{
|
||||||
|
type: 'edit',
|
||||||
|
btnName: '编辑',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'delete',
|
||||||
|
btnName: '删除',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
addOrEditTitle: '',
|
||||||
|
centervisible: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getDataList();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
buttonClick(val) {
|
||||||
|
console.log(val)
|
||||||
|
switch (val.btnName) {
|
||||||
|
case 'search':
|
||||||
|
this.listQuery.name = val.name
|
||||||
|
this.listQuery.code = val.code
|
||||||
|
this.listQuery.factory = val.factory
|
||||||
|
this.listQuery.pageNo = 1;
|
||||||
|
this.getDataList();
|
||||||
|
break;
|
||||||
|
case 'add':
|
||||||
|
this.addOrEditTitle = '新增';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.customerList.init();
|
||||||
|
});
|
||||||
|
this.centervisible = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getDataList() {
|
||||||
|
getCustomerConfigPage({...this.listQuery}).then(res => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.tableData = res.data.list;
|
||||||
|
this.total = res.data.total;
|
||||||
|
}else{
|
||||||
|
this.tableData = [];
|
||||||
|
this.total = 0;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleClick(val) {
|
||||||
|
switch (val.type) {
|
||||||
|
case 'edit':
|
||||||
|
this.addOrEditTitle = '编辑';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.customerList.init(val.data.id);
|
||||||
|
});
|
||||||
|
this.centervisible = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.handleDelete(val.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleCancel() {
|
||||||
|
this.$refs.customerList.formClear();
|
||||||
|
this.centervisible = false;
|
||||||
|
this.addOrEditTitle = '';
|
||||||
|
},
|
||||||
|
handleConfirm() {
|
||||||
|
this.$refs.customerList.submitForm();
|
||||||
|
},
|
||||||
|
successSubmit() {
|
||||||
|
this.handleCancel();
|
||||||
|
this.getDataList();
|
||||||
|
},
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
handleDelete(row) {
|
||||||
|
let _this = this;
|
||||||
|
_this.$modal.confirm('确定删除客户"' + row.name + '吗?').then(function() {
|
||||||
|
delCustomerConfig({id:row.id}).then(response => {
|
||||||
|
_this.$modal.msgSuccess("删除成功");
|
||||||
|
_this.getDataList();
|
||||||
|
})
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<search-bar :formConfigs="formConfig" ref="searchBarForm" @headBtnClick="buttonClick" />
|
||||||
|
<base-table v-if="tableData.length" class="right-aside" :table-props="tableProps"
|
||||||
|
:page="listQuery.pageNo" :limit="listQuery.pageSize" :table-data="tableData">
|
||||||
|
<method-btn v-if="tableBtn.length" slot="handleBtn" :width="200" label="操作" :method-list="tableBtn"
|
||||||
|
@clickBtn="handleClick" />
|
||||||
|
</base-table>
|
||||||
|
<pagination :limit.sync="listQuery.pageSize" :page.sync="listQuery.pageNo" :total="total" @pagination="getDataList" :background="true" />
|
||||||
|
<!-- 新增 -->
|
||||||
|
<base-dialog
|
||||||
|
:dialogTitle="addOrEditTitle"
|
||||||
|
:dialogVisible="centervisible"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@confirm="handleConfirm"
|
||||||
|
:before-close="handleCancel"
|
||||||
|
width="50%">
|
||||||
|
<group-key-add ref="groupKey" @successSubmit="successSubmit" />
|
||||||
|
</base-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import GroupKeyAdd from './components/groupKeyAdd.vue';
|
||||||
|
import { getImportantWorkConfigPage,delImportantWorkConfig } from '@/api/basicInfoConfig';
|
||||||
|
import { publicFormatter } from '@/utils/dict';
|
||||||
|
import moment from 'moment';
|
||||||
|
const tableProps = [
|
||||||
|
{
|
||||||
|
prop: 'importantWork',
|
||||||
|
label: '重点工作'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'unit',
|
||||||
|
label: '单位',
|
||||||
|
filter: publicFormatter('lb_dw')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'time',
|
||||||
|
label: '所属年份',
|
||||||
|
filter: (val) => val ? moment(val).format('YYYY') : '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'calculateMethod',
|
||||||
|
label: '累计值计算方式',
|
||||||
|
filter: publicFormatter('important_work_method')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'targetProperty',
|
||||||
|
label: '数值目标属性',
|
||||||
|
filter: publicFormatter('target_property')
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export default {
|
||||||
|
name: 'GroupKeyTaskConfiguration',
|
||||||
|
components:{GroupKeyAdd},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
formConfig: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '重点工作',
|
||||||
|
placeholder: '重点工作',
|
||||||
|
param: 'importantWork'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'datePicker',
|
||||||
|
label: '所属年份',
|
||||||
|
dateType: 'year',
|
||||||
|
format: 'yyyy',
|
||||||
|
valueFormat: 'yyyy',
|
||||||
|
placeholder: '所属年份',
|
||||||
|
param: 'time',
|
||||||
|
width: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
btnName: '查询',
|
||||||
|
name: 'search',
|
||||||
|
color: 'primary',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
btnName: '新增',
|
||||||
|
name: 'add',
|
||||||
|
color: 'success',
|
||||||
|
plain: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
listQuery:{
|
||||||
|
importantWork:'',
|
||||||
|
time:'',
|
||||||
|
pageSize:20,
|
||||||
|
pageNo:1
|
||||||
|
},
|
||||||
|
total:0,
|
||||||
|
tableData:[],
|
||||||
|
tableProps,
|
||||||
|
tableBtn:[
|
||||||
|
{
|
||||||
|
type: 'edit',
|
||||||
|
btnName: '编辑',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'delete',
|
||||||
|
btnName: '删除',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
addOrEditTitle: '',
|
||||||
|
centervisible: false,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$refs.searchBarForm.formInline.time = moment().endOf('year').endOf('month').endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
this.listQuery.time = moment().endOf('year').endOf('month').endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
this.getDataList();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
buttonClick(val) {
|
||||||
|
switch (val.btnName) {
|
||||||
|
case 'search':
|
||||||
|
this.listQuery.importantWork = val.importantWork;
|
||||||
|
this.listQuery.time = val.time ? moment(val.time).endOf('year').endOf('month').endOf('day').format('YYYY-MM-DD HH:mm:ss') : '';
|
||||||
|
this.listQuery.pageNo = 1;
|
||||||
|
this.getDataList();
|
||||||
|
break;
|
||||||
|
case 'add':
|
||||||
|
this.addOrEditTitle = '新增';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.groupKey.init();
|
||||||
|
});
|
||||||
|
this.centervisible = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getDataList() {
|
||||||
|
getImportantWorkConfigPage({...this.listQuery}).then(res => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.tableData = res.data.list;
|
||||||
|
this.total = res.data.total;
|
||||||
|
}else{
|
||||||
|
this.tableData = [];
|
||||||
|
this.total = 0;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleClick(val) {
|
||||||
|
switch (val.type) {
|
||||||
|
case 'edit':
|
||||||
|
this.addOrEditTitle = '编辑';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.groupKey.init(val.data.id);
|
||||||
|
});
|
||||||
|
this.centervisible = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.handleDelete(val.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleCancel() {
|
||||||
|
this.$refs.groupKey.formClear();
|
||||||
|
this.centervisible = false;
|
||||||
|
this.addOrEditTitle = '';
|
||||||
|
},
|
||||||
|
handleConfirm() {
|
||||||
|
this.$refs.groupKey.submitForm();
|
||||||
|
},
|
||||||
|
successSubmit() {
|
||||||
|
this.handleCancel();
|
||||||
|
this.getDataList();
|
||||||
|
},
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
handleDelete(row) {
|
||||||
|
let _this = this;
|
||||||
|
_this.$modal.confirm('确定删除重点工作"' + row.importantWork + '吗?').then(function() {
|
||||||
|
delImportantWorkConfig({id:row.id}).then(response => {
|
||||||
|
_this.$modal.msgSuccess("删除成功");
|
||||||
|
_this.getDataList();
|
||||||
|
|
||||||
|
})
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<search-bar :formConfigs="formConfig" ref="searchBarForm" @headBtnClick="buttonClick" />
|
||||||
|
<base-table v-if="tableData.length" class="right-aside" :table-props="tableProps"
|
||||||
|
:page="listQuery.pageNo" :limit="listQuery.pageSize" :table-data="tableData">
|
||||||
|
<method-btn v-if="tableBtn.length" slot="handleBtn" :width="200" label="操作" :method-list="tableBtn"
|
||||||
|
@clickBtn="handleClick" />
|
||||||
|
</base-table>
|
||||||
|
<pagination :limit.sync="listQuery.pageSize" :page.sync="listQuery.pageNo" :total="total" @pagination="getDataList" :background="true" />
|
||||||
|
<!-- 新增 -->
|
||||||
|
<base-dialog
|
||||||
|
:dialogTitle="addOrEditTitle"
|
||||||
|
:dialogVisible="centervisible"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@confirm="handleConfirm"
|
||||||
|
:before-close="handleCancel"
|
||||||
|
width="50%">
|
||||||
|
<product-info-add ref="prodectInfo" @successSubmit="successSubmit" />
|
||||||
|
</base-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ProductInfoAdd from './components/productInfoAdd.vue';
|
||||||
|
import { getProductConfigPage,delProductConfig } from '@/api/basicInfoConfig';
|
||||||
|
import { publicFormatter } from '@/utils/dict';
|
||||||
|
const tableProps = [
|
||||||
|
{
|
||||||
|
prop: 'product',
|
||||||
|
label: '产品名称'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'process',
|
||||||
|
label: '工艺',
|
||||||
|
filter: publicFormatter('process')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'spec',
|
||||||
|
label: '规格'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export default {
|
||||||
|
name: 'ProductInfoConfiguration',
|
||||||
|
components:{ProductInfoAdd},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
formConfig: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '产品名称',
|
||||||
|
placeholder: '产品名称',
|
||||||
|
param: 'product'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
btnName: '查询',
|
||||||
|
name: 'search',
|
||||||
|
color: 'primary',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
btnName: '新增',
|
||||||
|
name: 'add',
|
||||||
|
color: 'success',
|
||||||
|
plain: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
listQuery:{
|
||||||
|
product:'',
|
||||||
|
pageSize:20,
|
||||||
|
pageNo:1
|
||||||
|
},
|
||||||
|
total:0,
|
||||||
|
tableData:[],
|
||||||
|
tableProps,
|
||||||
|
tableBtn:[
|
||||||
|
{
|
||||||
|
type: 'edit',
|
||||||
|
btnName: '编辑',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'delete',
|
||||||
|
btnName: '删除',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
addOrEditTitle: '',
|
||||||
|
centervisible: false,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getDataList();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
buttonClick(val) {
|
||||||
|
switch (val.btnName) {
|
||||||
|
case 'search':
|
||||||
|
this.listQuery.product = val.product;
|
||||||
|
this.listQuery.pageNo = 1;
|
||||||
|
this.getDataList();
|
||||||
|
break;
|
||||||
|
case 'add':
|
||||||
|
this.addOrEditTitle = '新增';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.prodectInfo.init();
|
||||||
|
});
|
||||||
|
this.centervisible = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getDataList() {
|
||||||
|
getProductConfigPage({
|
||||||
|
...this.listQuery
|
||||||
|
}).then(res => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.tableData = res.data.list;
|
||||||
|
this.total = res.data.total;
|
||||||
|
}else{
|
||||||
|
this.tableData = [];
|
||||||
|
this.total = 0;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleClick(val) {
|
||||||
|
switch (val.type) {
|
||||||
|
case 'edit':
|
||||||
|
this.addOrEditTitle = '编辑';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.prodectInfo.init(val.data.id);
|
||||||
|
});
|
||||||
|
this.centervisible = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.handleDelete(val.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleCancel() {
|
||||||
|
this.$refs.prodectInfo.formClear();
|
||||||
|
this.centervisible = false;
|
||||||
|
this.addOrEditTitle = '';
|
||||||
|
},
|
||||||
|
handleConfirm() {
|
||||||
|
this.$refs.prodectInfo.submitForm();
|
||||||
|
},
|
||||||
|
successSubmit() {
|
||||||
|
this.handleCancel();
|
||||||
|
this.getDataList();
|
||||||
|
},
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
handleDelete(row) {
|
||||||
|
let _this = this;
|
||||||
|
_this.$modal.confirm('确定删除产品"' + row.product + '吗?').then(function() {
|
||||||
|
delProductConfig({id:row.id}).then(response => {
|
||||||
|
_this.getDataList();
|
||||||
|
_this.$modal.msgSuccess("删除成功");
|
||||||
|
})
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -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'/>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,8 +75,8 @@
|
|||||||
: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>
|
||||||
@@ -88,9 +90,15 @@
|
|||||||
</el-radio-group>
|
</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 +107,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 +141,11 @@ export default {
|
|||||||
open: false,
|
open: false,
|
||||||
// 弹出层标题
|
// 弹出层标题
|
||||||
title: "预算填报导入",
|
title: "预算填报导入",
|
||||||
// 是否禁用上传
|
|
||||||
isUploading: false,
|
|
||||||
fileList:[],
|
fileList:[],
|
||||||
currentFile:null,
|
currentFile:null,
|
||||||
timeDim: 2
|
timeDim: 2,
|
||||||
|
// HTTP 上传中(点击确定后 axios 上传,展示不确定进度条)
|
||||||
|
httpUploading: false
|
||||||
},
|
},
|
||||||
getDataList: null, // 动态切换的查询接口
|
getDataList: null, // 动态切换的查询接口
|
||||||
updateData: null, // 动态切换的更新接口
|
updateData: null, // 动态切换的更新接口
|
||||||
@@ -359,6 +368,8 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleLevelChange(id) {
|
handleLevelChange(id) {
|
||||||
|
this.form.levelId = id;
|
||||||
|
this.getDataPage()
|
||||||
this.$emit('updateLevel', id)
|
this.$emit('updateLevel', id)
|
||||||
},
|
},
|
||||||
// 表格单元格数据变更回调
|
// 表格单元格数据变更回调
|
||||||
@@ -388,7 +399,7 @@ export default {
|
|||||||
console.log('当前编辑状态:', isEdit, '当前时间维度:', this.timeType);
|
console.log('当前编辑状态:', isEdit, '当前时间维度:', this.timeType);
|
||||||
// 基础表格列配置(只读模式使用)
|
// 基础表格列配置(只读模式使用)
|
||||||
const baseTableProps = [
|
const baseTableProps = [
|
||||||
// { prop: 'type', label: '指标类型', align: 'center' },
|
{ prop: 'type', label: '指标类型', align: 'center' },
|
||||||
{ prop: 'name', label: '指标名称', align: 'center' },
|
{ prop: 'name', label: '指标名称', align: 'center' },
|
||||||
{ prop: 'unit', label: '单位', align: 'center', filter: publicFormatter('lb_dw') },
|
{ prop: 'unit', label: '单位', align: 'center', filter: publicFormatter('lb_dw') },
|
||||||
{ prop: 'target', label: '预算值', align: 'center' },
|
{ prop: 'target', label: '预算值', align: 'center' },
|
||||||
@@ -455,15 +466,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,10 +520,14 @@ export default {
|
|||||||
importTemplate() {},
|
importTemplate() {},
|
||||||
// 提交上传文件
|
// 提交上传文件
|
||||||
async submitFileForm() {
|
async submitFileForm() {
|
||||||
try {
|
|
||||||
if (!this.upload.currentFile) {
|
if (!this.upload.currentFile) {
|
||||||
return this.$message.error('请先选择要上传的文件!')
|
return this.$message.error('请先选择要上传的文件!')
|
||||||
}
|
}
|
||||||
|
if (this.upload.httpUploading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.upload.httpUploading = true
|
||||||
|
try {
|
||||||
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.upload.timeDim) // 年月维度字段
|
||||||
@@ -489,7 +542,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) {
|
||||||
@@ -499,7 +552,6 @@ export default {
|
|||||||
this.upload.timeDim = 2
|
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 +562,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 {
|
||||||
// 内联样式已优化行间距,此处可留空或补充
|
// 内联样式已优化行间距,此处可留空或补充
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ export default {
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 5%;
|
right: 5%;
|
||||||
top: 10%;
|
top: 25px;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
|
|||||||
@@ -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)";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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" 字段
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<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>
|
||||||
@@ -11,8 +11,8 @@ export default {
|
|||||||
// 接收父组件传递的 series 数据
|
// 接收父组件传递的 series 数据
|
||||||
props: {
|
props: {
|
||||||
chartSeries: {
|
chartSeries: {
|
||||||
type: Array,
|
type: Object,
|
||||||
default: () => [] // 默认空数组,避免报错
|
default: () => {{}} // 默认空数组,避免报错
|
||||||
},
|
},
|
||||||
xAxisData: {
|
xAxisData: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@@ -68,7 +68,6 @@ export default {
|
|||||||
// 单独提取更新图表的方法,方便复用
|
// 单独提取更新图表的方法,方便复用
|
||||||
updateChart() {
|
updateChart() {
|
||||||
if (!this.myChart) return;
|
if (!this.myChart) return;
|
||||||
console.log('this.chartSeries', this.chartSeries,this.xAxisData);
|
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
@@ -78,7 +77,7 @@ export default {
|
|||||||
label: { backgroundColor: '#6a7985' }
|
label: { backgroundColor: '#6a7985' }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
grid: { top: 35, bottom: 20, right: 25, left: 70 },
|
grid: { top: 35, bottom: 3, right: 15, left: 18, containLabel: true},
|
||||||
xAxis: [
|
xAxis: [
|
||||||
{
|
{
|
||||||
type: 'category',
|
type: 'category',
|
||||||
@@ -108,19 +107,14 @@ export default {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
yAxis: {
|
yAxis: {
|
||||||
// type: 'value',
|
name: this.chartSeries.unit,
|
||||||
name: '元/㎡',
|
|
||||||
// nameLocation:'center',
|
|
||||||
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' },
|
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' },
|
||||||
// min: () => 0,
|
|
||||||
// max: (value) => Math.ceil(value.max),
|
|
||||||
|
|
||||||
axisTick: { show: false },
|
axisTick: { show: false },
|
||||||
axisLabel: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 12 },
|
axisLabel: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 12 },
|
||||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }
|
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }
|
||||||
},
|
},
|
||||||
series: this.chartSeries // 使用父组件传递的 series 数据
|
series: this.chartSeries.series // 使用父组件传递的 series 数据
|
||||||
};
|
};
|
||||||
|
|
||||||
this.myChart.setOption(option, true); // 第二个参数 true 表示替换现有配置
|
this.myChart.setOption(option, true); // 第二个参数 true 表示替换现有配置
|
||||||
|
|||||||
@@ -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 // 本月值
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,29 +1,26 @@
|
|||||||
<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 id="productionStatusChart" style="height: 200px; margin-top: 12px;" v-if="showChart"></div> -->
|
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
@@ -52,13 +49,16 @@ export default {
|
|||||||
dateData: {
|
dateData: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
activeTab: 'purchase', // 激活的标签:purchase=采购,inventory=存货
|
activeTab: 'purchase', // 激活的标签:purchase=采购,inventory=存货
|
||||||
showChart: true, // 控制图表是否显示
|
showChart: true, // 控制图表是否显示
|
||||||
chart: null // 图表实例
|
chart: null, // 图表实例
|
||||||
|
financeData:{},
|
||||||
|
costData:{},
|
||||||
|
currentTap: 'month'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -71,8 +71,29 @@ export default {
|
|||||||
this.updateChart()
|
this.updateChart()
|
||||||
},
|
},
|
||||||
deep: true
|
deep: true
|
||||||
|
},
|
||||||
|
finance: {
|
||||||
|
handler() {
|
||||||
|
if(this.currentTap === 'month') {
|
||||||
|
this.financeData = this.finance.mon
|
||||||
|
}else{
|
||||||
|
this.financeData = this.finance.total
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
cost: {
|
||||||
|
handler() {
|
||||||
|
if(this.currentTap === 'month') {
|
||||||
|
this.costData = this.cost.mon
|
||||||
|
}else{
|
||||||
|
this.costData = this.cost.total
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// 初始化图表
|
// 初始化图表
|
||||||
this.$nextTick(() => this.updateChart())
|
this.$nextTick(() => this.updateChart())
|
||||||
@@ -85,6 +106,17 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
tabChange(val) {
|
||||||
|
if(val === 'month') {
|
||||||
|
this.currentTap = 'month'
|
||||||
|
this.financeData = this.finance.mon
|
||||||
|
this.costData = this.cost.mon
|
||||||
|
}else{
|
||||||
|
this.currentTap = 'total'
|
||||||
|
this.financeData = this.finance.total
|
||||||
|
this.costData = this.cost.total
|
||||||
|
}
|
||||||
|
},
|
||||||
// 处理标题点击切换标签
|
// 处理标题点击切换标签
|
||||||
handleTabSwitch(tabType) {
|
handleTabSwitch(tabType) {
|
||||||
this.activeTab = tabType // tabType由Container组件传递:'purchase'或'inventory'
|
this.activeTab = tabType // tabType由Container组件传递:'purchase'或'inventory'
|
||||||
|
|||||||
@@ -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 class="lineBottom" style="height: 210px; width: 100%">
|
</div>
|
||||||
|
<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 {
|
||||||
|
|||||||
@@ -16,7 +16,17 @@
|
|||||||
<span class="title-text">{{ nameTwo }}</span>
|
<span class="title-text">{{ nameTwo }}</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<span class="change-text" :class="{ 'change-text-right': isLeftTransparent }">点击切换</span>
|
<!-- <span class="change-text" :class="{ 'change-text-right': isLeftTransparent }">点击切换</span> -->
|
||||||
|
<div class="tab-group">
|
||||||
|
<!-- 月度Tab:点击切换状态,动态绑定样式 -->
|
||||||
|
<div class="tab-item" :class="{ active: activeTab === 'month' }" @click="handleTabClick('month')">
|
||||||
|
月度
|
||||||
|
</div>
|
||||||
|
<!-- 累计Tab:点击切换状态,动态绑定样式 -->
|
||||||
|
<div class="tab-item" :class="{ active: activeTab === 'total' }" @click="handleTabClick('total')">
|
||||||
|
累计
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="container-body">
|
<div class="container-body">
|
||||||
@@ -40,10 +50,18 @@ export default {
|
|||||||
return {
|
return {
|
||||||
// 初始状态:左侧不透明(1),右侧透明(0.3)
|
// 初始状态:左侧不透明(1),右侧透明(0.3)
|
||||||
isLeftTransparent: true, // 左侧透明度状态(true=1,false=0.3)
|
isLeftTransparent: true, // 左侧透明度状态(true=1,false=0.3)
|
||||||
isRightTransparent: false // 右侧透明度状态(true=1,false=0.3)
|
isRightTransparent: false, // 右侧透明度状态(true=1,false=0.3)
|
||||||
|
activeTab: 'month', // 默认激活的Tab为月度
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
handleTabClick(tabType) {
|
||||||
|
this.activeTab = tabType;
|
||||||
|
// 向父组件派发Tab切换事件,传递当前选中的Tab类型
|
||||||
|
this.$emit('tabChange', tabType);
|
||||||
|
// 可选:同时传递更详细的信息(如标签名)
|
||||||
|
// this.$emit('tabChange', { type: tabType, name: tabType === 'month' ? '月度' : '累计' });
|
||||||
|
},
|
||||||
// 点击左侧标题:左侧保持不透明,右侧变透明,并派发采购标签事件
|
// 点击左侧标题:左侧保持不透明,右侧变透明,并派发采购标签事件
|
||||||
handleLeftClick() {
|
handleLeftClick() {
|
||||||
this.isLeftTransparent = true; // 左侧不透明
|
this.isLeftTransparent = true; // 左侧不透明
|
||||||
@@ -151,7 +169,7 @@ export default {
|
|||||||
background: linear-gradient(90deg, #FFFFFF 0%, rgba(253, 255, 255, 0) 100%);
|
background: linear-gradient(90deg, #FFFFFF 0%, rgba(253, 255, 255, 0) 100%);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
left: 240px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -258,4 +276,41 @@ export default {
|
|||||||
// text-align: left;
|
// text-align: left;
|
||||||
// font-style: normal;
|
// font-style: normal;
|
||||||
}
|
}
|
||||||
|
.tab-group {
|
||||||
|
display: inline-flex;
|
||||||
|
position: absolute;
|
||||||
|
right: 5%;
|
||||||
|
top:20px;
|
||||||
|
z-index: 9999;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
gap: 8px; // Tab之间的间距
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab基础样式(统一)
|
||||||
|
.tab-item {
|
||||||
|
padding: 0 24px;
|
||||||
|
width: 79px;
|
||||||
|
height: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.2s ease; // 样式切换动画
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未激活的Tab样式(原first-child样式)
|
||||||
|
.tab-item:not(.active) {
|
||||||
|
background: #ECF4FE;
|
||||||
|
color: #0B58FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 激活的Tab样式(原last-child样式)
|
||||||
|
.tab-item.active {
|
||||||
|
background: #3071FF;
|
||||||
|
color: #F9FCFF;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
157
src/views/home/components/heatBarChart.vue
Normal file
157
src/views/home/components/heatBarChart.vue
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
<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 // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
lineData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 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);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -1,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)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -169,7 +178,7 @@ export default {
|
|||||||
|
|
||||||
// 基础表格列配置(只读模式使用)
|
// 基础表格列配置(只读模式使用)
|
||||||
const baseTableProps = [
|
const baseTableProps = [
|
||||||
// { prop: 'type', label: '指标类型', align: 'center' },
|
{ prop: 'type', label: '指标类型', align: 'center' },
|
||||||
{ prop: 'name', label: '指标名称', align: 'center' },
|
{ prop: 'name', label: '指标名称', align: 'center' },
|
||||||
{ prop: 'unitLabel', label: '单位', align: 'center' },
|
{ prop: 'unitLabel', label: '单位', align: 'center' },
|
||||||
{ prop: 'value', label: '实际值', align: 'center' },
|
{ prop: 'value', label: '实际值', align: 'center' },
|
||||||
@@ -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() {
|
||||||
try {
|
|
||||||
if (!this.upload.currentFile) {
|
if (!this.upload.currentFile) {
|
||||||
return this.$message.error('请先选择要上传的文件!')
|
return this.$message.error('请先选择要上传的文件!')
|
||||||
}
|
}
|
||||||
|
if (this.upload.httpUploading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.upload.httpUploading = true
|
||||||
|
try {
|
||||||
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 {
|
||||||
// 内联样式已优化行间距,此处可留空或补充
|
// 内联样式已优化行间距,此处可留空或补充
|
||||||
|
|||||||
316
src/views/home/components/keyProductContainer.vue
Normal file
316
src/views/home/components/keyProductContainer.vue
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
<template>
|
||||||
|
<div class="cockpitContainer" :class="['cockpitContainer__' + size]">
|
||||||
|
<div class="container-top">
|
||||||
|
<!-- 左侧标题:点击切换到采购标签,并更新透明度 -->
|
||||||
|
<div class="content-top-left title-wrapper" @click="handleLeftClick"
|
||||||
|
:style="{ opacity: isLeftTransparent ? 1 : 0.3 }">
|
||||||
|
<svg-icon class="title-icon" style="font-size: 32px; margin-left: 16px" :icon-class="icon" />
|
||||||
|
<span class="title-text">{{ name }}</span>
|
||||||
|
<!-- <span v-if="!isLeftTransparent" class="change-text">点击切换</span> -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- 右侧标题:点击切换到存货标签,并更新透明度 -->
|
||||||
|
<div class="content-top-right title-wrapper" v-if="nameTwo" @click="handleRightClick"
|
||||||
|
:style="{ opacity: isRightTransparent ? 1 : 0.3 }">
|
||||||
|
<svg-icon class="title-icon" style="font-size: 32px; margin-left: 16px" :icon-class="iconTwo || icon" />
|
||||||
|
<span class="title-text">{{ nameTwo }}</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- <span class="change-text" :class="{ 'change-text-right': isLeftTransparent }">点击切换</span> -->
|
||||||
|
<div class="tab-group">
|
||||||
|
<!-- 月度Tab:点击切换状态,动态绑定样式 -->
|
||||||
|
<div class="tab-item" :class="{ active: activeTab === 'month' }" @click="handleTabClick('month')">
|
||||||
|
月度
|
||||||
|
</div>
|
||||||
|
<!-- 累计Tab:点击切换状态,动态绑定样式 -->
|
||||||
|
<div class="tab-item" :class="{ active: activeTab === 'total' }" @click="handleTabClick('total')">
|
||||||
|
累计
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="container-body">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Container',
|
||||||
|
components: {},
|
||||||
|
props: {
|
||||||
|
name: { type: String, required: true },
|
||||||
|
nameTwo: { type: String, required: false },
|
||||||
|
size: { type: String, default: 'default' },
|
||||||
|
icon: { type: String, default: '' },
|
||||||
|
iconTwo: { type: String, default: '' }
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 初始状态:左侧不透明(1),右侧透明(0.3)
|
||||||
|
isLeftTransparent: true, // 左侧透明度状态(true=1,false=0.3)
|
||||||
|
isRightTransparent: false, // 右侧透明度状态(true=1,false=0.3)
|
||||||
|
activeTab: 'month', // 默认激活的Tab为月度
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleTabClick(tabType) {
|
||||||
|
this.activeTab = tabType;
|
||||||
|
// 向父组件派发Tab切换事件,传递当前选中的Tab类型
|
||||||
|
this.$emit('tabChange', tabType);
|
||||||
|
// 可选:同时传递更详细的信息(如标签名)
|
||||||
|
// this.$emit('tabChange', { type: tabType, name: tabType === 'month' ? '月度' : '累计' });
|
||||||
|
},
|
||||||
|
// 点击左侧标题:左侧保持不透明,右侧变透明,并派发采购标签事件
|
||||||
|
handleLeftClick() {
|
||||||
|
this.isLeftTransparent = true; // 左侧不透明
|
||||||
|
this.isRightTransparent = false; // 右侧透明
|
||||||
|
this.$emit('switchTab', 'product'); // 通知父组件切换到采购内容
|
||||||
|
},
|
||||||
|
// 点击右侧标题:右侧保持不透明,左侧变透明,并派发存货标签事件
|
||||||
|
handleRightClick() {
|
||||||
|
this.isLeftTransparent = false; // 左侧透明
|
||||||
|
this.isRightTransparent = true; // 右侧不透明
|
||||||
|
this.$emit('switchTab', 'heat'); // 通知父组件切换到存货内容
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
// 样式保持不变,确保透明度过渡生效
|
||||||
|
.cockpitContainer {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.container-top {
|
||||||
|
position: relative;
|
||||||
|
height: 60px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-top-left {
|
||||||
|
width: 566px;
|
||||||
|
height: 60px;
|
||||||
|
background: linear-gradient(90deg, #FFFFFF 0%, rgba(253, 255, 255, 0) 100%);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 1;
|
||||||
|
transition: opacity 0.3s ease; // 透明度过渡动画
|
||||||
|
}
|
||||||
|
.title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
// margin-left: 10px;
|
||||||
|
/* 垂直居中关键属性 */
|
||||||
|
height: 100%;
|
||||||
|
/* 继承父容器高度,确保垂直居中范围 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
font-size: 30px;
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-left: 10px;
|
||||||
|
/* 图标和文字之间的间距 */
|
||||||
|
flex-shrink: 0;
|
||||||
|
/* 防止图标被压缩 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 移除固定行高,避免影响垂直对齐
|
||||||
|
// line-height: 60px;
|
||||||
|
}
|
||||||
|
/* 左侧标题 - 左上角折现边框 */
|
||||||
|
.content-top-left::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid;
|
||||||
|
border-image: linear-gradient(277deg, rgba(255, 255, 255, 0), rgba(92, 140, 255, 1)) 1 1;
|
||||||
|
clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%, 0 20px);
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 左侧标题 - 左上角折现细节 */
|
||||||
|
.content-top-left::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
background: #E1f0fd;
|
||||||
|
border-top: 1px solid rgba(92, 140, 255, 1);
|
||||||
|
border-left: 1px solid rgba(92, 140, 255, 1);
|
||||||
|
transform: rotate(135deg) translate(-50%, -50%);
|
||||||
|
transform-origin: top left;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-top-right {
|
||||||
|
width: 368px;
|
||||||
|
height: 60px;
|
||||||
|
background: linear-gradient(90deg, #FFFFFF 0%, rgba(253, 255, 255, 0) 100%);
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 240px;
|
||||||
|
z-index: 10;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.3s ease; // 透明度过渡动画
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右侧标题 - 左上角折现边框 */
|
||||||
|
.content-top-right::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid;
|
||||||
|
border-image: linear-gradient(277deg, rgba(255, 255, 255, 0), rgba(92, 140, 255, 1)) 1 1;
|
||||||
|
clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%, 0 20px);
|
||||||
|
z-index: 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右侧标题 - 左上角折现细节 */
|
||||||
|
.content-top-right::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
background: #E1f0fd;
|
||||||
|
border-top: 1px solid rgba(92, 140, 255, 1);
|
||||||
|
border-left: 1px solid rgba(92, 140, 255, 1);
|
||||||
|
transform: rotate(135deg) translate(-50%, -50%);
|
||||||
|
transform-origin: top left;
|
||||||
|
z-index: 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
margin-left: 6px;
|
||||||
|
height: 32px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #000000;
|
||||||
|
// line-height: 60px;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__topBasic {
|
||||||
|
background: url(../../../assets/img/top-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bottomBasic {
|
||||||
|
background: url(../../../assets/img/bottom-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-body {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-body {
|
||||||
|
padding: 20px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.change-text{
|
||||||
|
position: absolute;
|
||||||
|
top: 26px;
|
||||||
|
left: 300px;
|
||||||
|
z-index: 999;
|
||||||
|
// width: 48px;
|
||||||
|
// height: 17px;
|
||||||
|
// margin-left: 80px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 17px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
.change-text-right {
|
||||||
|
// position: absolute;
|
||||||
|
// top: 26px;
|
||||||
|
left: auto; // 清除左侧定位
|
||||||
|
right: 30px;
|
||||||
|
// z-index: 999;
|
||||||
|
// // width: 48px;
|
||||||
|
// // height: 17px;
|
||||||
|
// // margin-left: 80px;
|
||||||
|
// font-family: PingFangSC, PingFang SC;
|
||||||
|
// font-weight: 400;
|
||||||
|
// font-size: 12px;
|
||||||
|
// color: #0B58FF;
|
||||||
|
// line-height: 17px;
|
||||||
|
// text-align: left;
|
||||||
|
// font-style: normal;
|
||||||
|
}
|
||||||
|
.tab-group {
|
||||||
|
display: inline-flex;
|
||||||
|
position: absolute;
|
||||||
|
right: 0px;
|
||||||
|
top:20px;
|
||||||
|
z-index: 9999;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
gap: 8px; // Tab之间的间距
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab基础样式(统一)
|
||||||
|
.tab-item {
|
||||||
|
padding: 0 24px;
|
||||||
|
width: 79px;
|
||||||
|
height: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.2s ease; // 样式切换动画
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未激活的Tab样式(原first-child样式)
|
||||||
|
.tab-item:not(.active) {
|
||||||
|
background: #ECF4FE;
|
||||||
|
color: #0B58FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 激活的Tab样式(原last-child样式)
|
||||||
|
.tab-item.active {
|
||||||
|
background: #3071FF;
|
||||||
|
color: #F9FCFF;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,33 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<div style="flex: 1">
|
<div style="flex: 1">
|
||||||
<Container name="生产重点指标" icon="cockpitItemIcon" size="topBasic" topSize="basic">
|
<Container name="生产重点指标" nameTwo="热耗" icon="cockpitItemIcon" size="topBasic" topSize="basic" @switchTab="handleTabSwitch" @tabChange='tabChange'>
|
||||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
<div class="kpi-content" style="padding: 14px 16px; display: flex;flex-direction: column; width: 100%;">
|
<div class="kpi-content" style="padding: 14px 16px; display: flex;flex-direction: column; width: 100%;">
|
||||||
<!-- 2. .top 保持 flex,无需固定高度,自动跟随子元素拉伸 -->
|
<!-- 2. .top 保持 flex,无需固定高度,自动跟随子元素拉伸 -->
|
||||||
|
<template v-if="activeTab === 'product'">
|
||||||
<div class="top" style="display: flex; width: 100%;">
|
<div class="top" style="display: flex; width: 100%;">
|
||||||
<top-item :rawItemList='productData' :dateData="dateData" />
|
<top-item :rawItemList='productData' :dateData="dateData" />
|
||||||
</div>
|
</div>
|
||||||
<div class="bottom" style="display: flex;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
<div class="bottom" style="display: flex;margin-top: 8px;background-color: rgba(249, 252, 255, 1);">
|
||||||
<!-- <top-item /> -->
|
<!-- <top-item /> -->
|
||||||
<coreBottomBar :lineData="productData.line" :dateData="dateData" />
|
<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>
|
||||||
|
|||||||
@@ -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'
|
|
||||||
};
|
};
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -154,7 +154,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',
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ 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 {
|
||||||
@@ -131,7 +131,7 @@ export default {
|
|||||||
],
|
],
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
name: '元/㎡',
|
name: this.unit,
|
||||||
// nameLocation:'center',
|
// nameLocation:'center',
|
||||||
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' },
|
nameTextStyle: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 14, align: 'right' },
|
||||||
min: 0,
|
min: 0,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
<div class="coreItem">
|
<div class="coreItem">
|
||||||
<div class="item" @click="handleRoute(item.route)" v-for="(item, index) in itemList" :key="index">
|
<div class="item" @click="handleRoute(item.route)" v-for="(item, index) in itemList" :key="index" v-if='index<4'>
|
||||||
<div class="name">{{ item.name }}</div>
|
<div class="name">{{ item.name }}</div>
|
||||||
<div class="item-content">
|
<div class="item-content">
|
||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
@@ -12,8 +13,8 @@
|
|||||||
<!-- 实际值:根据 实际值≥目标值 动态绑定类名 -->
|
<!-- 实际值:根据 实际值≥目标值 动态绑定类名 -->
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<div class="number" :class="{
|
<div class="number" :class="{
|
||||||
'number-exceed': item.currentValue >= item.targetValue,
|
'number-exceed': item.progress >= 100,
|
||||||
'number-below': item.currentValue < item.targetValue
|
'number-below': item.progress < 100
|
||||||
}">
|
}">
|
||||||
{{ item.currentValue }}
|
{{ item.currentValue }}
|
||||||
</div>
|
</div>
|
||||||
@@ -26,8 +27,8 @@
|
|||||||
<div class="progress-group">
|
<div class="progress-group">
|
||||||
<div class="progress-container">
|
<div class="progress-container">
|
||||||
<div class="progress-bar" :style="{ width: item.progressWidth + '%' }" :class="{
|
<div class="progress-bar" :style="{ width: item.progressWidth + '%' }" :class="{
|
||||||
'bar-exceed': item.currentValue >= item.targetValue,
|
'bar-exceed': item.progress >= 100,
|
||||||
'bar-below': item.currentValue < item.targetValue
|
'bar-below': item.progress < 100
|
||||||
}"></div>
|
}"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,14 +36,57 @@
|
|||||||
<!-- 完成率:同步绑定类名 -->
|
<!-- 完成率:同步绑定类名 -->
|
||||||
<div class="yield" style="display: flex;justify-content: space-between;">
|
<div class="yield" style="display: flex;justify-content: space-between;">
|
||||||
<div class="progress-percent" :class="{
|
<div class="progress-percent" :class="{
|
||||||
'percent-exceed': item.currentValue >= item.targetValue,
|
'percent-exceed': item.progress >= 100,
|
||||||
'percent-below': item.currentValue < item.targetValue
|
'percent-below': item.progress < 100
|
||||||
}">完成率</div>
|
}">完成率</div>
|
||||||
<div class="progress-percent" :class="{
|
<div class="progress-percent" :class="{
|
||||||
'percent-exceed': item.currentValue >= item.targetValue,
|
'percent-exceed': item.progress >= 100,
|
||||||
'percent-below': item.currentValue < item.targetValue
|
'percent-below': item.progress < 100
|
||||||
}">
|
}">
|
||||||
{{ item.progressDisplay }}
|
{{ item.progress }}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="itemBottom">
|
||||||
|
<div class="item" v-for="(item, index) in itemList" :key="index" @click="handleRoute(item.route)" v-if='index>=4'>
|
||||||
|
<div class="unit">{{ item.name }}</div>
|
||||||
|
<div class="item-content">
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<div class="left">
|
||||||
|
<div class="number">{{ item.targetValue }}</div>
|
||||||
|
<div class="title">目标值</div>
|
||||||
|
</div>
|
||||||
|
<div class="line"></div>
|
||||||
|
<!-- 实际值:根据与目标值的比较动态变色 -->
|
||||||
|
<div class="right">
|
||||||
|
<div class="number" :class="{
|
||||||
|
'exceed-target': item.progress > 100,
|
||||||
|
'below-target': item.progress < 100,
|
||||||
|
'equal-target': item.progress == 100
|
||||||
|
}">
|
||||||
|
{{ item.currentValue }}
|
||||||
|
</div>
|
||||||
|
<div class="title">实际值</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 进度条和百分比:同步变色逻辑 -->
|
||||||
|
<div class="progress-group">
|
||||||
|
<div class="progress-container">
|
||||||
|
<div class="progress-bar" :style="{ width: item.progress + '%' }" :class="{
|
||||||
|
'exceed-pro-target': item.progress > 100,
|
||||||
|
'below-pro-target': item.progress < 100,
|
||||||
|
'equal-pro-target': item.progress == 100
|
||||||
|
}"></div>
|
||||||
|
</div>
|
||||||
|
<div class="progress-percent" :class="{
|
||||||
|
'exceed-target': item.progress > 100,
|
||||||
|
'below-target': item.progress < 100,
|
||||||
|
'equal-target': item.progress == 100
|
||||||
|
}">
|
||||||
|
{{ item.progress }}%
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -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,14 +263,11 @@ export default {
|
|||||||
.coreItem {
|
.coreItem {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
// padding: 8px; // 避免边缘item hover阴影被截断
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
width: 170px;
|
width: 170px;
|
||||||
height: 228px;
|
height: 168px;
|
||||||
background: #f9fcff;
|
background: #f9fcff;
|
||||||
padding: 12px 0px 17px 12px;
|
padding: 12px 0px 0px 12px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
@@ -235,7 +276,6 @@ export default {
|
|||||||
box-shadow: 0px 4px 12px 2px #B5CDE5;
|
box-shadow: 0px 4px 12px 2px #B5CDE5;
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
height: 18px;
|
height: 18px;
|
||||||
font-family: PingFangSC, PingFang SC;
|
font-family: PingFangSC, PingFang SC;
|
||||||
@@ -246,36 +286,32 @@ 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% - 26px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.line {
|
.line {
|
||||||
width: 149px;
|
width: 149px;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background: linear-gradient(to left, rgba(255, 0, 0, 0), #cbcbcb);
|
background: linear-gradient(to left, rgba(255, 0, 0, 0), #cbcbcb);
|
||||||
}
|
}
|
||||||
|
|
||||||
.left,
|
.left,
|
||||||
.right {
|
.right {
|
||||||
margin-top: 11px;
|
margin-top: 0px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 实际值 - 基础样式(无颜色) */
|
/* 实际值 - 基础样式(无颜色) */
|
||||||
.number {
|
.number {
|
||||||
height: 22px;
|
height: 22px;
|
||||||
@@ -286,17 +322,14 @@ export default {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 实际值 - 实际值≥目标值(绿色) */
|
/* 实际值 - 实际值≥目标值(绿色) */
|
||||||
.number-exceed {
|
.number-exceed {
|
||||||
color: rgba(54, 181, 138, 1) !important;
|
color: rgba(54, 181, 138, 1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 实际值 - 实际值<目标值(黄色) */
|
/* 实际值 - 实际值<目标值(黄色) */
|
||||||
.number-below {
|
.number-below {
|
||||||
color: rgba(249, 164, 74, 1) !important;
|
color: rgba(249, 164, 74, 1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
height: 14px;
|
height: 14px;
|
||||||
font-family: PingFangSC, PingFang SC;
|
font-family: PingFangSC, PingFang SC;
|
||||||
@@ -307,14 +340,12 @@ export default {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-group {
|
.progress-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-top: 15px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-container {
|
.progress-container {
|
||||||
width: 138px;
|
width: 138px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
@@ -322,26 +353,22 @@ export default {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 进度条 - 基础样式(无颜色) */
|
/* 进度条 - 基础样式(无颜色) */
|
||||||
.progress-bar {
|
.progress-bar {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
transition: width 0.5s ease;
|
transition: width 0.5s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 进度条 - 实际值≥目标值(绿色) */
|
/* 进度条 - 实际值≥目标值(绿色) */
|
||||||
.bar-exceed {
|
.bar-exceed {
|
||||||
background: rgba(98, 213, 180, 1) !important;
|
background: rgba(98, 213, 180, 1) !important;
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 进度条 - 实际值<目标值(黄色) */
|
/* 进度条 - 实际值<目标值(黄色) */
|
||||||
.bar-below {
|
.bar-below {
|
||||||
background: rgba(249, 164, 74, 1) !important;
|
background: rgba(249, 164, 74, 1) !important;
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 百分比 - 基础样式(无颜色) */
|
/* 百分比 - 基础样式(无颜色) */
|
||||||
.progress-percent {
|
.progress-percent {
|
||||||
font-family: PingFangSC, PingFang SC;
|
font-family: PingFangSC, PingFang SC;
|
||||||
@@ -349,20 +376,185 @@ export default {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 百分比 - 实际值≥目标值(绿色) */
|
/* 百分比 - 实际值≥目标值(绿色) */
|
||||||
.percent-exceed {
|
.percent-exceed {
|
||||||
color: rgba(54, 181, 138, 1) !important;
|
color: rgba(54, 181, 138, 1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 百分比 - 实际值<目标值(黄色) */
|
/* 百分比 - 实际值<目标值(黄色) */
|
||||||
.percent-below {
|
.percent-below {
|
||||||
color: rgba(249, 164, 74, 1) !important;
|
color: rgba(249, 164, 74, 1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.yield {
|
.yield {
|
||||||
width: 138px;
|
width: 138px;
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.itemBottom {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 5px;
|
||||||
|
.item {
|
||||||
|
width: 350px;
|
||||||
|
height: 90px;
|
||||||
|
background: #f9fcff;
|
||||||
|
padding: 8px 8px 0px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer; // 提示可点击
|
||||||
|
transition: all 0.3s ease; // 动画过渡
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0px 4px 12px 2px #B5CDE5;
|
||||||
|
transform: translateY(-2px); // 轻微上浮增强交互感
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: calc(100% - 29px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
width: 1px;
|
||||||
|
height: 46px;
|
||||||
|
background: linear-gradient(to bottom, rgba(255, 0, 0, 0), #cbcbcb);
|
||||||
|
}
|
||||||
|
|
||||||
|
.left,
|
||||||
|
.right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
height: 22px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 24px;
|
||||||
|
color: rgba(103, 103, 103, 0.79);
|
||||||
|
/* 默认颜色(等于目标值时) */
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 14px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #868687;
|
||||||
|
line-height: 14px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-container {
|
||||||
|
width: 280px;
|
||||||
|
height: 10px;
|
||||||
|
background: #ECEFF7;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(98, 213, 180, 1);
|
||||||
|
/* 默认进度条颜色(等于目标值时) */
|
||||||
|
border-radius: 8px;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: width 0.5s ease; // 进度条动画
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-percent {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #868687;
|
||||||
|
/* 默认百分比颜色(等于目标值时) */
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 实际值 > 目标值:绿色样式 */
|
||||||
|
.exceed-target {
|
||||||
|
color: rgba(98, 213, 180, 1) !important;
|
||||||
|
/* 文字绿色 */
|
||||||
|
// background: rgba(98, 213, 180, 1) !important;
|
||||||
|
/* 进度条绿色 */
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 实际值 < 目标值:黄色样式 */
|
||||||
|
.below-target {
|
||||||
|
color: rgba(249, 164, 74, 1) !important;
|
||||||
|
/* 文字黄色 */
|
||||||
|
// background: rgba(249, 164, 74, 1) !important;
|
||||||
|
/* 进度条黄色 */
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exceed-pro-target {
|
||||||
|
// color: rgba(98, 213, 180, 1) !important;
|
||||||
|
/* 文字绿色 */
|
||||||
|
background: rgba(98, 213, 180, 1) !important;
|
||||||
|
/* 进度条绿色 */
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 实际值 < 目标值:黄色样式 */
|
||||||
|
.below-pro-target {
|
||||||
|
// color: rgba(249, 164, 74, 1) !important;
|
||||||
|
/* 文字黄色 */
|
||||||
|
background: rgba(249, 164, 74, 1) !important;
|
||||||
|
/* 进度条黄色 */
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
/* 实际值 = 目标值:默认灰色(可自定义) */
|
||||||
|
.equal-target{
|
||||||
|
color: rgba(98, 213, 180, 1) !important;
|
||||||
|
/* 文字绿色 */
|
||||||
|
// background: rgba(98, 213, 180, 1) !important;
|
||||||
|
/* 进度条绿色 */
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
.equal-pro-target {
|
||||||
|
// color: rgba(98, 213, 180, 1) !important;
|
||||||
|
/* 文字绿色 */
|
||||||
|
background: rgba(98, 213, 180, 1) !important;
|
||||||
|
/* 进度条绿色 */
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -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' }
|
||||||
];
|
];
|
||||||
|
|
||||||
// 遍历映射关系,转换数据
|
// 遍历映射关系,转换数据
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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坐标
|
||||||
|
|||||||
@@ -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, // 是否正在拖拽
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
216
src/views/home/depreciationAnalysis/depreciationAnalysis.vue
Normal file
216
src/views/home/depreciationAnalysis/depreciationAnalysis.vue
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
<template>
|
||||||
|
<div id="dayReport" class="dayReport" :style="styles">
|
||||||
|
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||||
|
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||||
|
<ReportHeader :dateData="dateData" top-title="折旧分析" :is-full-screen="isFullScreen"
|
||||||
|
@screenfullChange="screenfullChange" @timeRangeChange="handleTimeChange" />
|
||||||
|
<div class="main-body" style="
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
padding: 0px 16px 0 272px;
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div class="top" style="margin-top: -20px; display: flex; gap: 16px">
|
||||||
|
<div class="top-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns:1624px;
|
||||||
|
">
|
||||||
|
<operatingLineChart :dateData="dateData" :monthData="monthData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||||
|
<div class="left-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns: 1624px;
|
||||||
|
">
|
||||||
|
<operatingLineChartCumulative :dateData="dateData" :ytdData="ytdData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import ReportHeader from "../components/noRouterHeader.vue";
|
||||||
|
import { Sidebar } from "../../../layout/components";
|
||||||
|
import screenfull from "screenfull";
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
import operatingLineChart from "../depreciationAnalysisComponents/operatingLineChart";
|
||||||
|
import operatingLineChartCumulative from "../depreciationAnalysisComponents/operatingLineChartCumulative.vue";
|
||||||
|
|
||||||
|
import { getDepreciationAnalysisData } from '@/api/cockpit'
|
||||||
|
export default {
|
||||||
|
name: "DepreciationAnalysis",
|
||||||
|
components: {
|
||||||
|
ReportHeader,
|
||||||
|
operatingLineChartCumulative,
|
||||||
|
operatingLineChart,
|
||||||
|
Sidebar,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
weekArr: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
|
||||||
|
isFullScreen: false,
|
||||||
|
timer: null,
|
||||||
|
beilv: 1,
|
||||||
|
value: 100,
|
||||||
|
sort:1,
|
||||||
|
selectDate:{},
|
||||||
|
monthData: {},
|
||||||
|
ytdData:{},
|
||||||
|
dateData:{}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.init();
|
||||||
|
this.windowWidth(document.documentElement.clientWidth);
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
theme: (state) => state.settings.theme,
|
||||||
|
sideTheme: (state) => state.settings.sideTheme,
|
||||||
|
sidebar: (state) => state.app.sidebar,
|
||||||
|
device: (state) => state.app.device,
|
||||||
|
needTagsView: (state) => state.settings.tagsView,
|
||||||
|
fixedHeader: (state) => state.settings.fixedHeader,
|
||||||
|
}),
|
||||||
|
classObj() {
|
||||||
|
return {
|
||||||
|
hideSidebar: !this.sidebar.opened,
|
||||||
|
openSidebar: this.sidebar.opened,
|
||||||
|
withoutAnimation: this.sidebar.withoutAnimation,
|
||||||
|
mobile: this.device === "mobile",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
variables() {
|
||||||
|
return variables;
|
||||||
|
},
|
||||||
|
// ...mapGetters(['sidebar']),
|
||||||
|
styles() {
|
||||||
|
const v = Math.floor(this.value * this.beilv * 100) / 10000;
|
||||||
|
return {
|
||||||
|
transform: `scale(${v})`,
|
||||||
|
transformOrigin: "left top",
|
||||||
|
// overflow: hidden;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
clientWidth(val) {
|
||||||
|
if (!this.timer) {
|
||||||
|
this.clientWidth = val;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
this.timer = true;
|
||||||
|
let _this = this;
|
||||||
|
setTimeout(function () {
|
||||||
|
_this.timer = false;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
// 这里可以添加修改时的方法
|
||||||
|
this.windowWidth(val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.destroy();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const _this = this;
|
||||||
|
_this.beilv = document.documentElement.clientWidth / 1920;
|
||||||
|
window.onresize = () => {
|
||||||
|
return (() => {
|
||||||
|
_this.clientWidth = `${document.documentElement.clientWidth}`;
|
||||||
|
this.beilv = _this.clientWidth / 1920;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getData() {
|
||||||
|
getDepreciationAnalysisData({
|
||||||
|
startTime: this.dateData.startTime,
|
||||||
|
endTime: this.dateData.endTime,
|
||||||
|
sort: this.sort,
|
||||||
|
index: undefined,
|
||||||
|
factory: undefined
|
||||||
|
// timeDim: obj.mode
|
||||||
|
}).then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
this.monthData= res.data.month
|
||||||
|
this.ytdData = res.data.ytd
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleTimeChange(obj) {
|
||||||
|
console.log(obj, 'obj');
|
||||||
|
this.dateData= obj
|
||||||
|
this.getData()
|
||||||
|
},
|
||||||
|
handleClickOutside() {
|
||||||
|
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
||||||
|
},
|
||||||
|
windowWidth(value) {
|
||||||
|
this.clientWidth = value;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
},
|
||||||
|
change() {
|
||||||
|
this.isFullScreen = screenfull.isFullscreen;
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.on("change", this.change);
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.off("change", this.change);
|
||||||
|
},
|
||||||
|
// 全屏
|
||||||
|
screenfullChange() {
|
||||||
|
console.log("screenfull.enabled", screenfull.isEnabled);
|
||||||
|
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.toggle(this.$refs.dayReportB);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "~@/assets/styles/mixin.scss";
|
||||||
|
@import "~@/assets/styles/variables.scss";
|
||||||
|
.dayReport {
|
||||||
|
width: 1920px;
|
||||||
|
height: 1080px;
|
||||||
|
background: url("../../../assets/img/backp.png") no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
.hideSidebar .fixed-header {
|
||||||
|
width: calc(100% - 54px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarHide .fixed-header {
|
||||||
|
width: calc(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile .fixed-header {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
419
src/views/home/depreciationAnalysisComponents/Header.vue
Normal file
419
src/views/home/depreciationAnalysisComponents/Header.vue
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
<template>
|
||||||
|
<header class="report-header">
|
||||||
|
<!-- 左侧区域:logo + 标题 -->
|
||||||
|
<div class="left-content">
|
||||||
|
<img style="height: 36px;" src="../../../assets/img/cnbm.png" alt="benmaLogo" >
|
||||||
|
<div class="top-title">{{ topTitle }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="center-content">
|
||||||
|
<!-- 循环 pageRoutes,不再硬编码文字 -->
|
||||||
|
<div class="item" v-for="(page, index) in pageRoutes" :key="index" @click="goToPage(page.path, index)">
|
||||||
|
<span class="item-text">{{ page.text }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- :class="{ 'no-skew': activeIndex === index }
|
||||||
|
" -->
|
||||||
|
<!-- 右侧区域:全屏按钮 -->
|
||||||
|
<div class="right-content">
|
||||||
|
<el-button type="text" class="screen-btn" :title="isFullScreen ? '退出全屏' : '全屏'" @click="changeFullScreen">
|
||||||
|
<svg-icon style="color: #0B58FF;" v-if="isFullScreen" icon-class="unFullScreenView" />
|
||||||
|
<svg-icon style="color: #0B58FF;" v-else icon-class="fullScreenView" />
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 时间选择区域:日/月/年按钮 + label + 日期选择器 -->
|
||||||
|
<div class="timeType">
|
||||||
|
<!-- <div class="item" v-for="(item, index) in timeTypes" :key="index" @click="activeTime = index"
|
||||||
|
:class="{ 'no-skew': activeTime === index }">
|
||||||
|
<span class="item-text">{{ item.text }}</span>
|
||||||
|
</div> -->
|
||||||
|
<div class="dateP">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">月份选择</span>
|
||||||
|
</div>
|
||||||
|
<el-date-picker v-model="date" :type="getPickerType" :placeholder="getPickerPlaceholder"
|
||||||
|
class="custom-date-picker" style="width: 132px;height: 29px;" @change="emitTimeRange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment'
|
||||||
|
export default {
|
||||||
|
name: 'Header',
|
||||||
|
props: {
|
||||||
|
isFullScreen: { type: Boolean, default: false },
|
||||||
|
topTitle: { type: String, default: '' }
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentTime: '',
|
||||||
|
timeTimer: null,
|
||||||
|
date: undefined,
|
||||||
|
activeIndex: -1,
|
||||||
|
activeTime: 1, // 0=日,1=月,2=年(默认选中“日”)
|
||||||
|
pageRoutes: [
|
||||||
|
{ text: '营业收入', path: '/operatingRevenue/operatingRevenueIndex' },
|
||||||
|
{ text: '利润分析', path: '/profitAnalysis' },
|
||||||
|
{ text: '产销率库存分析', path: '/PSIAnal' },
|
||||||
|
{ text: '成本分析', path: '/cost/cost' },
|
||||||
|
{ text: '驾驶舱报表', path: '/cockpit' }
|
||||||
|
],
|
||||||
|
// 定义时间类型配置:text=按钮文字,pickerType=选择器类型,placeholder=占位符
|
||||||
|
timeTypes: [
|
||||||
|
{ text: '日', pickerType: 'date', placeholder: '选择日期' },
|
||||||
|
{ text: '月', pickerType: 'month', placeholder: '选择月份' },
|
||||||
|
{ text: '年', pickerType: 'year', placeholder: '选择年份' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 动态获取日期选择器类型
|
||||||
|
getPickerType() {
|
||||||
|
return this.timeTypes[this.activeTime].pickerType;
|
||||||
|
},
|
||||||
|
// 动态获取日期选择器占位符
|
||||||
|
getPickerPlaceholder() {
|
||||||
|
return this.timeTypes[this.activeTime].placeholder;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goToPage(path, index) {
|
||||||
|
// 1. 跳转到对应路由
|
||||||
|
this.$router.push(path);
|
||||||
|
// 2. 更新activeIndex,让当前点击项高亮
|
||||||
|
this.activeIndex = index;
|
||||||
|
},
|
||||||
|
changeFullScreen() { this.$emit('screenfullChange') },
|
||||||
|
padZero(num) { return num < 10 ? '0' + num : num },
|
||||||
|
/**
|
||||||
|
* 核心方法1:根据维度计算时间区间(首次进入时基于赋值的当月日期,计算“当月第一天0点→次月第一天0点”)
|
||||||
|
* @returns {Object} 包含 start(开始时间)、end(结束时间)、dimension(维度)的区间对象
|
||||||
|
*/
|
||||||
|
calculateTimeRange() {
|
||||||
|
let startTime = 0;
|
||||||
|
let endTime = 0;
|
||||||
|
const mode = this.activeTime + 1; // 1=日,2=月,3=年
|
||||||
|
const defaultMoment = moment(); // 默认当前时间
|
||||||
|
|
||||||
|
const targetMoment = this.date
|
||||||
|
? moment(this.date, this.getPickerType === 'date' ? 'YYYY-MM-DD' : (this.getPickerType === 'month' ? 'YYYY-MM' : 'YYYY'))
|
||||||
|
: defaultMoment;
|
||||||
|
|
||||||
|
if (!targetMoment.isValid()) {
|
||||||
|
console.error('无效日期:', this.date);
|
||||||
|
return { startTime, endTime, mode };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 日维度:00:00:00 → 23:59:59(无毫秒)
|
||||||
|
if (this.activeTime === 0) {
|
||||||
|
startTime = targetMoment.startOf('day').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('day').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 月维度:当月1日00:00:00 → 当月最后一天23:59:59(无毫秒)
|
||||||
|
else if (this.activeTime === 1) {
|
||||||
|
startTime = targetMoment.startOf('month').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('month').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 年维度:当年1月1日00:00:00 → 当年最后一天23:59:59(无毫秒)
|
||||||
|
else if (this.activeTime === 2) {
|
||||||
|
startTime = targetMoment.startOf('year').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('year').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调试输出:验证是否去掉毫秒
|
||||||
|
console.log('时间范围计算结果:', {
|
||||||
|
mode,
|
||||||
|
startTime: moment(startTime * 1000).format('YYYY-MM-DD HH:mm:ss'), // 格式:2025-11-30 00:00:00
|
||||||
|
endTime: moment(endTime * 1000).format('YYYY-MM-DD HH:mm:ss'), // 格式:2025-11-30 23:59:59(无毫秒)
|
||||||
|
startTimeStamp: startTime, // 秒级时间戳(如:1764422400)
|
||||||
|
endTimeStamp: endTime // 秒级时间戳(如:1764508799)
|
||||||
|
});
|
||||||
|
|
||||||
|
return { startTime, endTime, mode };
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 核心方法2:传递时间区间给父组件(首次进入时触发,传递“当月第一天0点→次月第一天0点”)
|
||||||
|
*/
|
||||||
|
emitTimeRange() {
|
||||||
|
const timeRange = this.calculateTimeRange();
|
||||||
|
this.$emit('timeRangeChange', timeRange);
|
||||||
|
// 调试用:查看首次传递的区间(如{start: "2025-10-01T00:00:00", end: "2025-11-01T00:00:00", dimension: "月"})
|
||||||
|
console.log('当前时间区间:', timeRange);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 维度切换时:清空选择的日期,并传递当前维度的默认区间
|
||||||
|
activeTime(newVal, oldVal) {
|
||||||
|
if (newVal !== oldVal) {
|
||||||
|
this.date = undefined;
|
||||||
|
// this.emitTimeRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 核心逻辑:首次进入页面,计算当月默认日期并赋值给选择器,同时传递区间
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = this.padZero(now.getMonth() + 1); // 月份从0开始,+1后补零(如1月→01)
|
||||||
|
// 赋值当月默认日期(格式:YYYY-MM,适配month类型选择器)
|
||||||
|
this.date = `${year}-${month}`;
|
||||||
|
// 确保选择器渲染完成后,传递“当月第一天0点→次月第一天0点”的区间
|
||||||
|
this.$nextTick(() => this.emitTimeRange());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
/* 原有样式不变,仅补充label文字的倾斜抵消样式 */
|
||||||
|
@font-face {
|
||||||
|
font-family: "YouSheBiaoTiHei";
|
||||||
|
src: url('../../../assets/fonts/YouSheBiaoTiHe.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-header {
|
||||||
|
height: 117px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
background: url('../../../assets/img/topTitle.png') no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: 0 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
/* 确保timeType绝对定位生效 */
|
||||||
|
|
||||||
|
.left-content {
|
||||||
|
margin-top: 11px;
|
||||||
|
margin-left: 44px;
|
||||||
|
height: 55px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-title {
|
||||||
|
height: 55px;
|
||||||
|
font-family: "YouSheBiaoTiHei", sans-serif;
|
||||||
|
font-size: 42px;
|
||||||
|
color: #1E1651;
|
||||||
|
line-height: 55px;
|
||||||
|
letter-spacing: 6px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 18px;
|
||||||
|
margin-left: 70px;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
width: 180px;
|
||||||
|
height: 50px;
|
||||||
|
background: #E1EEFC;
|
||||||
|
transform: skew(-20deg);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #1E1651;
|
||||||
|
line-height: 50px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0px 13px 16px 0px rgba(179, 217, 255, 0.43),
|
||||||
|
0px 2px 4px 0px rgba(92, 140, 255, 0.25),
|
||||||
|
inset 0px -43px 13px 0px rgba(255, 255, 255, 0.51);
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(20deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.no-skew {
|
||||||
|
background: none !important;
|
||||||
|
transform: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
color: #1E1651;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 垂直居中,避免元素高低错位 */
|
||||||
|
top: 42px;
|
||||||
|
right:10px;
|
||||||
|
margin-top: 18px;
|
||||||
|
gap: 0;
|
||||||
|
/* 清除间隙,让按钮与选择器紧密连接 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item {
|
||||||
|
width: 40px;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(236, 244, 254, 1);
|
||||||
|
transform: skew(-20deg);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(11, 88, 255, 1);
|
||||||
|
line-height: 28px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
/* 选中按钮与未选中按钮倾斜角度统一,避免切换时跳动 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item .item-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(20deg);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item.no-skew {
|
||||||
|
background: rgba(11, 88, 255, 1);
|
||||||
|
color: rgba(249, 252, 255, 1);
|
||||||
|
transform: skew(-20deg) !important;
|
||||||
|
/* 统一倾斜角度,修复原30deg的错位 */
|
||||||
|
box-shadow: 0 2px 8px rgba(11, 88, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item.no-skew .item-text {
|
||||||
|
transform: skew(20deg) !important;
|
||||||
|
/* 同步统一文字倾斜角度 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateP {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateP .label {
|
||||||
|
width: 165px;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(236, 244, 254, 1);
|
||||||
|
transform: skew(-25deg);
|
||||||
|
/* 与按钮倾斜角度统一(原30deg改为25deg,避免视觉错位) */
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 28px;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 补充:label文字抵消倾斜(原代码遗漏,导致文字倾斜) */
|
||||||
|
.dateP .label-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(25deg);
|
||||||
|
/* 与label倾斜角度相反,确保文字正立 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-right: 4px;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-time {
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 24px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screen-btn {
|
||||||
|
width: 26px;
|
||||||
|
margin-left: 300px;
|
||||||
|
color: #00fff0;
|
||||||
|
font-size: 26px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 日期选择器样式保持不变 */
|
||||||
|
::v-deep .custom-date-picker {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
width: 165px !important;
|
||||||
|
height: 28px !important;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 !important;
|
||||||
|
|
||||||
|
/* 1. 调整输入框文字:确保行高与输入框高度一致,垂直居中 */
|
||||||
|
.el-input__inner {
|
||||||
|
height: 28px !important;
|
||||||
|
width: 165px !important;
|
||||||
|
text-align: center;
|
||||||
|
padding-left: 15px !important;
|
||||||
|
padding-right: 32px !important;
|
||||||
|
/* 给图标留空间,避免文字被遮挡 */
|
||||||
|
font-size: 14px !important;
|
||||||
|
line-height: 28px !important;
|
||||||
|
/* 行高=输入框高度,文字垂直居中 */
|
||||||
|
color: rgba(237, 245, 253, 1) !important;
|
||||||
|
vertical-align: middle !important;
|
||||||
|
/* 强制文字垂直对齐 */
|
||||||
|
clip-path: polygon(18px 0, 100% 0, 100% 100%, 0 100%);
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
background-color: rgba(11, 88, 255, 1) !important;
|
||||||
|
border-left: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. 调整图标容器:让图标与文字在同一水平线上 */
|
||||||
|
.el-input__prefix {
|
||||||
|
left: auto !important;
|
||||||
|
right: 8px !important;
|
||||||
|
top: 50% !important;
|
||||||
|
/* 从40%改为50%,基于输入框垂直居中 */
|
||||||
|
transform: translateY(-50%) !important;
|
||||||
|
/* 向上偏移自身50%,精准居中 */
|
||||||
|
display: inline-flex !important;
|
||||||
|
/* 让容器内图标垂直居中 */
|
||||||
|
align-items: center !important;
|
||||||
|
/* 图标在容器内垂直居中 */
|
||||||
|
height: 28px !important;
|
||||||
|
/* 容器高度=输入框高度,避免偏移 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. 调整图标本身:确保图标大小和对齐方式 */
|
||||||
|
.el-input__icon {
|
||||||
|
color: #ffffff !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
line-height: 28px !important;
|
||||||
|
/* 图标行高=输入框高度,与文字对齐 */
|
||||||
|
vertical-align: middle !important;
|
||||||
|
/* 强制图标垂直对齐 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. 图标伪类:确保颜色和对齐继承 */
|
||||||
|
.el-icon-date::before {
|
||||||
|
color: #ffffff !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
line-height: inherit !important;
|
||||||
|
/* 继承父级行高,避免错位 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
282
src/views/home/depreciationAnalysisComponents/container.vue
Normal file
282
src/views/home/depreciationAnalysisComponents/container.vue
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
<template>
|
||||||
|
<div class="cockpitContainer" :class="['cockpitContainer__' + size]">
|
||||||
|
<div class="content-top" :class="['content-top__' + topSize]">
|
||||||
|
<!-- 使用 flex 容器包裹图标和文字,实现垂直居中 -->
|
||||||
|
<div class="title-wrapper">
|
||||||
|
<svg-icon class="title-icon" :icon-class="icon" />
|
||||||
|
<span class="title-text">
|
||||||
|
{{ name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cockpitContainer-body">
|
||||||
|
<slot>
|
||||||
|
<div class="test-body">something test....</div>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Container',
|
||||||
|
components: {},
|
||||||
|
// eslint-disable-next-line vue/require-prop-types
|
||||||
|
props: ['name', 'size', 'icon', 'topSize'],
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.cockpitContainer {
|
||||||
|
display: inline-block;
|
||||||
|
// width: 100%;
|
||||||
|
// height: 100%;
|
||||||
|
padding: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.content-top {
|
||||||
|
height: 60px;
|
||||||
|
|
||||||
|
.title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 10px;
|
||||||
|
/* 垂直居中关键属性 */
|
||||||
|
height: 100%;
|
||||||
|
/* 继承父容器高度,确保垂直居中范围 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
font-size: 30px;
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
/* 图标和文字之间的间距 */
|
||||||
|
flex-shrink: 0;
|
||||||
|
/* 防止图标被压缩 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 移除固定行高,避免影响垂直对齐
|
||||||
|
// line-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// width: 547px;
|
||||||
|
// background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
&__basic {
|
||||||
|
// width: 547px;
|
||||||
|
background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__middle {
|
||||||
|
background: url(../../../assets/img/topTileMiddle.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__large {
|
||||||
|
background: url(../../../assets/img/topTitleLargeBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__KFAPTopTitle {
|
||||||
|
background: url(../../../assets/img/KFAPTopTitle.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiTopTitleBasic {
|
||||||
|
background: url(../../../assets/img/psiTopTitleBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__rawTopTitleLarge {
|
||||||
|
background: url(../../../assets/img/rawTopTitleLarge.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__topBasic {
|
||||||
|
background: url(../../../assets/img/top-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bottomBasic {
|
||||||
|
background: url(../../../assets/img/bottom-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingBasic {
|
||||||
|
background: url(../../../assets/img/operating-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingLarge {
|
||||||
|
background: url(../../../assets/img/operating-large.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__profitTopBasic {
|
||||||
|
background: url(../../../assets/img/profitTopBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__profitMiddleBasic {
|
||||||
|
background: url(../../../assets/img/profitMiddleBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiBasicBg {
|
||||||
|
background: url(../../../assets/img/psiBasicBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiMiddleBg {
|
||||||
|
background: url(../../../assets/img/psiMiddleBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingRevenueBg {
|
||||||
|
background: url(../../../assets/img/operatingRevenueBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__costBasicBg {
|
||||||
|
background: url(../../../assets/img/costBasicBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__rawTopBg {
|
||||||
|
background: url(../../../assets/img/rawTopBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__opLargeBg {
|
||||||
|
background: url(../../../assets/img/opLargeBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// &__left {
|
||||||
|
// background: url(../../../../../../../assets/img/left.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__energyConsumption {
|
||||||
|
// background: url(../../../../../../../assets/img/energyConsumption.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__left2 {
|
||||||
|
// background: url(../../assets/left_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__left3 {
|
||||||
|
// background: url(../../assets/left_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__mid2 {
|
||||||
|
// background: url(../../assets/mid_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__mid3 {
|
||||||
|
// background: url(../../assets/mid_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right1 {
|
||||||
|
// background: url(../../assets/right_1.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right2 {
|
||||||
|
// background: url(../../assets/right_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right3 {
|
||||||
|
// background: url(../../assets/right_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekRight2 {
|
||||||
|
// background: url(../../assets/week_right_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekMidTop {
|
||||||
|
// background: url(../../assets/week-mid-top.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekMidMid {
|
||||||
|
// background: url(../../assets/week-mid-mid.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: ' ';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
// background: inherit;
|
||||||
|
/* 设置模糊,不用 filter */
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-body {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="chartBox" style="width: 100%; height: 108px; position: relative;">
|
||||||
|
<div :id="id" style="width: 100%; height:100%;"></div>
|
||||||
|
<div class="bottomTip">
|
||||||
|
<div class="precent">
|
||||||
|
<span class="precentNum">{{ detailData.rate || 0 }}% </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'EnergyConsumption',
|
||||||
|
// components: { Container },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
detailData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
// electricComu: 0,
|
||||||
|
// steamComu: 20, // 调整为符合max范围的数值(0-8)
|
||||||
|
// // electricity: [120, 150, 130, 180, 160, 200, 190],
|
||||||
|
// // steam: [80, 95, 85, 110, 100, 120, 115],
|
||||||
|
// // dates: ['1日', '2日', '3日', '4日', '5日', '6日', '7日']
|
||||||
|
})
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
default: () => ('monthG')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// electricityChart: null,
|
||||||
|
// steamChart: null,
|
||||||
|
// specialTicks: [2, 4, 6, 8], // 统一的刻度显示
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
detailData: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true, // 初始化时立即执行
|
||||||
|
handler() {
|
||||||
|
this.updateGauges()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initGauges()
|
||||||
|
// window.addEventListener('resize', this.handleResize)
|
||||||
|
this.observeContainerResize()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
observeContainerResize() {
|
||||||
|
// 修复:获取正确的容器(组件内的.gauge-container)
|
||||||
|
const container = this.$el.querySelector('.gauge-container')
|
||||||
|
if (container && window.ResizeObserver) {
|
||||||
|
this.resizeObserver = new ResizeObserver(entries => {
|
||||||
|
if (this.electricityChart) {
|
||||||
|
this.electricityChart.resize() // 直接触发resize,无需防抖
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.resizeObserver.observe(container)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initGauges() {
|
||||||
|
// console.log('this.id',this.id);
|
||||||
|
|
||||||
|
// 初始化电气图表实例
|
||||||
|
const electricityDom = document.getElementById(this.id)
|
||||||
|
if (electricityDom) {
|
||||||
|
// 修复:正确创建并存储图表实例
|
||||||
|
this.electricityChart = echarts.init(electricityDom)
|
||||||
|
// 首次更新数据
|
||||||
|
this.updateGauges()
|
||||||
|
}
|
||||||
|
// 蒸汽图表若未使用,可注释/删除
|
||||||
|
// const steamDom = document.getElementById('steamGauge')
|
||||||
|
// if (steamDom) {
|
||||||
|
// this.steamChart = echarts.init(steamDom)
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
updateGauges() {
|
||||||
|
// 修复:先判断实例是否存在,再更新配置
|
||||||
|
if (!this.electricityChart) return
|
||||||
|
|
||||||
|
// 修复:兜底获取rate值,确保数值有效
|
||||||
|
const rate = Number(this.detailData?.rate) || 0
|
||||||
|
console.log('当前rate值:', rate); // 调试:确认rate值正确
|
||||||
|
|
||||||
|
// 关键:第二个参数传true,清空原有配置,强制更新
|
||||||
|
this.electricityChart.setOption(this.getElectricityGaugeOption(rate), true)
|
||||||
|
},
|
||||||
|
// 用电量仪表盘配置(保留原有样式,优化数值范围)
|
||||||
|
getElectricityGaugeOption(value) {
|
||||||
|
const electricityGradient = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: '#0B58FF' },
|
||||||
|
{ offset: 1, color: '#32FFCD' }
|
||||||
|
])
|
||||||
|
|
||||||
|
return {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '月度',
|
||||||
|
type: 'gauge',
|
||||||
|
radius: '95', // 修复:添加%,避免数值错误
|
||||||
|
center: ['50%', '90%'],
|
||||||
|
startAngle: 180,
|
||||||
|
endAngle: 0,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
splitNumber: 4,
|
||||||
|
label: { show: false },
|
||||||
|
progress: {
|
||||||
|
show: true,
|
||||||
|
overlap: false,
|
||||||
|
roundCap: true,
|
||||||
|
clip: false,
|
||||||
|
width: 14,
|
||||||
|
itemStyle: { color: electricityGradient }
|
||||||
|
},
|
||||||
|
pointer: {
|
||||||
|
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
|
||||||
|
length: '75%',
|
||||||
|
width: 16,
|
||||||
|
itemStyle: { color: '#288AFF' },
|
||||||
|
offsetCenter: [0, '10%']
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
roundCap: true,
|
||||||
|
lineStyle: { width: 12, color: [[1, '#E6EBF7']] }
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
length: 10,
|
||||||
|
lineStyle: { width: 5, color: '#D6DAE5' },
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
splitNumber: 2,
|
||||||
|
length: 6,
|
||||||
|
lineStyle: { width: 2, color: '#D6DAE5' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
detail: { show: false },
|
||||||
|
data: [{ value: value, unit: '' }] // 确保数值正确传入
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 未使用的蒸汽仪表盘可注释/删除
|
||||||
|
// getSteamGaugeOption(value) { ... }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
.chartBox {
|
||||||
|
.bottomTip {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.precent {
|
||||||
|
line-height: 3px;
|
||||||
|
|
||||||
|
&::after,
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 54px;
|
||||||
|
height: 5px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
margin-right: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用电量线条颜色
|
||||||
|
.precent::before {
|
||||||
|
|
||||||
|
background: linear-gradient(90deg, rgba(40, 138, 255, 0) 0%, rgba(12, 125, 254, 0.4) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ::after:从 透明 到 rgba(12, 125, 254, 0.4)(90度渐变,左到右) */
|
||||||
|
.precent::after {
|
||||||
|
background: linear-gradient(90deg, rgba(12, 125, 254, 0.4) 0%, rgba(40, 138, 255, 0) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蒸汽线条颜色
|
||||||
|
// .steam-precent::after,
|
||||||
|
// .steam-precent::before {
|
||||||
|
// background: linear-gradient(90deg, rgba(11, 168, 255, 0.26) 0%, rgba(54, 239, 230, 1) 100%);
|
||||||
|
// }
|
||||||
|
|
||||||
|
.precentNum {
|
||||||
|
display: inline-block;
|
||||||
|
// width: 52px;
|
||||||
|
height: 22px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蒸汽数字颜色
|
||||||
|
.steam-num {
|
||||||
|
color: rgba(54, 239, 230, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
389
src/views/home/depreciationAnalysisComponents/operatingBar.vue
Normal file
389
src/views/home/depreciationAnalysisComponents/operatingBar.vue
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<div class="header-row">
|
||||||
|
<div class="base-title">各基地情况</div>
|
||||||
|
<div class="barTop">
|
||||||
|
<div class="right-container">
|
||||||
|
<div class="legend">
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon line yield"></span>完成率
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square target"></span>预算
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square achieved"></span>实际·达标
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square unachieved"></span>实际·未达标
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<div class="item-button category-btn">
|
||||||
|
<span class="item-text">展示顺序</span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-container">
|
||||||
|
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||||
|
<span class="item-text profit-text">{{ selectedSort || '请选择' }}</span>
|
||||||
|
<span class="dropdown-arrow" :class="{ 'rotate': isDropdownShow }"></span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-options" v-if="isDropdownShow">
|
||||||
|
<div class="dropdown-option" v-for="(item, index) in profitOptions" :key="index"
|
||||||
|
@click.stop="selectProfit(item)">
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||||
|
<operatingLineBar :dateData="dateData" :chartData="chartD" style="height: 100%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSale.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData", 'dateData'],
|
||||||
|
emits: ['sort-change'], // 声明事件(Vue3 推荐)
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
isDropdownShow: false,
|
||||||
|
selectedSort: '实际值:高~低', // 选中的label
|
||||||
|
selectedSortValue: 1, // 选中的value,用于排序逻辑
|
||||||
|
profitOptions: [
|
||||||
|
{ label: '实际值:高~低', value: 1 },
|
||||||
|
{ label: '实际值:低~高', value: 2 },
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 排序后的数据源(核心:根据selectedSortValue重新排序)
|
||||||
|
currentDataSource() {
|
||||||
|
if (!this.chartData?.factory) return {};
|
||||||
|
|
||||||
|
// 深拷贝原始数据,避免修改原数据
|
||||||
|
const factory = JSON.parse(JSON.stringify(this.chartData.factory));
|
||||||
|
if (!factory.locations.length || !this.selectedSortValue) return factory;
|
||||||
|
|
||||||
|
// 构建带索引的数组,方便同步所有字段排序
|
||||||
|
const dataWithIndex = factory.locations.map((name, index) => ({
|
||||||
|
index,
|
||||||
|
name,
|
||||||
|
real: factory.reals[index],
|
||||||
|
target: factory.targets[index],
|
||||||
|
rate: factory.rates[index],
|
||||||
|
diff: factory.diff[index],
|
||||||
|
flag: factory.flags[index]
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 根据选中的排序规则排序
|
||||||
|
switch (this.selectedSortValue) {
|
||||||
|
case 1: // 实际值:高~低
|
||||||
|
dataWithIndex.sort((a, b) => b.real - a.real);
|
||||||
|
break;
|
||||||
|
case 2: // 实际值:低~高
|
||||||
|
dataWithIndex.sort((a, b) => a.real - b.real);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步更新所有数组
|
||||||
|
factory.locations = dataWithIndex.map(item => item.name);
|
||||||
|
factory.reals = dataWithIndex.map(item => item.real);
|
||||||
|
factory.targets = dataWithIndex.map(item => item.target);
|
||||||
|
factory.rates = dataWithIndex.map(item => item.rate);
|
||||||
|
factory.diff = dataWithIndex.map(item => item.diff);
|
||||||
|
factory.flags = dataWithIndex.map(item => item.flag);
|
||||||
|
|
||||||
|
return factory;
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
return this.currentDataSource.locations || [];
|
||||||
|
},
|
||||||
|
// 最终传递给图表的排序后数据
|
||||||
|
chartD() {
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 40,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const safeFlag = data.flags || [];
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals || []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
return salesData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectProfit(item) {
|
||||||
|
// 更新选中的label和value
|
||||||
|
this.selectedSort = item.label;
|
||||||
|
this.selectedSortValue = item.value;
|
||||||
|
this.isDropdownShow = false;
|
||||||
|
// 向父组件传递排序事件(可选,保持原有逻辑)
|
||||||
|
this.$emit('sort-change', item.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 监听父组件传入的chartData变化,重置选中状态(可选)
|
||||||
|
watch: {
|
||||||
|
'chartData.factory': {
|
||||||
|
handler() {
|
||||||
|
// 若需要切换数据源后重置排序,可取消注释
|
||||||
|
// this.selectedSort = null;
|
||||||
|
// this.selectedSortValue = null;
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.header-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0 0 0 16px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
width: auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
margin-right: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.dropdown-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
fontSize: 12px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-btn {
|
||||||
|
width: 75px;
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-bottom-left-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-btn {
|
||||||
|
width: 123px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 18px 0 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #3071ff;
|
||||||
|
color: rgba(249, 252, 255, .8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-text {
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 6px solid currentColor;
|
||||||
|
border-top: 4px solid transparent;
|
||||||
|
border-bottom: 4px solid transparent;
|
||||||
|
border-right: 4px solid transparent;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
|
||||||
|
&.rotate {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-options {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
right: 0;
|
||||||
|
margin-top: 2px;
|
||||||
|
width: 123px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.dropdown-option {
|
||||||
|
padding: 6px 12px;
|
||||||
|
fontSize: 12px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
color: #3071ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChipBottom" id="cockpitEffChipBottom" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
series: [],
|
||||||
|
allPlaceNames: []
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
validator: (value) => {
|
||||||
|
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChipBottom;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatter: (params) => {
|
||||||
|
let html = `${params[0].axisValue}<br/>`;
|
||||||
|
params.forEach(item => {
|
||||||
|
const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
);
|
||||||
|
html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 60,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
splitNumber: 4,
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: (value) => Math.ceil((value.max || 0) * 1.1),
|
||||||
|
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
scale:true,
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
series: [],
|
||||||
|
allPlaceNames: []
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
validator: (value) => {
|
||||||
|
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 60,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
// {
|
||||||
|
// type: 'value',
|
||||||
|
// nameTextStyle: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// align: 'left'
|
||||||
|
// },
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
// axisTick: { show: false },
|
||||||
|
// axisLabel: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// formatter: '{value}%'
|
||||||
|
// },
|
||||||
|
// splitLine: { show: false },
|
||||||
|
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
// splitNumber: 4
|
||||||
|
// }
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null, // 存储图表实例
|
||||||
|
resizeHandler: null, // 存储resize事件处理函数
|
||||||
|
// 核心:基地名称与序号的映射表(固定顺序)
|
||||||
|
baseNameToIndexMap: {
|
||||||
|
'宜兴': 7,
|
||||||
|
'漳州': 8,
|
||||||
|
'自贡': 3,
|
||||||
|
'桐城': 2,
|
||||||
|
'洛阳': 9,
|
||||||
|
'合肥': 5,
|
||||||
|
'宿迁': 6,
|
||||||
|
'秦皇岛': 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.initChart(); // 初始化图表(只执行一次)
|
||||||
|
this.updateChart(); // 更新图表数据
|
||||||
|
});
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
this.updateChart(); // 仅更新数据,不重新创建实例
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 组件销毁时清理资源
|
||||||
|
this.destroyChart();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 初始化图表(只在mounted中执行一次)
|
||||||
|
initChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只创建一次图表实例
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
// 绑定点击事件(只绑定一次,永久生效)
|
||||||
|
this.myChart.getZr().on('click', (params) => {
|
||||||
|
console.log('params', params);
|
||||||
|
|
||||||
|
// 提取点击的基地名称
|
||||||
|
// const itemName = params.name;
|
||||||
|
let itemName = undefined
|
||||||
|
// 根据映射表获取对应的序号(未匹配到则返回0或其他默认值)
|
||||||
|
let pointInPixel = [params.offsetX, params.offsetY];
|
||||||
|
if (this.myChart.containPixel('grid', pointInPixel)) {
|
||||||
|
let pointInGrid = this.myChart.convertFromPixel({
|
||||||
|
seriesIndex: 0
|
||||||
|
}, pointInPixel);
|
||||||
|
let xIndex = pointInGrid[0]; //索引
|
||||||
|
let handleIndex = Number(xIndex);
|
||||||
|
let seriesObj = this.myChart.getOption(); //图表object对象
|
||||||
|
var op = this.myChart.getOption();
|
||||||
|
//获得图表中点击的列
|
||||||
|
itemName = op.xAxis[0].data[handleIndex]; //获取点击的列名
|
||||||
|
console.log(itemName, 'monthmonthmonth');
|
||||||
|
console.log(handleIndex, seriesObj);
|
||||||
|
};
|
||||||
|
const baseIndex = this.baseNameToIndexMap[itemName] || 0;
|
||||||
|
|
||||||
|
console.log(`你点击了【${itemName}】(序号:${baseIndex})`);
|
||||||
|
if (itemName === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定义resize处理函数(命名函数,方便移除)
|
||||||
|
this.resizeHandler = () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
};
|
||||||
|
// 绑定resize事件(只绑定一次)
|
||||||
|
window.addEventListener('resize', this.resizeHandler);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新图表数据(数据变化时执行)
|
||||||
|
updateChart() {
|
||||||
|
if (!this.myChart) {
|
||||||
|
return; // 实例未初始化则返回
|
||||||
|
}
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || [];
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 5,
|
||||||
|
right: 20,
|
||||||
|
left: 25,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0],
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// type: 'value',
|
||||||
|
// nameTextStyle: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// align: 'left'
|
||||||
|
// },
|
||||||
|
// axisTick: { show: false },
|
||||||
|
// axisLabel: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// formatter: '{value}%'
|
||||||
|
// },
|
||||||
|
// splitLine: { show: false },
|
||||||
|
// axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
// splitNumber: 4
|
||||||
|
// }
|
||||||
|
],
|
||||||
|
series: chartSeries
|
||||||
|
};
|
||||||
|
|
||||||
|
// 只更新配置,不重新创建实例
|
||||||
|
this.myChart.setOption(option, true); // 第二个参数true表示清空原有配置,避免数据残留
|
||||||
|
},
|
||||||
|
|
||||||
|
// 销毁图表资源
|
||||||
|
destroyChart() {
|
||||||
|
// 移除resize事件
|
||||||
|
if (this.resizeHandler) {
|
||||||
|
window.removeEventListener('resize', this.resizeHandler);
|
||||||
|
}
|
||||||
|
// 销毁图表实例
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
this.myChart = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 380px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
console.log('chartData', this.chartData);
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
console.log('xData', xData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom:5,
|
||||||
|
right: 10,
|
||||||
|
left: 25,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
//
|
||||||
|
splitNumber: 4,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 200px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
refName: {
|
||||||
|
type: String,
|
||||||
|
default: () => 'cockpitEffChip',
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs[this.refName];
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
console.log('chartData', this.chartData);
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
console.log('xData', xData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
// axisPointer: {
|
||||||
|
// type: 'cross',
|
||||||
|
// label: {
|
||||||
|
// backgroundColor: '#6a7985'
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 20,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 5,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
// name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true, // 显示Y轴轴线(关键)
|
||||||
|
lineStyle: {
|
||||||
|
color: '#E5E6EB', // 轴线颜色(浅灰色,可自定义)
|
||||||
|
width: 1, // 轴线宽度
|
||||||
|
type: 'solid' // 实线(可选:dashed虚线、dotted点线)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 2,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
},
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,327 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="当月数据对比" icon="cockpitItemIcon" size="operatingLarge" topSize="large">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="left" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 348px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
">
|
||||||
|
集团情况
|
||||||
|
</div>
|
||||||
|
<operatingTopBar :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
<div class="right" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 1220px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<operatingBar :dateData="dateData" :chartData="chartData" @sort-change="sortChange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import operatingBar from "./operatingBar.vue";
|
||||||
|
import operatingTopBar from "./operatingTopBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, operatingBar, operatingTopBar },
|
||||||
|
props: {
|
||||||
|
monthData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
group: {
|
||||||
|
rate: 0,
|
||||||
|
diff: 0,
|
||||||
|
real: 0,
|
||||||
|
target: 0
|
||||||
|
},
|
||||||
|
factory: []
|
||||||
|
}),
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: null, // 初始化 chartData 为 null
|
||||||
|
groupData: {}, // 集团数据
|
||||||
|
factoryData: [] // 工厂数据
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
monthData: {
|
||||||
|
handler() {
|
||||||
|
this.processChartData();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
sortChange(value) {
|
||||||
|
this.$emit('sort-change', value);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 核心处理函数:在所有数据都准备好后,才组装 chartData
|
||||||
|
*/
|
||||||
|
processChartData() {
|
||||||
|
// 1. 处理集团数据 - 提取各字段到对应数组
|
||||||
|
this.groupData = this.monthData.group || { rate: 0, diff: 0, real: 0, target: 0 };
|
||||||
|
|
||||||
|
// 集团各维度数据数组(单条数据,对应凯盛新能)
|
||||||
|
const groupTarget = [this.groupData.target]; // 预算值数组
|
||||||
|
const groupDiff = [this.groupData.diff]; // 差值数组
|
||||||
|
const groupReal = [this.groupData.real]; // 实际值数组
|
||||||
|
const groupRate = [this.groupData.rate]; // 完成率数组
|
||||||
|
// 新增:集团rate对应的flag
|
||||||
|
const groupFlag = [this.groupData.rate];
|
||||||
|
|
||||||
|
console.log('集团数据数组:', {
|
||||||
|
groupTarget,
|
||||||
|
groupDiff,
|
||||||
|
groupReal,
|
||||||
|
groupRate,
|
||||||
|
groupFlag,
|
||||||
|
rawGroupData: this.groupData
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 处理工厂数据 - 提取每个工厂的对应字段到数组
|
||||||
|
this.factoryData = this.monthData.factory || [];
|
||||||
|
// 提取工厂名称数组
|
||||||
|
const factoryNames = this.factoryData.map(item => item.title || '');
|
||||||
|
// 提取工厂各维度数据数组
|
||||||
|
const factoryBudget = this.factoryData.map(item => item.budget || 0);
|
||||||
|
const factoryReal = this.factoryData.map(item => item.real || 0);
|
||||||
|
const factoryRate = this.factoryData.map(item => item.rate || 0);
|
||||||
|
const factoryDiff = this.factoryData.map(item => item.diff || 0);
|
||||||
|
// 新增:每个工厂rate对应的flag数组
|
||||||
|
const factoryFlags = this.factoryData.map(item => item.rate >= 100 ? 1 : 0);
|
||||||
|
|
||||||
|
// 3. 组装最终的chartData(供子组件使用)
|
||||||
|
this.chartData = {
|
||||||
|
// 集团数据(对应凯盛新能)
|
||||||
|
group: {
|
||||||
|
locations: ['凯盛新能'], // 集团名称
|
||||||
|
targets: groupTarget, // 集团预算值数组
|
||||||
|
diff: groupDiff, // 集团差值数组
|
||||||
|
reals: groupReal, // 集团实际值数组
|
||||||
|
rate: groupRate, // 集团完成率数组
|
||||||
|
flags: groupFlag // 新增:集团rate对应的flag
|
||||||
|
},
|
||||||
|
// 工厂数据
|
||||||
|
factory: {
|
||||||
|
locations: factoryNames, // 工厂名称数组
|
||||||
|
targets: factoryBudget, // 工厂预算数组
|
||||||
|
reals: factoryReal, // 工厂实际值数组
|
||||||
|
rates: factoryRate, // 工厂完成率数组
|
||||||
|
diff: factoryDiff, // 工厂差值数组
|
||||||
|
flags: factoryFlags // 新增:工厂rate对应的flags数组
|
||||||
|
},
|
||||||
|
// 原始数据备份(方便后续使用)
|
||||||
|
rawData: {
|
||||||
|
group: this.groupData,
|
||||||
|
factory: this.factoryData
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('最终处理后的图表数据:', this.chartData);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||||
|
.scroll-container {
|
||||||
|
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||||
|
max-height: 210px;
|
||||||
|
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||||
|
overflow-y: auto;
|
||||||
|
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||||
|
/* Chrome/Safari */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* IE/Edge */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||||
|
.proBarInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px 27px;
|
||||||
|
/* 调整内边距,优化排版 */
|
||||||
|
margin-bottom: 10px;
|
||||||
|
/* 设备项之间的垂直间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 原有样式保留,优化细节 */
|
||||||
|
.proBarInfoEqInfo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
/* 垂直居中,避免序号/文字错位 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
width: 21px;
|
||||||
|
height: 23px;
|
||||||
|
background: rgba(0, 106, 205, 0.22);
|
||||||
|
backdrop-filter: blur(1.5px);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #68b5ff;
|
||||||
|
line-height: 23px;
|
||||||
|
/* 垂直居中文字 */
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eq-name {
|
||||||
|
margin-left: 8px;
|
||||||
|
/* 增加与序号的间距 */
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eqStatus {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitLine {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
border: 1px solid #adadad;
|
||||||
|
margin: 0 8px;
|
||||||
|
/* 优化分割线间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #00ffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoLeft {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 序号和设备名垂直居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoRight {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 状态/分割线/百分比垂直居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarWrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
/* 进度条与上方信息的间距 */
|
||||||
|
border-radius: 5px;
|
||||||
|
/* 进度条圆角,优化视觉 */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLine {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #acacac 100%);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLineTop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg,
|
||||||
|
rgba(53, 223, 247, 0) 0%,
|
||||||
|
rgba(54, 220, 246, 0.92) 92%,
|
||||||
|
#36f6e5 100%,
|
||||||
|
#37acf5 100%);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
/* 进度变化时添加过渡动画,更流畅 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图表相关样式保留 */
|
||||||
|
.chartImgBottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 45px;
|
||||||
|
left: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
left: 57px;
|
||||||
|
bottom: 42px;
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #00e8ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,343 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="累计数据对比" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="left" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 348px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
">
|
||||||
|
集团情况
|
||||||
|
</div>
|
||||||
|
<operatingTopBar :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
<div class="right" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 1220px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<!-- <top-item /> -->
|
||||||
|
<operatingBar :dateData="dateData" @sort-change="sortChange" :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import operatingBar from "./operatingBar.vue";
|
||||||
|
import operatingTopBar from "./operatingTopBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, operatingBar, operatingTopBar },
|
||||||
|
props: {
|
||||||
|
salesTrendMap: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
ytdData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: null, // 初始化 chartData 为 null
|
||||||
|
groupData: {}, // 集团数据
|
||||||
|
factoryData: [] // 工厂数据
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
ytdData: {
|
||||||
|
handler() {
|
||||||
|
this.processChartData();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
sortChange(value) {
|
||||||
|
this.$emit('sort-change', value);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 判断rate对应的flag值(<1为0,>1为1)
|
||||||
|
* @param {number} rate 处理后的rate值(已*100)
|
||||||
|
* @returns {0|1} flag值
|
||||||
|
*/
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心处理函数:在所有数据都准备好后,才组装 chartData
|
||||||
|
*/
|
||||||
|
processChartData() {
|
||||||
|
// 1. 处理集团数据 - 提取各字段到对应数组
|
||||||
|
this.groupData = this.ytdData.group || { rate: 0, diff: 0, real: 0, target: 0 };
|
||||||
|
|
||||||
|
// 集团各维度数据数组(单条数据,对应凯盛新能)
|
||||||
|
const groupTarget = [this.groupData.target]; // 预算值数组
|
||||||
|
const groupDiff = [this.groupData.diff]; // 差值数组
|
||||||
|
const groupReal = [this.groupData.real]; // 实际值数组
|
||||||
|
const groupRate = [this.groupData.rate]; // 完成率数组
|
||||||
|
// 新增:集团rate对应的flag
|
||||||
|
const groupFlag = [this.getRateFlag(groupRate[0], groupReal[0], groupTarget[0])];
|
||||||
|
|
||||||
|
console.log('集团数据数组:', {
|
||||||
|
groupTarget,
|
||||||
|
groupDiff,
|
||||||
|
groupReal,
|
||||||
|
groupRate,
|
||||||
|
groupFlag,
|
||||||
|
rawGroupData: this.groupData
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 处理工厂数据 - 提取每个工厂的对应字段到数组
|
||||||
|
this.factoryData = this.ytdData.factory || [];
|
||||||
|
// 提取工厂名称数组
|
||||||
|
const factoryNames = this.factoryData.map(item => item.title || '');
|
||||||
|
// 提取工厂各维度数据数组
|
||||||
|
const factoryBudget = this.factoryData.map(item => item.budget || 0);
|
||||||
|
const factoryReal = this.factoryData.map(item => item.real || 0);
|
||||||
|
const factoryRate = this.factoryData.map(item => item.rate || 0);
|
||||||
|
const factoryDiff = this.factoryData.map(item => item.diff || 0);
|
||||||
|
// 新增:每个工厂rate对应的flag数组
|
||||||
|
const factoryFlags = this.factoryData.map(item => this.getRateFlag(item.rate, item.real, item.budget));
|
||||||
|
|
||||||
|
// 3. 组装最终的chartData(供子组件使用)
|
||||||
|
this.chartData = {
|
||||||
|
// 集团数据(对应凯盛新能)
|
||||||
|
group: {
|
||||||
|
locations: ['凯盛新能'], // 集团名称
|
||||||
|
targets: groupTarget, // 集团预算值数组
|
||||||
|
diff: groupDiff, // 集团差值数组
|
||||||
|
reals: groupReal, // 集团实际值数组
|
||||||
|
rate: groupRate, // 集团完成率数组
|
||||||
|
flags: groupFlag // 新增:集团rate对应的flag
|
||||||
|
},
|
||||||
|
// 工厂数据
|
||||||
|
factory: {
|
||||||
|
locations: factoryNames, // 工厂名称数组
|
||||||
|
targets: factoryBudget, // 工厂预算数组
|
||||||
|
reals: factoryReal, // 工厂实际值数组
|
||||||
|
rates: factoryRate, // 工厂完成率数组
|
||||||
|
diff: factoryDiff, // 工厂差值数组
|
||||||
|
flags: factoryFlags // 新增:工厂rate对应的flags数组
|
||||||
|
},
|
||||||
|
// 原始数据备份(方便后续使用)
|
||||||
|
rawData: {
|
||||||
|
group: this.groupData,
|
||||||
|
factory: this.factoryData
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('最终处理后的图表数据:', this.chartData);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||||
|
.scroll-container {
|
||||||
|
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||||
|
max-height: 210px;
|
||||||
|
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||||
|
overflow-y: auto;
|
||||||
|
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||||
|
/* Chrome/Safari */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* IE/Edge */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||||
|
.proBarInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px 27px;
|
||||||
|
/* 调整内边距,优化排版 */
|
||||||
|
margin-bottom: 10px;
|
||||||
|
/* 设备项之间的垂直间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 原有样式保留,优化细节 */
|
||||||
|
.proBarInfoEqInfo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
/* 垂直居中,避免序号/文字错位 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
width: 21px;
|
||||||
|
height: 23px;
|
||||||
|
background: rgba(0, 106, 205, 0.22);
|
||||||
|
backdrop-filter: blur(1.5px);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #68b5ff;
|
||||||
|
line-height: 23px;
|
||||||
|
/* 垂直居中文字 */
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eq-name {
|
||||||
|
margin-left: 8px;
|
||||||
|
/* 增加与序号的间距 */
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eqStatus {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitLine {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
border: 1px solid #adadad;
|
||||||
|
margin: 0 8px;
|
||||||
|
/* 优化分割线间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #00ffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoLeft {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 序号和设备名垂直居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoRight {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 状态/分割线/百分比垂直居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarWrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
/* 进度条与上方信息的间距 */
|
||||||
|
border-radius: 5px;
|
||||||
|
/* 进度条圆角,优化视觉 */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLine {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #acacac 100%);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLineTop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(
|
||||||
|
65deg,
|
||||||
|
rgba(53, 223, 247, 0) 0%,
|
||||||
|
rgba(54, 220, 246, 0.92) 92%,
|
||||||
|
#36f6e5 100%,
|
||||||
|
#37acf5 100%
|
||||||
|
);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
/* 进度变化时添加过渡动画,更流畅 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图表相关样式保留 */
|
||||||
|
.chartImgBottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 45px;
|
||||||
|
left: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
left: 57px;
|
||||||
|
bottom: 42px;
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #00e8ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
<template>
|
||||||
|
<div class="lineBottom" style="height: 180px; width: 100%">
|
||||||
|
<operatingLineBarSaleSingle :refName=" 'totalOperating' " :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBarSaleSingle from './operatingLineBarSaleSingle.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBarSaleSingle },
|
||||||
|
props: ["detailData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
locations() {
|
||||||
|
return ['预算', '实际'];
|
||||||
|
},
|
||||||
|
chartD() {
|
||||||
|
// 背景图片路径(若不需要可注释)
|
||||||
|
// const bgImageUrl = require('@/assets/img/labelBg.png');
|
||||||
|
console.log('this.detailData', this.detailData);
|
||||||
|
const rate = this.detailData?.rate || 0
|
||||||
|
const diff = this.detailData?.diff || 0
|
||||||
|
console.log('diff', diff);
|
||||||
|
|
||||||
|
const seriesData = [
|
||||||
|
{
|
||||||
|
value: this.detailData?.budget || 0,
|
||||||
|
flag: 1, // 实际项:达标(绿色)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: this.detailData?.real || 0,
|
||||||
|
flag: this.detailData?.flag, // 实际项:达标(绿色)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
allPlaceNames: ['预算', '实际'],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 60,
|
||||||
|
barCategoryGap: '50%',
|
||||||
|
data: seriesData,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const currentFlag = params.data.flag || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
console.log('data', data);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFangSC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
margin-right: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
width: 283px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0b58ff;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 8px;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button.active {
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
background: #3071ff;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<div class="lineBottom" style="height: 320px; width: 100%">
|
||||||
|
<operatingLineBar :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSaleGroup.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentDataSource() {
|
||||||
|
console.log('yyyy', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData.group
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
console.log('this.chartData', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData.group.locations
|
||||||
|
},
|
||||||
|
// 根据按钮切换生成对应的 chartData
|
||||||
|
chartD() {
|
||||||
|
// 销量场景数据
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
const diff = this.currentDataSource.diff[0]
|
||||||
|
const rate = this.currentDataSource.rate[0]
|
||||||
|
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||||
|
const safeFlag = data.flags;
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals // 实际销量(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据按钮状态返回对应数据
|
||||||
|
return this.activeButton === 0 ? salesData : grossProfitData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end; // 标题左、右侧容器右,整体两端对齐
|
||||||
|
align-items: center; // 垂直居中,避免上下错位
|
||||||
|
gap: 16px; // 标题与右侧容器的最小间距,防止拥挤
|
||||||
|
width: 100%; // 确保占满父容器,实现两端对齐
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 标题固定在左侧,不挤压右侧空间
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; // 图例和按钮组垂直居中
|
||||||
|
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||||
|
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 图例:在右侧容器内横向排列
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px; // 图例项之间间距,避免重叠
|
||||||
|
align-items: center;
|
||||||
|
// 移除原margin-left,避免位置偏移
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap; // 防止图例文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图例颜色
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
width: 283px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
border-radius: 12px;
|
||||||
|
// 移除原margin-right,由右侧容器统一控制
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0b58ff;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 8px; // 确保文字间距生效
|
||||||
|
padding-left: 8px; // 抵消letter-spacing导致的文字左偏
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button.active {
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
background: #3071ff;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
<template>
|
||||||
|
<div id="dayReport" class="dayReport" :style="styles">
|
||||||
|
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||||
|
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||||
|
<ReportHeader :dateData="dateData" top-title="电费分析" :is-full-screen="isFullScreen"
|
||||||
|
@screenfullChange="screenfullChange" @timeRangeChange="handleTimeChange" />
|
||||||
|
<div class="main-body" style="
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
padding: 0px 16px 0 272px;
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div class="top" style="margin-top: -20px; display: flex; gap: 16px">
|
||||||
|
<div class="top-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns:1624px;
|
||||||
|
">
|
||||||
|
<operatingLineChart :dateData="dateData" :monthData="monthData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||||
|
<div class="left-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns: 1624px;
|
||||||
|
">
|
||||||
|
<operatingLineChartCumulative :dateData="dateData" :ytdData="ytdData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import ReportHeader from "../components/noRouterHeader.vue";
|
||||||
|
import { Sidebar } from "../../../layout/components";
|
||||||
|
import screenfull from "screenfull";
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
import operatingLineChart from "../electricityCostAnalysisComponents/operatingLineChart";
|
||||||
|
import operatingLineChartCumulative from "../electricityCostAnalysisComponents/operatingLineChartCumulative.vue";
|
||||||
|
|
||||||
|
import { getElectricityCostAnalysisData } from '@/api/cockpit'
|
||||||
|
export default {
|
||||||
|
name: "electricityCostAnalysis",
|
||||||
|
components: {
|
||||||
|
ReportHeader,
|
||||||
|
operatingLineChartCumulative,
|
||||||
|
operatingLineChart,
|
||||||
|
Sidebar,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
weekArr: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
|
||||||
|
isFullScreen: false,
|
||||||
|
timer: null,
|
||||||
|
beilv: 1,
|
||||||
|
value: 100,
|
||||||
|
sort:1,
|
||||||
|
selectDate:{},
|
||||||
|
monthData: {},
|
||||||
|
ytdData:{},
|
||||||
|
dateData: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.init();
|
||||||
|
this.windowWidth(document.documentElement.clientWidth);
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
theme: (state) => state.settings.theme,
|
||||||
|
sideTheme: (state) => state.settings.sideTheme,
|
||||||
|
sidebar: (state) => state.app.sidebar,
|
||||||
|
device: (state) => state.app.device,
|
||||||
|
needTagsView: (state) => state.settings.tagsView,
|
||||||
|
fixedHeader: (state) => state.settings.fixedHeader,
|
||||||
|
}),
|
||||||
|
classObj() {
|
||||||
|
return {
|
||||||
|
hideSidebar: !this.sidebar.opened,
|
||||||
|
openSidebar: this.sidebar.opened,
|
||||||
|
withoutAnimation: this.sidebar.withoutAnimation,
|
||||||
|
mobile: this.device === "mobile",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
variables() {
|
||||||
|
return variables;
|
||||||
|
},
|
||||||
|
// ...mapGetters(['sidebar']),
|
||||||
|
styles() {
|
||||||
|
const v = Math.floor(this.value * this.beilv * 100) / 10000;
|
||||||
|
return {
|
||||||
|
transform: `scale(${v})`,
|
||||||
|
transformOrigin: "left top",
|
||||||
|
// overflow: hidden;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
clientWidth(val) {
|
||||||
|
if (!this.timer) {
|
||||||
|
this.clientWidth = val;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
this.timer = true;
|
||||||
|
let _this = this;
|
||||||
|
setTimeout(function () {
|
||||||
|
_this.timer = false;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
// 这里可以添加修改时的方法
|
||||||
|
this.windowWidth(val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.destroy();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const _this = this;
|
||||||
|
_this.beilv = document.documentElement.clientWidth / 1920;
|
||||||
|
window.onresize = () => {
|
||||||
|
return (() => {
|
||||||
|
_this.clientWidth = `${document.documentElement.clientWidth}`;
|
||||||
|
this.beilv = _this.clientWidth / 1920;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getData() {
|
||||||
|
getElectricityCostAnalysisData({
|
||||||
|
startTime: this.dateData.startTime,
|
||||||
|
endTime: this.dateData.endTime,
|
||||||
|
sort: this.sort,
|
||||||
|
index: undefined,
|
||||||
|
factory: undefined
|
||||||
|
}).then((res) => {
|
||||||
|
this.monthData= res.data.month
|
||||||
|
this.ytdData = res.data.ytd
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleTimeChange(obj) {
|
||||||
|
this.dateData= obj
|
||||||
|
this.getData()
|
||||||
|
},
|
||||||
|
handleClickOutside() {
|
||||||
|
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
||||||
|
},
|
||||||
|
windowWidth(value) {
|
||||||
|
this.clientWidth = value;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
},
|
||||||
|
change() {
|
||||||
|
this.isFullScreen = screenfull.isFullscreen;
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.on("change", this.change);
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.off("change", this.change);
|
||||||
|
},
|
||||||
|
// 全屏
|
||||||
|
screenfullChange() {
|
||||||
|
console.log("screenfull.enabled", screenfull.isEnabled);
|
||||||
|
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.toggle(this.$refs.dayReportB);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "~@/assets/styles/mixin.scss";
|
||||||
|
@import "~@/assets/styles/variables.scss";
|
||||||
|
.dayReport {
|
||||||
|
width: 1920px;
|
||||||
|
height: 1080px;
|
||||||
|
background: url("../../../assets/img/backp.png") no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
.hideSidebar .fixed-header {
|
||||||
|
width: calc(100% - 54px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarHide .fixed-header {
|
||||||
|
width: calc(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile .fixed-header {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,291 @@
|
|||||||
|
<template>
|
||||||
|
<div id="dayReport" class="dayReport" :style="styles">
|
||||||
|
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||||
|
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||||
|
<ReportHeader :dateData="dateData" size="psi" @timeRangeChange="handleTimeChange" top-title="基地电费分析"
|
||||||
|
:is-full-screen="isFullScreen" @screenfullChange="screenfullChange" />
|
||||||
|
<div class="main-body" style="
|
||||||
|
margin-top: -20px;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
padding: 0px 16px 0 272px;
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div class="top" style="display: flex; gap: 16px">
|
||||||
|
<div class="top-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns: 1624px;
|
||||||
|
">
|
||||||
|
<changeBase :factory="factory" @baseChange="selectChange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="top" style="display: flex; gap: 16px;margin-top: -20px;">
|
||||||
|
<div class="left-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns: 804px 804px;
|
||||||
|
">
|
||||||
|
<monthlyOverview :month="month" :monData="monData" :title="'月度概览'" />
|
||||||
|
<totalOverview :totalData="totalData" :title="'累计概览'" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="middle" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||||
|
<div class="left-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns: 1624px;
|
||||||
|
">
|
||||||
|
<relatedIndicatorsAnalysis :dateData="dateData" :factory="factory" :relatedData="relatedData"
|
||||||
|
:title="'相关指标分析'" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bottom" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||||
|
<div class="left-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns: 1624px;
|
||||||
|
">
|
||||||
|
<dataTrend @getData="changeItem" :trendData="trend" :title="'数据趋势'" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import ReportHeader from "../components/noRouterHeader.vue";
|
||||||
|
import { Sidebar } from "../../../layout/components";
|
||||||
|
import screenfull from "screenfull";
|
||||||
|
import changeBase from "../components/changeBase.vue";
|
||||||
|
import monthlyOverview from "../electricityCostAnalysisComponents/monthlyOverview.vue";
|
||||||
|
import totalOverview from "../electricityCostAnalysisComponents/totalOverview.vue";
|
||||||
|
import relatedIndicatorsAnalysis from "../electricityCostAnalysisComponents/relatedIndicatorsAnalysis.vue";
|
||||||
|
import dataTrend from "../electricityCostAnalysisComponents/dataTrend.vue";
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
import { getElectricityCostAnalysisFData } from '@/api/cockpit'
|
||||||
|
import moment from "moment";
|
||||||
|
export default {
|
||||||
|
name: "electricityCostAnalysisBase",
|
||||||
|
components: {
|
||||||
|
ReportHeader,
|
||||||
|
changeBase,
|
||||||
|
monthlyOverview,
|
||||||
|
Sidebar,
|
||||||
|
totalOverview,
|
||||||
|
dataTrend,
|
||||||
|
relatedIndicatorsAnalysis
|
||||||
|
// psiLineChart
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isFullScreen: false,
|
||||||
|
timer: null,
|
||||||
|
beilv: 1,
|
||||||
|
month: '',
|
||||||
|
value: 100,
|
||||||
|
factory: 5,
|
||||||
|
dateData: {},
|
||||||
|
monData: {},
|
||||||
|
totalData: {},
|
||||||
|
trend: [],
|
||||||
|
relatedData: {},
|
||||||
|
trendName: '原片电费'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.init();
|
||||||
|
this.windowWidth(document.documentElement.clientWidth);
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
theme: (state) => state.settings.theme,
|
||||||
|
sideTheme: (state) => state.settings.sideTheme,
|
||||||
|
sidebar: (state) => state.app.sidebar,
|
||||||
|
device: (state) => state.app.device,
|
||||||
|
needTagsView: (state) => state.settings.tagsView,
|
||||||
|
fixedHeader: (state) => state.settings.fixedHeader,
|
||||||
|
}),
|
||||||
|
classObj() {
|
||||||
|
return {
|
||||||
|
hideSidebar: !this.sidebar.opened,
|
||||||
|
openSidebar: this.sidebar.opened,
|
||||||
|
withoutAnimation: this.sidebar.withoutAnimation,
|
||||||
|
mobile: this.device === "mobile",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
variables() {
|
||||||
|
return variables;
|
||||||
|
},
|
||||||
|
// ...mapGetters(['sidebar']),
|
||||||
|
styles() {
|
||||||
|
const v = Math.floor(this.value * this.beilv * 100) / 10000;
|
||||||
|
return {
|
||||||
|
transform: `scale(${v})`,
|
||||||
|
transformOrigin: "left top",
|
||||||
|
// overflow: hidden;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
clientWidth(val) {
|
||||||
|
if (!this.timer) {
|
||||||
|
this.clientWidth = val;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
this.timer = true;
|
||||||
|
let _this = this;
|
||||||
|
setTimeout(function () {
|
||||||
|
_this.timer = false;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
// 这里可以添加修改时的方法
|
||||||
|
this.windowWidth(val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.destroy();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const _this = this;
|
||||||
|
_this.beilv = document.documentElement.clientWidth / 1920;
|
||||||
|
window.onresize = () => {
|
||||||
|
return (() => {
|
||||||
|
_this.clientWidth = `${document.documentElement.clientWidth}`;
|
||||||
|
this.beilv = _this.clientWidth / 1920;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
if(this.$route.query.factory){
|
||||||
|
this.factory =Number(this.$route.query.factory)
|
||||||
|
}else if(this.$store.getters.levelList.length > 0 && this.$store.getters.levelList[0].id !== 1) {
|
||||||
|
this.factory = this.$store.getters.levelList[0].id
|
||||||
|
}else{
|
||||||
|
this.factory = this.$store.getters.levelList[1].id
|
||||||
|
}
|
||||||
|
this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleChange(value) {
|
||||||
|
this.index = value
|
||||||
|
this.getData()
|
||||||
|
},
|
||||||
|
changeItem(item) {
|
||||||
|
console.log('item', item);
|
||||||
|
|
||||||
|
this.trendName = item
|
||||||
|
this.getData()
|
||||||
|
},
|
||||||
|
getData() {
|
||||||
|
const requestParams = {
|
||||||
|
startTime: this.dateData.startTime,
|
||||||
|
endTime: this.dateData.endTime,
|
||||||
|
sort: 1,
|
||||||
|
index: this.trendName,
|
||||||
|
factory: this.factory
|
||||||
|
};
|
||||||
|
// 调用接口
|
||||||
|
getElectricityCostAnalysisFData(requestParams).then((res) => {
|
||||||
|
this.monData = res.data.month
|
||||||
|
this.totalData = res.data.ytd
|
||||||
|
this.relatedData = {
|
||||||
|
relatedMon: res.data.monthAnalysis,
|
||||||
|
relatedTotal: res.data.ytdAnalysis
|
||||||
|
}
|
||||||
|
this.trend = res.data.trend
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleTimeChange(obj) {
|
||||||
|
this.month = obj.targetMonth
|
||||||
|
this.dateData = {
|
||||||
|
startTime: obj.startTime,
|
||||||
|
endTime: obj.endTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getData()
|
||||||
|
},
|
||||||
|
selectChange(data) {
|
||||||
|
console.log('选中的数据:', data);
|
||||||
|
this.factory = data
|
||||||
|
if (this.dateData.startTime && this.dateData.endTime) {
|
||||||
|
this.getData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleClickOutside() {
|
||||||
|
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
||||||
|
},
|
||||||
|
windowWidth(value) {
|
||||||
|
this.clientWidth = value;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
},
|
||||||
|
change() {
|
||||||
|
this.isFullScreen = screenfull.isFullscreen;
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.on("change", this.change);
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.off("change", this.change);
|
||||||
|
},
|
||||||
|
// 全屏
|
||||||
|
screenfullChange() {
|
||||||
|
console.log("screenfull.enabled", screenfull.isEnabled);
|
||||||
|
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.toggle(this.$refs.dayReportB);
|
||||||
|
},
|
||||||
|
changeDate(val) {
|
||||||
|
this.date = val;
|
||||||
|
if (this.date === moment().format("yyyy-MM-DD")) {
|
||||||
|
this.loopTime();
|
||||||
|
} else {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "~@/assets/styles/mixin.scss";
|
||||||
|
@import "~@/assets/styles/variables.scss";
|
||||||
|
|
||||||
|
.dayReport {
|
||||||
|
width: 1920px;
|
||||||
|
height: 1080px;
|
||||||
|
background: url("../../../assets/img/backp.png") no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideSidebar .fixed-header {
|
||||||
|
width: calc(100% - 54px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarHide .fixed-header {
|
||||||
|
width: calc(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile .fixed-header {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
419
src/views/home/electricityCostAnalysisComponents/Header.vue
Normal file
419
src/views/home/electricityCostAnalysisComponents/Header.vue
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
<template>
|
||||||
|
<header class="report-header">
|
||||||
|
<!-- 左侧区域:logo + 标题 -->
|
||||||
|
<div class="left-content">
|
||||||
|
<img style="height: 36px;" src="../../../assets/img/cnbm.png" alt="benmaLogo" >
|
||||||
|
<div class="top-title">{{ topTitle }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="center-content">
|
||||||
|
<!-- 循环 pageRoutes,不再硬编码文字 -->
|
||||||
|
<div class="item" v-for="(page, index) in pageRoutes" :key="index" @click="goToPage(page.path, index)">
|
||||||
|
<span class="item-text">{{ page.text }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- :class="{ 'no-skew': activeIndex === index }
|
||||||
|
" -->
|
||||||
|
<!-- 右侧区域:全屏按钮 -->
|
||||||
|
<div class="right-content">
|
||||||
|
<el-button type="text" class="screen-btn" :title="isFullScreen ? '退出全屏' : '全屏'" @click="changeFullScreen">
|
||||||
|
<svg-icon style="color: #0B58FF;" v-if="isFullScreen" icon-class="unFullScreenView" />
|
||||||
|
<svg-icon style="color: #0B58FF;" v-else icon-class="fullScreenView" />
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 时间选择区域:日/月/年按钮 + label + 日期选择器 -->
|
||||||
|
<div class="timeType">
|
||||||
|
<!-- <div class="item" v-for="(item, index) in timeTypes" :key="index" @click="activeTime = index"
|
||||||
|
:class="{ 'no-skew': activeTime === index }">
|
||||||
|
<span class="item-text">{{ item.text }}</span>
|
||||||
|
</div> -->
|
||||||
|
<div class="dateP">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">月份选择</span>
|
||||||
|
</div>
|
||||||
|
<el-date-picker v-model="date" :type="getPickerType" :placeholder="getPickerPlaceholder"
|
||||||
|
class="custom-date-picker" style="width: 132px;height: 29px;" @change="emitTimeRange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment'
|
||||||
|
export default {
|
||||||
|
name: 'Header',
|
||||||
|
props: {
|
||||||
|
isFullScreen: { type: Boolean, default: false },
|
||||||
|
topTitle: { type: String, default: '' }
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentTime: '',
|
||||||
|
timeTimer: null,
|
||||||
|
date: undefined,
|
||||||
|
activeIndex: -1,
|
||||||
|
activeTime: 1, // 0=日,1=月,2=年(默认选中“日”)
|
||||||
|
pageRoutes: [
|
||||||
|
{ text: '营业收入', path: '/operatingRevenue/operatingRevenueIndex' },
|
||||||
|
{ text: '利润分析', path: '/profitAnalysis' },
|
||||||
|
{ text: '产销率库存分析', path: '/PSIAnal' },
|
||||||
|
{ text: '成本分析', path: '/cost/cost' },
|
||||||
|
{ text: '驾驶舱报表', path: '/cockpit' }
|
||||||
|
],
|
||||||
|
// 定义时间类型配置:text=按钮文字,pickerType=选择器类型,placeholder=占位符
|
||||||
|
timeTypes: [
|
||||||
|
{ text: '日', pickerType: 'date', placeholder: '选择日期' },
|
||||||
|
{ text: '月', pickerType: 'month', placeholder: '选择月份' },
|
||||||
|
{ text: '年', pickerType: 'year', placeholder: '选择年份' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 动态获取日期选择器类型
|
||||||
|
getPickerType() {
|
||||||
|
return this.timeTypes[this.activeTime].pickerType;
|
||||||
|
},
|
||||||
|
// 动态获取日期选择器占位符
|
||||||
|
getPickerPlaceholder() {
|
||||||
|
return this.timeTypes[this.activeTime].placeholder;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goToPage(path, index) {
|
||||||
|
// 1. 跳转到对应路由
|
||||||
|
this.$router.push(path);
|
||||||
|
// 2. 更新activeIndex,让当前点击项高亮
|
||||||
|
this.activeIndex = index;
|
||||||
|
},
|
||||||
|
changeFullScreen() { this.$emit('screenfullChange') },
|
||||||
|
padZero(num) { return num < 10 ? '0' + num : num },
|
||||||
|
/**
|
||||||
|
* 核心方法1:根据维度计算时间区间(首次进入时基于赋值的当月日期,计算“当月第一天0点→次月第一天0点”)
|
||||||
|
* @returns {Object} 包含 start(开始时间)、end(结束时间)、dimension(维度)的区间对象
|
||||||
|
*/
|
||||||
|
calculateTimeRange() {
|
||||||
|
let startTime = 0;
|
||||||
|
let endTime = 0;
|
||||||
|
const mode = this.activeTime + 1; // 1=日,2=月,3=年
|
||||||
|
const defaultMoment = moment(); // 默认当前时间
|
||||||
|
|
||||||
|
const targetMoment = this.date
|
||||||
|
? moment(this.date, this.getPickerType === 'date' ? 'YYYY-MM-DD' : (this.getPickerType === 'month' ? 'YYYY-MM' : 'YYYY'))
|
||||||
|
: defaultMoment;
|
||||||
|
|
||||||
|
if (!targetMoment.isValid()) {
|
||||||
|
console.error('无效日期:', this.date);
|
||||||
|
return { startTime, endTime, mode };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 日维度:00:00:00 → 23:59:59(无毫秒)
|
||||||
|
if (this.activeTime === 0) {
|
||||||
|
startTime = targetMoment.startOf('day').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('day').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 月维度:当月1日00:00:00 → 当月最后一天23:59:59(无毫秒)
|
||||||
|
else if (this.activeTime === 1) {
|
||||||
|
startTime = targetMoment.startOf('month').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('month').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 年维度:当年1月1日00:00:00 → 当年最后一天23:59:59(无毫秒)
|
||||||
|
else if (this.activeTime === 2) {
|
||||||
|
startTime = targetMoment.startOf('year').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('year').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调试输出:验证是否去掉毫秒
|
||||||
|
console.log('时间范围计算结果:', {
|
||||||
|
mode,
|
||||||
|
startTime: moment(startTime * 1000).format('YYYY-MM-DD HH:mm:ss'), // 格式:2025-11-30 00:00:00
|
||||||
|
endTime: moment(endTime * 1000).format('YYYY-MM-DD HH:mm:ss'), // 格式:2025-11-30 23:59:59(无毫秒)
|
||||||
|
startTimeStamp: startTime, // 秒级时间戳(如:1764422400)
|
||||||
|
endTimeStamp: endTime // 秒级时间戳(如:1764508799)
|
||||||
|
});
|
||||||
|
|
||||||
|
return { startTime, endTime, mode };
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 核心方法2:传递时间区间给父组件(首次进入时触发,传递“当月第一天0点→次月第一天0点”)
|
||||||
|
*/
|
||||||
|
emitTimeRange() {
|
||||||
|
const timeRange = this.calculateTimeRange();
|
||||||
|
this.$emit('timeRangeChange', timeRange);
|
||||||
|
// 调试用:查看首次传递的区间(如{start: "2025-10-01T00:00:00", end: "2025-11-01T00:00:00", dimension: "月"})
|
||||||
|
console.log('当前时间区间:', timeRange);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 维度切换时:清空选择的日期,并传递当前维度的默认区间
|
||||||
|
activeTime(newVal, oldVal) {
|
||||||
|
if (newVal !== oldVal) {
|
||||||
|
this.date = undefined;
|
||||||
|
// this.emitTimeRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 核心逻辑:首次进入页面,计算当月默认日期并赋值给选择器,同时传递区间
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = this.padZero(now.getMonth() + 1); // 月份从0开始,+1后补零(如1月→01)
|
||||||
|
// 赋值当月默认日期(格式:YYYY-MM,适配month类型选择器)
|
||||||
|
this.date = `${year}-${month}`;
|
||||||
|
// 确保选择器渲染完成后,传递“当月第一天0点→次月第一天0点”的区间
|
||||||
|
this.$nextTick(() => this.emitTimeRange());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
/* 原有样式不变,仅补充label文字的倾斜抵消样式 */
|
||||||
|
@font-face {
|
||||||
|
font-family: "YouSheBiaoTiHei";
|
||||||
|
src: url('../../../assets/fonts/YouSheBiaoTiHe.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-header {
|
||||||
|
height: 117px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
background: url('../../../assets/img/topTitle.png') no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: 0 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
/* 确保timeType绝对定位生效 */
|
||||||
|
|
||||||
|
.left-content {
|
||||||
|
margin-top: 11px;
|
||||||
|
margin-left: 44px;
|
||||||
|
height: 55px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-title {
|
||||||
|
height: 55px;
|
||||||
|
font-family: "YouSheBiaoTiHei", sans-serif;
|
||||||
|
font-size: 42px;
|
||||||
|
color: #1E1651;
|
||||||
|
line-height: 55px;
|
||||||
|
letter-spacing: 6px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 18px;
|
||||||
|
margin-left: 70px;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
width: 180px;
|
||||||
|
height: 50px;
|
||||||
|
background: #E1EEFC;
|
||||||
|
transform: skew(-20deg);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #1E1651;
|
||||||
|
line-height: 50px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0px 13px 16px 0px rgba(179, 217, 255, 0.43),
|
||||||
|
0px 2px 4px 0px rgba(92, 140, 255, 0.25),
|
||||||
|
inset 0px -43px 13px 0px rgba(255, 255, 255, 0.51);
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(20deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.no-skew {
|
||||||
|
background: none !important;
|
||||||
|
transform: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
color: #1E1651;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 垂直居中,避免元素高低错位 */
|
||||||
|
top: 42px;
|
||||||
|
right:10px;
|
||||||
|
margin-top: 18px;
|
||||||
|
gap: 0;
|
||||||
|
/* 清除间隙,让按钮与选择器紧密连接 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item {
|
||||||
|
width: 40px;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(236, 244, 254, 1);
|
||||||
|
transform: skew(-20deg);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(11, 88, 255, 1);
|
||||||
|
line-height: 28px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
/* 选中按钮与未选中按钮倾斜角度统一,避免切换时跳动 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item .item-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(20deg);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item.no-skew {
|
||||||
|
background: rgba(11, 88, 255, 1);
|
||||||
|
color: rgba(249, 252, 255, 1);
|
||||||
|
transform: skew(-20deg) !important;
|
||||||
|
/* 统一倾斜角度,修复原30deg的错位 */
|
||||||
|
box-shadow: 0 2px 8px rgba(11, 88, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item.no-skew .item-text {
|
||||||
|
transform: skew(20deg) !important;
|
||||||
|
/* 同步统一文字倾斜角度 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateP {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateP .label {
|
||||||
|
width: 165px;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(236, 244, 254, 1);
|
||||||
|
transform: skew(-25deg);
|
||||||
|
/* 与按钮倾斜角度统一(原30deg改为25deg,避免视觉错位) */
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 28px;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 补充:label文字抵消倾斜(原代码遗漏,导致文字倾斜) */
|
||||||
|
.dateP .label-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(25deg);
|
||||||
|
/* 与label倾斜角度相反,确保文字正立 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-right: 4px;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-time {
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 24px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screen-btn {
|
||||||
|
width: 26px;
|
||||||
|
margin-left: 300px;
|
||||||
|
color: #00fff0;
|
||||||
|
font-size: 26px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 日期选择器样式保持不变 */
|
||||||
|
::v-deep .custom-date-picker {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
width: 165px !important;
|
||||||
|
height: 28px !important;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 !important;
|
||||||
|
|
||||||
|
/* 1. 调整输入框文字:确保行高与输入框高度一致,垂直居中 */
|
||||||
|
.el-input__inner {
|
||||||
|
height: 28px !important;
|
||||||
|
width: 165px !important;
|
||||||
|
text-align: center;
|
||||||
|
padding-left: 15px !important;
|
||||||
|
padding-right: 32px !important;
|
||||||
|
/* 给图标留空间,避免文字被遮挡 */
|
||||||
|
font-size: 14px !important;
|
||||||
|
line-height: 28px !important;
|
||||||
|
/* 行高=输入框高度,文字垂直居中 */
|
||||||
|
color: rgba(237, 245, 253, 1) !important;
|
||||||
|
vertical-align: middle !important;
|
||||||
|
/* 强制文字垂直对齐 */
|
||||||
|
clip-path: polygon(18px 0, 100% 0, 100% 100%, 0 100%);
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
background-color: rgba(11, 88, 255, 1) !important;
|
||||||
|
border-left: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. 调整图标容器:让图标与文字在同一水平线上 */
|
||||||
|
.el-input__prefix {
|
||||||
|
left: auto !important;
|
||||||
|
right: 8px !important;
|
||||||
|
top: 50% !important;
|
||||||
|
/* 从40%改为50%,基于输入框垂直居中 */
|
||||||
|
transform: translateY(-50%) !important;
|
||||||
|
/* 向上偏移自身50%,精准居中 */
|
||||||
|
display: inline-flex !important;
|
||||||
|
/* 让容器内图标垂直居中 */
|
||||||
|
align-items: center !important;
|
||||||
|
/* 图标在容器内垂直居中 */
|
||||||
|
height: 28px !important;
|
||||||
|
/* 容器高度=输入框高度,避免偏移 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. 调整图标本身:确保图标大小和对齐方式 */
|
||||||
|
.el-input__icon {
|
||||||
|
color: #ffffff !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
line-height: 28px !important;
|
||||||
|
/* 图标行高=输入框高度,与文字对齐 */
|
||||||
|
vertical-align: middle !important;
|
||||||
|
/* 强制图标垂直对齐 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. 图标伪类:确保颜色和对齐继承 */
|
||||||
|
.el-icon-date::before {
|
||||||
|
color: #ffffff !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
line-height: inherit !important;
|
||||||
|
/* 继承父级行高,避免错位 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
271
src/views/home/electricityCostAnalysisComponents/container.vue
Normal file
271
src/views/home/electricityCostAnalysisComponents/container.vue
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
<template>
|
||||||
|
<div class="cockpitContainer" :class="['cockpitContainer__' + size]">
|
||||||
|
<div class="content-top" :class="['content-top__' + topSize]">
|
||||||
|
<!-- 使用 flex 容器包裹图标和文字,实现垂直居中 -->
|
||||||
|
<div class="title-wrapper">
|
||||||
|
<svg-icon class="title-icon" :icon-class="icon" />
|
||||||
|
<span class="title-text">
|
||||||
|
{{ name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cockpitContainer-body">
|
||||||
|
<slot>
|
||||||
|
<div class="test-body">something test....</div>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Container',
|
||||||
|
components: {},
|
||||||
|
// eslint-disable-next-line vue/require-prop-types
|
||||||
|
props: ['name', 'size', 'icon', 'topSize'],
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.cockpitContainer {
|
||||||
|
display: inline-block;
|
||||||
|
// width: 100%;
|
||||||
|
// height: 100%;
|
||||||
|
padding: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.content-top {
|
||||||
|
height: 60px;
|
||||||
|
.title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 10px;
|
||||||
|
/* 垂直居中关键属性 */
|
||||||
|
height: 100%;
|
||||||
|
/* 继承父容器高度,确保垂直居中范围 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
font-size: 30px;
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
/* 图标和文字之间的间距 */
|
||||||
|
flex-shrink: 0;
|
||||||
|
/* 防止图标被压缩 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 移除固定行高,避免影响垂直对齐
|
||||||
|
// line-height: 60px;
|
||||||
|
}
|
||||||
|
// width: 547px;
|
||||||
|
// background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
&__basic {
|
||||||
|
// width: 547px;
|
||||||
|
background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__middle {
|
||||||
|
background: url(../../../assets/img/topTileMiddle.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__large {
|
||||||
|
background: url(../../../assets/img/topTitleLargeBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__KFAPTopTitle {
|
||||||
|
background: url(../../../assets/img/KFAPTopTitle.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiTopTitleBasic {
|
||||||
|
background: url(../../../assets/img/psiTopTitleBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
&__rawTopTitleLarge {
|
||||||
|
background: url(../../../assets/img/rawTopTitleLarge.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__topBasic {
|
||||||
|
background: url(../../../assets/img/top-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bottomBasic {
|
||||||
|
background: url(../../../assets/img/bottom-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingBasic {
|
||||||
|
background: url(../../../assets/img/operating-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingLarge {
|
||||||
|
background: url(../../../assets/img/operating-large.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__profitTopBasic {
|
||||||
|
background: url(../../../assets/img/profitTopBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__profitMiddleBasic {
|
||||||
|
background: url(../../../assets/img/profitMiddleBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiBasicBg {
|
||||||
|
background: url(../../../assets/img/psiBasicBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiMiddleBg {
|
||||||
|
background: url(../../../assets/img/psiMiddleBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingRevenueBg {
|
||||||
|
background: url(../../../assets/img/operatingRevenueBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__costBasicBg {
|
||||||
|
background: url(../../../assets/img/costBasicBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
&__rawTopBg {
|
||||||
|
background: url(../../../assets/img/rawTopBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
// &__left {
|
||||||
|
// background: url(../../../../../../../assets/img/left.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__energyConsumption {
|
||||||
|
// background: url(../../../../../../../assets/img/energyConsumption.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__left2 {
|
||||||
|
// background: url(../../assets/left_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__left3 {
|
||||||
|
// background: url(../../assets/left_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__mid2 {
|
||||||
|
// background: url(../../assets/mid_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__mid3 {
|
||||||
|
// background: url(../../assets/mid_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right1 {
|
||||||
|
// background: url(../../assets/right_1.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right2 {
|
||||||
|
// background: url(../../assets/right_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right3 {
|
||||||
|
// background: url(../../assets/right_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekRight2 {
|
||||||
|
// background: url(../../assets/week_right_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekMidTop {
|
||||||
|
// background: url(../../assets/week-mid-top.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekMidMid {
|
||||||
|
// background: url(../../assets/week-mid-mid.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: ' ';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
// background: inherit;
|
||||||
|
/* 设置模糊,不用 filter */
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-body {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
236
src/views/home/electricityCostAnalysisComponents/dataTrend.vue
Normal file
236
src/views/home/electricityCostAnalysisComponents/dataTrend.vue
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="数据趋势" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="right" style="
|
||||||
|
height: 191px;
|
||||||
|
display: flex;
|
||||||
|
width: 1595px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<!-- 直接使用计算属性 chartData,无需手动更新 -->
|
||||||
|
<dataTrendBar @handleGetItemData="getData" :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import dataTrendBar from "./dataTrendBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, dataTrendBar },
|
||||||
|
props: {
|
||||||
|
trendData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
/**
|
||||||
|
* chartData 计算属性:自动响应 trendData 变化,格式化并提取各字段数组
|
||||||
|
* @returns {Object} 包含6个独立数组的格式化数据
|
||||||
|
*/
|
||||||
|
chartData() {
|
||||||
|
// 初始化6个独立数组
|
||||||
|
const timeArr = []; // 格式化后的年月数组
|
||||||
|
const valueArr = []; // 实际值数组
|
||||||
|
const diffValueArr = []; // 差异值数组
|
||||||
|
const targetValueArr = []; // 预算值数组
|
||||||
|
const proportionArr = []; // 占比数组
|
||||||
|
const completedArr = []; // 完成率数组
|
||||||
|
|
||||||
|
// 遍历传入的 trendData 数组(响应式依赖,变化时自动重算)
|
||||||
|
this.trendData.forEach((item) => {
|
||||||
|
timeArr.push(item.title);
|
||||||
|
|
||||||
|
// 2. 提取其他字段,兜底为0(防止null/undefined影响图表渲染)
|
||||||
|
valueArr.push(item.real ?? 0);
|
||||||
|
diffValueArr.push(item.diff ?? 0);
|
||||||
|
targetValueArr.push(item.budget ?? 0);
|
||||||
|
proportionArr.push(item.rate ?? 0);
|
||||||
|
completedArr.push(item.rate>=100 ? 1 : 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 组装并返回格式化后的数据(结构与原一致)
|
||||||
|
return {
|
||||||
|
time: timeArr,
|
||||||
|
value: valueArr,
|
||||||
|
diffValue: diffValueArr,
|
||||||
|
targetValue: targetValueArr,
|
||||||
|
proportion: proportionArr,
|
||||||
|
completed: completedArr,
|
||||||
|
rawData: this.trendData, // 透传原始数据,方便子组件使用
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 格式化时间戳为年月格式(YYYY-MM)
|
||||||
|
* @param {Number} timestamp 13位毫秒级时间戳
|
||||||
|
* @returns {String} 格式化后的年月字符串(如:2025-10)
|
||||||
|
*/
|
||||||
|
getData(value) {
|
||||||
|
this.$emit('getData', value)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 原有样式保持不变 */
|
||||||
|
.scroll-container {
|
||||||
|
max-height: 210px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px 27px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
width: 21px;
|
||||||
|
height: 23px;
|
||||||
|
background: rgba(0, 106, 205, 0.22);
|
||||||
|
backdrop-filter: blur(1.5px);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #68b5ff;
|
||||||
|
line-height: 23px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eq-name {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eqStatus {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitLine {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
border: 1px solid #adadad;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #00ffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoLeft {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoRight {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarWrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLine {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #acacac 100%);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLineTop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg,
|
||||||
|
rgba(53, 223, 247, 0) 0%,
|
||||||
|
rgba(54, 220, 246, 0.92) 92%,
|
||||||
|
#36f6e5 100%,
|
||||||
|
#37acf5 100%);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chartImgBottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 45px;
|
||||||
|
left: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
left: 57px;
|
||||||
|
bottom: 42px;
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #00e8ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,485 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<!-- 新增行容器:包裹“各基地情况”和barTop -->
|
||||||
|
<div class="header-row">
|
||||||
|
<div class="barTop">
|
||||||
|
<!-- 关键:新增右侧容器,包裹图例和按钮组,实现整体靠右 -->
|
||||||
|
<div class="right-container">
|
||||||
|
<div class="legend">
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon line yield"></span>
|
||||||
|
完成率
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square target"></span>
|
||||||
|
目标
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square achieved"></span>
|
||||||
|
实际·达标
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square unachieved"></span>
|
||||||
|
实际·未达标
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<div class="item-button category-btn">
|
||||||
|
<span class="item-text">类目选择</span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-container">
|
||||||
|
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||||
|
<span class="item-text profit-text">{{ selectedProfit || '请选择' }}</span>
|
||||||
|
<span class="dropdown-arrow" :class="{ 'rotate': isDropdownShow }"></span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-options" v-if="isDropdownShow">
|
||||||
|
<div class="dropdown-option" v-for="(item, index) in profitOptions" :key="index"
|
||||||
|
@click.stop="selectProfit(item)">
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||||
|
<operatingLineBar :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSale.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isDropdownShow: false,
|
||||||
|
selectedProfit: '总电费', // 选中的名称,初始为null
|
||||||
|
profitOptions:[
|
||||||
|
{name:'总电费',unit:'万元'},
|
||||||
|
{name:'原片电费',unit:'万元'},
|
||||||
|
{name:'加工电费',unit:'万元'},
|
||||||
|
{name:'外围电费',unit:'万元'},
|
||||||
|
{name:'发电量',unit:'度'},
|
||||||
|
{name:'日均发电量',unit:'度'}
|
||||||
|
],
|
||||||
|
unit:'万元',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentDataSource() {
|
||||||
|
return this.chartData
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
return this.chartData.time
|
||||||
|
},
|
||||||
|
// 根据按钮切换生成对应的 chartData
|
||||||
|
chartD() {
|
||||||
|
// 销量场景数据
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
console.log(this.currentDataSource, 'currentDataSource');
|
||||||
|
console.log('this.currentDataSource', data);
|
||||||
|
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
unit: this.unit,
|
||||||
|
series: [
|
||||||
|
// 1. 完成率(折线图)
|
||||||
|
{
|
||||||
|
name: '完成率',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1, // 绑定右侧Y轴(需在子组件启用配置)
|
||||||
|
lineStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, .5)',
|
||||||
|
width: 2
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderColor: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
radius: 4
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.2,
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||||
|
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: data.proportion || [], // 完成率(%)
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 2. 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||||
|
barWidth: 14,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.targetValue || [] // 目标销量(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图,含达标状态)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 14,
|
||||||
|
label: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||||
|
const safeFlag = data.completed || [];
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.value || [] // 实际销量(万元)
|
||||||
|
},
|
||||||
|
// 4. 实际差值标签(独立scatter系列,zlevel=1确保标签在最上层)
|
||||||
|
{
|
||||||
|
name: '__实际差值标签',
|
||||||
|
type: 'scatter',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
zlevel: 1,
|
||||||
|
symbolSize: 0,
|
||||||
|
tooltip: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
data: (data.value || []).map((value, index) => ({
|
||||||
|
value: value,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
width: 68,
|
||||||
|
height: 20,
|
||||||
|
formatter: () => {
|
||||||
|
const diff = data.diffValue || [];
|
||||||
|
const flags = data.completed || [];
|
||||||
|
const currentDiff = diff[index] || 0;
|
||||||
|
const currentFlag = flags[index] || 0;
|
||||||
|
if (currentFlag === 1) {
|
||||||
|
return `{achieved|${currentDiff}}{text|差值}`;
|
||||||
|
} else {
|
||||||
|
return `{unachieved|${currentDiff}}{text|差值}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||||
|
{ offset: 0.2, color: '#ffffff' },
|
||||||
|
{ offset: 1, color: '#ffffff' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
shadowColor: 'rgba(191,203,215,0.5)',
|
||||||
|
shadowBlur: 2,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowOffsetY: 2,
|
||||||
|
borderRadius: 4,
|
||||||
|
borderColor: '#BFCBD577',
|
||||||
|
borderWidth: 0,
|
||||||
|
lineHeight: 20,
|
||||||
|
rich: {
|
||||||
|
text: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 10, 5, 0],
|
||||||
|
align: 'center',
|
||||||
|
color: '#464646',
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
},
|
||||||
|
achieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#76DABE',
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
},
|
||||||
|
unachieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#F9A44A',
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
return salesData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectProfit(item) {
|
||||||
|
this.selectedProfit = item.name;
|
||||||
|
this.unit = item.unit;
|
||||||
|
this.isDropdownShow = false;
|
||||||
|
this.$emit('handleGetItemData', item.name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
// 新增:头部行容器,实现一行排列
|
||||||
|
.header-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end; // 左右两端对齐
|
||||||
|
align-items: center; // 垂直居中
|
||||||
|
// width: 100%;
|
||||||
|
margin-bottom: 8px; // 与下方图表区保留间距(可根据需求调整)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 各基地情况标题样式
|
||||||
|
.base-title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0 0 0 16px; // 保留原有内边距
|
||||||
|
white-space: nowrap; // 防止文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
// 移除原有flex和justify-content,由header-row控制
|
||||||
|
width: auto; // 自适应宽度
|
||||||
|
// 保留原有align-items,确保内部元素垂直居中
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; // 图例和按钮组垂直居中
|
||||||
|
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||||
|
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 图例:在右侧容器内横向排列
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px; // 图例项之间间距,避免重叠
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap; // 防止图例文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图例颜色
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.dropdown-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 999; // 提高z-index,确保菜单不被遮挡
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-btn {
|
||||||
|
width: 75px;
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-bottom-left-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-btn {
|
||||||
|
width: 123px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 18px 0 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #3071ff;
|
||||||
|
color: rgba(249, 252, 255, .8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-text {
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 6px solid currentColor;
|
||||||
|
border-top: 4px solid transparent;
|
||||||
|
border-bottom: 4px solid transparent;
|
||||||
|
border-right: 4px solid transparent;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
|
||||||
|
&.rotate {
|
||||||
|
transform: rotate(90deg); // 箭头旋转方向可根据需求调整,比如改为rotate(-90deg)更符合向上展开的视觉
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-options {
|
||||||
|
position: absolute;
|
||||||
|
// 关键修改1:调整top值,让菜单显示在选择框上方,calc(-100% - 2px)表示向上偏移自身100%再加2px间距
|
||||||
|
bottom: 100%;
|
||||||
|
right: 0;
|
||||||
|
// 移除多余的margin-top,避免额外间距
|
||||||
|
// margin-top: 2px;
|
||||||
|
width: 123px;
|
||||||
|
background: #ffffff;
|
||||||
|
// 关键修改2:调整border-radius,让菜单顶部圆角匹配选择框的右上角,底部圆角为0(更美观)
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.dropdown-option {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
color: #3071ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="chartBox" style="width: 100%; height: 108px; position: relative;">
|
||||||
|
<div :id="id" style="width: 100%; height:100%;"></div>
|
||||||
|
<div class="bottomTip">
|
||||||
|
<div class="precent">
|
||||||
|
<span class="precentNum">{{ detailData.completeRate ? detailData.completeRate : 0 }}% </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'EnergyConsumption',
|
||||||
|
// components: { Container },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
detailData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
// electricComu: 0,
|
||||||
|
// steamComu: 20, // 调整为符合max范围的数值(0-8)
|
||||||
|
// // electricity: [120, 150, 130, 180, 160, 200, 190],
|
||||||
|
// // steam: [80, 95, 85, 110, 100, 120, 115],
|
||||||
|
// // dates: ['1日', '2日', '3日', '4日', '5日', '6日', '7日']
|
||||||
|
})
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
default: () => ('monthG')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// electricityChart: null,
|
||||||
|
// steamChart: null,
|
||||||
|
// specialTicks: [2, 4, 6, 8], // 统一的刻度显示
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
detailData: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true, // 初始化时立即执行
|
||||||
|
handler() {
|
||||||
|
this.updateGauges()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initGauges()
|
||||||
|
// window.addEventListener('resize', this.handleResize)
|
||||||
|
this.observeContainerResize()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
observeContainerResize() {
|
||||||
|
// 修复:获取正确的容器(组件内的.gauge-container)
|
||||||
|
const container = this.$el.querySelector('.gauge-container')
|
||||||
|
if (container && window.ResizeObserver) {
|
||||||
|
this.resizeObserver = new ResizeObserver(entries => {
|
||||||
|
if (this.electricityChart) {
|
||||||
|
this.electricityChart.resize() // 直接触发resize,无需防抖
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.resizeObserver.observe(container)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initGauges() {
|
||||||
|
// console.log('this.id',this.id);
|
||||||
|
|
||||||
|
// 初始化电气图表实例
|
||||||
|
const electricityDom = document.getElementById(this.id)
|
||||||
|
if (electricityDom) {
|
||||||
|
// 修复:正确创建并存储图表实例
|
||||||
|
this.electricityChart = echarts.init(electricityDom)
|
||||||
|
// 首次更新数据
|
||||||
|
this.updateGauges()
|
||||||
|
}
|
||||||
|
// 蒸汽图表若未使用,可注释/删除
|
||||||
|
// const steamDom = document.getElementById('steamGauge')
|
||||||
|
// if (steamDom) {
|
||||||
|
// this.steamChart = echarts.init(steamDom)
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
updateGauges() {
|
||||||
|
// 修复:先判断实例是否存在,再更新配置
|
||||||
|
if (!this.electricityChart) return
|
||||||
|
|
||||||
|
// 修复:兜底获取rate值,确保数值有效
|
||||||
|
const rate = Number(this.detailData?.completeRate) || 0
|
||||||
|
console.log('当前rate值:', rate); // 调试:确认rate值正确
|
||||||
|
|
||||||
|
// 关键:第二个参数传true,清空原有配置,强制更新
|
||||||
|
this.electricityChart.setOption(this.getElectricityGaugeOption(rate), true)
|
||||||
|
},
|
||||||
|
// 用电量仪表盘配置(保留原有样式,优化数值范围)
|
||||||
|
getElectricityGaugeOption(value) {
|
||||||
|
const electricityGradient = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: '#0B58FF' },
|
||||||
|
{ offset: 1, color: '#32FFCD' }
|
||||||
|
])
|
||||||
|
|
||||||
|
return {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '月度',
|
||||||
|
type: 'gauge',
|
||||||
|
radius: '95', // 修复:添加%,避免数值错误
|
||||||
|
center: ['50%', '90%'],
|
||||||
|
startAngle: 180,
|
||||||
|
endAngle: 0,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
splitNumber: 4,
|
||||||
|
label: { show: false },
|
||||||
|
progress: {
|
||||||
|
show: true,
|
||||||
|
overlap: false,
|
||||||
|
roundCap: true,
|
||||||
|
clip: false,
|
||||||
|
width: 14,
|
||||||
|
itemStyle: { color: electricityGradient }
|
||||||
|
},
|
||||||
|
pointer: {
|
||||||
|
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
|
||||||
|
length: '75%',
|
||||||
|
width: 16,
|
||||||
|
itemStyle: { color: '#288AFF' },
|
||||||
|
offsetCenter: [0, '10%']
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
roundCap: true,
|
||||||
|
lineStyle: { width: 12, color: [[1, '#E6EBF7']] }
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
length: 10,
|
||||||
|
lineStyle: { width: 5, color: '#D6DAE5' },
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
splitNumber: 2,
|
||||||
|
length: 6,
|
||||||
|
lineStyle: { width: 2, color: '#D6DAE5' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
detail: { show: false },
|
||||||
|
data: [{ value: value, unit: '' }] // 确保数值正确传入
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 未使用的蒸汽仪表盘可注释/删除
|
||||||
|
// getSteamGaugeOption(value) { ... }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
.chartBox {
|
||||||
|
.bottomTip {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.precent {
|
||||||
|
line-height: 3px;
|
||||||
|
|
||||||
|
&::after,
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 54px;
|
||||||
|
height: 5px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
margin-right: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用电量线条颜色
|
||||||
|
.precent::before {
|
||||||
|
|
||||||
|
background: linear-gradient(90deg, rgba(40, 138, 255, 0) 0%, rgba(12, 125, 254, 0.4) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ::after:从 透明 到 rgba(12, 125, 254, 0.4)(90度渐变,左到右) */
|
||||||
|
.precent::after {
|
||||||
|
background: linear-gradient(90deg, rgba(12, 125, 254, 0.4) 0%, rgba(40, 138, 255, 0) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蒸汽线条颜色
|
||||||
|
// .steam-precent::after,
|
||||||
|
// .steam-precent::before {
|
||||||
|
// background: linear-gradient(90deg, rgba(11, 168, 255, 0.26) 0%, rgba(54, 239, 230, 1) 100%);
|
||||||
|
// }
|
||||||
|
|
||||||
|
.precentNum {
|
||||||
|
display: inline-block;
|
||||||
|
// width: 52px;
|
||||||
|
height: 22px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蒸汽数字颜色
|
||||||
|
.steam-num {
|
||||||
|
color: rgba(54, 239, 230, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||||
|
<div class="dashboard">
|
||||||
|
<div class="title">
|
||||||
|
{{ month }}月完成率
|
||||||
|
</div>
|
||||||
|
<div class="number">
|
||||||
|
<div class="yield">
|
||||||
|
{{ formatRate(factoryData?.completeRate) }}%
|
||||||
|
</div>
|
||||||
|
<div class="mom">
|
||||||
|
环比{{ formatRate(factoryData?.thb) }}%
|
||||||
|
<img v-if="factoryData?.thb >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="上升">
|
||||||
|
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="下降">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="line" style="padding: 0px;">
|
||||||
|
<!-- 传递包含flag的factoryData给柱状图组件 -->
|
||||||
|
<verticalBarChart :detailData="factoryData"></verticalBarChart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import electricityGauge from './electricityGauge.vue'
|
||||||
|
import verticalBarChart from './verticalBarChart.vue'
|
||||||
|
// 引入箭头图片(根据实际路径调整,若模板中直接用路径可注释)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, electricityGauge, verticalBarChart },
|
||||||
|
props: {
|
||||||
|
monData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
month: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
/**
|
||||||
|
* 自动提取monData中的工厂数据,并新增flag字段
|
||||||
|
*/
|
||||||
|
factoryData() { // 整合原始数据 + 计算flag
|
||||||
|
return {
|
||||||
|
completeRate: this.monData.rate ? Number(this.monData.rate) : 0,
|
||||||
|
diff: this.monData.diff,
|
||||||
|
real: this.monData.real,
|
||||||
|
target: this.monData.target,
|
||||||
|
thb: this.monData.momRate,
|
||||||
|
// ...rawData,
|
||||||
|
flag: this.monData.rate >= 100 ? 1 : 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 格式化百分比数值:处理空值/非数字,兜底为0
|
||||||
|
*/
|
||||||
|
formatRate(value) {
|
||||||
|
if (isNaN(value) || value === null || value === undefined) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
/* 原有样式保持不变 */
|
||||||
|
.scroll-container {
|
||||||
|
max-height: 210px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 264px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 46px;
|
||||||
|
color: #0B58FF;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 箭头样式优化
|
||||||
|
.arrow {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
width: 500px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||||
|
<div class="dashboard left">
|
||||||
|
<div class="title">
|
||||||
|
销量·万元
|
||||||
|
</div>
|
||||||
|
<div class="line">
|
||||||
|
<operatingSingleBar></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dashboard right">
|
||||||
|
<div class="title">
|
||||||
|
单价·万元
|
||||||
|
</div>
|
||||||
|
<div class="line">
|
||||||
|
<operatingSingleBar></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import operatingSingleBar from './operatingSingleBar.vue'
|
||||||
|
import verticalBarChart from './verticalBarChart.vue'
|
||||||
|
|
||||||
|
// import * as echarts from 'echarts'
|
||||||
|
// import rawItem from './raw-Item.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, operatingSingleBar, verticalBarChart },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
itemData: { // 接收父组件传递的设备数据数组
|
||||||
|
type: Array,
|
||||||
|
default: () => [] // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
title: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
month: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
itemData: {
|
||||||
|
handler(newValue, oldValue) {
|
||||||
|
// this.updateChart()
|
||||||
|
},
|
||||||
|
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// computed: {
|
||||||
|
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||||
|
// sortedItemData() {
|
||||||
|
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||||
|
// const totalCostItems = this.itemData.filter(item =>
|
||||||
|
// item.name && item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 过滤出不包含“总成本”的项
|
||||||
|
// const otherItems = this.itemData.filter(item =>
|
||||||
|
// !item.name || !item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 合并:总成本项在前,其他项在后
|
||||||
|
// return [...totalCostItems, ...otherItems];
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
mounted() {
|
||||||
|
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||||
|
// this.$nextTick(() => this.updateChart())
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||||
|
.scroll-container {
|
||||||
|
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||||
|
max-height: 210px;
|
||||||
|
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||||
|
overflow-y: auto;
|
||||||
|
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||||
|
/* Chrome/Safari */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* IE/Edge */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 382px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
// width: 190px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
// width: 190px;
|
||||||
|
height: 32px;
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
width: 120px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// .line {
|
||||||
|
// width: 500px;
|
||||||
|
// height: 205px;
|
||||||
|
// background: #F9FCFF;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .leftTitle {
|
||||||
|
// .item {
|
||||||
|
// width: 67px;
|
||||||
|
// height: 180px;
|
||||||
|
// padding: 37px 23px;
|
||||||
|
// background: #F9FCFF;
|
||||||
|
// font-family: PingFangSC, PingFang SC;
|
||||||
|
// font-weight: 400;
|
||||||
|
// font-size: 18px;
|
||||||
|
// color: #000000;
|
||||||
|
// line-height: 25px;
|
||||||
|
// letter-spacing: 1px;
|
||||||
|
// // text-align: left;
|
||||||
|
// font-style: normal;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- <style>
|
||||||
|
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style> -->
|
||||||
@@ -0,0 +1,514 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<div class="header-row">
|
||||||
|
<div class="base-title">各基地情况</div>
|
||||||
|
<div class="barTop">
|
||||||
|
<div class="right-container">
|
||||||
|
<div class="legend">
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon line yield"></span>完成率
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square target"></span>预算
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square achieved"></span>实际·达标
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square unachieved"></span>实际·未达标
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<div class="item-button category-btn">
|
||||||
|
<span class="item-text">展示顺序</span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-container">
|
||||||
|
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||||
|
<span class="item-text profit-text">{{ selectedSort || '请选择' }}</span>
|
||||||
|
<span class="dropdown-arrow" :class="{ 'rotate': isDropdownShow }"></span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-options" v-if="isDropdownShow">
|
||||||
|
<div class="dropdown-option" v-for="(item, index) in profitOptions" :key="index"
|
||||||
|
@click.stop="selectProfit(item)">
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||||
|
<operatingLineBar :dateData="dateData" :chartData="chartD" style="height: 100%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSale.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData",'dateData'],
|
||||||
|
emits: ['sort-change'], // 声明事件(Vue3 推荐)
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
isDropdownShow: false,
|
||||||
|
selectedSort: '实际值:高~低', // 选中的label
|
||||||
|
selectedSortValue: 1, // 选中的value,用于排序逻辑
|
||||||
|
profitOptions: [
|
||||||
|
{ label: '实际值:高~低', value: 1 },
|
||||||
|
{ label: '实际值:低~高', value: 2 },
|
||||||
|
{ label: '完成率:高~低', value: 3 },
|
||||||
|
{ label: '完成率:低~高', value: 4 },
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 排序后的数据源(核心:根据selectedSortValue重新排序)
|
||||||
|
currentDataSource() {
|
||||||
|
if (!this.chartData) return {};
|
||||||
|
|
||||||
|
// 深拷贝原始数据,避免修改原数据
|
||||||
|
const factory = JSON.parse(JSON.stringify(this.chartData));
|
||||||
|
if (!factory.locations.length || !this.selectedSortValue) return factory;
|
||||||
|
|
||||||
|
// 构建带索引的数组,方便同步所有字段排序
|
||||||
|
const dataWithIndex = factory.locations.map((name, index) => ({
|
||||||
|
index,
|
||||||
|
name,
|
||||||
|
real: factory.reals[index],
|
||||||
|
target: factory.targets[index],
|
||||||
|
rate: factory.rate[index],
|
||||||
|
diff: factory.diff[index],
|
||||||
|
flag: factory.flags[index]
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 根据选中的排序规则排序
|
||||||
|
switch (this.selectedSortValue) {
|
||||||
|
case 1: // 实际值:高~低
|
||||||
|
dataWithIndex.sort((a, b) => b.real - a.real);
|
||||||
|
break;
|
||||||
|
case 2: // 实际值:低~高
|
||||||
|
dataWithIndex.sort((a, b) => a.real - b.real);
|
||||||
|
break;
|
||||||
|
case 3: // 预算值:高~低
|
||||||
|
dataWithIndex.sort((a, b) => b.rate - a.rate);
|
||||||
|
break;
|
||||||
|
case 4: // 预算值:低~高
|
||||||
|
dataWithIndex.sort((a, b) => a.rate - b.rate);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步更新所有数组
|
||||||
|
factory.locations = dataWithIndex.map(item => item.name);
|
||||||
|
factory.reals = dataWithIndex.map(item => item.real);
|
||||||
|
factory.targets = dataWithIndex.map(item => item.target);
|
||||||
|
factory.rates = dataWithIndex.map(item => item.rate);
|
||||||
|
factory.diff = dataWithIndex.map(item => item.diff);
|
||||||
|
factory.flags = dataWithIndex.map(item => item.flag);
|
||||||
|
|
||||||
|
return factory;
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
return this.currentDataSource.locations || [];
|
||||||
|
},
|
||||||
|
// 最终传递给图表的排序后数据
|
||||||
|
chartD() {
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
unit:'万元',
|
||||||
|
series: [
|
||||||
|
// 完成率(折线图)
|
||||||
|
{
|
||||||
|
name: '完成率',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
lineStyle: { color: 'rgba(40, 138, 255, .5)', width: 2 },
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderColor: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
radius: 4
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.2,
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||||
|
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: data.rate || [],
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 40,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.targets || []
|
||||||
|
},
|
||||||
|
// 实际(柱状图)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 40,
|
||||||
|
label: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const safeFlag = data.flags || [];
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals || []
|
||||||
|
},
|
||||||
|
// 实际差值标签(独立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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
return salesData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectProfit(item) {
|
||||||
|
// 更新选中的label和value
|
||||||
|
this.selectedSort = item.label;
|
||||||
|
this.selectedSortValue = item.value;
|
||||||
|
this.isDropdownShow = false;
|
||||||
|
// 向父组件传递排序事件(可选,保持原有逻辑)
|
||||||
|
this.$emit('sort-change', item.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 监听父组件传入的chartData变化,重置选中状态(可选)
|
||||||
|
watch: {
|
||||||
|
'chartData.factory': {
|
||||||
|
handler() {
|
||||||
|
// 若需要切换数据源后重置排序,可取消注释
|
||||||
|
// this.selectedSort = null;
|
||||||
|
// this.selectedSortValue = null;
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.header-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0 0 0 16px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
width: auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
margin-right: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.dropdown-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-btn {
|
||||||
|
width: 75px;
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-bottom-left-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-btn {
|
||||||
|
width: 123px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 18px 0 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #3071ff;
|
||||||
|
color: rgba(249, 252, 255, .8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-text {
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 6px solid currentColor;
|
||||||
|
border-top: 4px solid transparent;
|
||||||
|
border-bottom: 4px solid transparent;
|
||||||
|
border-right: 4px solid transparent;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
|
||||||
|
&.rotate {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-options {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
right: 0;
|
||||||
|
margin-top: 2px;
|
||||||
|
width: 123px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.dropdown-option {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
color: #3071ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChipBottom" id="cockpitEffChipBottom" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
series: [],
|
||||||
|
allPlaceNames: []
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
validator: (value) => {
|
||||||
|
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChipBottom;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatter: (params) => {
|
||||||
|
let html = `${params[0].axisValue}<br/>`;
|
||||||
|
params.forEach(item => {
|
||||||
|
const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
);
|
||||||
|
html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 60,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
splitNumber: 4,
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: (value) => Math.ceil((value.max || 0) * 1.1),
|
||||||
|
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
scale:true,
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
series: [],
|
||||||
|
allPlaceNames: []
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
validator: (value) => {
|
||||||
|
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 60,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
// {
|
||||||
|
// type: 'value',
|
||||||
|
// nameTextStyle: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// align: 'left'
|
||||||
|
// },
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
// axisTick: { show: false },
|
||||||
|
// axisLabel: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// formatter: '{value}%'
|
||||||
|
// },
|
||||||
|
// splitLine: { show: false },
|
||||||
|
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
// splitNumber: 4
|
||||||
|
// }
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null, // 存储图表实例
|
||||||
|
resizeHandler: null, // 存储resize事件处理函数
|
||||||
|
// 核心:基地名称与序号的映射表(固定顺序)
|
||||||
|
baseNameToIndexMap: {
|
||||||
|
'宜兴': 7,
|
||||||
|
'漳州': 8,
|
||||||
|
'自贡': 3,
|
||||||
|
'桐城': 2,
|
||||||
|
'洛阳': 9,
|
||||||
|
'合肥': 5,
|
||||||
|
'宿迁': 6,
|
||||||
|
'秦皇岛': 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.initChart(); // 初始化图表(只执行一次)
|
||||||
|
this.updateChart(); // 更新图表数据
|
||||||
|
});
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
this.updateChart(); // 仅更新数据,不重新创建实例
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 组件销毁时清理资源
|
||||||
|
this.destroyChart();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 初始化图表(只在mounted中执行一次)
|
||||||
|
initChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
this.myChart.getZr().on('click', (params) => {
|
||||||
|
console.log('params', params);
|
||||||
|
|
||||||
|
// 提取点击的基地名称
|
||||||
|
// const itemName = params.name;
|
||||||
|
let itemName = undefined
|
||||||
|
// 根据映射表获取对应的序号(未匹配到则返回0或其他默认值)
|
||||||
|
let pointInPixel = [params.offsetX, params.offsetY];
|
||||||
|
if (this.myChart.containPixel('grid', pointInPixel)) {
|
||||||
|
let pointInGrid = this.myChart.convertFromPixel({
|
||||||
|
seriesIndex: 0
|
||||||
|
}, pointInPixel);
|
||||||
|
let xIndex = pointInGrid[0]; //索引
|
||||||
|
let handleIndex = Number(xIndex);
|
||||||
|
let seriesObj = this.myChart.getOption(); //图表object对象
|
||||||
|
var op = this.myChart.getOption();
|
||||||
|
//获得图表中点击的列
|
||||||
|
itemName = op.xAxis[0].data[handleIndex]; //获取点击的列名
|
||||||
|
console.log(itemName, 'monthmonthmonth');
|
||||||
|
console.log(handleIndex, seriesObj);
|
||||||
|
};
|
||||||
|
const baseIndex = this.baseNameToIndexMap[itemName] || 0;
|
||||||
|
|
||||||
|
console.log(`你点击了【${itemName}】(序号:${baseIndex})`);
|
||||||
|
if (itemName === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 路由跳转时携带序号(或名称+序号)
|
||||||
|
this.$router.push({
|
||||||
|
path: 'electricityCostAnalysisBase',
|
||||||
|
query: { // 使用query传递参数(推荐),也可使用params
|
||||||
|
// baseName: itemName,
|
||||||
|
factory: baseIndex,
|
||||||
|
dateData: this.dateData
|
||||||
|
}
|
||||||
|
// 若仍需用base作为参数:
|
||||||
|
// base: itemName,
|
||||||
|
// params: { baseIndex: baseIndex }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定义resize处理函数(命名函数,方便移除)
|
||||||
|
this.resizeHandler = () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
};
|
||||||
|
// 绑定resize事件(只绑定一次)
|
||||||
|
window.addEventListener('resize', this.resizeHandler);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新图表数据(数据变化时执行)
|
||||||
|
updateChart() {
|
||||||
|
if (!this.myChart) {
|
||||||
|
return; // 实例未初始化则返回
|
||||||
|
}
|
||||||
|
|
||||||
|
const { allPlaceNames,unit, series } = this.chartData || {};
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || [];
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 5,
|
||||||
|
right: 20,
|
||||||
|
left: 25,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0],
|
||||||
|
// 可选:X轴标签显示“序号+名称”(如“1 宜兴”)
|
||||||
|
// formatter: (value) => {
|
||||||
|
// const index = this.baseNameToIndexMap[value] || '';
|
||||||
|
// return index ? `${index} ${value}` : value;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: unit,
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries
|
||||||
|
};
|
||||||
|
|
||||||
|
// 只更新配置,不重新创建实例
|
||||||
|
this.myChart.setOption(option, true); // 第二个参数true表示清空原有配置,避免数据残留
|
||||||
|
},
|
||||||
|
|
||||||
|
// 销毁图表资源
|
||||||
|
destroyChart() {
|
||||||
|
// 移除resize事件
|
||||||
|
if (this.resizeHandler) {
|
||||||
|
window.removeEventListener('resize', this.resizeHandler);
|
||||||
|
}
|
||||||
|
// 销毁图表实例
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
this.myChart = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 380px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
console.log('chartData', this.chartData);
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
console.log('xData', xData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 20,
|
||||||
|
right: 10,
|
||||||
|
left: 25,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
//
|
||||||
|
splitNumber: 4,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 200px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
console.log('chartData', this.chartData);
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
console.log('xData', xData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
// axisPointer: {
|
||||||
|
// type: 'cross',
|
||||||
|
// label: {
|
||||||
|
// backgroundColor: '#6a7985'
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 25,
|
||||||
|
bottom: 25,
|
||||||
|
right: 10,
|
||||||
|
left: 2,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
// offset: 10
|
||||||
|
// boundaryGap: ['50%', '50%'],
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
// name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true, // 显示Y轴轴线(关键)
|
||||||
|
lineStyle: {
|
||||||
|
color: '#E5E6EB', // 轴线颜色(浅灰色,可自定义)
|
||||||
|
width: 1, // 轴线宽度
|
||||||
|
type: 'solid' // 实线(可选:dashed虚线、dotted点线)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 2,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
},
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="当月数据对比" icon="cockpitItemIcon" size="operatingLarge" topSize="large">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="left" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 348px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
">
|
||||||
|
集团情况
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{chartData.topBarData.rate[0]}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.topBarData.flags>0?'#30B590':'#FF9423'}" >{{chartData.topBarData.diff[0]}}</span></span>
|
||||||
|
</div>
|
||||||
|
<operatingTopBar :chartData="chartData?.topBarData || {}" />
|
||||||
|
</div>
|
||||||
|
<div class="right" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 1220px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<operatingBar :dateData="dateData" :chartData="chartData?.barData || {}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import operatingBar from "./operatingBar.vue";
|
||||||
|
import operatingTopBar from "./operatingTopBar.vue";
|
||||||
|
|
||||||
|
// 序号→地名映射表(levelId=序号)
|
||||||
|
const baseIndexToNameMap = {
|
||||||
|
7: "宜兴",
|
||||||
|
8: "漳州",
|
||||||
|
3: "自贡",
|
||||||
|
2: "桐城",
|
||||||
|
9: "洛阳",
|
||||||
|
5: "合肥",
|
||||||
|
10: "秦皇岛",
|
||||||
|
6: "宿迁"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, operatingBar, operatingTopBar },
|
||||||
|
props: {
|
||||||
|
monthData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: {
|
||||||
|
topBarData: { // levelId=1的整合数据
|
||||||
|
locations: [], // 固定为["凯盛新能"]
|
||||||
|
diff: [], // 差值数组
|
||||||
|
targets: [], // 预算值数组
|
||||||
|
reals: [], // 实际值数组
|
||||||
|
rate: [], // 完成率数组
|
||||||
|
flags: [] // 完成状态数组(0/1)
|
||||||
|
},
|
||||||
|
barData: { // levelId≠1的整合数据
|
||||||
|
locations: [], // levelId对应的baseIndexToNameMap中的地名
|
||||||
|
diff: [], // 对应差值数组
|
||||||
|
targets: [], // 预算值数组
|
||||||
|
reals: [], // 实际值数组
|
||||||
|
rate: [], // 完成率数组
|
||||||
|
flags: [] // 完成状态数组(0/1)
|
||||||
|
// baseIndexes: []// 对应levelId(序号)数组
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
monthData: {
|
||||||
|
handler() {
|
||||||
|
this.processChartData();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
processChartData() {
|
||||||
|
// 初始化空数据结构
|
||||||
|
const initTopBarData = {
|
||||||
|
locations: [], diff: [], targets: [], reals: [], rate: [], flags: []
|
||||||
|
};
|
||||||
|
const initBarData = { locations: [], diff: [], targets: [], reals: [], rate: [], flags: [] };
|
||||||
|
// 1. 处理levelId=1的整合数据(逻辑不变)
|
||||||
|
const level1Data = this.monthData.group;
|
||||||
|
if (!level1Data) return;
|
||||||
|
const topBarData = { ...initTopBarData };
|
||||||
|
topBarData.locations = ["凯盛新能"];
|
||||||
|
topBarData.diff.push(level1Data.diff || 0);
|
||||||
|
|
||||||
|
topBarData.targets.push(level1Data.target || 0);
|
||||||
|
topBarData.reals.push(level1Data.real || 0);
|
||||||
|
topBarData.rate.push(level1Data.rate || 0);
|
||||||
|
topBarData.flags.push(level1Data.rate >= 100 ? 1 : 0);
|
||||||
|
// 2. 处理levelId≠1的整合数据(核心:levelId匹配地名)
|
||||||
|
const barData = { ...initBarData };
|
||||||
|
// 筛选有效数据:levelId≠1 且 levelId在baseIndexToNameMap中
|
||||||
|
const validOtherData = this.monthData.factory
|
||||||
|
|
||||||
|
// 遍历有效数据,填充locations(levelId→地名)
|
||||||
|
validOtherData.forEach(item => {
|
||||||
|
barData.locations.push(item.title); // locations=levelId对应的地名(如levelId=7→宜兴)
|
||||||
|
barData.diff.push(item.diff || 0);
|
||||||
|
barData.targets.push(item.budget || 0);
|
||||||
|
barData.reals.push(item.real || 0);
|
||||||
|
barData.rate.push(item.rate || 0);
|
||||||
|
barData.flags.push(item.rate >= 100 ? 1 : 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. 更新chartData
|
||||||
|
this.chartData = { topBarData, barData };
|
||||||
|
console.log('levelId=1数据:', this.chartData.topBarData);
|
||||||
|
console.log('levelId≠1数据(locations=levelId对应地名):', this.chartData.barData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 原有样式保持不变 */
|
||||||
|
.scroll-container {
|
||||||
|
max-height: 210px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px 27px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
width: 21px;
|
||||||
|
height: 23px;
|
||||||
|
background: rgba(0, 106, 205, 0.22);
|
||||||
|
backdrop-filter: blur(1.5px);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #68b5ff;
|
||||||
|
line-height: 23px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eq-name {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eqStatus {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitLine {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
border: 1px solid #adadad;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #00ffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoLeft {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoRight {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarWrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLine {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #acacac 100%);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLineTop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg,
|
||||||
|
rgba(53, 223, 247, 0) 0%,
|
||||||
|
rgba(54, 220, 246, 0.92) 92%,
|
||||||
|
#36f6e5 100%,
|
||||||
|
#37acf5 100%);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chartImgBottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 45px;
|
||||||
|
left: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
left: 57px;
|
||||||
|
bottom: 42px;
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #00e8ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,295 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="累计数据对比" icon="cockpitItemIcon" size="operatingLarge" topSize="large">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="left" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 348px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
">
|
||||||
|
集团情况
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{chartData.topBarData.rate[0]}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.topBarData.flags>0?'#30B590':'#FF9423'}" >{{chartData.topBarData.diff[0]}}</span></span>
|
||||||
|
</div>
|
||||||
|
<operatingTopBar :chartData="chartData?.topBarData || {}" />
|
||||||
|
</div>
|
||||||
|
<div class="right" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 1220px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<operatingBar :dateData="dateData" :chartData="chartData?.barData || {}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import operatingBar from "./operatingBar.vue";
|
||||||
|
import operatingTopBar from "./operatingTopBar.vue";
|
||||||
|
|
||||||
|
// 序号→地名映射表(levelId=序号)
|
||||||
|
const baseIndexToNameMap = {
|
||||||
|
7: "宜兴",
|
||||||
|
8: "漳州",
|
||||||
|
3: "自贡",
|
||||||
|
2: "桐城",
|
||||||
|
9: "洛阳",
|
||||||
|
5: "合肥",
|
||||||
|
10: "秦皇岛",
|
||||||
|
6: "宿迁"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, operatingBar, operatingTopBar },
|
||||||
|
props: {
|
||||||
|
ytdData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: {
|
||||||
|
topBarData: { // levelId=1的整合数据
|
||||||
|
locations: [], // 固定为["凯盛新能"]
|
||||||
|
diff: [], // 差值数组
|
||||||
|
targets: [], // 预算值数组
|
||||||
|
reals: [], // 实际值数组
|
||||||
|
rate: [], // 完成率数组
|
||||||
|
flags: [] // 完成状态数组(0/1)
|
||||||
|
},
|
||||||
|
barData: { // levelId≠1的整合数据
|
||||||
|
locations: [], // levelId对应的baseIndexToNameMap中的地名
|
||||||
|
diff: [], // 对应差值数组
|
||||||
|
targets: [], // 预算值数组
|
||||||
|
reals: [], // 实际值数组
|
||||||
|
rate: [], // 完成率数组
|
||||||
|
flags: [] // 完成状态数组(0/1)
|
||||||
|
// baseIndexes: []// 对应levelId(序号)数组
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
ytdData: {
|
||||||
|
handler() {
|
||||||
|
this.processChartData();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
processChartData() {
|
||||||
|
// 初始化空数据结构
|
||||||
|
const initTopBarData = {
|
||||||
|
locations: [], diff: [], targets: [], reals: [], rate: [], flags: []
|
||||||
|
};
|
||||||
|
const initBarData = { locations: [], diff: [], targets: [], reals: [], rate: [], flags: [] };
|
||||||
|
|
||||||
|
// 1. 处理levelId=1的整合数据(逻辑不变)
|
||||||
|
const level1Data = this.ytdData.group;
|
||||||
|
if (!level1Data) return;
|
||||||
|
const topBarData = { ...initTopBarData };
|
||||||
|
topBarData.locations = ["凯盛新能"];
|
||||||
|
topBarData.diff.push(level1Data.diff || 0);
|
||||||
|
topBarData.targets.push(level1Data.target || 0);
|
||||||
|
topBarData.reals.push(level1Data.real || 0);
|
||||||
|
topBarData.rate.push(level1Data.rate || 0);
|
||||||
|
topBarData.flags.push(level1Data.rate >= 100 ? 1 : 0);
|
||||||
|
|
||||||
|
// 2. 处理levelId≠1的整合数据(核心:levelId匹配地名)
|
||||||
|
const barData = { ...initBarData };
|
||||||
|
// 筛选有效数据:levelId≠1 且 levelId在baseIndexToNameMap中
|
||||||
|
const validOtherData = this.ytdData.factory
|
||||||
|
|
||||||
|
// 遍历有效数据,填充locations(levelId→地名)
|
||||||
|
validOtherData.forEach(item => {
|
||||||
|
barData.locations.push(item.title); // locations=levelId对应的地名(如levelId=7→宜兴)
|
||||||
|
barData.diff.push(item.diff || 0);
|
||||||
|
barData.targets.push(item.budget || 0);
|
||||||
|
barData.reals.push(item.real || 0);
|
||||||
|
barData.rate.push(item.rate || 0);
|
||||||
|
barData.flags.push(item.rate >= 100 ? 1 : 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. 更新chartData
|
||||||
|
this.chartData = { topBarData, barData };
|
||||||
|
console.log('levelId=1数据:', this.chartData.topBarData);
|
||||||
|
console.log('levelId≠1数据(locations=levelId对应地名):', this.chartData.barData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 原有样式保持不变 */
|
||||||
|
.scroll-container {
|
||||||
|
max-height: 210px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px 27px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
width: 21px;
|
||||||
|
height: 23px;
|
||||||
|
background: rgba(0, 106, 205, 0.22);
|
||||||
|
backdrop-filter: blur(1.5px);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #68b5ff;
|
||||||
|
line-height: 23px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eq-name {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eqStatus {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitLine {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
border: 1px solid #adadad;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #00ffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoLeft {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoRight {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarWrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLine {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #acacac 100%);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLineTop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg,
|
||||||
|
rgba(53, 223, 247, 0) 0%,
|
||||||
|
rgba(54, 220, 246, 0.92) 92%,
|
||||||
|
#36f6e5 100%,
|
||||||
|
#37acf5 100%);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chartImgBottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 45px;
|
||||||
|
left: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
left: 57px;
|
||||||
|
bottom: 42px;
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #00e8ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<template>
|
||||||
|
<div class="lineBottom" style="height: 160px; width: 100%">
|
||||||
|
<operatingLineBarSaleSingle :refName="'totalOperating'" :chartData="chartD" style="height: 100%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBarSaleSingle from './operatingLineBarSaleSingle.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBarSaleSingle },
|
||||||
|
props: ["detailData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
locations() {
|
||||||
|
return ['预算', '实际'];
|
||||||
|
},
|
||||||
|
chartD() {
|
||||||
|
// 背景图片路径(若不需要可注释)
|
||||||
|
// const bgImageUrl = require('@/assets/img/labelBg.png');
|
||||||
|
const rate = this.detailData?.rate? Number(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],
|
||||||
|
},
|
||||||
|
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 || 0, // 实际项:达标(绿色)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
allPlaceNames: ['预算', '实际'],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 60,
|
||||||
|
barCategoryGap: '50%',
|
||||||
|
data: seriesData,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const currentFlag = params.data.flag || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
console.log('data', data);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFangSC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
margin-right: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
width: 283px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0b58ff;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 8px;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button.active {
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
background: #3071ff;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,365 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<div class="lineBottom" style="height: 320px; width: 100%">
|
||||||
|
<operatingLineBar :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSaleGroup.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentDataSource() {
|
||||||
|
console.log('yyyy', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
console.log('this.1111', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData.locations
|
||||||
|
},
|
||||||
|
// 根据按钮切换生成对应的 chartData
|
||||||
|
chartD() {
|
||||||
|
// 销量场景数据
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
const diff = data.diff[0]
|
||||||
|
const rate = data.rate[0]
|
||||||
|
console.log(this.currentDataSource, 'currentDataSource');
|
||||||
|
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
// 1. 完成率(折线图)
|
||||||
|
// {
|
||||||
|
// name: '完成率',
|
||||||
|
// type: 'line',
|
||||||
|
// yAxisIndex: 1, // 绑定右侧Y轴(需在子组件启用配置)
|
||||||
|
// lineStyle: {
|
||||||
|
// color: 'rgba(40, 138, 255, .5)',
|
||||||
|
// width: 2
|
||||||
|
// },
|
||||||
|
// itemStyle: {
|
||||||
|
// color: 'rgba(40, 138, 255, 1)',
|
||||||
|
// borderColor: 'rgba(40, 138, 255, 1)',
|
||||||
|
// borderWidth: 2,
|
||||||
|
// radius: 4
|
||||||
|
// },
|
||||||
|
// areaStyle: {
|
||||||
|
// opacity: 0.2,
|
||||||
|
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
// { offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||||
|
// { offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||||
|
// ])
|
||||||
|
// },
|
||||||
|
// data: data.rates, // 完成率(%)
|
||||||
|
// symbol: 'circle',
|
||||||
|
// symbolSize: 6
|
||||||
|
// },
|
||||||
|
// 2. 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.targets // 目标销量(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图,含达标状态)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||||
|
const safeFlag = data.flags;
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals // 实际销量(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 毛利率场景数据
|
||||||
|
const grossProfitData = {
|
||||||
|
series: [
|
||||||
|
// 1. 完成率(折线图)
|
||||||
|
{
|
||||||
|
name: '完成率',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
lineStyle: { color: 'rgba(40, 138, 255, .5)', width: 2 },
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderColor: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
radius: 4
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.2,
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||||
|
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: [106.7, 96.9, 106.5, 106.1, 93.8, 105.9], // 毛利率完成率(%)
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 2. 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 14,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: [30, 32, 31, 33, 32, 34] // 目标毛利率(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 14,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const safeFlag = [1, 0, 1, 1, 0, 1]; // 达标状态
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: [32, 31, 33, 35, 30, 36] // 实际毛利率(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据按钮状态返回对应数据
|
||||||
|
return this.activeButton === 0 ? salesData : grossProfitData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end; // 标题左、右侧容器右,整体两端对齐
|
||||||
|
align-items: center; // 垂直居中,避免上下错位
|
||||||
|
gap: 16px; // 标题与右侧容器的最小间距,防止拥挤
|
||||||
|
width: 100%; // 确保占满父容器,实现两端对齐
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 标题固定在左侧,不挤压右侧空间
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; // 图例和按钮组垂直居中
|
||||||
|
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||||
|
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 图例:在右侧容器内横向排列
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px; // 图例项之间间距,避免重叠
|
||||||
|
align-items: center;
|
||||||
|
// 移除原margin-left,避免位置偏移
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap; // 防止图例文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图例颜色
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
width: 283px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
border-radius: 12px;
|
||||||
|
// 移除原margin-right,由右侧容器统一控制
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0b58ff;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 8px; // 确保文字间距生效
|
||||||
|
padding-left: 8px; // 抵消letter-spacing导致的文字左偏
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button.active {
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
background: #3071ff;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :isShowTab="true" :name="title" icon="cockpitItemIcon" size="opLargeBg" topSize="large"
|
||||||
|
@tabChange="handleChange">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px">
|
||||||
|
<div
|
||||||
|
v-for="item in sortedIndicators"
|
||||||
|
:key="item.key"
|
||||||
|
class="dashboard"
|
||||||
|
@click="item.route && handleDashboardClick(item.route)"
|
||||||
|
>
|
||||||
|
<div class="title">
|
||||||
|
{{ item.name }}·{{ item.unit }}
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 14px;text-align: right;padding-right: 5px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{item.data.rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:item.data.rate>=100?'#30B590':'#FF9423'}" >{{item.data.diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
<operatingSingleBar :detailData="item.data"></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from '../components/container.vue'
|
||||||
|
import operatingSingleBar from './operatingSingleBar.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, operatingSingleBar },
|
||||||
|
props: {
|
||||||
|
// 接收父组件传递的 月度+累计 组合数据
|
||||||
|
relatedData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
relatedMon: [], // 月度数据(数组格式,存储销量/单价等数据)
|
||||||
|
relatedTotal: [] // 累计数据(数组格式,存储销量/单价等数据)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
factory: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 可选:动态标题
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
// 核心:当前激活的数据集(月度/累计),默认初始化月度数据
|
||||||
|
activeData: this.relatedData.relatedMon || [],
|
||||||
|
currentTab: 'month'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
indicatorDefs() {
|
||||||
|
return [
|
||||||
|
{ key: 'elecFee', name: '原片电费', unit: '万元', route: null},
|
||||||
|
{ key: 'elecCost', name: '加工电费', unit: '万元', route: null},
|
||||||
|
{ key: 'elecCostW', name: '外围电费', unit: '万元', route: null},
|
||||||
|
{ key: 'fdl', name: '发电量', unit: '度', route: null},
|
||||||
|
{ key: 'rjfdl', name: '日均发电量', unit: '度', route: null},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
indicators() {
|
||||||
|
const fallback = { targetValue: 0, value: 0, completed: 0, diffValue: 0 }
|
||||||
|
const list = (Array.isArray(this.activeData) ? this.activeData : [])
|
||||||
|
|
||||||
|
return this.indicatorDefs.map(def => {
|
||||||
|
const data = list.find(item => item && item.title === def.name) || fallback
|
||||||
|
return {
|
||||||
|
...def,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
flag:data.rate >= 100 ? 1 : 0
|
||||||
|
},
|
||||||
|
sortValue: Number((data && data.real) ?? 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
sortedIndicators() {
|
||||||
|
const unitOrder = ['万元', '度']
|
||||||
|
const unitRank = (u) => {
|
||||||
|
const idx = unitOrder.indexOf(u)
|
||||||
|
return idx === -1 ? 999 : idx
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.indicators.slice().sort((a, b) => {
|
||||||
|
const ur = unitRank(a.unit) - unitRank(b.unit)
|
||||||
|
if (ur !== 0) return ur
|
||||||
|
const vr = (b.sortValue ?? -Infinity) - (a.sortValue ?? -Infinity)
|
||||||
|
if (vr !== 0) return vr
|
||||||
|
return String(a.key).localeCompare(String(b.key))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 可选:监听 relatedData 初始变化(若父组件异步传递数据,确保 activeData 同步更新)
|
||||||
|
relatedData: {
|
||||||
|
handler(newVal) {
|
||||||
|
if(this.currentTab === 'month') {
|
||||||
|
this.activeData = newVal.relatedMon || [];
|
||||||
|
}else{
|
||||||
|
this.activeData = newVal.relatedTotal || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
console.log('组件挂载时的激活数据:', this.activeData);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleDashboardClick(path) {
|
||||||
|
this.$router.push({
|
||||||
|
path: path,
|
||||||
|
query: {
|
||||||
|
factory: this.$route.query.factory ? this.$route.query.factory : this.factory,
|
||||||
|
dateData: this.dateData
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Tab 切换处理函数
|
||||||
|
* @param {String} value 切换值('month' = 月度,'total' = 累计,可根据实际Tab值调整)
|
||||||
|
*/
|
||||||
|
handleChange(value) {
|
||||||
|
console.log('Tab 切换值:', value);
|
||||||
|
this.currentTab = value;
|
||||||
|
// 根据 Tab 值更新当前激活的数据集
|
||||||
|
if (value === 'month') {
|
||||||
|
// 切换为月度数据
|
||||||
|
this.activeData = this.relatedData.relatedMon || [];
|
||||||
|
} else {
|
||||||
|
// 切换为累计数据(非 month 均视为累计,可根据实际需求调整判断条件)
|
||||||
|
this.activeData = this.relatedData.relatedTotal || [];
|
||||||
|
}
|
||||||
|
console.log('当前激活数据集:', this.activeData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||||
|
.scroll-container {
|
||||||
|
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||||
|
max-height: 210px;
|
||||||
|
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||||
|
overflow-y: auto;
|
||||||
|
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||||
|
/* Chrome/Safari */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* IE/Edge */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 312px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
// width: 190px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
// width: 190px;
|
||||||
|
height: 32px;
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
width: 120px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||||
|
<div class="dashboard">
|
||||||
|
<div class="title">
|
||||||
|
累计完成率
|
||||||
|
</div>
|
||||||
|
<div class="number">
|
||||||
|
<div class="yield">
|
||||||
|
{{ formatRate(factoryData?.completeRate) }}%
|
||||||
|
</div>
|
||||||
|
<div class="mom">
|
||||||
|
同比{{ formatRate(factoryData?.thb) }}%
|
||||||
|
<img v-if="factoryData?.thb >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="上升">
|
||||||
|
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="下降">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="line" style="padding: 0px;">
|
||||||
|
<!-- 传递包含flag的factoryData给柱状图组件 -->
|
||||||
|
<verticalBarChart :detailData="factoryData"></verticalBarChart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import electricityGauge from './electricityGauge.vue'
|
||||||
|
import verticalBarChart from './verticalBarChart.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, electricityGauge, verticalBarChart },
|
||||||
|
props: {
|
||||||
|
totalData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
month: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
/**
|
||||||
|
* 自动提取monData中的工厂数据,并新增flag字段
|
||||||
|
*/
|
||||||
|
factoryData() { // 整合原始数据 + 计算flag
|
||||||
|
return {
|
||||||
|
completeRate: this.totalData.rate ? Number(this.totalData.rate) : 0,
|
||||||
|
diff: this.totalData.diff,
|
||||||
|
real: this.totalData.real,
|
||||||
|
target: this.totalData.target,
|
||||||
|
thb: this.totalData.yoyRate,
|
||||||
|
// ...rawData,
|
||||||
|
flag: this.totalData.rate >= 100 ? 1 : 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 格式化百分比数值:处理空值/非数字,兜底为0
|
||||||
|
*/
|
||||||
|
formatRate(value) {
|
||||||
|
if (isNaN(value) || value === null || value === undefined) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||||
|
.scroll-container {
|
||||||
|
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||||
|
max-height: 210px;
|
||||||
|
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||||
|
overflow-y: auto;
|
||||||
|
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||||
|
/* Chrome/Safari */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* IE/Edge */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 264px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
// width: 190px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 46px;
|
||||||
|
color: #0B58FF;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
width: 500px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .leftTitle {
|
||||||
|
// .item {
|
||||||
|
// width: 67px;
|
||||||
|
// height: 180px;
|
||||||
|
// padding: 37px 23px;
|
||||||
|
// background: #F9FCFF;
|
||||||
|
// font-family: PingFangSC, PingFang SC;
|
||||||
|
// font-weight: 400;
|
||||||
|
// font-size: 18px;
|
||||||
|
// color: #000000;
|
||||||
|
// line-height: 25px;
|
||||||
|
// letter-spacing: 1px;
|
||||||
|
// // text-align: left;
|
||||||
|
// font-style: normal;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- <style>
|
||||||
|
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style> -->
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
<template>
|
||||||
|
<div style="width: 100%; height: 210px;position: relative;">
|
||||||
|
<div style='font-size: 16px;position: absolute;left: 20px;top:10px'>
|
||||||
|
万元
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;position: absolute;right: 20px;top:10px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{detailData.completeRate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:detailData.flag>0?'#30B590':'#FF9423'}" >{{detailData.diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 210px;"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
refName: {
|
||||||
|
type: String,
|
||||||
|
default: 'verticalBarChart',
|
||||||
|
},
|
||||||
|
detailData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
completeRate: 0,
|
||||||
|
diff: 0,
|
||||||
|
real: 0,
|
||||||
|
target: 0,
|
||||||
|
thb: 0,
|
||||||
|
flag: 0
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => this.updateChart());
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
detailData: {
|
||||||
|
handler() {
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs[this.refName];
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('this.detailData', this.detailData);
|
||||||
|
|
||||||
|
// 修复:优化实例销毁逻辑,避免重复dispose
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.clear(); // 先清空,再重新渲染
|
||||||
|
} else {
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解构数据,避免重复取值
|
||||||
|
const { diff, completeRate, real, target, flag } = this.detailData;
|
||||||
|
// 确保数值为数字类型
|
||||||
|
const realValue = Number(real) || 0;
|
||||||
|
const targetValue = Number(target) || 0;
|
||||||
|
const diffValue = Number(diff) || 0;
|
||||||
|
const rateValue = Number(completeRate) || 0;
|
||||||
|
const flagValue = Number(flag) || 0;
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: { backgroundColor: '#6a7985' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 40,
|
||||||
|
bottom: 15,
|
||||||
|
right: 80,
|
||||||
|
left: 10,
|
||||||
|
containLabel: true,
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'value',
|
||||||
|
axisTick: { show: false },
|
||||||
|
min: 0,
|
||||||
|
splitNumber: 4,
|
||||||
|
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLabel: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 12, interval: 0, padding: [5, 0, 0, 0] }
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'category',
|
||||||
|
axisLabel: { color: 'rgba(0, 0, 0, 0.75)', fontSize: 12, interval: 0, padding: [5, 0, 0, 0] },
|
||||||
|
axisLine: { show: true, lineStyle: { color: '#E5E6EB', width: 1, type: 'solid' } },
|
||||||
|
axisTick: { show: false },
|
||||||
|
data: ['实际', '预算']
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 24,
|
||||||
|
// 修复:拆分数据项,确保每个柱子的样式独立生效
|
||||||
|
data: [
|
||||||
|
// 实际值柱子(核心:绑定flag颜色)
|
||||||
|
{
|
||||||
|
value: realValue,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'right',
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
// 修复:flag颜色判断独立绑定到实际值柱子
|
||||||
|
itemStyle: {
|
||||||
|
color: flagValue === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 预算值柱子(固定蓝色渐变)
|
||||||
|
{
|
||||||
|
value: targetValue,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'right',
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 1, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: '#82CCFF' },
|
||||||
|
{ offset: 1, color: '#4B9DFF' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.myChart.setOption(option, true); // 新增:true表示替换所有配置,避免缓存
|
||||||
|
|
||||||
|
// 优化:防抖resize,避免频繁触发
|
||||||
|
const resizeHandler = () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
};
|
||||||
|
window.removeEventListener('resize', resizeHandler); // 先移除再添加,避免重复绑定
|
||||||
|
window.addEventListener('resize', resizeHandler);
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', resizeHandler);
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
this.myChart = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||||
|
<div class="dashboard left">
|
||||||
|
<div class="title">
|
||||||
|
销量·万元
|
||||||
|
</div>
|
||||||
|
<div class="line">
|
||||||
|
<operatingSingleBar></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dashboard right">
|
||||||
|
<div class="title">
|
||||||
|
单价·万元
|
||||||
|
</div>
|
||||||
|
<div class="line">
|
||||||
|
<operatingSingleBar></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import operatingSingleBar from './operatingSingleBar.vue'
|
||||||
|
import verticalBarChart from './verticalBarChart.vue'
|
||||||
|
|
||||||
|
// import * as echarts from 'echarts'
|
||||||
|
// import rawItem from './raw-Item.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, operatingSingleBar, verticalBarChart },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
itemData: { // 接收父组件传递的设备数据数组
|
||||||
|
type: Array,
|
||||||
|
default: () => [] // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
title: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
month: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
itemData: {
|
||||||
|
handler(newValue, oldValue) {
|
||||||
|
// this.updateChart()
|
||||||
|
},
|
||||||
|
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// computed: {
|
||||||
|
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||||
|
// sortedItemData() {
|
||||||
|
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||||
|
// const totalCostItems = this.itemData.filter(item =>
|
||||||
|
// item.name && item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 过滤出不包含“总成本”的项
|
||||||
|
// const otherItems = this.itemData.filter(item =>
|
||||||
|
// !item.name || !item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 合并:总成本项在前,其他项在后
|
||||||
|
// return [...totalCostItems, ...otherItems];
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
mounted() {
|
||||||
|
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||||
|
// this.$nextTick(() => this.updateChart())
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||||
|
.scroll-container {
|
||||||
|
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||||
|
max-height: 210px;
|
||||||
|
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||||
|
overflow-y: auto;
|
||||||
|
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||||
|
/* Chrome/Safari */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* IE/Edge */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 382px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
// width: 190px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
// width: 190px;
|
||||||
|
height: 32px;
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
width: 120px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// .line {
|
||||||
|
// width: 500px;
|
||||||
|
// height: 205px;
|
||||||
|
// background: #F9FCFF;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .leftTitle {
|
||||||
|
// .item {
|
||||||
|
// width: 67px;
|
||||||
|
// height: 180px;
|
||||||
|
// padding: 37px 23px;
|
||||||
|
// background: #F9FCFF;
|
||||||
|
// font-family: PingFangSC, PingFang SC;
|
||||||
|
// font-weight: 400;
|
||||||
|
// font-size: 18px;
|
||||||
|
// color: #000000;
|
||||||
|
// line-height: 25px;
|
||||||
|
// letter-spacing: 1px;
|
||||||
|
// // text-align: left;
|
||||||
|
// font-style: normal;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- <style>
|
||||||
|
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style> -->
|
||||||
@@ -26,39 +26,26 @@
|
|||||||
grid-template-columns: 1624px;
|
grid-template-columns: 1624px;
|
||||||
">
|
">
|
||||||
<operatingLineChartCumulative :dateData="dateData" :ytdData="ytdData" />
|
<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 "../expenseAnalysisComponents/operatingLineChart";
|
import operatingLineChart from "../expenseAnalysisComponents/operatingLineChart";
|
||||||
import operatingLineChartCumulative from "../expenseAnalysisComponents/operatingLineChartCumulative.vue";
|
import operatingLineChartCumulative from "../expenseAnalysisComponents/operatingLineChartCumulative.vue";
|
||||||
|
|
||||||
import { getExpenseAnalysisGroupData } from '@/api/cockpit'
|
import { getExpenseAnalysisGroupData } from '@/api/cockpit'
|
||||||
import moment from "moment";
|
|
||||||
export default {
|
export default {
|
||||||
name: "DayReport",
|
name: "DayReport",
|
||||||
components: {
|
components: {
|
||||||
ReportHeader,
|
ReportHeader,
|
||||||
operatingLineChartCumulative,
|
operatingLineChartCumulative,
|
||||||
operatingLineChart,
|
operatingLineChart,
|
||||||
// premProdStatus,
|
|
||||||
Sidebar,
|
Sidebar,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -152,12 +139,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) {
|
||||||
@@ -207,28 +188,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>
|
||||||
|
|||||||
@@ -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,7 +61,6 @@ import screenfull from "screenfull";
|
|||||||
import changeBase from "../components/changeBase.vue";
|
import changeBase from "../components/changeBase.vue";
|
||||||
import monthlyOverview from "../expenseAnalysisComponents/monthlyOverview.vue";
|
import monthlyOverview from "../expenseAnalysisComponents/monthlyOverview.vue";
|
||||||
import totalOverview from "../expenseAnalysisComponents/totalOverview.vue";
|
import totalOverview from "../expenseAnalysisComponents/totalOverview.vue";
|
||||||
// import totalOverview from "../operatingComponents/totalOverview.vue";
|
|
||||||
import monthlyRelatedMetrics from "../expenseAnalysisComponents/monthlyRelatedMetrics.vue";
|
import monthlyRelatedMetrics from "../expenseAnalysisComponents/monthlyRelatedMetrics.vue";
|
||||||
import yearRelatedMetrics from "../expenseAnalysisComponents/yearRelatedMetrics.vue";
|
import yearRelatedMetrics from "../expenseAnalysisComponents/yearRelatedMetrics.vue";
|
||||||
import dataTrend from "../expenseAnalysisComponents/dataTrend.vue";
|
import dataTrend from "../expenseAnalysisComponents/dataTrend.vue";
|
||||||
@@ -77,15 +68,7 @@ import dataTrend from "../expenseAnalysisComponents/dataTrend.vue";
|
|||||||
import profitLineChart from "../costComponents/profitLineChart.vue";
|
import profitLineChart from "../costComponents/profitLineChart.vue";
|
||||||
import { mapState } from "vuex";
|
import { mapState } from "vuex";
|
||||||
import { getExpenseAnalysisFactoryData } from '@/api/cockpit'
|
import { getExpenseAnalysisFactoryData } 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: "DayReport",
|
||||||
components: {
|
components: {
|
||||||
@@ -289,28 +272,7 @@ export default {
|
|||||||
} 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>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user