Compare commits
19 Commits
projects/l
...
e770dc4fed
| Author | SHA1 | Date | |
|---|---|---|---|
| e770dc4fed | |||
| 4f7466bb29 | |||
| bb66f97b95 | |||
| 2465f89d26 | |||
| 09c7fd1f63 | |||
| 195979b3e0 | |||
| 0e6caec8d8 | |||
| acc327212e | |||
| dd1f639c77 | |||
| b36acbf1e6 | |||
| 660bc4b58b | |||
| 2a316f89c6 | |||
| 4d3b2b13b8 | |||
| 1d5af53e1a | |||
| d379d7bb5b | |||
| 418c29095b | |||
| ef230b3836 | |||
| 9f2f7036fd | |||
| 2f3586e2f2 |
7
.env.dev
7
.env.dev
@@ -9,7 +9,12 @@ VUE_APP_TITLE = 洛玻集团驾驶舱
|
|||||||
# VUE_APP_BASE_API = 'http://172.16.32.95:7070'
|
# VUE_APP_BASE_API = 'http://172.16.32.95:7070'
|
||||||
# VUE_APP_BASE_API = 'http://172.16.33.83:7070'
|
# VUE_APP_BASE_API = 'http://172.16.33.83:7070'
|
||||||
|
|
||||||
VUE_APP_BASE_API = 'http://192.168.0.35:7070'
|
# 杨姗姗
|
||||||
|
# VUE_APP_BASE_API = 'http://172.16.20.218:7070'
|
||||||
|
# 小田
|
||||||
|
VUE_APP_BASE_API = 'http://172.16.19.232:7070'
|
||||||
|
# 测试
|
||||||
|
# VUE_APP_BASE_API = 'http://192.168.0.35:8080'
|
||||||
|
|
||||||
|
|
||||||
# 路由懒加载
|
# 路由懒加载
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -20,3 +20,4 @@ selenium-debug.log
|
|||||||
*.local
|
*.local
|
||||||
|
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
sync_luobo.bat
|
||||||
@@ -117,6 +117,22 @@ export function updateDataBackUpDetail(data) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function copyLastMonthData(data) {
|
||||||
|
return request({
|
||||||
|
url: "/lb/index-target-month/copyLastMonth",
|
||||||
|
method: "post",
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function copyLastYearData(data) {
|
||||||
|
return request({
|
||||||
|
url: "/lb/index-target-year/copyLastYear",
|
||||||
|
method: "post",
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function getSalesRevenueGroupData(data) {
|
export function getSalesRevenueGroupData(data) {
|
||||||
return request({
|
return request({
|
||||||
@@ -254,6 +270,13 @@ export function getCalendar(data) {
|
|||||||
data: data,
|
data: data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
export function getCalendarYear(data) {
|
||||||
|
return request({
|
||||||
|
url: "lb/index-target-year/getCalendarYear",
|
||||||
|
method: "post",
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
export function getLevelStruc(data) {
|
export function getLevelStruc(data) {
|
||||||
return request({
|
return request({
|
||||||
url: "/lb/index-target-month/getLevelStruc",
|
url: "/lb/index-target-month/getLevelStruc",
|
||||||
|
|||||||
11
src/assets/icons/svg/logout.svg
Normal file
11
src/assets/icons/svg/logout.svg
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<g id="组_1" data-name="组 1" transform="translate(-970 -281.875)">
|
||||||
|
<g id="编辑备份" transform="translate(970 281.5)">
|
||||||
|
<rect id="矩形" width="16" height="16" transform="translate(0 0.375)" opacity="0"/>
|
||||||
|
<path id="形状" d="M12.281,5.406a.512.512,0,0,0,1.023,0V2.559A2.559,2.559,0,0,0,10.746,0H2.559A2.559,2.559,0,0,0,0,2.559v8.188A2.559,2.559,0,0,0,2.559,13.3H5.415a.512.512,0,1,0,0-1.023H2.559a1.535,1.535,0,0,1-1.535-1.535V2.559A1.535,1.535,0,0,1,2.559,1.023h8.188a1.535,1.535,0,0,1,1.535,1.535Z" transform="translate(1.535 1.535)" fill="#3d7aff"/>
|
||||||
|
</g>
|
||||||
|
<g id="移出" transform="translate(978 290.608)">
|
||||||
|
<path id="形状结合" d="M0,2.821a.746.746,0,0,0,.529.73l.118.011,3.68-.011L3.459,4.4a.656.656,0,0,0,.816,1.02l.1-.083L6.146,3.6a1.107,1.107,0,0,0,.321-.634l.012-.2,0-.026a.61.61,0,0,0-.015-.125,1.106,1.106,0,0,0-.189-.455l-.107-.129L4.39.2a.656.656,0,0,0-1.022.814L4.23,2.108.654,2.1l-.125.011A.88.88,0,0,0,0,2.821Z" transform="translate(0 0)" fill="#3d7aff"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
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 |
BIN
src/assets/images/avatar.png
Normal file
BIN
src/assets/images/avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
@@ -143,7 +143,7 @@ h6 {
|
|||||||
|
|
||||||
.pagination-container .el-pagination {
|
.pagination-container .el-pagination {
|
||||||
right: 0;
|
right: 0;
|
||||||
position: absolute;
|
position: absolute !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
|
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
|
||||||
<div class="avatar-wrapper">
|
<div class="avatar-wrapper">
|
||||||
<img :src="avatar" class="user-avatar">
|
<img :src="require(`../../assets/images/choicepart/avatar.png`)" class="user-avatar">
|
||||||
<span v-if="nickname" class="user-nickname">{{ nickname }}</span>
|
<span v-if="nickname" class="user-nickname">{{ nickname }}</span>
|
||||||
<i class="el-icon-caret-bottom" />
|
<i class="el-icon-caret-bottom" />
|
||||||
</div>
|
</div>
|
||||||
@@ -188,7 +188,7 @@ export default {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: -20px;
|
right: -20px;
|
||||||
top: 25px;
|
top: 18px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="sidebar-logo-container" :class="{'collapse':collapse}" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
|
<div class="sidebar-logo-container" :class="{'collapse':collapse}" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
|
||||||
<transition name="sidebarLogoFade">
|
<transition name="sidebarLogoFade">
|
||||||
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
|
<div v-if="collapse" key="collapse" class="sidebar-logo-link">
|
||||||
<img v-if="logo" :src="logo" class="sidebar-logo" />
|
<img v-if="logo" :src="logo" class="sidebar-logo" />
|
||||||
<h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
|
<h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
|
||||||
</router-link>
|
</div>
|
||||||
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
|
<div v-else key="expand" class="sidebar-logo-link">
|
||||||
<img v-if="logo" :src="logo" class="sidebar-logo" />
|
<img v-if="logo" :src="logo" class="sidebar-logo" />
|
||||||
<h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
|
<h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
|
||||||
</router-link>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -8,6 +8,48 @@ import { isRelogin } from '@/utils/request'
|
|||||||
|
|
||||||
NProgress.configure({ showSpinner: false })
|
NProgress.configure({ showSpinner: false })
|
||||||
|
|
||||||
|
// 从菜单树中查找第一个可见叶子菜单路径(按接口返回顺序)
|
||||||
|
function findFirstLeafPathFromMenus(menus) {
|
||||||
|
if (!Array.isArray(menus) || menus.length === 0) return null
|
||||||
|
|
||||||
|
function dfs(list, parentPath = '') {
|
||||||
|
for (const item of list) {
|
||||||
|
if (item.visible === false) continue
|
||||||
|
const rawPath = item.path || ''
|
||||||
|
const currentPath = rawPath.startsWith('/')
|
||||||
|
? rawPath
|
||||||
|
: `${parentPath}/${rawPath}`.replace(/\/+/g, '/')
|
||||||
|
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
const found = dfs(item.children, currentPath)
|
||||||
|
if (found != null) return found
|
||||||
|
} else {
|
||||||
|
return currentPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
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']
|
||||||
|
|
||||||
@@ -17,21 +59,35 @@ router.beforeEach((to, from, next) => {
|
|||||||
to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
|
to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
|
||||||
/* has token*/
|
/* has token*/
|
||||||
if (to.path === '/login') {
|
if (to.path === '/login') {
|
||||||
next({ path: '/' })
|
next({ path: store.getters.defaultPath || '/' })
|
||||||
NProgress.done()
|
NProgress.done()
|
||||||
} else {
|
} else {
|
||||||
if (store.getters.roles.length === 0) {
|
if (store.getters.roles.length === 0) {
|
||||||
isRelogin.show = true
|
isRelogin.show = true
|
||||||
// 获取字典数据 add by 芋艿
|
// 获取字典数据 add by 芋艿
|
||||||
store.dispatch('dict/loadDictDatas')
|
store.dispatch('dict/loadDictDatas')
|
||||||
|
// 获取部门权限
|
||||||
|
store.dispatch('GetLevel')
|
||||||
// 判断当前用户是否已拉取完 user_info 信息
|
// 判断当前用户是否已拉取完 user_info 信息
|
||||||
store.dispatch('GetInfo').then(userInfo => {
|
store.dispatch('GetInfo').then(userInfo => {
|
||||||
isRelogin.show = false
|
isRelogin.show = false
|
||||||
// 触发 GenerateRoutes 事件时,将 menus 菜单树传递进去
|
// 触发 GenerateRoutes 事件时,将 menus 菜单树传递进去
|
||||||
store.dispatch('GenerateRoutes', userInfo.menus).then(accessRoutes => {
|
store.dispatch('GenerateRoutes', userInfo.menus).then(accessRoutes => {
|
||||||
// 根据 roles 权限生成可访问的路由表
|
// 根据 roles 权限生成可访问的路由表
|
||||||
router.addRoutes(accessRoutes) // 动态添加可访问路由表
|
router.addRoutes(accessRoutes) // 动态添加可访问路由表
|
||||||
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
|
const menuDefaultPath = findFirstLeafPathFromMenus(userInfo.menus)
|
||||||
|
const routeFallbackPath = findFirstLeafPathFromRoutes(accessRoutes)
|
||||||
|
const defaultPath = menuDefaultPath || routeFallbackPath || '/'
|
||||||
|
store.dispatch('SetDefaultPath', defaultPath)
|
||||||
|
// 仅当目标为根路径 '/' 时跳默认页;否则保持当前路径(含刷新场景),避免刷新被误判为“未匹配”而跳到默认页
|
||||||
|
if (to.path === '/') {
|
||||||
|
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 {
|
||||||
|
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成,当前页刷新时保留 to 的路径
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
store.dispatch('LogOut').then(() => {
|
store.dispatch('LogOut').then(() => {
|
||||||
@@ -40,7 +96,12 @@ router.beforeEach((to, from, next) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
next()
|
if (to.path === '/') {
|
||||||
|
const defaultPath = store.getters.defaultPath || '/'
|
||||||
|
next({ path: defaultPath, replace: true })
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -64,13 +64,13 @@ export const constantRoutes = [
|
|||||||
component: (resolve) => require(["@/views/error/401"], resolve),
|
component: (resolve) => require(["@/views/error/401"], resolve),
|
||||||
hidden: true,
|
hidden: true,
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
path: "/",
|
// path: "/",
|
||||||
// component: () => import('@/views/choicePart'),
|
// // component: () => import('@/views/choicePart'),
|
||||||
component: () => import("@/views/home"),
|
// component: () => import("@/views/home"),
|
||||||
hidden: true,
|
// hidden: true,
|
||||||
meta: { requireToken: true },
|
// meta: { requireToken: true },
|
||||||
},
|
// },
|
||||||
// {
|
// {
|
||||||
// path: "/operatingRevenue",
|
// path: "/operatingRevenue",
|
||||||
// // component: () => import('@/views/choicePart'),
|
// // component: () => import('@/views/choicePart'),
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ const getters = {
|
|||||||
topbarRouters:state => state.permission.topbarRouters,
|
topbarRouters:state => state.permission.topbarRouters,
|
||||||
defaultRoutes:state => state.permission.defaultRoutes,
|
defaultRoutes:state => state.permission.defaultRoutes,
|
||||||
sidebarRouters:state => state.permission.sidebarRouters,
|
sidebarRouters:state => state.permission.sidebarRouters,
|
||||||
|
defaultPath: state => state.permission.defaultPath,
|
||||||
// 数据字典
|
// 数据字典
|
||||||
dict_datas: state => state.dict.dictDatas
|
dict_datas: state => state.dict.dictDatas,
|
||||||
|
// 部门层级
|
||||||
|
levelList: state => state.user.levelList
|
||||||
}
|
}
|
||||||
export default getters
|
export default getters
|
||||||
|
|||||||
@@ -11,8 +11,12 @@ const permission = {
|
|||||||
addRoutes: [],
|
addRoutes: [],
|
||||||
sidebarRouters: [], // 左侧边菜单的路由,被 Sidebar/index.vue 使用
|
sidebarRouters: [], // 左侧边菜单的路由,被 Sidebar/index.vue 使用
|
||||||
topbarRouters: [], // 顶部菜单的路由,被 TopNav/index.vue 使用
|
topbarRouters: [], // 顶部菜单的路由,被 TopNav/index.vue 使用
|
||||||
|
defaultPath: '/', // 登录后默认跳转路径(由菜单 jumpFlag===1 决定)
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
|
SET_DEFAULT_PATH: (state, path) => {
|
||||||
|
state.defaultPath = path || '/'
|
||||||
|
},
|
||||||
SET_ROUTES: (state, routes) => {
|
SET_ROUTES: (state, routes) => {
|
||||||
state.addRoutes = routes
|
state.addRoutes = routes
|
||||||
state.routes = constantRoutes.concat(routes)
|
state.routes = constantRoutes.concat(routes)
|
||||||
@@ -48,6 +52,9 @@ const permission = {
|
|||||||
commit('SET_TOPBAR_ROUTES', sidebarRoutes)
|
commit('SET_TOPBAR_ROUTES', sidebarRoutes)
|
||||||
resolve(rewriteRoutes)
|
resolve(rewriteRoutes)
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
SetDefaultPath({commit}, path) {
|
||||||
|
commit('SET_DEFAULT_PATH', path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {login, logout, getInfo, socialLogin, smsLogin} from '@/api/login'
|
import {login, logout, getInfo, socialLogin, smsLogin} from '@/api/login'
|
||||||
import {setToken, removeToken} from '@/utils/auth'
|
import {setToken, removeToken} from '@/utils/auth'
|
||||||
|
import {getLevelStruc} from '@/api/cockpit'
|
||||||
|
|
||||||
const user = {
|
const user = {
|
||||||
state: {
|
state: {
|
||||||
@@ -7,7 +8,8 @@ const user = {
|
|||||||
name: '',
|
name: '',
|
||||||
avatar: '',
|
avatar: '',
|
||||||
roles: [],
|
roles: [],
|
||||||
permissions: []
|
permissions: [],
|
||||||
|
levelList:[]
|
||||||
},
|
},
|
||||||
|
|
||||||
mutations: {
|
mutations: {
|
||||||
@@ -28,6 +30,9 @@ const user = {
|
|||||||
},
|
},
|
||||||
SET_PERMISSIONS: (state, permissions) => {
|
SET_PERMISSIONS: (state, permissions) => {
|
||||||
state.permissions = permissions
|
state.permissions = permissions
|
||||||
|
},
|
||||||
|
SET_LEVEL_LIST: (state, levelList) => {
|
||||||
|
state.levelList = levelList
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -122,7 +127,21 @@ const user = {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
// 获取层级
|
||||||
|
GetLevel({ commit, state }) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
getLevelStruc().then(res => {
|
||||||
|
// 如果未加载到数据,则直接返回
|
||||||
|
if (!res || !res.data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
commit('SET_LEVEL_LIST', res.data)
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
}).catch(error => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
// 退出系统
|
// 退出系统
|
||||||
LogOut({ commit, state }) {
|
LogOut({ commit, state }) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|||||||
@@ -81,6 +81,9 @@ export const DICT_TYPE = {
|
|||||||
PROMOTION_COUPON_TAKE_TYPE: 'promotion_coupon_take_type', // 优惠劵的领取方式
|
PROMOTION_COUPON_TAKE_TYPE: 'promotion_coupon_take_type', // 优惠劵的领取方式
|
||||||
PROMOTION_ACTIVITY_STATUS: 'promotion_activity_status', // 优惠活动的状态
|
PROMOTION_ACTIVITY_STATUS: 'promotion_activity_status', // 优惠活动的状态
|
||||||
PROMOTION_CONDITION_TYPE: 'promotion_condition_type', // 营销的条件类型枚举
|
PROMOTION_CONDITION_TYPE: 'promotion_condition_type', // 营销的条件类型枚举
|
||||||
|
|
||||||
|
// ========== 模块 ==========
|
||||||
|
LB_DW: 'lb_dw'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -139,3 +142,15 @@ export function getDictDataLabel(dictType, value) {
|
|||||||
const dict = getDictData(dictType, value);
|
const dict = getDictData(dictType, value);
|
||||||
return dict ? dict.label : '';
|
return dict ? dict.label : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// table中用来过滤字典
|
||||||
|
export function publicFormatter(dictTable) {
|
||||||
|
const dictDatas = getDictDatas(dictTable)
|
||||||
|
return function (val) {
|
||||||
|
const arr = {}
|
||||||
|
dictDatas.map((item) => {
|
||||||
|
arr[item.value] = item.label
|
||||||
|
})
|
||||||
|
return arr?.[val]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import {Message, MessageBox, Notification} from 'element-ui'
|
import {Message, MessageBox, Notification, Loading} from 'element-ui'
|
||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
import {getAccessToken, getRefreshToken, getTenantId, setToken, getVisitTenantId} from '@/utils/auth'
|
import {getAccessToken, getRefreshToken, getTenantId, setToken, getVisitTenantId} from '@/utils/auth'
|
||||||
import errorCode from '@/utils/errorCode'
|
import errorCode from '@/utils/errorCode'
|
||||||
@@ -31,8 +31,36 @@ const service = axios.create({
|
|||||||
// 禁用 Cookie 等信息
|
// 禁用 Cookie 等信息
|
||||||
withCredentials: false,
|
withCredentials: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
let loadingInstance = null
|
||||||
|
function startLoading() {
|
||||||
|
loadingInstance = Loading.service({
|
||||||
|
fullscreen: false,
|
||||||
|
text: '拼命加载中...',
|
||||||
|
background: 'rgba(0, 0, 0, 0.1)'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function endLoading() {
|
||||||
|
loadingInstance.close()
|
||||||
|
}
|
||||||
|
let needLoadingRequestCount = 0
|
||||||
|
function showFullScreenLoading() {
|
||||||
|
if (needLoadingRequestCount === 0) {
|
||||||
|
startLoading()
|
||||||
|
}
|
||||||
|
needLoadingRequestCount++
|
||||||
|
}
|
||||||
|
function tryHideFullScreenLoading() {
|
||||||
|
if (needLoadingRequestCount <= 0) return
|
||||||
|
needLoadingRequestCount--
|
||||||
|
if (needLoadingRequestCount === 0) {
|
||||||
|
endLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
// request拦截器
|
// request拦截器
|
||||||
service.interceptors.request.use(config => {
|
service.interceptors.request.use(config => {
|
||||||
|
showFullScreenLoading()
|
||||||
// 是否需要设置 token
|
// 是否需要设置 token
|
||||||
const isToken = (config.headers || {}).isToken === false
|
const isToken = (config.headers || {}).isToken === false
|
||||||
if (getAccessToken() && !isToken) {
|
if (getAccessToken() && !isToken) {
|
||||||
@@ -88,12 +116,14 @@ service.interceptors.request.use(config => {
|
|||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
}, error => {
|
}, error => {
|
||||||
|
tryHideFullScreenLoading()
|
||||||
console.log(error)
|
console.log(error)
|
||||||
Promise.reject(error)
|
Promise.reject(error)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 响应拦截器
|
// 响应拦截器
|
||||||
service.interceptors.response.use(async res => {
|
service.interceptors.response.use(async res => {
|
||||||
|
tryHideFullScreenLoading()
|
||||||
let { data } = res
|
let { data } = res
|
||||||
// 未设置状态码则默认成功状态
|
// 未设置状态码则默认成功状态
|
||||||
// 二进制数据则直接返回,例如说 Excel 导出
|
// 二进制数据则直接返回,例如说 Excel 导出
|
||||||
@@ -202,6 +232,7 @@ service.interceptors.response.use(async res => {
|
|||||||
}
|
}
|
||||||
}, error => {
|
}, error => {
|
||||||
console.log('err' + error)
|
console.log('err' + error)
|
||||||
|
tryHideFullScreenLoading()
|
||||||
let {message} = error;
|
let {message} = error;
|
||||||
if (message === "Network Error") {
|
if (message === "Network Error") {
|
||||||
message = "后端接口连接异常";
|
message = "后端接口连接异常";
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
<div class="bullshit__info">
|
<div class="bullshit__info">
|
||||||
对不起,您正在寻找的页面不存在。尝试检查URL的错误,然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容。
|
对不起,您正在寻找的页面不存在。尝试检查URL的错误,然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容。
|
||||||
</div>
|
</div>
|
||||||
<router-link to="/" class="bullshit__return-home">
|
<router-link to="/login" class="bullshit__return-home">
|
||||||
返回首页
|
返回登录页
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
246
src/views/home/accountsReceivable/accountsReceivable.vue
Normal file
246
src/views/home/accountsReceivable/accountsReceivable.vue
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
<template>
|
||||||
|
<div id="dayReport" class="dayReport" :style="styles">
|
||||||
|
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||||
|
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||||
|
<ReportHeader :dateData="dateData" top-title="应收账款" :is-full-screen="isFullScreen"
|
||||||
|
@screenfullChange="screenfullChange" @timeRangeChange="handleTimeChange" />
|
||||||
|
<div class="main-body" style="
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
padding: 0px 16px 0 272px;
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div class="top" style="margin-top: -20px; display: flex; gap: 16px">
|
||||||
|
<div class="top-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns:1624px;
|
||||||
|
">
|
||||||
|
<operatingLineChart :dateData="dateData" :monthData="monthData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||||
|
<div class="left-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns: 1624px;
|
||||||
|
">
|
||||||
|
<operatingLineChartCumulative :dateData="dateData" :ytdData="ytdData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import ReportHeader from "../components/noRouterHeader.vue";
|
||||||
|
import { Sidebar } from "../../../layout/components";
|
||||||
|
import screenfull from "screenfull";
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
import operatingLineChart from "../accountsReceivableComponents/operatingLineChart";
|
||||||
|
import operatingLineChartCumulative from "../accountsReceivableComponents/operatingLineChartCumulative.vue";
|
||||||
|
|
||||||
|
import { getSalesRevenueGroupData } from '@/api/cockpit'
|
||||||
|
export default {
|
||||||
|
name: "AccountsReceivable",
|
||||||
|
components: {
|
||||||
|
ReportHeader,
|
||||||
|
operatingLineChartCumulative,
|
||||||
|
operatingLineChart,
|
||||||
|
Sidebar,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
weekArr: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
|
||||||
|
isFullScreen: false,
|
||||||
|
timer: null,
|
||||||
|
beilv: 1,
|
||||||
|
value: 100,
|
||||||
|
sort:1,
|
||||||
|
selectDate:{},
|
||||||
|
monthData: {},
|
||||||
|
ytdData:{},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.init();
|
||||||
|
this.windowWidth(document.documentElement.clientWidth);
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
theme: (state) => state.settings.theme,
|
||||||
|
sideTheme: (state) => state.settings.sideTheme,
|
||||||
|
sidebar: (state) => state.app.sidebar,
|
||||||
|
device: (state) => state.app.device,
|
||||||
|
needTagsView: (state) => state.settings.tagsView,
|
||||||
|
fixedHeader: (state) => state.settings.fixedHeader,
|
||||||
|
}),
|
||||||
|
classObj() {
|
||||||
|
return {
|
||||||
|
hideSidebar: !this.sidebar.opened,
|
||||||
|
openSidebar: this.sidebar.opened,
|
||||||
|
withoutAnimation: this.sidebar.withoutAnimation,
|
||||||
|
mobile: this.device === "mobile",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
variables() {
|
||||||
|
return variables;
|
||||||
|
},
|
||||||
|
// ...mapGetters(['sidebar']),
|
||||||
|
styles() {
|
||||||
|
const v = Math.floor(this.value * this.beilv * 100) / 10000;
|
||||||
|
return {
|
||||||
|
transform: `scale(${v})`,
|
||||||
|
transformOrigin: "left top",
|
||||||
|
// overflow: hidden;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
clientWidth(val) {
|
||||||
|
if (!this.timer) {
|
||||||
|
this.clientWidth = val;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
this.timer = true;
|
||||||
|
let _this = this;
|
||||||
|
setTimeout(function () {
|
||||||
|
_this.timer = false;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
// 这里可以添加修改时的方法
|
||||||
|
this.windowWidth(val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.destroy();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const _this = this;
|
||||||
|
_this.beilv = document.documentElement.clientWidth / 1920;
|
||||||
|
window.onresize = () => {
|
||||||
|
return (() => {
|
||||||
|
_this.clientWidth = `${document.documentElement.clientWidth}`;
|
||||||
|
this.beilv = _this.clientWidth / 1920;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// sortChange(value) {
|
||||||
|
// this.sort = value
|
||||||
|
// this.getData()
|
||||||
|
// },
|
||||||
|
getData() {
|
||||||
|
getSalesRevenueGroupData({
|
||||||
|
startTime: this.dateData.startTime,
|
||||||
|
endTime: this.dateData.endTime,
|
||||||
|
sort: this.sort,
|
||||||
|
index: undefined,
|
||||||
|
factory: undefined
|
||||||
|
// timeDim: obj.mode
|
||||||
|
}).then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
this.monthData= res.data.month
|
||||||
|
this.ytdData = res.data.ytd
|
||||||
|
|
||||||
|
// this.saleData = res.data.SaleData
|
||||||
|
// this.premiumProduct = res.data.premiumProduct
|
||||||
|
// this.salesTrendMap = res.data.salesTrendMap
|
||||||
|
// this.grossMarginTrendMap = res.data.grossMarginTrendMap
|
||||||
|
// this.salesProportion = res.data.salesProportion ? res.data.salesProportion : {}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleTimeChange(obj) {
|
||||||
|
console.log(obj, 'obj');
|
||||||
|
this.dateData= obj
|
||||||
|
this.getData()
|
||||||
|
},
|
||||||
|
handleClickOutside() {
|
||||||
|
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
||||||
|
},
|
||||||
|
windowWidth(value) {
|
||||||
|
this.clientWidth = value;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
},
|
||||||
|
change() {
|
||||||
|
this.isFullScreen = screenfull.isFullscreen;
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.on("change", this.change);
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.off("change", this.change);
|
||||||
|
},
|
||||||
|
// 全屏
|
||||||
|
screenfullChange() {
|
||||||
|
console.log("screenfull.enabled", screenfull.isEnabled);
|
||||||
|
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.toggle(this.$refs.dayReportB);
|
||||||
|
},
|
||||||
|
// 导出
|
||||||
|
// exportPDF() {
|
||||||
|
// this.$message.success('正在导出,请稍等!')
|
||||||
|
// const element = document.getElementById('dayRepDom')
|
||||||
|
// element.style.display = 'block'
|
||||||
|
// const fileName = '株洲碲化镉生产日报' + moment().format('yyMMDD') + '.pdf'
|
||||||
|
// html2canvas(element, {
|
||||||
|
// dpi: 300, // Set to 300 DPI
|
||||||
|
// scale: 3 // Adjusts your resolution
|
||||||
|
// }).then(function(canvas) {
|
||||||
|
// const imgWidth = 595.28
|
||||||
|
// const imgHeight = 841.89
|
||||||
|
// const pageData = canvas.toDataURL('image/jpeg', 1.0)
|
||||||
|
// const PDF = new JsPDF('', 'pt', [imgWidth, imgHeight])
|
||||||
|
// PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
|
||||||
|
// setTimeout(() => {
|
||||||
|
// PDF.save(fileName) // 导出文件名
|
||||||
|
// }, 1000)
|
||||||
|
// })
|
||||||
|
// element.style.display = 'none'
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "~@/assets/styles/mixin.scss";
|
||||||
|
@import "~@/assets/styles/variables.scss";
|
||||||
|
.dayReport {
|
||||||
|
width: 1920px;
|
||||||
|
height: 1080px;
|
||||||
|
background: url("../../../assets/img/backp.png") no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
.hideSidebar .fixed-header {
|
||||||
|
width: calc(100% - 54px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarHide .fixed-header {
|
||||||
|
width: calc(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile .fixed-header {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
419
src/views/home/accountsReceivableComponents/Header.vue
Normal file
419
src/views/home/accountsReceivableComponents/Header.vue
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
<template>
|
||||||
|
<header class="report-header">
|
||||||
|
<!-- 左侧区域:logo + 标题 -->
|
||||||
|
<div class="left-content">
|
||||||
|
<img style="height: 36px;" src="../../../assets/img/cnbm.png" alt="benmaLogo" >
|
||||||
|
<div class="top-title">{{ topTitle }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="center-content">
|
||||||
|
<!-- 循环 pageRoutes,不再硬编码文字 -->
|
||||||
|
<div class="item" v-for="(page, index) in pageRoutes" :key="index" @click="goToPage(page.path, index)">
|
||||||
|
<span class="item-text">{{ page.text }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- :class="{ 'no-skew': activeIndex === index }
|
||||||
|
" -->
|
||||||
|
<!-- 右侧区域:全屏按钮 -->
|
||||||
|
<div class="right-content">
|
||||||
|
<el-button type="text" class="screen-btn" :title="isFullScreen ? '退出全屏' : '全屏'" @click="changeFullScreen">
|
||||||
|
<svg-icon style="color: #0B58FF;" v-if="isFullScreen" icon-class="unFullScreenView" />
|
||||||
|
<svg-icon style="color: #0B58FF;" v-else icon-class="fullScreenView" />
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 时间选择区域:日/月/年按钮 + label + 日期选择器 -->
|
||||||
|
<div class="timeType">
|
||||||
|
<!-- <div class="item" v-for="(item, index) in timeTypes" :key="index" @click="activeTime = index"
|
||||||
|
:class="{ 'no-skew': activeTime === index }">
|
||||||
|
<span class="item-text">{{ item.text }}</span>
|
||||||
|
</div> -->
|
||||||
|
<div class="dateP">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">月份选择</span>
|
||||||
|
</div>
|
||||||
|
<el-date-picker v-model="date" :type="getPickerType" :placeholder="getPickerPlaceholder"
|
||||||
|
class="custom-date-picker" style="width: 132px;height: 29px;" @change="emitTimeRange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment'
|
||||||
|
export default {
|
||||||
|
name: 'Header',
|
||||||
|
props: {
|
||||||
|
isFullScreen: { type: Boolean, default: false },
|
||||||
|
topTitle: { type: String, default: '' }
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentTime: '',
|
||||||
|
timeTimer: null,
|
||||||
|
date: undefined,
|
||||||
|
activeIndex: -1,
|
||||||
|
activeTime: 1, // 0=日,1=月,2=年(默认选中“日”)
|
||||||
|
pageRoutes: [
|
||||||
|
{ text: '营业收入', path: '/operatingRevenue/operatingRevenueIndex' },
|
||||||
|
{ text: '利润分析', path: '/profitAnalysis' },
|
||||||
|
{ text: '产销率库存分析', path: '/PSIAnal' },
|
||||||
|
{ text: '成本分析', path: '/cost/cost' },
|
||||||
|
{ text: '驾驶舱报表', path: '/cockpit' }
|
||||||
|
],
|
||||||
|
// 定义时间类型配置:text=按钮文字,pickerType=选择器类型,placeholder=占位符
|
||||||
|
timeTypes: [
|
||||||
|
{ text: '日', pickerType: 'date', placeholder: '选择日期' },
|
||||||
|
{ text: '月', pickerType: 'month', placeholder: '选择月份' },
|
||||||
|
{ text: '年', pickerType: 'year', placeholder: '选择年份' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 动态获取日期选择器类型
|
||||||
|
getPickerType() {
|
||||||
|
return this.timeTypes[this.activeTime].pickerType;
|
||||||
|
},
|
||||||
|
// 动态获取日期选择器占位符
|
||||||
|
getPickerPlaceholder() {
|
||||||
|
return this.timeTypes[this.activeTime].placeholder;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goToPage(path, index) {
|
||||||
|
// 1. 跳转到对应路由
|
||||||
|
this.$router.push(path);
|
||||||
|
// 2. 更新activeIndex,让当前点击项高亮
|
||||||
|
this.activeIndex = index;
|
||||||
|
},
|
||||||
|
changeFullScreen() { this.$emit('screenfullChange') },
|
||||||
|
padZero(num) { return num < 10 ? '0' + num : num },
|
||||||
|
/**
|
||||||
|
* 核心方法1:根据维度计算时间区间(首次进入时基于赋值的当月日期,计算“当月第一天0点→次月第一天0点”)
|
||||||
|
* @returns {Object} 包含 start(开始时间)、end(结束时间)、dimension(维度)的区间对象
|
||||||
|
*/
|
||||||
|
calculateTimeRange() {
|
||||||
|
let startTime = 0;
|
||||||
|
let endTime = 0;
|
||||||
|
const mode = this.activeTime + 1; // 1=日,2=月,3=年
|
||||||
|
const defaultMoment = moment(); // 默认当前时间
|
||||||
|
|
||||||
|
const targetMoment = this.date
|
||||||
|
? moment(this.date, this.getPickerType === 'date' ? 'YYYY-MM-DD' : (this.getPickerType === 'month' ? 'YYYY-MM' : 'YYYY'))
|
||||||
|
: defaultMoment;
|
||||||
|
|
||||||
|
if (!targetMoment.isValid()) {
|
||||||
|
console.error('无效日期:', this.date);
|
||||||
|
return { startTime, endTime, mode };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 日维度:00:00:00 → 23:59:59(无毫秒)
|
||||||
|
if (this.activeTime === 0) {
|
||||||
|
startTime = targetMoment.startOf('day').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('day').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 月维度:当月1日00:00:00 → 当月最后一天23:59:59(无毫秒)
|
||||||
|
else if (this.activeTime === 1) {
|
||||||
|
startTime = targetMoment.startOf('month').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('month').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 年维度:当年1月1日00:00:00 → 当年最后一天23:59:59(无毫秒)
|
||||||
|
else if (this.activeTime === 2) {
|
||||||
|
startTime = targetMoment.startOf('year').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('year').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调试输出:验证是否去掉毫秒
|
||||||
|
console.log('时间范围计算结果:', {
|
||||||
|
mode,
|
||||||
|
startTime: moment(startTime * 1000).format('YYYY-MM-DD HH:mm:ss'), // 格式:2025-11-30 00:00:00
|
||||||
|
endTime: moment(endTime * 1000).format('YYYY-MM-DD HH:mm:ss'), // 格式:2025-11-30 23:59:59(无毫秒)
|
||||||
|
startTimeStamp: startTime, // 秒级时间戳(如:1764422400)
|
||||||
|
endTimeStamp: endTime // 秒级时间戳(如:1764508799)
|
||||||
|
});
|
||||||
|
|
||||||
|
return { startTime, endTime, mode };
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 核心方法2:传递时间区间给父组件(首次进入时触发,传递“当月第一天0点→次月第一天0点”)
|
||||||
|
*/
|
||||||
|
emitTimeRange() {
|
||||||
|
const timeRange = this.calculateTimeRange();
|
||||||
|
this.$emit('timeRangeChange', timeRange);
|
||||||
|
// 调试用:查看首次传递的区间(如{start: "2025-10-01T00:00:00", end: "2025-11-01T00:00:00", dimension: "月"})
|
||||||
|
console.log('当前时间区间:', timeRange);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 维度切换时:清空选择的日期,并传递当前维度的默认区间
|
||||||
|
activeTime(newVal, oldVal) {
|
||||||
|
if (newVal !== oldVal) {
|
||||||
|
this.date = undefined;
|
||||||
|
// this.emitTimeRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 核心逻辑:首次进入页面,计算当月默认日期并赋值给选择器,同时传递区间
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = this.padZero(now.getMonth() + 1); // 月份从0开始,+1后补零(如1月→01)
|
||||||
|
// 赋值当月默认日期(格式:YYYY-MM,适配month类型选择器)
|
||||||
|
this.date = `${year}-${month}`;
|
||||||
|
// 确保选择器渲染完成后,传递“当月第一天0点→次月第一天0点”的区间
|
||||||
|
this.$nextTick(() => this.emitTimeRange());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
/* 原有样式不变,仅补充label文字的倾斜抵消样式 */
|
||||||
|
@font-face {
|
||||||
|
font-family: "YouSheBiaoTiHei";
|
||||||
|
src: url('../../../assets/fonts/YouSheBiaoTiHe.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-header {
|
||||||
|
height: 117px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
background: url('../../../assets/img/topTitle.png') no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: 0 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
/* 确保timeType绝对定位生效 */
|
||||||
|
|
||||||
|
.left-content {
|
||||||
|
margin-top: 11px;
|
||||||
|
margin-left: 44px;
|
||||||
|
height: 55px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-title {
|
||||||
|
height: 55px;
|
||||||
|
font-family: "YouSheBiaoTiHei", sans-serif;
|
||||||
|
font-size: 42px;
|
||||||
|
color: #1E1651;
|
||||||
|
line-height: 55px;
|
||||||
|
letter-spacing: 6px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 18px;
|
||||||
|
margin-left: 70px;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
width: 180px;
|
||||||
|
height: 50px;
|
||||||
|
background: #E1EEFC;
|
||||||
|
transform: skew(-20deg);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #1E1651;
|
||||||
|
line-height: 50px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0px 13px 16px 0px rgba(179, 217, 255, 0.43),
|
||||||
|
0px 2px 4px 0px rgba(92, 140, 255, 0.25),
|
||||||
|
inset 0px -43px 13px 0px rgba(255, 255, 255, 0.51);
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(20deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.no-skew {
|
||||||
|
background: none !important;
|
||||||
|
transform: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
color: #1E1651;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 垂直居中,避免元素高低错位 */
|
||||||
|
top: 42px;
|
||||||
|
right:10px;
|
||||||
|
margin-top: 18px;
|
||||||
|
gap: 0;
|
||||||
|
/* 清除间隙,让按钮与选择器紧密连接 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item {
|
||||||
|
width: 40px;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(236, 244, 254, 1);
|
||||||
|
transform: skew(-20deg);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(11, 88, 255, 1);
|
||||||
|
line-height: 28px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
/* 选中按钮与未选中按钮倾斜角度统一,避免切换时跳动 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item .item-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(20deg);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item.no-skew {
|
||||||
|
background: rgba(11, 88, 255, 1);
|
||||||
|
color: rgba(249, 252, 255, 1);
|
||||||
|
transform: skew(-20deg) !important;
|
||||||
|
/* 统一倾斜角度,修复原30deg的错位 */
|
||||||
|
box-shadow: 0 2px 8px rgba(11, 88, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item.no-skew .item-text {
|
||||||
|
transform: skew(20deg) !important;
|
||||||
|
/* 同步统一文字倾斜角度 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateP {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateP .label {
|
||||||
|
width: 165px;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(236, 244, 254, 1);
|
||||||
|
transform: skew(-25deg);
|
||||||
|
/* 与按钮倾斜角度统一(原30deg改为25deg,避免视觉错位) */
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 28px;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 补充:label文字抵消倾斜(原代码遗漏,导致文字倾斜) */
|
||||||
|
.dateP .label-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(25deg);
|
||||||
|
/* 与label倾斜角度相反,确保文字正立 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-right: 4px;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-time {
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 24px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screen-btn {
|
||||||
|
width: 26px;
|
||||||
|
margin-left: 300px;
|
||||||
|
color: #00fff0;
|
||||||
|
font-size: 26px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 日期选择器样式保持不变 */
|
||||||
|
::v-deep .custom-date-picker {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
width: 165px !important;
|
||||||
|
height: 28px !important;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 !important;
|
||||||
|
|
||||||
|
/* 1. 调整输入框文字:确保行高与输入框高度一致,垂直居中 */
|
||||||
|
.el-input__inner {
|
||||||
|
height: 28px !important;
|
||||||
|
width: 165px !important;
|
||||||
|
text-align: center;
|
||||||
|
padding-left: 15px !important;
|
||||||
|
padding-right: 32px !important;
|
||||||
|
/* 给图标留空间,避免文字被遮挡 */
|
||||||
|
font-size: 14px !important;
|
||||||
|
line-height: 28px !important;
|
||||||
|
/* 行高=输入框高度,文字垂直居中 */
|
||||||
|
color: rgba(237, 245, 253, 1) !important;
|
||||||
|
vertical-align: middle !important;
|
||||||
|
/* 强制文字垂直对齐 */
|
||||||
|
clip-path: polygon(18px 0, 100% 0, 100% 100%, 0 100%);
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
background-color: rgba(11, 88, 255, 1) !important;
|
||||||
|
border-left: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. 调整图标容器:让图标与文字在同一水平线上 */
|
||||||
|
.el-input__prefix {
|
||||||
|
left: auto !important;
|
||||||
|
right: 8px !important;
|
||||||
|
top: 50% !important;
|
||||||
|
/* 从40%改为50%,基于输入框垂直居中 */
|
||||||
|
transform: translateY(-50%) !important;
|
||||||
|
/* 向上偏移自身50%,精准居中 */
|
||||||
|
display: inline-flex !important;
|
||||||
|
/* 让容器内图标垂直居中 */
|
||||||
|
align-items: center !important;
|
||||||
|
/* 图标在容器内垂直居中 */
|
||||||
|
height: 28px !important;
|
||||||
|
/* 容器高度=输入框高度,避免偏移 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. 调整图标本身:确保图标大小和对齐方式 */
|
||||||
|
.el-input__icon {
|
||||||
|
color: #ffffff !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
line-height: 28px !important;
|
||||||
|
/* 图标行高=输入框高度,与文字对齐 */
|
||||||
|
vertical-align: middle !important;
|
||||||
|
/* 强制图标垂直对齐 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. 图标伪类:确保颜色和对齐继承 */
|
||||||
|
.el-icon-date::before {
|
||||||
|
color: #ffffff !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
line-height: inherit !important;
|
||||||
|
/* 继承父级行高,避免错位 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
282
src/views/home/accountsReceivableComponents/container.vue
Normal file
282
src/views/home/accountsReceivableComponents/container.vue
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
<template>
|
||||||
|
<div class="cockpitContainer" :class="['cockpitContainer__' + size]">
|
||||||
|
<div class="content-top" :class="['content-top__' + topSize]">
|
||||||
|
<!-- 使用 flex 容器包裹图标和文字,实现垂直居中 -->
|
||||||
|
<div class="title-wrapper">
|
||||||
|
<svg-icon class="title-icon" :icon-class="icon" />
|
||||||
|
<span class="title-text">
|
||||||
|
{{ name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cockpitContainer-body">
|
||||||
|
<slot>
|
||||||
|
<div class="test-body">something test....</div>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Container',
|
||||||
|
components: {},
|
||||||
|
// eslint-disable-next-line vue/require-prop-types
|
||||||
|
props: ['name', 'size', 'icon', 'topSize'],
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.cockpitContainer {
|
||||||
|
display: inline-block;
|
||||||
|
// width: 100%;
|
||||||
|
// height: 100%;
|
||||||
|
padding: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.content-top {
|
||||||
|
height: 60px;
|
||||||
|
|
||||||
|
.title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 10px;
|
||||||
|
/* 垂直居中关键属性 */
|
||||||
|
height: 100%;
|
||||||
|
/* 继承父容器高度,确保垂直居中范围 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
font-size: 30px;
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
/* 图标和文字之间的间距 */
|
||||||
|
flex-shrink: 0;
|
||||||
|
/* 防止图标被压缩 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 移除固定行高,避免影响垂直对齐
|
||||||
|
// line-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// width: 547px;
|
||||||
|
// background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
&__basic {
|
||||||
|
// width: 547px;
|
||||||
|
background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__middle {
|
||||||
|
background: url(../../../assets/img/topTileMiddle.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__large {
|
||||||
|
background: url(../../../assets/img/topTitleLargeBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__KFAPTopTitle {
|
||||||
|
background: url(../../../assets/img/KFAPTopTitle.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiTopTitleBasic {
|
||||||
|
background: url(../../../assets/img/psiTopTitleBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__rawTopTitleLarge {
|
||||||
|
background: url(../../../assets/img/rawTopTitleLarge.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__topBasic {
|
||||||
|
background: url(../../../assets/img/top-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bottomBasic {
|
||||||
|
background: url(../../../assets/img/bottom-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingBasic {
|
||||||
|
background: url(../../../assets/img/operating-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingLarge {
|
||||||
|
background: url(../../../assets/img/operating-large.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__profitTopBasic {
|
||||||
|
background: url(../../../assets/img/profitTopBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__profitMiddleBasic {
|
||||||
|
background: url(../../../assets/img/profitMiddleBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiBasicBg {
|
||||||
|
background: url(../../../assets/img/psiBasicBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiMiddleBg {
|
||||||
|
background: url(../../../assets/img/psiMiddleBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingRevenueBg {
|
||||||
|
background: url(../../../assets/img/operatingRevenueBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__costBasicBg {
|
||||||
|
background: url(../../../assets/img/costBasicBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__rawTopBg {
|
||||||
|
background: url(../../../assets/img/rawTopBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__opLargeBg {
|
||||||
|
background: url(../../../assets/img/opLargeBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// &__left {
|
||||||
|
// background: url(../../../../../../../assets/img/left.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__energyConsumption {
|
||||||
|
// background: url(../../../../../../../assets/img/energyConsumption.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__left2 {
|
||||||
|
// background: url(../../assets/left_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__left3 {
|
||||||
|
// background: url(../../assets/left_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__mid2 {
|
||||||
|
// background: url(../../assets/mid_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__mid3 {
|
||||||
|
// background: url(../../assets/mid_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right1 {
|
||||||
|
// background: url(../../assets/right_1.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right2 {
|
||||||
|
// background: url(../../assets/right_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right3 {
|
||||||
|
// background: url(../../assets/right_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekRight2 {
|
||||||
|
// background: url(../../assets/week_right_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekMidTop {
|
||||||
|
// background: url(../../assets/week-mid-top.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekMidMid {
|
||||||
|
// background: url(../../assets/week-mid-mid.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: ' ';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
// background: inherit;
|
||||||
|
/* 设置模糊,不用 filter */
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-body {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
298
src/views/home/accountsReceivableComponents/dataTrend.vue
Normal file
298
src/views/home/accountsReceivableComponents/dataTrend.vue
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="数据趋势" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="right" style="
|
||||||
|
height: 191px;
|
||||||
|
display: flex;
|
||||||
|
width: 1595px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<dataTrendBar @changeItem="handleChange" :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import dataTrendBar from "./dataTrendBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, dataTrendBar },
|
||||||
|
props: {
|
||||||
|
trend: {
|
||||||
|
type: Array,
|
||||||
|
// 默认值与实际数据结构一致(12个月)
|
||||||
|
default: () => [
|
||||||
|
// { title: "2025年01月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年02月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年03月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年04月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年05月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年06月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年07月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年08月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年09月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年10月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年11月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年12月", budget: 0, real: 0, rate: 0, diff: 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: {
|
||||||
|
months: [], // 月份数组(2025年01月 - 2025年12月)
|
||||||
|
rates: [], // 每月完成率(百分比)
|
||||||
|
reals: [], // 每月实际值
|
||||||
|
budgets: [],// 每月预算值
|
||||||
|
diffs: [], // 每月差值
|
||||||
|
flags: [] // 每月达标标识(≥100 → 1,<100 → 0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
trend: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.processTrendData(newVal);
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.processTrendData(this.trend);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleChange(value) {
|
||||||
|
this.$emit("handleChange", value);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 处理趋势数据(适配12个月的数组结构)
|
||||||
|
* @param {Array} trendData - 原始趋势数组(12个月)
|
||||||
|
*/
|
||||||
|
processTrendData(trendData) {
|
||||||
|
// 数据兜底:确保是数组且长度为12
|
||||||
|
const validTrend = Array.isArray(trendData)
|
||||||
|
? trendData
|
||||||
|
: []
|
||||||
|
|
||||||
|
// 初始化空数组
|
||||||
|
const months = [];
|
||||||
|
const rates = [];
|
||||||
|
const reals = [];
|
||||||
|
const budgets = [];
|
||||||
|
const diffs = [];
|
||||||
|
const flags = [];
|
||||||
|
|
||||||
|
// 遍历12个月数据
|
||||||
|
validTrend.forEach(item => {
|
||||||
|
// 基础数据提取(兜底处理)
|
||||||
|
const month = item.title ?? '';
|
||||||
|
const budget = Number(item.budget) || 0;
|
||||||
|
const real = Number(item.real) || 0;
|
||||||
|
const rate = Number(item.rate) || 0;
|
||||||
|
const diff = Number(item.diff) || 0;
|
||||||
|
|
||||||
|
// 计算达标标识(≥100 → 1,<100 → 0)
|
||||||
|
const flag = this.getRateFlag(rate, real, budget);
|
||||||
|
|
||||||
|
// 填充数组
|
||||||
|
months.push(month);
|
||||||
|
rates.push(rate); // 转为百分比并取整
|
||||||
|
reals.push(real);
|
||||||
|
budgets.push(budget);
|
||||||
|
diffs.push(diff);
|
||||||
|
flags.push(flag);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新chartData(响应式)
|
||||||
|
this.chartData = {
|
||||||
|
months,
|
||||||
|
rates,
|
||||||
|
reals,
|
||||||
|
budgets,
|
||||||
|
diffs,
|
||||||
|
flags
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('处理后的趋势数据:', this.chartData);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算达标标识
|
||||||
|
* @param {Number} rate - 完成率(原始值,如1.2 → 120%)
|
||||||
|
* @returns {Number} 1: 达标(≥100%),0: 未达标(<100%)
|
||||||
|
*/
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 滚动容器样式 */
|
||||||
|
.scroll-container {
|
||||||
|
max-height: 210px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 设备项样式优化 */
|
||||||
|
.proBarInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px 27px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
width: 21px;
|
||||||
|
height: 23px;
|
||||||
|
background: rgba(0, 106, 205, 0.22);
|
||||||
|
backdrop-filter: blur(1.5px);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #68b5ff;
|
||||||
|
line-height: 23px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eq-name {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eqStatus {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitLine {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
border: 1px solid #adadad;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #00ffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoLeft {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoRight {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarWrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLine {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #acacac 100%);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLineTop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg,
|
||||||
|
rgba(53, 223, 247, 0) 0%,
|
||||||
|
rgba(54, 220, 246, 0.92) 92%,
|
||||||
|
#36f6e5 100%,
|
||||||
|
#37acf5 100%);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图表相关样式 */
|
||||||
|
.chartImgBottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 45px;
|
||||||
|
left: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
left: 57px;
|
||||||
|
bottom: 42px;
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #00e8ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 全局 tooltip 样式 */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
477
src/views/home/accountsReceivableComponents/dataTrendBar.vue
Normal file
477
src/views/home/accountsReceivableComponents/dataTrendBar.vue
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<!-- 新增行容器:包裹“各基地情况”和barTop -->
|
||||||
|
<div class="header-row">
|
||||||
|
<div class="barTop">
|
||||||
|
<!-- 关键:新增右侧容器,包裹图例和按钮组,实现整体靠右 -->
|
||||||
|
<div class="right-container">
|
||||||
|
<div class="legend">
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon line yield"></span>
|
||||||
|
完成率
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square target"></span>
|
||||||
|
预算
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square achieved"></span>
|
||||||
|
实际·达标
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square unachieved"></span>
|
||||||
|
实际·未达标
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<div class="item-button category-btn">
|
||||||
|
<span class="item-text">类目选择</span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-container">
|
||||||
|
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||||
|
<span class="item-text profit-text">{{ selectedProfit || '请选择' }}</span>
|
||||||
|
<span class="dropdown-arrow" :class="{ 'rotate': isDropdownShow }"></span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-options" v-if="isDropdownShow">
|
||||||
|
<div class="dropdown-option" v-for="(item, index) in profitOptions" :key="index"
|
||||||
|
@click.stop="selectProfit(item)">
|
||||||
|
{{ item }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||||
|
<operatingLineBar :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSale.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
isDropdownShow: false,
|
||||||
|
selectedProfit: '营业收入', // 选中的名称,初始为null
|
||||||
|
profitOptions: [
|
||||||
|
'营业收入',
|
||||||
|
'单价',
|
||||||
|
'销量',
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// profitOptions() {
|
||||||
|
// return this.categoryData.map(item => item.name) || [];
|
||||||
|
// },
|
||||||
|
currentDataSource() {
|
||||||
|
console.log('yyyy', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
console.log('this.chartData', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData.months
|
||||||
|
},
|
||||||
|
// 根据按钮切换生成对应的 chartData
|
||||||
|
chartD() {
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
console.log(this.currentDataSource, 'currentDataSource');
|
||||||
|
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
// 1. 完成率(折线图)
|
||||||
|
{
|
||||||
|
name: '完成率',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1, // 绑定右侧Y轴(需在子组件启用配置)
|
||||||
|
lineStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, .5)',
|
||||||
|
width: 2
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderColor: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
radius: 4
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.2,
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||||
|
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: data.rates, // 完成率(%)
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 2. 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||||
|
barWidth: 14,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.budgets // 目标销量(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图,含达标状态)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 14,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
// 固定label尺寸:68px×20px
|
||||||
|
width: 68,
|
||||||
|
height: 20,
|
||||||
|
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||||
|
formatter: (params) => {
|
||||||
|
const diff = data.diffs || [];
|
||||||
|
const flags = data.flags || [];
|
||||||
|
const currentDiff = diff[params.dataIndex] || 0;
|
||||||
|
const currentFlag = flags[params.dataIndex] || 0;
|
||||||
|
|
||||||
|
const prefix = currentFlag === 1 ? '+' : '-';
|
||||||
|
|
||||||
|
// 根据标志位选择不同的样式类
|
||||||
|
if (currentFlag === 1) {
|
||||||
|
// 达标 - 使用 rate-achieved 样式
|
||||||
|
return `{achieved|${currentDiff}}{text|差值}`;
|
||||||
|
} else {
|
||||||
|
// 未达标 - 使用 rate-unachieved 样式
|
||||||
|
return `{unachieved|${currentDiff}}{text|差值}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||||
|
{ offset: 0.2, color: '#ffffff' },
|
||||||
|
{ offset: 1, color: '#ffffff' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
shadowColor: 'rgba(191,203,215,0.5)',
|
||||||
|
shadowBlur: 2,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowOffsetY: 2,
|
||||||
|
borderRadius: 4,
|
||||||
|
borderColor: '#BFCBD577',
|
||||||
|
borderWidth: 0,
|
||||||
|
lineHeight: 20,
|
||||||
|
rich: {
|
||||||
|
text: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 10, 5, 0],
|
||||||
|
align: 'center',
|
||||||
|
color: '#464646',
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
},
|
||||||
|
achieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#76DABE', // 与达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
},
|
||||||
|
// 未达标样式
|
||||||
|
unachieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||||
|
const safeFlag = data.flags;
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals // 实际销量(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 根据按钮状态返回对应数据
|
||||||
|
return salesData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectProfit(item) {
|
||||||
|
this.selectedProfit = item;
|
||||||
|
this.isDropdownShow = false;
|
||||||
|
this.$emit("changeItem", item);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
// 新增:头部行容器,实现一行排列
|
||||||
|
.header-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end; // 左右两端对齐
|
||||||
|
align-items: center; // 垂直居中
|
||||||
|
// width: 100%;
|
||||||
|
margin-bottom: 8px; // 与下方图表区保留间距(可根据需求调整)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 各基地情况标题样式
|
||||||
|
.base-title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0 0 0 16px; // 保留原有内边距
|
||||||
|
white-space: nowrap; // 防止文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
// 移除原有flex和justify-content,由header-row控制
|
||||||
|
width: auto; // 自适应宽度
|
||||||
|
// 保留原有align-items,确保内部元素垂直居中
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; // 图例和按钮组垂直居中
|
||||||
|
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||||
|
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 图例:在右侧容器内横向排列
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px; // 图例项之间间距,避免重叠
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap; // 防止图例文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图例颜色
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.dropdown-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-btn {
|
||||||
|
width: 75px;
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-bottom-left-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-btn {
|
||||||
|
width: 123px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 18px 0 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #3071ff;
|
||||||
|
color: rgba(249, 252, 255, .8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-text {
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 6px solid currentColor;
|
||||||
|
border-top: 4px solid transparent;
|
||||||
|
border-bottom: 4px solid transparent;
|
||||||
|
border-right: 4px solid transparent;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
|
||||||
|
&.rotate {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-options {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
right: 0;
|
||||||
|
margin-top: 2px;
|
||||||
|
width: 123px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.dropdown-option {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
color: #3071ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
226
src/views/home/accountsReceivableComponents/electricityGauge.vue
Normal file
226
src/views/home/accountsReceivableComponents/electricityGauge.vue
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="chartBox" style="width: 100%; height: 108px; position: relative;">
|
||||||
|
<div :id="id" style="width: 100%; height:100%;"></div>
|
||||||
|
<div class="bottomTip">
|
||||||
|
<div class="precent">
|
||||||
|
<span class="precentNum">{{ detailData.rate || 0 }}% </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'EnergyConsumption',
|
||||||
|
// components: { Container },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
detailData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
// electricComu: 0,
|
||||||
|
// steamComu: 20, // 调整为符合max范围的数值(0-8)
|
||||||
|
// // electricity: [120, 150, 130, 180, 160, 200, 190],
|
||||||
|
// // steam: [80, 95, 85, 110, 100, 120, 115],
|
||||||
|
// // dates: ['1日', '2日', '3日', '4日', '5日', '6日', '7日']
|
||||||
|
})
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
default: () => ('monthG')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// electricityChart: null,
|
||||||
|
// steamChart: null,
|
||||||
|
// specialTicks: [2, 4, 6, 8], // 统一的刻度显示
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
detailData: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true, // 初始化时立即执行
|
||||||
|
handler() {
|
||||||
|
this.updateGauges()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initGauges()
|
||||||
|
// window.addEventListener('resize', this.handleResize)
|
||||||
|
this.observeContainerResize()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
observeContainerResize() {
|
||||||
|
// 修复:获取正确的容器(组件内的.gauge-container)
|
||||||
|
const container = this.$el.querySelector('.gauge-container')
|
||||||
|
if (container && window.ResizeObserver) {
|
||||||
|
this.resizeObserver = new ResizeObserver(entries => {
|
||||||
|
if (this.electricityChart) {
|
||||||
|
this.electricityChart.resize() // 直接触发resize,无需防抖
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.resizeObserver.observe(container)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initGauges() {
|
||||||
|
// console.log('this.id',this.id);
|
||||||
|
|
||||||
|
// 初始化电气图表实例
|
||||||
|
const electricityDom = document.getElementById(this.id)
|
||||||
|
if (electricityDom) {
|
||||||
|
// 修复:正确创建并存储图表实例
|
||||||
|
this.electricityChart = echarts.init(electricityDom)
|
||||||
|
// 首次更新数据
|
||||||
|
this.updateGauges()
|
||||||
|
}
|
||||||
|
// 蒸汽图表若未使用,可注释/删除
|
||||||
|
// const steamDom = document.getElementById('steamGauge')
|
||||||
|
// if (steamDom) {
|
||||||
|
// this.steamChart = echarts.init(steamDom)
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
updateGauges() {
|
||||||
|
// 修复:先判断实例是否存在,再更新配置
|
||||||
|
if (!this.electricityChart) return
|
||||||
|
|
||||||
|
// 修复:兜底获取rate值,确保数值有效
|
||||||
|
const rate = Number(this.detailData?.rate) || 0
|
||||||
|
console.log('当前rate值:', rate); // 调试:确认rate值正确
|
||||||
|
|
||||||
|
// 关键:第二个参数传true,清空原有配置,强制更新
|
||||||
|
this.electricityChart.setOption(this.getElectricityGaugeOption(rate), true)
|
||||||
|
},
|
||||||
|
// 用电量仪表盘配置(保留原有样式,优化数值范围)
|
||||||
|
getElectricityGaugeOption(value) {
|
||||||
|
const electricityGradient = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: '#0B58FF' },
|
||||||
|
{ offset: 1, color: '#32FFCD' }
|
||||||
|
])
|
||||||
|
|
||||||
|
return {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '月度',
|
||||||
|
type: 'gauge',
|
||||||
|
radius: '95', // 修复:添加%,避免数值错误
|
||||||
|
center: ['50%', '90%'],
|
||||||
|
startAngle: 180,
|
||||||
|
endAngle: 0,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
splitNumber: 4,
|
||||||
|
label: { show: false },
|
||||||
|
progress: {
|
||||||
|
show: true,
|
||||||
|
overlap: false,
|
||||||
|
roundCap: true,
|
||||||
|
clip: false,
|
||||||
|
width: 14,
|
||||||
|
itemStyle: { color: electricityGradient }
|
||||||
|
},
|
||||||
|
pointer: {
|
||||||
|
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
|
||||||
|
length: '75%',
|
||||||
|
width: 16,
|
||||||
|
itemStyle: { color: '#288AFF' },
|
||||||
|
offsetCenter: [0, '10%']
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
roundCap: true,
|
||||||
|
lineStyle: { width: 12, color: [[1, '#E6EBF7']] }
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
length: 10,
|
||||||
|
lineStyle: { width: 5, color: '#D6DAE5' },
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
splitNumber: 2,
|
||||||
|
length: 6,
|
||||||
|
lineStyle: { width: 2, color: '#D6DAE5' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
detail: { show: false },
|
||||||
|
data: [{ value: value, unit: '' }] // 确保数值正确传入
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 未使用的蒸汽仪表盘可注释/删除
|
||||||
|
// getSteamGaugeOption(value) { ... }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
.chartBox {
|
||||||
|
.bottomTip {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.precent {
|
||||||
|
line-height: 3px;
|
||||||
|
|
||||||
|
&::after,
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 54px;
|
||||||
|
height: 5px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
margin-right: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用电量线条颜色
|
||||||
|
.precent::before {
|
||||||
|
|
||||||
|
background: linear-gradient(90deg, rgba(40, 138, 255, 0) 0%, rgba(12, 125, 254, 0.4) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ::after:从 透明 到 rgba(12, 125, 254, 0.4)(90度渐变,左到右) */
|
||||||
|
.precent::after {
|
||||||
|
background: linear-gradient(90deg, rgba(12, 125, 254, 0.4) 0%, rgba(40, 138, 255, 0) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蒸汽线条颜色
|
||||||
|
// .steam-precent::after,
|
||||||
|
// .steam-precent::before {
|
||||||
|
// background: linear-gradient(90deg, rgba(11, 168, 255, 0.26) 0%, rgba(54, 239, 230, 1) 100%);
|
||||||
|
// }
|
||||||
|
|
||||||
|
.precentNum {
|
||||||
|
display: inline-block;
|
||||||
|
// width: 52px;
|
||||||
|
height: 22px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蒸汽数字颜色
|
||||||
|
.steam-num {
|
||||||
|
color: rgba(54, 239, 230, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
204
src/views/home/accountsReceivableComponents/monthlyOverview.vue
Normal file
204
src/views/home/accountsReceivableComponents/monthlyOverview.vue
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||||
|
<div class="dashboard">
|
||||||
|
<div class="title">
|
||||||
|
{{ month }}月完成率
|
||||||
|
</div>
|
||||||
|
<div class="number">
|
||||||
|
<div class="yield">
|
||||||
|
{{ monthData?.rate || 0 }}%
|
||||||
|
</div>
|
||||||
|
<div class="mom">
|
||||||
|
环比{{ monthData?.momRate }}%
|
||||||
|
<img v-if="monthData?.momRate >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="">
|
||||||
|
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="electricityGauge">
|
||||||
|
<electricityGauge :detailData="monthData" id="month"></electricityGauge>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
<div class="line" style="padding: 0px;">
|
||||||
|
<verticalBarChart :detailData="monthData">
|
||||||
|
</verticalBarChart>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import electricityGauge from './electricityGauge.vue'
|
||||||
|
import verticalBarChart from './verticalBarChart.vue'
|
||||||
|
|
||||||
|
// import * as echarts from 'echarts'
|
||||||
|
// import rawItem from './raw-Item.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, electricityGauge, verticalBarChart },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
monthData: { // 接收父组件传递的设备数据数组
|
||||||
|
type: Object,
|
||||||
|
default: () => {} // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
title: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
month: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// itemData: {
|
||||||
|
// handler(newValue, oldValue) {
|
||||||
|
// // this.updateChart()
|
||||||
|
// },
|
||||||
|
// deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
// computed: {
|
||||||
|
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||||
|
// sortedItemData() {
|
||||||
|
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||||
|
// const totalCostItems = this.itemData.filter(item =>
|
||||||
|
// item.name && item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 过滤出不包含“总成本”的项
|
||||||
|
// const otherItems = this.itemData.filter(item =>
|
||||||
|
// !item.name || !item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 合并:总成本项在前,其他项在后
|
||||||
|
// return [...totalCostItems, ...otherItems];
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
mounted() {
|
||||||
|
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||||
|
// this.$nextTick(() => this.updateChart())
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||||
|
.scroll-container {
|
||||||
|
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||||
|
max-height: 210px;
|
||||||
|
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||||
|
overflow-y: auto;
|
||||||
|
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||||
|
/* Chrome/Safari */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* IE/Edge */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 264px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
// width: 190px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 46px;
|
||||||
|
color: #0B58FF;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
width: 500px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .leftTitle {
|
||||||
|
// .item {
|
||||||
|
// width: 67px;
|
||||||
|
// height: 180px;
|
||||||
|
// padding: 37px 23px;
|
||||||
|
// background: #F9FCFF;
|
||||||
|
// font-family: PingFangSC, PingFang SC;
|
||||||
|
// font-weight: 400;
|
||||||
|
// font-size: 18px;
|
||||||
|
// color: #000000;
|
||||||
|
// line-height: 25px;
|
||||||
|
// letter-spacing: 1px;
|
||||||
|
// // text-align: left;
|
||||||
|
// font-style: normal;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- <style>
|
||||||
|
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style> -->
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px; width: 100%;">
|
||||||
|
<!-- 销量模块(直接传递整合了flag的salesData) -->
|
||||||
|
<div class="dashboard left" @click="handleDashboardClick('/salesVolumeAnalysis/salesVolumeAnalysisBase')">
|
||||||
|
<div style='position: relative;'>
|
||||||
|
<div class="title">
|
||||||
|
销量·万㎡
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{monthAnalysis[0].rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:monthAnalysis[0].flags>0?'#30B590':'#FF9423'}" >{{monthAnalysis[0].diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-wrap">
|
||||||
|
<operatingSingleBar :detailData="salesData"></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 单价模块(直接传递整合了flag的unitPriceData) -->
|
||||||
|
<div class="dashboard right" @click="handleDashboardClick('/unitPriceAnalysis/unitPriceAnalysisBase')">
|
||||||
|
<div style='position: relative;'>
|
||||||
|
<div class="title">
|
||||||
|
单价·元/㎡
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{monthAnalysis[1].rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:monthAnalysis[1].flags>0?'#30B590':'#FF9423'}" >{{monthAnalysis[1].diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-wrap">
|
||||||
|
<operatingSingleBar :detailData="unitPriceData"></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import operatingSingleBar from './operatingSingleBar.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, operatingSingleBar },
|
||||||
|
props: {
|
||||||
|
monthAnalysis: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [
|
||||||
|
{ title: "销量", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
{ title: "单价", budget: 0, real: 0, rate: 0, diff: 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
factory: {
|
||||||
|
type: [String,Number],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
month: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
// 初始化数据包含flag字段
|
||||||
|
salesData: { title: "销量", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 },
|
||||||
|
unitPriceData: { title: "单价", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
monthAnalysis: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.updateChart(newVal)
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateChart(this.monthAnalysis)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleDashboardClick(path) {
|
||||||
|
this.$router.push({
|
||||||
|
path: path,
|
||||||
|
query: {
|
||||||
|
factory: this.$route.query.factory ? this.$route.query.factory : this.factory,
|
||||||
|
dateData: this.dateData
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 判断flag的核心方法
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateChart(data) {
|
||||||
|
// 数据兜底
|
||||||
|
const salesItem = Array.isArray(data) && data[0] ? data[0] : { title: "销量", budget: 0, real: 0, rate: 0, diff: 0 };
|
||||||
|
const unitPriceItem = Array.isArray(data) && data[1] ? data[1] : { title: "单价", budget: 0, real: 0, rate: 0, diff: 0 };
|
||||||
|
|
||||||
|
// 核心修改:将flag整合到数据对象中,无需单独定义salesFlag/unitPriceFlag
|
||||||
|
this.salesData = {
|
||||||
|
...salesItem, // 合并原有字段
|
||||||
|
flag: this.getRateFlag(salesItem.rate, salesItem.real, salesItem.budget) // 新增flag字段
|
||||||
|
};
|
||||||
|
|
||||||
|
this.unitPriceData = {
|
||||||
|
...unitPriceItem, // 合并原有字段
|
||||||
|
flag: this.getRateFlag(unitPriceItem.rate, unitPriceItem.real, unitPriceItem.budget) // 新增flag字段
|
||||||
|
};
|
||||||
|
|
||||||
|
// 调试:确认整合后的数据
|
||||||
|
console.log('整合flag后的销量数据:', this.salesData);
|
||||||
|
console.log('整合flag后的单价数据:', this.unitPriceData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
.scroll-container {
|
||||||
|
max-height: 210px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topItem-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 300px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
margin: 0 4px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-wrap {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 30px;
|
||||||
|
height: 32px;
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
width: 97px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard.left {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard.right {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
503
src/views/home/accountsReceivableComponents/operatingBar.vue
Normal file
503
src/views/home/accountsReceivableComponents/operatingBar.vue
Normal file
@@ -0,0 +1,503 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<div class="header-row">
|
||||||
|
<div class="base-title">各基地情况</div>
|
||||||
|
<div class="barTop">
|
||||||
|
<div class="right-container">
|
||||||
|
<div class="legend">
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon line yield"></span>完成率
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square target"></span>预算
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square achieved"></span>实际·达标
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square unachieved"></span>实际·未达标
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<div class="item-button category-btn">
|
||||||
|
<span class="item-text">展示顺序</span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-container">
|
||||||
|
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||||
|
<span class="item-text profit-text">{{ selectedSort || '请选择' }}</span>
|
||||||
|
<span class="dropdown-arrow" :class="{ 'rotate': isDropdownShow }"></span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-options" v-if="isDropdownShow">
|
||||||
|
<div class="dropdown-option" v-for="(item, index) in profitOptions" :key="index"
|
||||||
|
@click.stop="selectProfit(item)">
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||||
|
<operatingLineBar :dateData="dateData" :chartData="chartD" style="height: 100%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSale.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData", 'dateData'],
|
||||||
|
emits: ['sort-change'], // 声明事件(Vue3 推荐)
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
isDropdownShow: false,
|
||||||
|
selectedSort: '实际值:高~低', // 选中的label
|
||||||
|
selectedSortValue: 1, // 选中的value,用于排序逻辑
|
||||||
|
profitOptions: [
|
||||||
|
{ label: '实际值:高~低', value: 1 },
|
||||||
|
{ label: '实际值:低~高', value: 2 },
|
||||||
|
{ label: '完成率:高~低', value: 3 },
|
||||||
|
{ label: '完成率:低~高', value: 4 },
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 排序后的数据源(核心:根据selectedSortValue重新排序)
|
||||||
|
currentDataSource() {
|
||||||
|
if (!this.chartData?.factory) return {};
|
||||||
|
|
||||||
|
// 深拷贝原始数据,避免修改原数据
|
||||||
|
const factory = JSON.parse(JSON.stringify(this.chartData.factory));
|
||||||
|
if (!factory.locations.length || !this.selectedSortValue) return factory;
|
||||||
|
|
||||||
|
// 构建带索引的数组,方便同步所有字段排序
|
||||||
|
const dataWithIndex = factory.locations.map((name, index) => ({
|
||||||
|
index,
|
||||||
|
name,
|
||||||
|
real: factory.reals[index],
|
||||||
|
target: factory.targets[index],
|
||||||
|
rate: factory.rates[index],
|
||||||
|
diff: factory.diff[index],
|
||||||
|
flag: factory.flags[index]
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 根据选中的排序规则排序
|
||||||
|
switch (this.selectedSortValue) {
|
||||||
|
case 1: // 实际值:高~低
|
||||||
|
dataWithIndex.sort((a, b) => b.real - a.real);
|
||||||
|
break;
|
||||||
|
case 2: // 实际值:低~高
|
||||||
|
dataWithIndex.sort((a, b) => a.real - b.real);
|
||||||
|
break;
|
||||||
|
case 3: // 预算值:高~低
|
||||||
|
dataWithIndex.sort((a, b) => b.rate - a.rate);
|
||||||
|
break;
|
||||||
|
case 4: // 预算值:低~高
|
||||||
|
dataWithIndex.sort((a, b) => a.rate - b.rate);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步更新所有数组
|
||||||
|
factory.locations = dataWithIndex.map(item => item.name);
|
||||||
|
factory.reals = dataWithIndex.map(item => item.real);
|
||||||
|
factory.targets = dataWithIndex.map(item => item.target);
|
||||||
|
factory.rates = dataWithIndex.map(item => item.rate);
|
||||||
|
factory.diff = dataWithIndex.map(item => item.diff);
|
||||||
|
factory.flags = dataWithIndex.map(item => item.flag);
|
||||||
|
|
||||||
|
return factory;
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
return this.currentDataSource.locations || [];
|
||||||
|
},
|
||||||
|
// 最终传递给图表的排序后数据
|
||||||
|
chartD() {
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
// 完成率(折线图)
|
||||||
|
{
|
||||||
|
name: '完成率',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
lineStyle: { color: 'rgba(40, 138, 255, .5)', width: 2 },
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderColor: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
radius: 4
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.2,
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||||
|
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: data.rates || [],
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 40,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.targets || []
|
||||||
|
},
|
||||||
|
// 实际(柱状图)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 40,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [32, 0],
|
||||||
|
width: 100,
|
||||||
|
height: 22,
|
||||||
|
formatter: (params) => {
|
||||||
|
const diff = data.diff || [];
|
||||||
|
const flags = data.flags || [];
|
||||||
|
const currentDiff = diff[params.dataIndex] || 0;
|
||||||
|
const currentFlag = flags[params.dataIndex] || 0;
|
||||||
|
|
||||||
|
const prefix = currentFlag === 1 ? '+' : '-';
|
||||||
|
|
||||||
|
// 根据标志位选择不同的样式类
|
||||||
|
if (currentFlag === 1) {
|
||||||
|
// 达标 - 使用 rate-achieved 样式
|
||||||
|
return `{achieved|${currentDiff}}{text|差值}`;
|
||||||
|
} else {
|
||||||
|
// 未达标 - 使用 rate-unachieved 样式
|
||||||
|
return `{unachieved|${currentDiff}}{text|差值}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||||
|
{ offset: 0.2, color: '#ffffff' },
|
||||||
|
{ offset: 1, color: '#ffffff' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
shadowColor: 'rgba(191,203,215,0.5)',
|
||||||
|
shadowBlur: 2,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowOffsetY: 2,
|
||||||
|
borderRadius: 4,
|
||||||
|
borderColor: '#BFCBD577',
|
||||||
|
borderWidth: 0,
|
||||||
|
lineHeight: 26,
|
||||||
|
rich: {
|
||||||
|
text: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 10, 5, 0],
|
||||||
|
align: 'center',
|
||||||
|
color: '#464646',
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
achieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#76DABE', // 与达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
// 未达标样式
|
||||||
|
unachieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 14,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const safeFlag = data.flags || [];
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals || []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
return salesData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectProfit(item) {
|
||||||
|
// 更新选中的label和value
|
||||||
|
this.selectedSort = item.label;
|
||||||
|
this.selectedSortValue = item.value;
|
||||||
|
this.isDropdownShow = false;
|
||||||
|
// 向父组件传递排序事件(可选,保持原有逻辑)
|
||||||
|
this.$emit('sort-change', item.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 监听父组件传入的chartData变化,重置选中状态(可选)
|
||||||
|
watch: {
|
||||||
|
'chartData.factory': {
|
||||||
|
handler() {
|
||||||
|
// 若需要切换数据源后重置排序,可取消注释
|
||||||
|
// this.selectedSort = null;
|
||||||
|
// this.selectedSortValue = null;
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.header-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0 0 0 16px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
width: auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
margin-right: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.dropdown-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
fontSize: 12px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-btn {
|
||||||
|
width: 75px;
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-bottom-left-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-btn {
|
||||||
|
width: 123px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 18px 0 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #3071ff;
|
||||||
|
color: rgba(249, 252, 255, .8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-text {
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 6px solid currentColor;
|
||||||
|
border-top: 4px solid transparent;
|
||||||
|
border-bottom: 4px solid transparent;
|
||||||
|
border-right: 4px solid transparent;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
|
||||||
|
&.rotate {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-options {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
right: 0;
|
||||||
|
margin-top: 2px;
|
||||||
|
width: 123px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.dropdown-option {
|
||||||
|
padding: 6px 12px;
|
||||||
|
fontSize: 12px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
color: #3071ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChipBottom" id="cockpitEffChipBottom" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
series: [],
|
||||||
|
allPlaceNames: []
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
validator: (value) => {
|
||||||
|
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChipBottom;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatter: (params) => {
|
||||||
|
let html = `${params[0].axisValue}<br/>`;
|
||||||
|
params.forEach(item => {
|
||||||
|
const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
);
|
||||||
|
html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 60,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
splitNumber: 4,
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: (value) => Math.ceil((value.max || 0) * 1.1),
|
||||||
|
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
scale:true,
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
172
src/views/home/accountsReceivableComponents/operatingLineBar.vue
Normal file
172
src/views/home/accountsReceivableComponents/operatingLineBar.vue
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
series: [],
|
||||||
|
allPlaceNames: []
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
validator: (value) => {
|
||||||
|
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 60,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
// {
|
||||||
|
// type: 'value',
|
||||||
|
// nameTextStyle: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// align: 'left'
|
||||||
|
// },
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
// axisTick: { show: false },
|
||||||
|
// axisLabel: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// formatter: '{value}%'
|
||||||
|
// },
|
||||||
|
// splitLine: { show: false },
|
||||||
|
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
// splitNumber: 4
|
||||||
|
// }
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null, // 存储图表实例
|
||||||
|
resizeHandler: null, // 存储resize事件处理函数
|
||||||
|
// 核心:基地名称与序号的映射表(固定顺序)
|
||||||
|
baseNameToIndexMap: {
|
||||||
|
'宜兴': 7,
|
||||||
|
'漳州': 8,
|
||||||
|
'自贡': 3,
|
||||||
|
'桐城': 2,
|
||||||
|
'洛阳': 9,
|
||||||
|
'合肥': 5,
|
||||||
|
'宿迁': 6,
|
||||||
|
'秦皇岛': 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.initChart(); // 初始化图表(只执行一次)
|
||||||
|
this.updateChart(); // 更新图表数据
|
||||||
|
});
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
this.updateChart(); // 仅更新数据,不重新创建实例
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 组件销毁时清理资源
|
||||||
|
this.destroyChart();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 初始化图表(只在mounted中执行一次)
|
||||||
|
initChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只创建一次图表实例
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
// 绑定点击事件(只绑定一次,永久生效)
|
||||||
|
this.myChart.getZr().on('click', (params) => {
|
||||||
|
console.log('params', params);
|
||||||
|
|
||||||
|
// 提取点击的基地名称
|
||||||
|
// const itemName = params.name;
|
||||||
|
let itemName = undefined
|
||||||
|
// 根据映射表获取对应的序号(未匹配到则返回0或其他默认值)
|
||||||
|
let pointInPixel = [params.offsetX, params.offsetY];
|
||||||
|
if (this.myChart.containPixel('grid', pointInPixel)) {
|
||||||
|
let pointInGrid = this.myChart.convertFromPixel({
|
||||||
|
seriesIndex: 0
|
||||||
|
}, pointInPixel);
|
||||||
|
let xIndex = pointInGrid[0]; //索引
|
||||||
|
let handleIndex = Number(xIndex);
|
||||||
|
let seriesObj = this.myChart.getOption(); //图表object对象
|
||||||
|
var op = this.myChart.getOption();
|
||||||
|
//获得图表中点击的列
|
||||||
|
itemName = op.xAxis[0].data[handleIndex]; //获取点击的列名
|
||||||
|
console.log(itemName, 'monthmonthmonth');
|
||||||
|
console.log(handleIndex, seriesObj);
|
||||||
|
};
|
||||||
|
const baseIndex = this.baseNameToIndexMap[itemName] || 0;
|
||||||
|
|
||||||
|
console.log(`你点击了【${itemName}】(序号:${baseIndex})`);
|
||||||
|
if (itemName === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 路由跳转时携带序号(或名称+序号)
|
||||||
|
this.$router.push({
|
||||||
|
path: 'operatingRevenueBase',
|
||||||
|
query: { // 使用query传递参数(推荐),也可使用params
|
||||||
|
// baseName: itemName,
|
||||||
|
factory: baseIndex,
|
||||||
|
dateData: this.dateData
|
||||||
|
}
|
||||||
|
// 若仍需用base作为参数:
|
||||||
|
// base: itemName,
|
||||||
|
// params: { baseIndex: baseIndex }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定义resize处理函数(命名函数,方便移除)
|
||||||
|
this.resizeHandler = () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
};
|
||||||
|
// 绑定resize事件(只绑定一次)
|
||||||
|
window.addEventListener('resize', this.resizeHandler);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新图表数据(数据变化时执行)
|
||||||
|
updateChart() {
|
||||||
|
if (!this.myChart) {
|
||||||
|
return; // 实例未初始化则返回
|
||||||
|
}
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || [];
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 5,
|
||||||
|
right: 20,
|
||||||
|
left: 25,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0],
|
||||||
|
// 可选:X轴标签显示“序号+名称”(如“1 宜兴”)
|
||||||
|
// formatter: (value) => {
|
||||||
|
// const index = this.baseNameToIndexMap[value] || '';
|
||||||
|
// return index ? `${index} ${value}` : value;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries
|
||||||
|
};
|
||||||
|
|
||||||
|
// 只更新配置,不重新创建实例
|
||||||
|
this.myChart.setOption(option, true); // 第二个参数true表示清空原有配置,避免数据残留
|
||||||
|
},
|
||||||
|
|
||||||
|
// 销毁图表资源
|
||||||
|
destroyChart() {
|
||||||
|
// 移除resize事件
|
||||||
|
if (this.resizeHandler) {
|
||||||
|
window.removeEventListener('resize', this.resizeHandler);
|
||||||
|
}
|
||||||
|
// 销毁图表实例
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
this.myChart = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 380px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
console.log('chartData', this.chartData);
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
console.log('xData', xData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom:20,
|
||||||
|
right: 10,
|
||||||
|
left: 25,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
//
|
||||||
|
splitNumber: 4,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 200px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
refName: {
|
||||||
|
type: String,
|
||||||
|
default: () => 'cockpitEffChip',
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs[this.refName];
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
console.log('chartData', this.chartData);
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
console.log('xData', xData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
// axisPointer: {
|
||||||
|
// type: 'cross',
|
||||||
|
// label: {
|
||||||
|
// backgroundColor: '#6a7985'
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 20,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 5,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
// name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true, // 显示Y轴轴线(关键)
|
||||||
|
lineStyle: {
|
||||||
|
color: '#E5E6EB', // 轴线颜色(浅灰色,可自定义)
|
||||||
|
width: 1, // 轴线宽度
|
||||||
|
type: 'solid' // 实线(可选:dashed虚线、dotted点线)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 2,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
},
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,349 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="当月数据对比" icon="cockpitItemIcon" size="operatingLarge" topSize="large">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="left" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 348px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
">
|
||||||
|
集团情况
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{chartData.group.rate[0]}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.group.flags>0?'#30B590':'#FF9423'}" >{{chartData.group.diff[0]}}</span></span>
|
||||||
|
</div>
|
||||||
|
<operatingTopBar :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
<div class="right" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 1220px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<!-- <top-item /> -->
|
||||||
|
<operatingBar :dateData="dateData" :chartData="chartData" @sort-change="sortChange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import operatingBar from "./operatingBar.vue";
|
||||||
|
import operatingTopBar from "./operatingTopBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, operatingBar, operatingTopBar },
|
||||||
|
props: {
|
||||||
|
monthData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
group: {
|
||||||
|
rate: 0,
|
||||||
|
diff: 0,
|
||||||
|
real: 0,
|
||||||
|
target: 0
|
||||||
|
},
|
||||||
|
factory: []
|
||||||
|
}),
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: null, // 初始化 chartData 为 null
|
||||||
|
groupData: {}, // 集团数据
|
||||||
|
factoryData: [] // 工厂数据
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
monthData: {
|
||||||
|
handler() {
|
||||||
|
this.processChartData();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
sortChange(value) {
|
||||||
|
this.$emit('sort-change', value);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 判断rate对应的flag值(<1为0,>1为1)
|
||||||
|
* @param {number} rate 处理后的rate值(已*100)
|
||||||
|
* @returns {0|1} flag值
|
||||||
|
*/
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 核心处理函数:在所有数据都准备好后,才组装 chartData
|
||||||
|
*/
|
||||||
|
processChartData() {
|
||||||
|
// 1. 处理集团数据 - 提取各字段到对应数组
|
||||||
|
this.groupData = this.monthData.group || { rate: 0, diff: 0, real: 0, target: 0 };
|
||||||
|
|
||||||
|
// 集团各维度数据数组(单条数据,对应凯盛新能)
|
||||||
|
const groupTarget = [this.groupData.target]; // 预算值数组
|
||||||
|
const groupDiff = [this.groupData.diff]; // 差值数组
|
||||||
|
const groupReal = [this.groupData.real]; // 实际值数组
|
||||||
|
const groupRate = [this.groupData.rate]; // 完成率数组
|
||||||
|
// 新增:集团rate对应的flag
|
||||||
|
const groupFlag = [this.getRateFlag(groupRate[0], groupReal[0], groupTarget[0])];
|
||||||
|
|
||||||
|
console.log('集团数据数组:', {
|
||||||
|
groupTarget,
|
||||||
|
groupDiff,
|
||||||
|
groupReal,
|
||||||
|
groupRate,
|
||||||
|
groupFlag,
|
||||||
|
rawGroupData: this.groupData
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 处理工厂数据 - 提取每个工厂的对应字段到数组
|
||||||
|
this.factoryData = this.monthData.factory || [];
|
||||||
|
// 提取工厂名称数组
|
||||||
|
const factoryNames = this.factoryData.map(item => item.title || '');
|
||||||
|
// 提取工厂各维度数据数组
|
||||||
|
const factoryBudget = this.factoryData.map(item => item.budget || 0);
|
||||||
|
const factoryReal = this.factoryData.map(item => item.real || 0);
|
||||||
|
const factoryRate = this.factoryData.map(item => item.rate || 0);
|
||||||
|
const factoryDiff = this.factoryData.map(item => item.diff || 0);
|
||||||
|
// 新增:每个工厂rate对应的flag数组
|
||||||
|
const factoryFlags = this.factoryData.map(item => this.getRateFlag(item.rate, item.real, item.budget));
|
||||||
|
|
||||||
|
// 3. 组装最终的chartData(供子组件使用)
|
||||||
|
this.chartData = {
|
||||||
|
// 集团数据(对应凯盛新能)
|
||||||
|
group: {
|
||||||
|
locations: ['凯盛新能'], // 集团名称
|
||||||
|
targets: groupTarget, // 集团预算值数组
|
||||||
|
diff: groupDiff, // 集团差值数组
|
||||||
|
reals: groupReal, // 集团实际值数组
|
||||||
|
rate: groupRate, // 集团完成率数组
|
||||||
|
flags: groupFlag // 新增:集团rate对应的flag
|
||||||
|
},
|
||||||
|
// 工厂数据
|
||||||
|
factory: {
|
||||||
|
locations: factoryNames, // 工厂名称数组
|
||||||
|
targets: factoryBudget, // 工厂预算数组
|
||||||
|
reals: factoryReal, // 工厂实际值数组
|
||||||
|
rates: factoryRate, // 工厂完成率数组
|
||||||
|
diff: factoryDiff, // 工厂差值数组
|
||||||
|
flags: factoryFlags // 新增:工厂rate对应的flags数组
|
||||||
|
},
|
||||||
|
// 原始数据备份(方便后续使用)
|
||||||
|
rawData: {
|
||||||
|
group: this.groupData,
|
||||||
|
factory: this.factoryData
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('最终处理后的图表数据:', this.chartData);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||||
|
.scroll-container {
|
||||||
|
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||||
|
max-height: 210px;
|
||||||
|
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||||
|
overflow-y: auto;
|
||||||
|
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||||
|
/* Chrome/Safari */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* IE/Edge */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||||
|
.proBarInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px 27px;
|
||||||
|
/* 调整内边距,优化排版 */
|
||||||
|
margin-bottom: 10px;
|
||||||
|
/* 设备项之间的垂直间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 原有样式保留,优化细节 */
|
||||||
|
.proBarInfoEqInfo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
/* 垂直居中,避免序号/文字错位 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
width: 21px;
|
||||||
|
height: 23px;
|
||||||
|
background: rgba(0, 106, 205, 0.22);
|
||||||
|
backdrop-filter: blur(1.5px);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #68b5ff;
|
||||||
|
line-height: 23px;
|
||||||
|
/* 垂直居中文字 */
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eq-name {
|
||||||
|
margin-left: 8px;
|
||||||
|
/* 增加与序号的间距 */
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eqStatus {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitLine {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
border: 1px solid #adadad;
|
||||||
|
margin: 0 8px;
|
||||||
|
/* 优化分割线间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #00ffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoLeft {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 序号和设备名垂直居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoRight {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 状态/分割线/百分比垂直居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarWrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
/* 进度条与上方信息的间距 */
|
||||||
|
border-radius: 5px;
|
||||||
|
/* 进度条圆角,优化视觉 */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLine {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #acacac 100%);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLineTop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg,
|
||||||
|
rgba(53, 223, 247, 0) 0%,
|
||||||
|
rgba(54, 220, 246, 0.92) 92%,
|
||||||
|
#36f6e5 100%,
|
||||||
|
#37acf5 100%);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
/* 进度变化时添加过渡动画,更流畅 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图表相关样式保留 */
|
||||||
|
.chartImgBottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 45px;
|
||||||
|
left: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
left: 57px;
|
||||||
|
bottom: 42px;
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #00e8ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="累计数据对比" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="left" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 348px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
">
|
||||||
|
集团情况
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{chartData.group.rate[0]}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.group.flags>0?'#30B590':'#FF9423'}" >{{chartData.group.diff[0]}}</span></span>
|
||||||
|
</div>
|
||||||
|
<operatingTopBar :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
<div class="right" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 1220px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<!-- <top-item /> -->
|
||||||
|
<operatingBar :dateData="dateData" @sort-change="sortChange" :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import operatingBar from "./operatingBar.vue";
|
||||||
|
import operatingTopBar from "./operatingTopBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, operatingBar, operatingTopBar },
|
||||||
|
props: {
|
||||||
|
salesTrendMap: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
ytdData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: null, // 初始化 chartData 为 null
|
||||||
|
groupData: {}, // 集团数据
|
||||||
|
factoryData: [] // 工厂数据
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
ytdData: {
|
||||||
|
handler() {
|
||||||
|
this.processChartData();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
sortChange(value) {
|
||||||
|
this.$emit('sort-change', value);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 判断rate对应的flag值(<1为0,>1为1)
|
||||||
|
* @param {number} rate 处理后的rate值(已*100)
|
||||||
|
* @returns {0|1} flag值
|
||||||
|
*/
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心处理函数:在所有数据都准备好后,才组装 chartData
|
||||||
|
*/
|
||||||
|
processChartData() {
|
||||||
|
// 1. 处理集团数据 - 提取各字段到对应数组
|
||||||
|
this.groupData = this.ytdData.group || { rate: 0, diff: 0, real: 0, target: 0 };
|
||||||
|
|
||||||
|
// 集团各维度数据数组(单条数据,对应凯盛新能)
|
||||||
|
const groupTarget = [this.groupData.target]; // 预算值数组
|
||||||
|
const groupDiff = [this.groupData.diff]; // 差值数组
|
||||||
|
const groupReal = [this.groupData.real]; // 实际值数组
|
||||||
|
const groupRate = [this.groupData.rate]; // 完成率数组
|
||||||
|
// 新增:集团rate对应的flag
|
||||||
|
const groupFlag = [this.getRateFlag(groupRate[0], groupReal[0], groupTarget[0])];
|
||||||
|
|
||||||
|
console.log('集团数据数组:', {
|
||||||
|
groupTarget,
|
||||||
|
groupDiff,
|
||||||
|
groupReal,
|
||||||
|
groupRate,
|
||||||
|
groupFlag,
|
||||||
|
rawGroupData: this.groupData
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 处理工厂数据 - 提取每个工厂的对应字段到数组
|
||||||
|
this.factoryData = this.ytdData.factory || [];
|
||||||
|
// 提取工厂名称数组
|
||||||
|
const factoryNames = this.factoryData.map(item => item.title || '');
|
||||||
|
// 提取工厂各维度数据数组
|
||||||
|
const factoryBudget = this.factoryData.map(item => item.budget || 0);
|
||||||
|
const factoryReal = this.factoryData.map(item => item.real || 0);
|
||||||
|
const factoryRate = this.factoryData.map(item => item.rate || 0);
|
||||||
|
const factoryDiff = this.factoryData.map(item => item.diff || 0);
|
||||||
|
// 新增:每个工厂rate对应的flag数组
|
||||||
|
const factoryFlags = this.factoryData.map(item => this.getRateFlag(item.rate, item.real, item.budget));
|
||||||
|
|
||||||
|
// 3. 组装最终的chartData(供子组件使用)
|
||||||
|
this.chartData = {
|
||||||
|
// 集团数据(对应凯盛新能)
|
||||||
|
group: {
|
||||||
|
locations: ['凯盛新能'], // 集团名称
|
||||||
|
targets: groupTarget, // 集团预算值数组
|
||||||
|
diff: groupDiff, // 集团差值数组
|
||||||
|
reals: groupReal, // 集团实际值数组
|
||||||
|
rate: groupRate, // 集团完成率数组
|
||||||
|
flags: groupFlag // 新增:集团rate对应的flag
|
||||||
|
},
|
||||||
|
// 工厂数据
|
||||||
|
factory: {
|
||||||
|
locations: factoryNames, // 工厂名称数组
|
||||||
|
targets: factoryBudget, // 工厂预算数组
|
||||||
|
reals: factoryReal, // 工厂实际值数组
|
||||||
|
rates: factoryRate, // 工厂完成率数组
|
||||||
|
diff: factoryDiff, // 工厂差值数组
|
||||||
|
flags: factoryFlags // 新增:工厂rate对应的flags数组
|
||||||
|
},
|
||||||
|
// 原始数据备份(方便后续使用)
|
||||||
|
rawData: {
|
||||||
|
group: this.groupData,
|
||||||
|
factory: this.factoryData
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('最终处理后的图表数据:', this.chartData);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||||
|
.scroll-container {
|
||||||
|
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||||
|
max-height: 210px;
|
||||||
|
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||||
|
overflow-y: auto;
|
||||||
|
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||||
|
/* Chrome/Safari */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* IE/Edge */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||||
|
.proBarInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px 27px;
|
||||||
|
/* 调整内边距,优化排版 */
|
||||||
|
margin-bottom: 10px;
|
||||||
|
/* 设备项之间的垂直间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 原有样式保留,优化细节 */
|
||||||
|
.proBarInfoEqInfo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
/* 垂直居中,避免序号/文字错位 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
width: 21px;
|
||||||
|
height: 23px;
|
||||||
|
background: rgba(0, 106, 205, 0.22);
|
||||||
|
backdrop-filter: blur(1.5px);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #68b5ff;
|
||||||
|
line-height: 23px;
|
||||||
|
/* 垂直居中文字 */
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eq-name {
|
||||||
|
margin-left: 8px;
|
||||||
|
/* 增加与序号的间距 */
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eqStatus {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitLine {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
border: 1px solid #adadad;
|
||||||
|
margin: 0 8px;
|
||||||
|
/* 优化分割线间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #00ffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoLeft {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 序号和设备名垂直居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoRight {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 状态/分割线/百分比垂直居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarWrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
/* 进度条与上方信息的间距 */
|
||||||
|
border-radius: 5px;
|
||||||
|
/* 进度条圆角,优化视觉 */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLine {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #acacac 100%);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLineTop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(
|
||||||
|
65deg,
|
||||||
|
rgba(53, 223, 247, 0) 0%,
|
||||||
|
rgba(54, 220, 246, 0.92) 92%,
|
||||||
|
#36f6e5 100%,
|
||||||
|
#37acf5 100%
|
||||||
|
);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
/* 进度变化时添加过渡动画,更流畅 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图表相关样式保留 */
|
||||||
|
.chartImgBottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 45px;
|
||||||
|
left: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
left: 57px;
|
||||||
|
bottom: 42px;
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #00e8ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
<template>
|
||||||
|
<div class="lineBottom" style="height: 180px; width: 100%">
|
||||||
|
<operatingLineBarSaleSingle :refName=" 'totalOperating' " :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBarSaleSingle from './operatingLineBarSaleSingle.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBarSaleSingle },
|
||||||
|
props: ["detailData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
locations() {
|
||||||
|
return ['预算', '实际'];
|
||||||
|
},
|
||||||
|
chartD() {
|
||||||
|
// 背景图片路径(若不需要可注释)
|
||||||
|
// const bgImageUrl = require('@/assets/img/labelBg.png');
|
||||||
|
console.log('this.detailData', this.detailData);
|
||||||
|
const rate = this.detailData?.rate || 0
|
||||||
|
const diff = this.detailData?.diff || 0
|
||||||
|
console.log('diff', diff);
|
||||||
|
|
||||||
|
const seriesData = [
|
||||||
|
{
|
||||||
|
value: this.detailData?.budget || 0,
|
||||||
|
flag: 1, // 实际项:达标(绿色)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: this.detailData?.real || 0,
|
||||||
|
flag: this.detailData?.flag, // 实际项:达标(绿色)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
allPlaceNames: ['预算', '实际'],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 60,
|
||||||
|
barCategoryGap: '50%',
|
||||||
|
data: seriesData,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const currentFlag = params.data.flag || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
console.log('data', data);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFangSC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
margin-right: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
width: 283px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0b58ff;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 8px;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button.active {
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
background: #3071ff;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
337
src/views/home/accountsReceivableComponents/operatingTopBar.vue
Normal file
337
src/views/home/accountsReceivableComponents/operatingTopBar.vue
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<div class="lineBottom" style="height: 320px; width: 100%">
|
||||||
|
<operatingLineBar :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSaleGroup.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentDataSource() {
|
||||||
|
console.log('yyyy', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData.group
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
console.log('this.chartData', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData.group.locations
|
||||||
|
},
|
||||||
|
// 根据按钮切换生成对应的 chartData
|
||||||
|
chartD() {
|
||||||
|
// 销量场景数据
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
const diff = this.currentDataSource.diff[0]
|
||||||
|
const rate = this.currentDataSource.rate[0]
|
||||||
|
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.targets // 目标销量(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图,含达标状态)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||||
|
const safeFlag = data.flags;
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals // 实际销量(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 毛利率场景数据
|
||||||
|
const grossProfitData = {
|
||||||
|
series: [
|
||||||
|
// 1. 完成率(折线图)
|
||||||
|
{
|
||||||
|
name: '完成率',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
lineStyle: { color: 'rgba(40, 138, 255, .5)', width: 2 },
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderColor: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
radius: 4
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.2,
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||||
|
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: [106.7, 96.9, 106.5, 106.1, 93.8, 105.9], // 毛利率完成率(%)
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 2. 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: [30, 32, 31, 33, 32, 34] // 目标毛利率(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const safeFlag = [1, 0, 1, 1, 0, 1]; // 达标状态
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: [32, 31, 33, 35, 30, 36] // 实际毛利率(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据按钮状态返回对应数据
|
||||||
|
return this.activeButton === 0 ? salesData : grossProfitData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end; // 标题左、右侧容器右,整体两端对齐
|
||||||
|
align-items: center; // 垂直居中,避免上下错位
|
||||||
|
gap: 16px; // 标题与右侧容器的最小间距,防止拥挤
|
||||||
|
width: 100%; // 确保占满父容器,实现两端对齐
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 标题固定在左侧,不挤压右侧空间
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; // 图例和按钮组垂直居中
|
||||||
|
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||||
|
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 图例:在右侧容器内横向排列
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px; // 图例项之间间距,避免重叠
|
||||||
|
align-items: center;
|
||||||
|
// 移除原margin-left,避免位置偏移
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap; // 防止图例文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图例颜色
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
width: 283px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
border-radius: 12px;
|
||||||
|
// 移除原margin-right,由右侧容器统一控制
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0b58ff;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 8px; // 确保文字间距生效
|
||||||
|
padding-left: 8px; // 抵消letter-spacing导致的文字左偏
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button.active {
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
background: #3071ff;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
202
src/views/home/accountsReceivableComponents/totalOverview.vue
Normal file
202
src/views/home/accountsReceivableComponents/totalOverview.vue
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||||
|
<div class="dashboard">
|
||||||
|
<div class="title">
|
||||||
|
累计完成率
|
||||||
|
</div>
|
||||||
|
<div class="number">
|
||||||
|
<div class="yield">
|
||||||
|
{{ ytdData?.rate || 0}}%
|
||||||
|
</div>
|
||||||
|
<div class="mom">
|
||||||
|
同比{{ ytdData?.yoyRate || 0}}%
|
||||||
|
<img v-if="ytdData?.yoyRate >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="">
|
||||||
|
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="electricityGauge">
|
||||||
|
<electricityGauge :id=" 'totalG' " :detailData="ytdData" id="totalGauge"></electricityGauge>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
<div class="line" style="padding: 0px;">
|
||||||
|
<verticalBarChart :refName=" 'totalVerticalBarChart' " :detailData="ytdData">
|
||||||
|
</verticalBarChart>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import electricityGauge from './electricityGauge.vue'
|
||||||
|
import verticalBarChart from './verticalBarChart.vue'
|
||||||
|
|
||||||
|
// import * as echarts from 'echarts'
|
||||||
|
// import rawItem from './raw-Item.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, electricityGauge, verticalBarChart },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
ytdData: { // 接收父组件传递的设备数据数组
|
||||||
|
type: Object,
|
||||||
|
default: () => {} // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
title: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
month: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
itemData: {
|
||||||
|
handler(newValue, oldValue) {
|
||||||
|
// this.updateChart()
|
||||||
|
},
|
||||||
|
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// computed: {
|
||||||
|
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||||
|
// sortedItemData() {
|
||||||
|
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||||
|
// const totalCostItems = this.itemData.filter(item =>
|
||||||
|
// item.name && item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 过滤出不包含“总成本”的项
|
||||||
|
// const otherItems = this.itemData.filter(item =>
|
||||||
|
// !item.name || !item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 合并:总成本项在前,其他项在后
|
||||||
|
// return [...totalCostItems, ...otherItems];
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
mounted() {
|
||||||
|
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||||
|
// this.$nextTick(() => this.updateChart())
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||||
|
.scroll-container {
|
||||||
|
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||||
|
max-height: 210px;
|
||||||
|
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||||
|
overflow-y: auto;
|
||||||
|
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||||
|
/* Chrome/Safari */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* IE/Edge */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 264px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
.title {
|
||||||
|
// width: 190px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 46px;
|
||||||
|
color: #0B58FF;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
width: 500px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .leftTitle {
|
||||||
|
// .item {
|
||||||
|
// width: 67px;
|
||||||
|
// height: 180px;
|
||||||
|
// padding: 37px 23px;
|
||||||
|
// background: #F9FCFF;
|
||||||
|
// font-family: PingFangSC, PingFang SC;
|
||||||
|
// font-weight: 400;
|
||||||
|
// font-size: 18px;
|
||||||
|
// color: #000000;
|
||||||
|
// line-height: 25px;
|
||||||
|
// letter-spacing: 1px;
|
||||||
|
// // text-align: left;
|
||||||
|
// font-style: normal;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- <style>
|
||||||
|
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style> -->
|
||||||
226
src/views/home/accountsReceivableComponents/verticalBarChart.vue
Normal file
226
src/views/home/accountsReceivableComponents/verticalBarChart.vue
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
<template>
|
||||||
|
<div style="width: 100%; height: 210px;position: relative;">
|
||||||
|
<div style='font-size: 16px;position: absolute;right: 20px;top:10px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{detailData.rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:detailData.flags>0?'#30B590':'#FF9423'}" >{{detailData.diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 210px;"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
refName: {
|
||||||
|
type: String,
|
||||||
|
default: () => 'verticalBarChart',
|
||||||
|
},
|
||||||
|
detailData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
detailData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs[this.refName];
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const diff = this.detailData.diff || 0
|
||||||
|
const rate = this.detailData.rate || 0
|
||||||
|
const flagValue = this.getRateFlag(this.detailData.rate, this.detailData.real, this.detailData.target) || 0
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 40,
|
||||||
|
bottom: 15,
|
||||||
|
right: 80,
|
||||||
|
left: 10,
|
||||||
|
containLabel: true,
|
||||||
|
show: false // 隐藏grid背景,避免干扰
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
// 横向柱状图的x轴必须设为数值轴,否则无法正常展示数值
|
||||||
|
type: 'value',
|
||||||
|
// offset: 0,
|
||||||
|
// boundaryGap: true ,
|
||||||
|
// boundaryGap: [10, 0], // 可根据需要开启,控制轴的留白
|
||||||
|
axisTick: { show: false },
|
||||||
|
min: 0,
|
||||||
|
//
|
||||||
|
splitNumber: 4,
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
// data: xData // 数值轴不需要手动设置data,由series的数据自动生成
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'category',
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.75)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true, // 显示Y轴轴线(关键)
|
||||||
|
lineStyle: {
|
||||||
|
color: '#E5E6EB', // 轴线颜色(浅灰色,可自定义)
|
||||||
|
width: 1, // 轴线宽度
|
||||||
|
type: 'solid' // 实线(可选:dashed虚线、dotted点线)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisTick: { show: false },
|
||||||
|
// padding: [300, 100, 100, 100],
|
||||||
|
data: ['实际', '预算'] // y轴分类:实际、预算
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
// name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 24,
|
||||||
|
// barCategoryGap: '50', // 柱子之间的间距(相对于柱子宽度)
|
||||||
|
// 数据长度与yAxis的分类数量匹配(实际、预算各一个值)
|
||||||
|
data: [{
|
||||||
|
value: this.detailData.real,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'right',
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: flagValue === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0]
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
value: this.detailData.target,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'right',
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
// 预算的渐变颜色(蓝系渐变)
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 1, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: '#82CCFF' }, // 浅蓝
|
||||||
|
{ offset: 1, color: '#4B9DFF' } // 深蓝
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
},],
|
||||||
|
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px; width: 100%;">
|
||||||
|
<!-- 销量模块(直接传递整合了flag的salesData) -->
|
||||||
|
<div class="dashboard left" @click="handleDashboardClick('/salesVolumeAnalysis/salesVolumeAnalysisBase')">
|
||||||
|
<div style='position: relative;'>
|
||||||
|
<div class="title">
|
||||||
|
销量·万㎡
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{ytdAnalysis[0].rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:ytdAnalysis[0].flags>0?'#30B590':'#FF9423'}" >{{ytdAnalysis[0].diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-wrap">
|
||||||
|
<operatingSingleBar :detailData="salesData"></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 单价模块(直接传递整合了flag的unitPriceData) -->
|
||||||
|
<div class="dashboard right" @click="handleDashboardClick('/unitPriceAnalysis/unitPriceAnalysisBase')">
|
||||||
|
<div style='position: relative;'>
|
||||||
|
<div class="title">
|
||||||
|
单价·元/㎡
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{ytdAnalysis[1].rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:ytdAnalysis[1].flags>0?'#30B590':'#FF9423'}" >{{ytdAnalysis[1].diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-wrap">
|
||||||
|
<operatingSingleBar :detailData="unitPriceData"></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import operatingSingleBar from './operatingSingleBar.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, operatingSingleBar },
|
||||||
|
props: {
|
||||||
|
ytdAnalysis: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [
|
||||||
|
{ title: "销量", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
{ title: "单价", budget: 0, real: 0, rate: 0, diff: 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
month: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
// 初始化数据包含flag字段
|
||||||
|
salesData: { title: "销量", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 },
|
||||||
|
unitPriceData: { title: "单价", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
ytdAnalysis: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.updateChart(newVal)
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateChart(this.ytdAnalysis)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleDashboardClick(path) {
|
||||||
|
this.$router.push({
|
||||||
|
path: path,
|
||||||
|
query: {
|
||||||
|
factory: this.$route.query.factory ? this.$route.query.factory : 5,
|
||||||
|
dateData: this.dateData
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 判断flag的核心方法
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateChart(data) {
|
||||||
|
// 数据兜底
|
||||||
|
const salesItem = Array.isArray(data) && data[0] ? data[0] : { title: "销量", budget: 0, real: 0, rate: 0, diff: 0 };
|
||||||
|
const unitPriceItem = Array.isArray(data) && data[1] ? data[1] : { title: "单价", budget: 0, real: 0, rate: 0, diff: 0 };
|
||||||
|
|
||||||
|
// 核心修改:将flag整合到数据对象中,无需单独定义salesFlag/unitPriceFlag
|
||||||
|
this.salesData = {
|
||||||
|
...salesItem, // 合并原有字段
|
||||||
|
flag: this.getRateFlag(salesItem.rate, salesItem.real, salesItem.budget) // 新增flag字段
|
||||||
|
};
|
||||||
|
|
||||||
|
this.unitPriceData = {
|
||||||
|
...unitPriceItem, // 合并原有字段
|
||||||
|
flag: this.getRateFlag(unitPriceItem.rate, unitPriceItem.real, unitPriceItem.budget) // 新增flag字段
|
||||||
|
};
|
||||||
|
|
||||||
|
// 调试:确认整合后的数据
|
||||||
|
console.log('整合flag后的销量数据:', this.salesData);
|
||||||
|
console.log('整合flag后的单价数据:', this.unitPriceData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
.scroll-container {
|
||||||
|
max-height: 210px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topItem-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 300px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
margin: 0 4px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-wrap {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 30px;
|
||||||
|
height: 32px;
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
width: 97px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard.left {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard.right {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<el-form ref="form" :rules="rules" label-width="110px" :model="form">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="客户名称" prop="name">
|
||||||
|
<el-input v-model="form.name"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="客户编码" prop="code">
|
||||||
|
<el-input v-model="form.code"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="所属基地" prop="daySpan">
|
||||||
|
<el-select v-model="form.daySpan" placeholder="请选择" style="width: 100%;">
|
||||||
|
<el-option label="否" :value= '0' ></el-option>
|
||||||
|
<el-option label="是" :value= '1' ></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
// import { getGroupClasses, updateGroupClasses, createGroupClasses, getCode } from '@/api/base/groupClasses'
|
||||||
|
export default {
|
||||||
|
name: 'CustomerInfoAdd',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
code: '',
|
||||||
|
daySpan:'',
|
||||||
|
},
|
||||||
|
isEdit: false, //是否是编辑
|
||||||
|
rules: {
|
||||||
|
name: [{ required: true, message: '请输入客户名称', trigger: 'blur' }],
|
||||||
|
code: [{ required: true, message: '请输入客户编码', trigger: 'blur' }],
|
||||||
|
daySpan: [{ required: true, message: '请选择所属基地', trigger: 'change' }],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
init(id) {
|
||||||
|
if (id) {
|
||||||
|
this.isEdit = true
|
||||||
|
this.form.id = id
|
||||||
|
// getGroupClasses(id).then((res) => {
|
||||||
|
// if (res.code === 0) {
|
||||||
|
// this.form = res.data
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitForm() {
|
||||||
|
this.$refs['form'].validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
if (this.isEdit) {
|
||||||
|
//编辑
|
||||||
|
updateGroupClasses({ ...this.form }).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$modal.msgSuccess("操作成功");
|
||||||
|
this.$emit('successSubmit')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
createGroupClasses({ ...this.form }).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$modal.msgSuccess("操作成功");
|
||||||
|
this.$emit('successSubmit')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
formClear() {
|
||||||
|
this.$refs.form.resetFields()
|
||||||
|
this.isEdit = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
100
src/views/home/basicInfoConfiguration/components/groupKeyAdd.vue
Normal file
100
src/views/home/basicInfoConfiguration/components/groupKeyAdd.vue
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<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="daySpan">
|
||||||
|
<el-select v-model="form.daySpan" placeholder="请选择" style="width: 100%;">
|
||||||
|
<el-option label="个" :value= '0' ></el-option>
|
||||||
|
<el-option label="件" :value= '1' ></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="所属年份" prop="daySpan">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="value3"
|
||||||
|
type="year"
|
||||||
|
placeholder="选择年">
|
||||||
|
</el-date-picker>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="累计值计算方式" prop="daySpan2">
|
||||||
|
<el-select v-model="form.daySpan" placeholder="请选择" style="width: 100%;">
|
||||||
|
<el-option label="月" :value= '0' ></el-option>
|
||||||
|
<el-option label="日" :value= '1' ></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
// import { getGroupClasses, updateGroupClasses, createGroupClasses, getCode } from '@/api/base/groupClasses'
|
||||||
|
export default {
|
||||||
|
name: 'groupKeyAdd',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
code: '',
|
||||||
|
daySpan:'',
|
||||||
|
},
|
||||||
|
isEdit: false, //是否是编辑
|
||||||
|
rules: {
|
||||||
|
name: [{ required: true, message: '请输入重点工作', trigger: 'blur' }],
|
||||||
|
daySpan: [{ required: true, message: '请选择单位', trigger: 'change' }],
|
||||||
|
daySpan1: [{ required: true, message: '请选择所属年份', trigger: 'change' }],
|
||||||
|
daySpan2: [{ required: true, message: '请选择累计值计算方式', trigger: 'change' }],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
init(id) {
|
||||||
|
if (id) {
|
||||||
|
this.isEdit = true
|
||||||
|
this.form.id = id
|
||||||
|
// getGroupClasses(id).then((res) => {
|
||||||
|
// if (res.code === 0) {
|
||||||
|
// this.form = res.data
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitForm() {
|
||||||
|
this.$refs['form'].validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
if (this.isEdit) {
|
||||||
|
//编辑
|
||||||
|
updateGroupClasses({ ...this.form }).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$modal.msgSuccess("操作成功");
|
||||||
|
this.$emit('successSubmit')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
createGroupClasses({ ...this.form }).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$modal.msgSuccess("操作成功");
|
||||||
|
this.$emit('successSubmit')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
formClear() {
|
||||||
|
this.$refs.form.resetFields()
|
||||||
|
this.isEdit = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<el-form ref="form" :rules="rules" label-width="110px" :model="form">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="产品名称" prop="name">
|
||||||
|
<el-input v-model="form.name"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="规格" prop="code">
|
||||||
|
<el-input v-model="form.code"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="工艺" prop="daySpan">
|
||||||
|
<el-select v-model="form.daySpan" placeholder="请选择" style="width: 100%;">
|
||||||
|
<el-option label="否" :value= '0' ></el-option>
|
||||||
|
<el-option label="是" :value= '1' ></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
// import { getGroupClasses, updateGroupClasses, createGroupClasses, getCode } from '@/api/base/groupClasses'
|
||||||
|
export default {
|
||||||
|
name: 'ProductInfoAdd',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
code: '',
|
||||||
|
daySpan:'',
|
||||||
|
},
|
||||||
|
isEdit: false, //是否是编辑
|
||||||
|
rules: {
|
||||||
|
name: [{ required: true, message: '请输入产品名称', trigger: 'blur' }],
|
||||||
|
code: [{ required: true, message: '请输入产品编码', trigger: 'blur' }],
|
||||||
|
daySpan: [{ required: true, message: '请选择工艺', trigger: 'change' }],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
init(id) {
|
||||||
|
if (id) {
|
||||||
|
this.isEdit = true
|
||||||
|
this.form.id = id
|
||||||
|
// getGroupClasses(id).then((res) => {
|
||||||
|
// if (res.code === 0) {
|
||||||
|
// this.form = res.data
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitForm() {
|
||||||
|
this.$refs['form'].validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
if (this.isEdit) {
|
||||||
|
//编辑
|
||||||
|
updateGroupClasses({ ...this.form }).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$modal.msgSuccess("操作成功");
|
||||||
|
this.$emit('successSubmit')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
createGroupClasses({ ...this.form }).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$modal.msgSuccess("操作成功");
|
||||||
|
this.$emit('successSubmit')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
formClear() {
|
||||||
|
this.$refs.form.resetFields()
|
||||||
|
this.isEdit = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<search-bar :formConfigs="formConfig" ref="searchBarForm" @headBtnClick="buttonClick" />
|
||||||
|
<base-table v-if="tableData.length" class="right-aside" :table-props="tableProps"
|
||||||
|
:page="listQuery.pageNo" :limit="listQuery.pageSize" :table-data="tableData">
|
||||||
|
<method-btn v-if="tableBtn.length" slot="handleBtn" :width="200" label="操作" :method-list="tableBtn"
|
||||||
|
@clickBtn="handleClick" />
|
||||||
|
</base-table>
|
||||||
|
<pagination :limit.sync="listQuery.pageSize" :page.sync="listQuery.pageNo" :total="total" @pagination="getDataList" :background="true" />
|
||||||
|
<!-- 新增 -->
|
||||||
|
<base-dialog
|
||||||
|
:dialogTitle="addOrEditTitle"
|
||||||
|
:dialogVisible="centervisible"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@confirm="handleConfirm"
|
||||||
|
:before-close="handleCancel"
|
||||||
|
width="50%">
|
||||||
|
<customer-info-add ref="customerList" @successSubmit="successSubmit" />
|
||||||
|
</base-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import CustomerInfoAdd from './components/customerInfoAdd.vue';
|
||||||
|
const tableProps = [
|
||||||
|
{
|
||||||
|
prop: 'name',
|
||||||
|
label: '客户名称'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'code',
|
||||||
|
label: '客户编码'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'jd',
|
||||||
|
label: '所属基地'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export default {
|
||||||
|
name: 'CustomerInfoConfiguration',
|
||||||
|
components:{CustomerInfoAdd},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
formConfig: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '客户名称',
|
||||||
|
placeholder: '客户名称',
|
||||||
|
param: 'cName'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '客户编码',
|
||||||
|
placeholder: '客户编码',
|
||||||
|
param: 'ccode'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
btnName: '查询',
|
||||||
|
name: 'search',
|
||||||
|
color: 'primary',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
btnName: '新增',
|
||||||
|
name: 'add',
|
||||||
|
color: 'success',
|
||||||
|
plain: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
listQuery:{
|
||||||
|
pageSize:20,
|
||||||
|
pageNo:1
|
||||||
|
},
|
||||||
|
total:2,
|
||||||
|
tableData:[{name:'111',id:1}],
|
||||||
|
tableProps,
|
||||||
|
tableBtn:[
|
||||||
|
{
|
||||||
|
type: 'edit',
|
||||||
|
btnName: '编辑',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'delete',
|
||||||
|
btnName: '删除',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
addOrEditTitle: '',
|
||||||
|
centervisible: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
buttonClick(val) {
|
||||||
|
console.log(val)
|
||||||
|
switch (val.btnName) {
|
||||||
|
case 'search':
|
||||||
|
this.listQuery.pageNo = 1;
|
||||||
|
this.getList();
|
||||||
|
break;
|
||||||
|
case 'add':
|
||||||
|
this.addOrEditTitle = '新增';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.customerList.init();
|
||||||
|
});
|
||||||
|
this.centervisible = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getDataList() {
|
||||||
|
|
||||||
|
},
|
||||||
|
handleClick(val) {
|
||||||
|
switch (val.type) {
|
||||||
|
case 'edit':
|
||||||
|
this.addOrEditTitle = '编辑';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.customerList.init(val.data.id);
|
||||||
|
});
|
||||||
|
this.centervisible = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.handleDelete(val.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleCancel() {
|
||||||
|
this.$refs.customerList.formClear();
|
||||||
|
this.centervisible = false;
|
||||||
|
this.addOrEditTitle = '';
|
||||||
|
},
|
||||||
|
handleConfirm() {
|
||||||
|
this.$refs.customerList.submitForm();
|
||||||
|
},
|
||||||
|
successSubmit() {
|
||||||
|
this.handleCancel();
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
handleDelete(row) {
|
||||||
|
this.$modal.confirm('确定删除客户"' + row.name + '吗?').then(function() {
|
||||||
|
deleteModel(row.id).then(response => {
|
||||||
|
that.getList();
|
||||||
|
that.$modal.msgSuccess("删除成功");
|
||||||
|
})
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<search-bar :formConfigs="formConfig" ref="searchBarForm" @headBtnClick="buttonClick" />
|
||||||
|
<base-table v-if="tableData.length" class="right-aside" :table-props="tableProps"
|
||||||
|
:page="listQuery.pageNo" :limit="listQuery.pageSize" :table-data="tableData">
|
||||||
|
<method-btn v-if="tableBtn.length" slot="handleBtn" :width="200" label="操作" :method-list="tableBtn"
|
||||||
|
@clickBtn="handleClick" />
|
||||||
|
</base-table>
|
||||||
|
<pagination :limit.sync="listQuery.pageSize" :page.sync="listQuery.pageNo" :total="total" @pagination="getDataList" :background="true" />
|
||||||
|
<!-- 新增 -->
|
||||||
|
<base-dialog
|
||||||
|
:dialogTitle="addOrEditTitle"
|
||||||
|
:dialogVisible="centervisible"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@confirm="handleConfirm"
|
||||||
|
:before-close="handleCancel"
|
||||||
|
width="50%">
|
||||||
|
<group-key-add ref="groupKey" @successSubmit="successSubmit" />
|
||||||
|
</base-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import GroupKeyAdd from './components/groupKeyAdd.vue';
|
||||||
|
const tableProps = [
|
||||||
|
{
|
||||||
|
prop: 'name',
|
||||||
|
label: '重点工作'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'code1',
|
||||||
|
label: '单位'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'code',
|
||||||
|
label: '所属年份'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'jd',
|
||||||
|
label: '累计值计算方式'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export default {
|
||||||
|
name: 'GroupKeyTaskConfiguration',
|
||||||
|
components:{GroupKeyAdd},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
formConfig: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '重点工作',
|
||||||
|
placeholder: '重点工作',
|
||||||
|
param: 'cName'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'datePicker',
|
||||||
|
label: '所属年份',
|
||||||
|
dateType: 'year',
|
||||||
|
format: 'yyyy',
|
||||||
|
valueFormat: 'yyyy',
|
||||||
|
placeholder: '所属年份',
|
||||||
|
param: 'searchTime1',
|
||||||
|
width: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
btnName: '查询',
|
||||||
|
name: 'search',
|
||||||
|
color: 'primary',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
btnName: '新增',
|
||||||
|
name: 'add',
|
||||||
|
color: 'success',
|
||||||
|
plain: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
listQuery:{
|
||||||
|
pageSize:20,
|
||||||
|
pageNo:1
|
||||||
|
},
|
||||||
|
total:2,
|
||||||
|
tableData:[{name:'111'}],
|
||||||
|
tableProps,
|
||||||
|
tableBtn:[
|
||||||
|
{
|
||||||
|
type: 'edit',
|
||||||
|
btnName: '编辑',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'delete',
|
||||||
|
btnName: '删除',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
addOrEditTitle: '',
|
||||||
|
centervisible: false,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
buttonClick(val) {
|
||||||
|
console.log(val)
|
||||||
|
switch (val.btnName) {
|
||||||
|
case 'search':
|
||||||
|
this.listQuery.pageNo = 1;
|
||||||
|
this.getList();
|
||||||
|
break;
|
||||||
|
case 'add':
|
||||||
|
this.addOrEditTitle = '新增';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.groupKey.init();
|
||||||
|
});
|
||||||
|
this.centervisible = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getDataList() {
|
||||||
|
|
||||||
|
},
|
||||||
|
handleClick(val) {
|
||||||
|
switch (val.type) {
|
||||||
|
case 'edit':
|
||||||
|
this.addOrEditTitle = '编辑';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.groupKey.init(val.data.id);
|
||||||
|
});
|
||||||
|
this.centervisible = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.handleDelete(val.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleCancel() {
|
||||||
|
this.$refs.groupKey.formClear();
|
||||||
|
this.centervisible = false;
|
||||||
|
this.addOrEditTitle = '';
|
||||||
|
},
|
||||||
|
handleConfirm() {
|
||||||
|
this.$refs.groupKey.submitForm();
|
||||||
|
},
|
||||||
|
successSubmit() {
|
||||||
|
this.handleCancel();
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
handleDelete(row) {
|
||||||
|
this.$modal.confirm('确定删除重点工作"' + row.name + '吗?').then(function() {
|
||||||
|
deleteModel(row.id).then(response => {
|
||||||
|
that.getList();
|
||||||
|
that.$modal.msgSuccess("删除成功");
|
||||||
|
})
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<search-bar :formConfigs="formConfig" ref="searchBarForm" @headBtnClick="buttonClick" />
|
||||||
|
<base-table v-if="tableData.length" class="right-aside" :table-props="tableProps"
|
||||||
|
:page="listQuery.pageNo" :limit="listQuery.pageSize" :table-data="tableData">
|
||||||
|
<method-btn v-if="tableBtn.length" slot="handleBtn" :width="200" label="操作" :method-list="tableBtn"
|
||||||
|
@clickBtn="handleClick" />
|
||||||
|
</base-table>
|
||||||
|
<pagination :limit.sync="listQuery.pageSize" :page.sync="listQuery.pageNo" :total="total" @pagination="getDataList" :background="true" />
|
||||||
|
<!-- 新增 -->
|
||||||
|
<base-dialog
|
||||||
|
:dialogTitle="addOrEditTitle"
|
||||||
|
:dialogVisible="centervisible"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@confirm="handleConfirm"
|
||||||
|
:before-close="handleCancel"
|
||||||
|
width="50%">
|
||||||
|
<product-info-add ref="prodectInfo" @successSubmit="successSubmit" />
|
||||||
|
</base-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ProductInfoAdd from './components/productInfoAdd.vue';
|
||||||
|
const tableProps = [
|
||||||
|
{
|
||||||
|
prop: 'name',
|
||||||
|
label: '产品名称'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'code',
|
||||||
|
label: '工艺'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'jd',
|
||||||
|
label: '规格'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export default {
|
||||||
|
name: 'ProductInfoConfiguration',
|
||||||
|
components:{ProductInfoAdd},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
formConfig: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '产品名称',
|
||||||
|
placeholder: '产品名称',
|
||||||
|
param: 'cName'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
btnName: '查询',
|
||||||
|
name: 'search',
|
||||||
|
color: 'primary',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
btnName: '新增',
|
||||||
|
name: 'add',
|
||||||
|
color: 'success',
|
||||||
|
plain: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
listQuery:{
|
||||||
|
pageSize:20,
|
||||||
|
pageNo:1
|
||||||
|
},
|
||||||
|
total:2,
|
||||||
|
tableData:[{name:'111'}],
|
||||||
|
tableProps,
|
||||||
|
tableBtn:[
|
||||||
|
{
|
||||||
|
type: 'edit',
|
||||||
|
btnName: '编辑',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'delete',
|
||||||
|
btnName: '删除',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
addOrEditTitle: '',
|
||||||
|
centervisible: false,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
buttonClick(val) {
|
||||||
|
console.log(val)
|
||||||
|
switch (val.btnName) {
|
||||||
|
case 'search':
|
||||||
|
this.listQuery.pageNo = 1;
|
||||||
|
this.getList();
|
||||||
|
break;
|
||||||
|
case 'add':
|
||||||
|
this.addOrEditTitle = '新增';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.prodectInfo.init();
|
||||||
|
});
|
||||||
|
this.centervisible = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getDataList() {
|
||||||
|
|
||||||
|
},
|
||||||
|
handleClick(val) {
|
||||||
|
switch (val.type) {
|
||||||
|
case 'edit':
|
||||||
|
this.addOrEditTitle = '编辑';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.prodectInfo.init(val.data.id);
|
||||||
|
});
|
||||||
|
this.centervisible = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.handleDelete(val.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleCancel() {
|
||||||
|
this.$refs.prodectInfo.formClear();
|
||||||
|
this.centervisible = false;
|
||||||
|
this.addOrEditTitle = '';
|
||||||
|
},
|
||||||
|
handleConfirm() {
|
||||||
|
this.$refs.prodectInfo.submitForm();
|
||||||
|
},
|
||||||
|
successSubmit() {
|
||||||
|
this.handleCancel();
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
handleDelete(row) {
|
||||||
|
this.$modal.confirm('确定删除产品"' + row.name + '吗?').then(function() {
|
||||||
|
deleteModel(row.id).then(response => {
|
||||||
|
that.getList();
|
||||||
|
that.$modal.msgSuccess("删除成功");
|
||||||
|
})
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -16,8 +16,8 @@
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
grid-template-columns:416px 1192px;
|
grid-template-columns:416px 1192px;
|
||||||
">
|
">
|
||||||
<indicatorCalendar :calendarList="calendarList" />
|
<indicatorCalendar :timeType="timeType" :calendarObj='calendarObj'/>
|
||||||
<indicatorDetails :timeType="timeType" />
|
<indicatorDetails :timeType="timeType" @updateLeft='getData' @updateLevel='getLevel'/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="top" style="margin-top: -20px; display: flex; gap: 16px">
|
<!-- <div class="top" style="margin-top: -20px; display: flex; gap: 16px">
|
||||||
@@ -53,8 +53,9 @@ import { mapState } from "vuex";
|
|||||||
// import operatingLineChart from "../operatingComponents/operatingLineChart";
|
// import operatingLineChart from "../operatingComponents/operatingLineChart";
|
||||||
// import operatingLineChartCumulative from "../operatingComponents/operatingLineChartCumulative.vue";
|
// import operatingLineChartCumulative from "../operatingComponents/operatingLineChartCumulative.vue";
|
||||||
|
|
||||||
import { getSalesRevenueGroupData, getCalendar } from '@/api/cockpit'
|
import { getSalesRevenueGroupData } from '@/api/cockpit'
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
import {getCalendar, getCalendarYear} from '@/api/cockpit';
|
||||||
export default {
|
export default {
|
||||||
name: "DayReport",
|
name: "DayReport",
|
||||||
components: {
|
components: {
|
||||||
@@ -78,7 +79,8 @@ export default {
|
|||||||
selectDate:{},
|
selectDate:{},
|
||||||
monthData: {},
|
monthData: {},
|
||||||
ytdData: {},
|
ytdData: {},
|
||||||
calendarList:{},
|
calendarObj:{},
|
||||||
|
levelId: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -144,37 +146,29 @@ export default {
|
|||||||
this.beilv = _this.clientWidth / 1920;
|
this.beilv = _this.clientWidth / 1920;
|
||||||
})();
|
})();
|
||||||
};
|
};
|
||||||
this.getData()
|
// this.getData()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// sortChange(value) {
|
|
||||||
// this.sort = value
|
|
||||||
// this.getData()
|
|
||||||
// },
|
|
||||||
getData() {
|
getData() {
|
||||||
getCalendar().then((res) => {
|
if(this.timeType == 'month'){
|
||||||
console.log(res, 'res');
|
getCalendar({levelId: this.levelId}).then((res) => {
|
||||||
this.calendarList = res.data
|
this.calendarObj = res.data
|
||||||
// this.monthData = res.data.month
|
})
|
||||||
// this.ytdData = res.data.ytd
|
}else{
|
||||||
})
|
getCalendarYear({levelId: this.levelId}).then((res) => {
|
||||||
// getSalesRevenueGroupData({
|
this.calendarObj = res.data
|
||||||
// startTime: this.dateData.startTime,
|
})
|
||||||
// endTime: this.dateData.endTime,
|
}
|
||||||
// sort: this.sort,
|
},
|
||||||
// index: undefined,
|
// 层级变动
|
||||||
// factory: undefined
|
getLevel(id) {
|
||||||
// // timeDim: obj.mode
|
this.levelId = id
|
||||||
// }).then((res) => {
|
this.getData()
|
||||||
// console.log(res);
|
|
||||||
// this.monthData= res.data.month
|
|
||||||
// this.ytdData = res.data.ytd
|
|
||||||
// })
|
|
||||||
},
|
},
|
||||||
handleTimeChange(obj) {
|
handleTimeChange(obj) {
|
||||||
console.log(obj, 'obj');
|
console.log(obj, 'obj');
|
||||||
this.timeType = obj
|
this.timeType = obj
|
||||||
// this.getData()
|
this.getData()
|
||||||
},
|
},
|
||||||
handleClickOutside() {
|
handleClickOutside() {
|
||||||
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
||||||
|
|||||||
@@ -8,6 +8,15 @@
|
|||||||
|
|
||||||
<!-- 右侧区域:全屏按钮 -->
|
<!-- 右侧区域:全屏按钮 -->
|
||||||
<div class="right-content">
|
<div class="right-content">
|
||||||
|
<el-dropdown trigger="click">
|
||||||
|
<el-button type="text" class="logout-btn" :title="'退出'">
|
||||||
|
<svg-icon style="color: #0B58FF;" icon-class="logout" />
|
||||||
|
</el-button>
|
||||||
|
<el-dropdown-menu slot="dropdown">
|
||||||
|
<el-dropdown-item @click.native='logout'>退出登录</el-dropdown-item>
|
||||||
|
<el-dropdown-item @click.native='handleToggle'>切换账号</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</el-dropdown>
|
||||||
<el-button type="text" class="screen-btn" @click="changeHomeSider">
|
<el-button type="text" class="screen-btn" @click="changeHomeSider">
|
||||||
<svg-icon style="color: #0B58FF;" v-if="openSider" icon-class="closeSider" />
|
<svg-icon style="color: #0B58FF;" v-if="openSider" icon-class="closeSider" />
|
||||||
<svg-icon style="color: #0B58FF;" v-else icon-class="openSider" />
|
<svg-icon style="color: #0B58FF;" v-else icon-class="openSider" />
|
||||||
@@ -36,6 +45,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
import {getPath} from "@/utils/ruoyi";
|
||||||
export default {
|
export default {
|
||||||
name: 'Header',
|
name: 'Header',
|
||||||
props: {
|
props: {
|
||||||
@@ -139,6 +149,19 @@ export default {
|
|||||||
const timeRange = this.calculateTimeRange();
|
const timeRange = this.calculateTimeRange();
|
||||||
this.$emit('timeRangeChange', timeRange);
|
this.$emit('timeRangeChange', timeRange);
|
||||||
console.log('触发时间范围变化:', timeRange);
|
console.log('触发时间范围变化:', timeRange);
|
||||||
|
},
|
||||||
|
|
||||||
|
async logout() {
|
||||||
|
this.$modal.confirm('确定注销并退出系统吗?', '提示').then(() => {
|
||||||
|
this.$store.dispatch('LogOut').then(() => {
|
||||||
|
location.href = getPath('/index');
|
||||||
|
})
|
||||||
|
}).catch(() => {});
|
||||||
|
},
|
||||||
|
handleToggle() {
|
||||||
|
this.$store.dispatch('LogOut').then(() => {
|
||||||
|
location.href = getPath('/index');
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -325,10 +348,11 @@ export default {
|
|||||||
|
|
||||||
.right-content {
|
.right-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-bottom: 60px;
|
margin-top: 12px;
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 10px;
|
gap: 21px;
|
||||||
|
height: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.current-time {
|
.current-time {
|
||||||
@@ -345,6 +369,15 @@ export default {
|
|||||||
color: #00fff0;
|
color: #00fff0;
|
||||||
font-size: 26px;
|
font-size: 26px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-btn {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
font-size: 30px;
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,10 @@
|
|||||||
style="display: flex; gap: 16px; flex-wrap: wrap; align-content: flex-start; row-gap: 8px;">
|
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': calendar.haveData,
|
||||||
'current': index === currentMonthIndex // 本月匹配current样式
|
'current': calendar.isActive // 本月匹配current样式
|
||||||
}" v-for="(month, index) in monthList" :key="index">
|
}" v-for="(calendar, index) in calendarList" :key="index">
|
||||||
{{ month.name }}
|
{{ calendar.name }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -25,49 +25,35 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Container from './container.vue'
|
import Container from './container.vue'
|
||||||
// import * as echarts from 'echarts'
|
|
||||||
// import topItem from './operating-item.vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ProductionStatus',
|
name: 'ProductionStatus',
|
||||||
components: { Container },
|
components: { Container },
|
||||||
// mixins: [resize],
|
// mixins: [resize],
|
||||||
props: {
|
props: {
|
||||||
calendarList: { // 接收父组件传递的年月状态对象
|
timeType: {
|
||||||
|
type: String,
|
||||||
|
default: 'month', // 默认月份维度
|
||||||
|
},
|
||||||
|
calendarObj: { // 接收父组件传递的年月状态对象
|
||||||
type: Object, // 注意:父组件传递的是对象,不是数组,修正props类型
|
type: Object, // 注意:父组件传递的是对象,不是数组,修正props类型
|
||||||
default: () => ({}) // 默认空对象,避免报错
|
default: () => ({}) // 默认空对象,避免报错
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
chart: null,
|
calendarList:[],// 日历列表
|
||||||
// 初始化12个月的列表,可根据实际需求修改haveData默认值
|
|
||||||
monthList: [
|
|
||||||
{ name: '1月', haveData: false, isActive: false },
|
|
||||||
{ name: '2月', haveData: false, isActive: false },
|
|
||||||
{ name: '3月', haveData: false, isActive: false },
|
|
||||||
{ name: '4月', haveData: false, isActive: false },
|
|
||||||
{ name: '5月', haveData: false, isActive: false },
|
|
||||||
{ name: '6月', haveData: false, isActive: false },
|
|
||||||
{ name: '7月', haveData: false, isActive: false },
|
|
||||||
{ name: '8月', haveData: false, isActive: false },
|
|
||||||
{ name: '9月', haveData: false, isActive: false },
|
|
||||||
{ name: '10月', haveData: false, isActive: false },
|
|
||||||
{ name: '11月', haveData: false, isActive: false },
|
|
||||||
{ name: '12月', haveData: false, isActive: false }
|
|
||||||
],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
// 计算属性:获取当前月份对应的索引(0-11,对应1月-12月)
|
|
||||||
currentMonthIndex() {
|
|
||||||
// new Date().getMonth() 返回 0(1月) - 11(12月),正好匹配monthList索引
|
|
||||||
return new Date().getMonth();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
// 监听calendarList变化,实时更新monthList的haveData状态
|
// 监听calendarList变化,实时更新monthList的haveData状态
|
||||||
calendarList: {
|
// timeType: {
|
||||||
|
// immediate: true, // 组件挂载时立即执行一次
|
||||||
|
// deep: true, // 深度监听对象内部属性变化
|
||||||
|
// handler() {
|
||||||
|
// this.updateMonthHaveData();
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
calendarObj:{
|
||||||
immediate: true, // 组件挂载时立即执行一次
|
immediate: true, // 组件挂载时立即执行一次
|
||||||
deep: true, // 深度监听对象内部属性变化
|
deep: true, // 深度监听对象内部属性变化
|
||||||
handler(newVal) {
|
handler(newVal) {
|
||||||
@@ -75,28 +61,24 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {},
|
||||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
|
||||||
// this.$nextTick(() => this.updateChart())
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
// 根据calendarList更新monthList的haveData状态
|
// 根据月或者年维度,获取不同的接口,拿到数据,更新左侧日历框
|
||||||
updateMonthHaveData(calendarObj) {
|
updateMonthHaveData(calendarObj) {
|
||||||
if (!calendarObj || typeof calendarObj !== 'object') return;
|
if(this.timeType == 'month'){
|
||||||
|
const keys = Object.keys(calendarObj);
|
||||||
// 遍历12个月,匹配对应年月
|
this.calendarList = []
|
||||||
this.monthList.forEach((month, index) => {
|
for(let i = 0; i < keys.length; i++) {
|
||||||
// 获取月份数字(索引+1,补两位,如1→01,10→10)
|
this.calendarList.push({name:i+1+'月',haveData:calendarObj[keys[i]],isActive:i==new Date().getMonth()})
|
||||||
const monthNum = (index + 1).toString().padStart(2, '0');
|
|
||||||
// 拼接成calendarList中的键格式(如2025-01)
|
|
||||||
const yearMonthKey = `2025-${monthNum}`; // 若年份不固定,可改为props传递年份
|
|
||||||
|
|
||||||
// 判断calendarObj中该键对应的值:1→true,0→false,无该键则保持默认false
|
|
||||||
if (calendarObj.hasOwnProperty(yearMonthKey)) {
|
|
||||||
month.haveData = calendarObj[yearMonthKey] === 1;
|
|
||||||
}
|
}
|
||||||
});
|
}else{
|
||||||
},
|
const keys = Object.keys(calendarObj);
|
||||||
|
this.calendarList = []
|
||||||
|
for(let i = 0; i < keys.length; i++) {
|
||||||
|
this.calendarList.push({name:keys[i]+'年',haveData:calendarObj[keys[i]],isActive:keys[i]==new Date().getFullYear()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -119,7 +101,6 @@ export default {
|
|||||||
line-height: 42px;
|
line-height: 42px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
cursor: pointer; // 鼠标悬浮手型
|
|
||||||
transition: all 0.2s ease; // 过渡效果,样式切换更平滑
|
transition: all 0.2s ease; // 过渡效果,样式切换更平滑
|
||||||
border: 2px solid transparent; // 透明边框,避免选中时布局偏移
|
border: 2px solid transparent; // 透明边框,避免选中时布局偏移
|
||||||
margin: 0; // 清除默认外边距,进一步缩小缝隙
|
margin: 0; // 清除默认外边距,进一步缩小缝隙
|
||||||
@@ -140,5 +121,3 @@ export default {
|
|||||||
border: 2px solid #0B58FF !important;
|
border: 2px solid #0B58FF !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style></style>
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<div style="width: 4px;height: 16px;background: #0B58FF;border-radius: 1px;margin-top: 10px;"></div>
|
<div style="width: 4px;height: 16px;background: #0B58FF;border-radius: 1px;margin-top: 10px;"></div>
|
||||||
<el-form :inline="true" :model="form" class="demo-form-inline">
|
<el-form :inline="true" :model="form" class="demo-form-inline">
|
||||||
<el-form-item label="所属层级">
|
<el-form-item label="所属层级">
|
||||||
<el-select v-model="form.levelId" placeholder="请选择">
|
<el-select v-model="form.levelId" placeholder="请选择" @change='handleLevelChange'>
|
||||||
<el-option v-for="item in levelLList" :key="item.id" :label="item.name" :value="item.id">
|
<el-option v-for="item in levelLList" :key="item.id" :label="item.name" :value="item.id">
|
||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
</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">导入</el-button> -->
|
<el-button type="primary" plain size="medium" @click='importExcel'>导入</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,14 +35,14 @@
|
|||||||
<div v-if="!isDetail" style="display: flex;gap: 8px;align-items: center;height: 32px;">
|
<div v-if="!isDetail" style="display: flex;gap: 8px;align-items: center;height: 32px;">
|
||||||
<div style="width: 4px;height: 16px;background: #0B58FF;border-radius: 1px;"></div>
|
<div style="width: 4px;height: 16px;background: #0B58FF;border-radius: 1px;"></div>
|
||||||
<div style="width: 58px;
|
<div style="width: 58px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
font-family: PingFangSC, PingFang SC;
|
font-family: PingFangSC, PingFang SC;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: rgba(0,0,0,0.85);
|
color: rgba(0,0,0,0.85);
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-style: normal;">指标详情</div>
|
font-style: normal;">指标详情</div>
|
||||||
<el-button style="background-color: #0B58FF;height: 32px;line-height: 10px;" type="primary"
|
<el-button style="background-color: #0B58FF;height: 32px;line-height: 10px;" type="primary"
|
||||||
@click="handleEdit">编辑</el-button>
|
@click="handleEdit">编辑</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,12 +53,12 @@ font-style: normal;">指标详情</div>
|
|||||||
<div
|
<div
|
||||||
style="width: 58px;height: 16px;font-family: PingFangSC, PingFang SC;font-weight: 400;font-size: 14px;color: rgba(0,0,0,0.85);line-height: 16px;text-align: right;font-style: normal;">
|
style="width: 58px;height: 16px;font-family: PingFangSC, PingFang SC;font-weight: 400;font-size: 14px;color: rgba(0,0,0,0.85);line-height: 16px;text-align: right;font-style: normal;">
|
||||||
快捷操作</div>
|
快捷操作</div>
|
||||||
<!-- <el-button style="height: 32px;line-height: 10px;" type="primary" plain size="medium"
|
|
||||||
@click="onSubmit">复制上月</el-button>
|
|
||||||
<el-button style="height: 32px;line-height: 10px;" type="primary" plain size="medium"
|
<el-button style="height: 32px;line-height: 10px;" type="primary" plain size="medium"
|
||||||
@click="onSubmit">全部上调5%</el-button>
|
@click="copyLastMonth">复制上月/年</el-button>
|
||||||
<el-button style="height: 32px;line-height: 10px;" type="primary" plain size="medium"
|
<el-button style="height: 32px;line-height: 10px;" type="primary" plain size="medium"
|
||||||
@click="onSubmit">全部下调5%</el-button> -->
|
@click="allUp">全部上调5%</el-button>
|
||||||
|
<el-button style="height: 32px;line-height: 10px;" type="primary" plain size="medium"
|
||||||
|
@click="allDown">全部下调5%</el-button>
|
||||||
<el-button style="height: 32px;line-height: 10px;" type="primary" plain size="medium"
|
<el-button style="height: 32px;line-height: 10px;" type="primary" plain size="medium"
|
||||||
@click="handleClear">清空配置</el-button>
|
@click="handleClear">清空配置</el-button>
|
||||||
<el-button style="background-color: #0B58FF;height: 32px;line-height: 10px;" type="primary"
|
<el-button style="background-color: #0B58FF;height: 32px;line-height: 10px;" type="primary"
|
||||||
@@ -69,24 +69,24 @@ font-style: normal;">指标详情</div>
|
|||||||
|
|
||||||
<!-- 表格组件:添加key属性强制刷新,绑定所有必要属性 -->
|
<!-- 表格组件:添加key属性强制刷新,绑定所有必要属性 -->
|
||||||
<base-table style="height: 700px;" :maxHeight=" '700' " @emitFun="inputChange" class="right-aside"
|
<base-table style="height: 700px;" :maxHeight=" '700' " @emitFun="inputChange" class="right-aside"
|
||||||
:table-props="tableProps" :page="form.pageNo" :limit="form.pageSize" :table-data="tableData" ref="baseTable"
|
:table-props="tableProps" :page="form.pageNo" :limit="form.pageSize" :table-data="tableData" ref="baseTable" id='calendarTable'
|
||||||
:key="`base-table-${isDetail}-${timeType}`"></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>
|
||||||
<el-upload ref="upload" :limit="1" accept=".xlsx, .xls" :headers="upload.headers"
|
<el-upload ref="upload" :limit="1" accept=".xlsx, .xls" action="#" :disabled="upload.isUploading"
|
||||||
:action="upload.url + '?updateSupport=' + upload.updateSupport" :disabled="upload.isUploading"
|
:on-change="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
|
||||||
:on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
|
|
||||||
<i class="el-icon-upload"></i>
|
<i class="el-icon-upload"></i>
|
||||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||||
<div class="el-upload__tip text-center" slot="tip">
|
<div class="el-upload__tip text-center" slot="tip">
|
||||||
<div class="el-upload__tip" slot="tip">
|
|
||||||
<el-checkbox v-model="upload.updateSupport" /> 是否更新已经存在的用户数据
|
|
||||||
</div>
|
|
||||||
<span>仅允许导入xls、xlsx格式文件。</span>
|
<span>仅允许导入xls、xlsx格式文件。</span>
|
||||||
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;"
|
|
||||||
@click="importTemplate">下载模板</el-link>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="el-upload__tip" slot="tip">
|
||||||
|
<el-radio-group v-model="upload.timeDim">
|
||||||
|
<el-radio :label="2">月预算</el-radio>
|
||||||
|
<el-radio :label="3">年预算</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
<div slot="footer" class="dialog-footer">
|
<div slot="footer" class="dialog-footer">
|
||||||
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
||||||
@@ -99,9 +99,11 @@ font-style: normal;">指标详情</div>
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Container from './container.vue'
|
import Container from './container.vue'
|
||||||
import { getLevelStruc, getTargetMonthPage, updateTargetMonthData, getTargetYearPage, updateTargetYearData, getDictListData } from '@/api/cockpit'
|
import { getLevelStruc, getTargetMonthPage, updateTargetMonthData, getTargetYearPage, updateTargetYearData,copyLastMonthData, copyLastYearData} from '@/api/cockpit'
|
||||||
import inputArea from './inputArea.vue' // 导入输入组件
|
import inputArea from './inputArea.vue' // 导入输入组件
|
||||||
import { getBaseHeader } from "@/utils/request";
|
import { publicFormatter } from '@/utils/dict';
|
||||||
|
import {getAccessToken, getTenantId} from '@/utils/auth'
|
||||||
|
import axios from 'axios';
|
||||||
export default {
|
export default {
|
||||||
name: 'ProductionStatus',
|
name: 'ProductionStatus',
|
||||||
components: {
|
components: {
|
||||||
@@ -118,34 +120,33 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
form: {
|
form: {
|
||||||
levelId: 1,
|
levelId: undefined,
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 100,
|
pageSize: 100,
|
||||||
date: undefined, // 统一存储日期(月份/年份)
|
date: undefined, // 统一存储日期(月份/年份)
|
||||||
startTime: undefined, // 起始时间戳
|
startTime: undefined, // 起始时间戳
|
||||||
endTime: undefined // 结束时间戳
|
endTime: undefined // 结束时间戳
|
||||||
},
|
},
|
||||||
dictData: [],
|
|
||||||
upload: {
|
upload: {
|
||||||
// 是否显示弹出层(用户导入)
|
// 是否显示弹出层
|
||||||
open: false,
|
open: false,
|
||||||
// 弹出层标题(用户导入)
|
// 弹出层标题
|
||||||
title: "",
|
title: "预算填报导入",
|
||||||
// 是否禁用上传
|
// 是否禁用上传
|
||||||
isUploading: false,
|
isUploading: false,
|
||||||
// 是否更新已经存在的用户数据
|
fileList:[],
|
||||||
updateSupport: 0,
|
currentFile:null,
|
||||||
// 设置上传的请求头部
|
timeDim: 2
|
||||||
headers: getBaseHeader(),
|
|
||||||
// 上传的地址
|
|
||||||
url: process.env.VUE_APP_BASE_API + '/admin-api/system/user/import'
|
|
||||||
},
|
},
|
||||||
getDataList: null, // 动态切换的查询接口
|
getDataList: null, // 动态切换的查询接口
|
||||||
updateData: null, // 动态切换的更新接口
|
updateData: null, // 动态切换的更新接口
|
||||||
isDetail: false, // 编辑状态标识:false=只读,true=编辑
|
isDetail: false, // 编辑状态标识:false=只读,true=编辑
|
||||||
tableData: [], // 表格数据
|
tableData: [], // 表格数据
|
||||||
levelLList: [], // 所属层级下拉数据
|
levelLList: [], // 所属层级下拉数据
|
||||||
tableProps: [] // 表格列配置
|
tableProps: [], // 表格列配置
|
||||||
|
tableKey:0,// 强制表格更新
|
||||||
|
allUpBtn: false, // 全部上调按钮状态
|
||||||
|
allDownBtn: false // 全部下调按钮状态
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -182,7 +183,6 @@ export default {
|
|||||||
this.initDefaultDate();
|
this.initDefaultDate();
|
||||||
// 2. 计算对应时间戳
|
// 2. 计算对应时间戳
|
||||||
this.calculateTimeStamp();
|
this.calculateTimeStamp();
|
||||||
this.getDictData()
|
|
||||||
// 3. 先初始化表格配置(优先于数据请求,避免表格空配置渲染)
|
// 3. 先初始化表格配置(优先于数据请求,避免表格空配置渲染)
|
||||||
this.initTableProps(this.isDetail);
|
this.initTableProps(this.isDetail);
|
||||||
// 4. 等待配置就绪后,再请求数据,避免异步冲突
|
// 4. 等待配置就绪后,再请求数据,避免异步冲突
|
||||||
@@ -191,14 +191,178 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getDictData() {
|
// 清空配置
|
||||||
getDictListData({ pageNo: 1, pageSize: 100, dictType: 'lb_dw' }).then((res) => {
|
handleClear() {
|
||||||
this.dictData = res.data.list
|
// 清空target
|
||||||
|
this.tableData.forEach(item => {
|
||||||
|
item.target = null;
|
||||||
})
|
})
|
||||||
|
this.tableKey++;
|
||||||
|
this.allDownBtn = false;
|
||||||
|
this.allUpBtn = false;
|
||||||
|
},
|
||||||
|
//复制上月/上年数据(调用接口不同)
|
||||||
|
copyLastMonth() {
|
||||||
|
if(this.timeType === 'month') {
|
||||||
|
this.$modal.confirm('是否确认复制上月数据?').then(() => {
|
||||||
|
this.copyLastMonthDataPage()
|
||||||
|
}).then(() => {
|
||||||
|
this.$modal.msgSuccess("复制成功");
|
||||||
|
}).catch(() => { });
|
||||||
|
}else{
|
||||||
|
this.$modal.confirm('是否确认复制上年数据?').then(() => {
|
||||||
|
this.copyLastYearDataPage()
|
||||||
|
}).then(() => {
|
||||||
|
this.$modal.msgSuccess("复制成功");
|
||||||
|
}).catch(() => { });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
copyLastMonthDataPage() {
|
||||||
|
copyLastMonthData({
|
||||||
|
levelId: this.form.levelId,
|
||||||
|
startTime: this.form.startTime,
|
||||||
|
endTime: this.form.endTime,
|
||||||
|
pageSize: this.form.pageSize,
|
||||||
|
pageNo: this.form.pageNo
|
||||||
|
}).then((res) => {
|
||||||
|
this.tableData = res.data.map(item => {
|
||||||
|
// 新增unitLabel字段,存储匹配后的显示名称
|
||||||
|
return {
|
||||||
|
...item
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.tableKey++;
|
||||||
|
this.allDownBtn = false;
|
||||||
|
this.allUpBtn = false;
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('获取表格数据失败:', err);
|
||||||
|
this.tableData = [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
copyLastYearDataPage() {
|
||||||
|
copyLastYearData({
|
||||||
|
levelId: this.form.levelId,
|
||||||
|
startTime: this.form.startTime,
|
||||||
|
endTime: this.form.endTime,
|
||||||
|
pageSize: this.form.pageSize,
|
||||||
|
pageNo: this.form.pageNo
|
||||||
|
}).then((res) => {
|
||||||
|
this.tableData = res.data.map(item => {
|
||||||
|
// 新增unitLabel字段,存储匹配后的显示名称
|
||||||
|
return {
|
||||||
|
...item
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.tableKey++;
|
||||||
|
this.allDownBtn = false;
|
||||||
|
this.allUpBtn = false;
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('获取表格数据失败:', err);
|
||||||
|
this.tableData = [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
//全部上调5%
|
||||||
|
allUp() {
|
||||||
|
if(this.allUpBtn || this.allDownBtn) {
|
||||||
|
this.$modal.msgWarning("数据已调整,请先保存数据");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for(let i = 0; i < this.tableData.length; i++) {
|
||||||
|
this.tableData[i].target = (this.tableData[i].target * 1.05).toFixed(2)
|
||||||
|
}
|
||||||
|
// 强制表格组件刷新
|
||||||
|
this.tableKey++;
|
||||||
|
this.allUpBtn = true;
|
||||||
|
},
|
||||||
|
// 全部下调5%
|
||||||
|
allDown() {
|
||||||
|
if(this.allUpBtn || this.allDownBtn) {
|
||||||
|
this.$modal.msgWarning("数据已调整,请先保存数据");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(let i = 0; i < this.tableData.length; i++) {
|
||||||
|
this.tableData[i].target = (this.tableData[i].target * 0.95).toFixed(2)
|
||||||
|
}
|
||||||
|
// 强制表格组件刷新
|
||||||
|
this.tableKey++;
|
||||||
|
this.allDownBtn = true;
|
||||||
|
},
|
||||||
|
// 查询按钮
|
||||||
|
onSubmit() {
|
||||||
|
// 清空原有表格数据,重新请求
|
||||||
|
this.tableData = [];
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.getDataPage();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 切换到编辑模式
|
||||||
|
handleEdit() {
|
||||||
|
this.isDetail = true;
|
||||||
|
// 先更新表格配置,再强制表格刷新
|
||||||
|
this.initTableProps(this.isDetail);
|
||||||
|
this.tableKey++;
|
||||||
|
},
|
||||||
|
// 保存数据(使用动态切换的updateData接口)
|
||||||
|
handleSave() {
|
||||||
|
this.$modal.confirm('是否确认保存数据?').then(() => {
|
||||||
|
return this.updateData(this.tableData)
|
||||||
|
}).then(() => {
|
||||||
|
this.isDetail = false;
|
||||||
|
// 重置表格配置
|
||||||
|
this.initTableProps(this.isDetail);
|
||||||
|
// 清空并重新请求数据,恢复原始状态
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.tableData = [];
|
||||||
|
this.getDataPage();
|
||||||
|
});
|
||||||
|
this.$modal.msgSuccess("保存成功");
|
||||||
|
this.$emit('updateLeft')
|
||||||
|
}).catch(() => { });
|
||||||
|
},
|
||||||
|
// 请求下拉数据和表格数据(使用动态切换的getDataList接口)
|
||||||
|
getData() {
|
||||||
|
if (!this.getDataList) {
|
||||||
|
console.warn('当前时间维度异常,无法获取数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 1. 请求所属层级下拉数据
|
||||||
|
getLevelStruc().then((res) => {
|
||||||
|
this.levelLList = res.data || [];
|
||||||
|
this.form.levelId = this.levelLList[0].id;
|
||||||
|
this.$emit('updateLevel', this.levelLList[0].id)
|
||||||
|
this.getDataPage()
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('获取所属层级失败:', err);
|
||||||
|
this.levelLList = [];
|
||||||
|
});
|
||||||
|
this.$emit('updateLeft')
|
||||||
|
},
|
||||||
|
getDataPage() {
|
||||||
|
// 2. 请求表格分页数据(使用动态接口)
|
||||||
|
this.getDataList({
|
||||||
|
levelId: this.form.levelId,
|
||||||
|
startTime: this.form.startTime,
|
||||||
|
endTime: this.form.endTime,
|
||||||
|
pageSize: this.form.pageSize,
|
||||||
|
pageNo: this.form.pageNo
|
||||||
|
}).then((res) => {
|
||||||
|
console.log('表格数据:', res);
|
||||||
|
this.tableData = res.data.map(item => {
|
||||||
|
// 新增unitLabel字段,存储匹配后的显示名称
|
||||||
|
return {
|
||||||
|
...item
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('获取表格数据失败:', err);
|
||||||
|
this.tableData = [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleLevelChange(id) {
|
||||||
|
this.$emit('updateLevel', id)
|
||||||
},
|
},
|
||||||
// 表格单元格数据变更回调
|
// 表格单元格数据变更回调
|
||||||
inputChange(val) {
|
inputChange(val) {
|
||||||
console.log('修改的数据:', val);
|
|
||||||
// 安全修改:判断索引是否存在,避免数组越界
|
// 安全修改:判断索引是否存在,避免数组越界
|
||||||
if (this.tableData[val._pageIndex - 1]) {
|
if (this.tableData[val._pageIndex - 1]) {
|
||||||
this.tableData[val._pageIndex - 1][val.prop] = val[val.prop];
|
this.tableData[val._pageIndex - 1][val.prop] = val[val.prop];
|
||||||
@@ -206,21 +370,31 @@ export default {
|
|||||||
this.tableData[val._pageIndex - 1].status = 1;
|
this.tableData[val._pageIndex - 1].status = 1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 取消编辑,恢复只读模式
|
||||||
|
handleCancel() {
|
||||||
|
this.isDetail = false;
|
||||||
|
this.allUpBtn = false;
|
||||||
|
this.allDownBtn = false;
|
||||||
|
// 重置表格配置
|
||||||
|
this.initTableProps(this.isDetail);
|
||||||
|
// 清空并重新请求数据,恢复原始状态
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.tableData = [];
|
||||||
|
this.getDataPage();
|
||||||
|
});
|
||||||
|
},
|
||||||
// 初始化表格列配置(核心:精准控制inputArea挂载)
|
// 初始化表格列配置(核心:精准控制inputArea挂载)
|
||||||
initTableProps(isEdit) {
|
initTableProps(isEdit) {
|
||||||
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' },
|
{ prop: 'unit', label: '单位', align: 'center', filter: publicFormatter('lb_dw') },
|
||||||
{ prop: 'target', label: '预估值', align: 'center' },
|
{ prop: 'target', label: '预算值', align: 'center' },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
// 编辑模式:仅给「预估值」列添加inputArea(精准挂载,避免无效配置)
|
// 编辑模式:仅给「预算值」列添加inputArea(精准挂载,避免无效配置)
|
||||||
this.tableProps = baseTableProps.map(item => {
|
this.tableProps = baseTableProps.map(item => {
|
||||||
if (item.prop === 'target') { // 只给需要编辑的列添加子组件
|
if (item.prop === 'target') { // 只给需要编辑的列添加子组件
|
||||||
return {
|
return {
|
||||||
@@ -232,75 +406,16 @@ export default {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 只读模式:深拷贝基础配置,避免引用污染
|
// 只读模式:深拷贝基础配置,避免引用污染
|
||||||
this.tableProps = JSON.parse(JSON.stringify(baseTableProps));
|
// this.tableProps = JSON.parse(JSON.stringify(baseTableProps));
|
||||||
|
this.tableProps = baseTableProps;
|
||||||
}
|
}
|
||||||
console.log('表格配置:', this.tableProps);
|
console.log('表格配置:', this.tableProps);
|
||||||
},
|
},
|
||||||
|
|
||||||
// 切换到编辑模式
|
|
||||||
handleEdit() {
|
|
||||||
this.isDetail = true;
|
|
||||||
// 先更新表格配置,再强制表格刷新(双重保障)
|
|
||||||
this.initTableProps(this.isDetail);
|
|
||||||
this.$nextTick(() => {
|
|
||||||
if (this.$refs.baseTable) {
|
|
||||||
this.$refs.baseTable.$forceUpdate(); // 强制表格组件刷新
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// 保存数据(使用动态切换的updateData接口)
|
|
||||||
handleSave() {
|
|
||||||
// if (!this.updateData) {
|
|
||||||
// this.$modal.msgWarning('当前时间维度异常,无法保存');
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
this.$modal.confirm('是否确认保存数据?').then(() => {
|
|
||||||
return this.updateData(this.tableData)
|
|
||||||
}).then(() => {
|
|
||||||
this.isDetail = false;
|
|
||||||
// 重置表格配置
|
|
||||||
this.initTableProps(this.isDetail);
|
|
||||||
// 清空并重新请求数据,恢复原始状态
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.tableData = [];
|
|
||||||
this.getData();
|
|
||||||
});
|
|
||||||
this.$modal.msgSuccess("保存成功");
|
|
||||||
}).catch(() => { });
|
|
||||||
},
|
|
||||||
|
|
||||||
// 取消编辑,恢复只读模式
|
|
||||||
handleCancel() {
|
|
||||||
this.isDetail = false;
|
|
||||||
// 重置表格配置
|
|
||||||
this.initTableProps(this.isDetail);
|
|
||||||
// 清空并重新请求数据,恢复原始状态
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.tableData = [];
|
|
||||||
this.getData();
|
|
||||||
});
|
|
||||||
console.log('已取消编辑,恢复原始数据');
|
|
||||||
},
|
|
||||||
|
|
||||||
// 清空配置
|
|
||||||
handleClear() {
|
|
||||||
this.isDetail = false;
|
|
||||||
// 重置表格配置
|
|
||||||
this.initTableProps(this.isDetail);
|
|
||||||
// 清空并重新请求数据,恢复原始状态
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.tableData = [];
|
|
||||||
this.getData();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// 初始化默认日期(根据timeType)
|
// 初始化默认日期(根据timeType)
|
||||||
initDefaultDate() {
|
initDefaultDate() {
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
this.form.date = currentDate;
|
this.form.date = currentDate;
|
||||||
},
|
},
|
||||||
|
|
||||||
// 计算时间戳(根据timeType切换:月/年)
|
// 计算时间戳(根据timeType切换:月/年)
|
||||||
calculateTimeStamp(selectDate) {
|
calculateTimeStamp(selectDate) {
|
||||||
let targetDate = selectDate || this.form.date;
|
let targetDate = selectDate || this.form.date;
|
||||||
@@ -330,7 +445,6 @@ export default {
|
|||||||
this.form.endTime = endDatePrecise.getTime();
|
this.form.endTime = endDatePrecise.getTime();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 日期选择器变更事件(统一处理月/年变更)
|
// 日期选择器变更事件(统一处理月/年变更)
|
||||||
handleDateChange(val) {
|
handleDateChange(val) {
|
||||||
if (!val) {
|
if (!val) {
|
||||||
@@ -342,62 +456,62 @@ export default {
|
|||||||
// 计算选中日期对应的时间戳
|
// 计算选中日期对应的时间戳
|
||||||
this.calculateTimeStamp(val);
|
this.calculateTimeStamp(val);
|
||||||
},
|
},
|
||||||
getUnitLabel(unitCode) {
|
// 导入
|
||||||
// 若字典为空或无匹配编码,返回原编码或空字符串
|
importExcel() {
|
||||||
if (!this.dictData || this.dictData.length === 0) {
|
this.upload.open = true
|
||||||
return unitCode || '';
|
|
||||||
}
|
|
||||||
// 查找匹配的字典项
|
|
||||||
const matchItem = this.dictData.find(item => item.value == unitCode);
|
|
||||||
// 返回匹配的label,无匹配则返回原unit编码
|
|
||||||
return matchItem ? matchItem.label : (unitCode || '');
|
|
||||||
},
|
},
|
||||||
// 请求下拉数据和表格数据(使用动态切换的getDataList接口)
|
// 文件上传中处理
|
||||||
getData() {
|
handleFileUploadProgress(file, fileList) {
|
||||||
if (!this.getDataList) {
|
console.log('文件上传中:',file, fileList)
|
||||||
console.warn('当前时间维度异常,无法获取数据');
|
this.upload.isUploading = true;
|
||||||
return;
|
this.upload.fileList = fileList;
|
||||||
}
|
this.upload.currentFile = file.raw;
|
||||||
|
},
|
||||||
// 1. 请求所属层级下拉数据
|
handleFileSuccess() {},
|
||||||
getLevelStruc().then((res) => {
|
importTemplate() {},
|
||||||
console.log('所属层级数据:', res);
|
// 提交上传文件
|
||||||
this.levelLList = res.data || [];
|
async submitFileForm() {
|
||||||
}).catch(err => {
|
try {
|
||||||
console.error('获取所属层级失败:', err);
|
if (!this.upload.currentFile) {
|
||||||
this.levelLList = [];
|
return this.$message.error('请先选择要上传的文件!')
|
||||||
});
|
}
|
||||||
|
const formData = new FormData()
|
||||||
// 2. 请求表格分页数据(使用动态接口)
|
formData.append('file', this.upload.currentFile) // 文件字段
|
||||||
this.getDataList({
|
formData.append('timeDim', this.upload.timeDim) // 年月维度字段
|
||||||
levelId: this.form.levelId,
|
formData.append('reportDate', this.form.endTime) // 时间维度字段
|
||||||
startTime: this.form.startTime,
|
formData.append('levelId', this.form.levelId) // 层级维度字段
|
||||||
endTime: this.form.endTime,
|
const response = await axios({
|
||||||
pageSize: this.form.pageSize,
|
url: process.env.VUE_APP_BASE_API + '/admin-api/lb/index-target-month/import',
|
||||||
pageNo: this.form.pageNo
|
method: 'post',
|
||||||
}).then((res) => {
|
data: formData,
|
||||||
console.log('表格数据:', res);
|
headers: {
|
||||||
this.tableData = res.data.map(item => {
|
'Content-Type': 'multipart/form-data',
|
||||||
// 新增unitLabel字段,存储匹配后的显示名称
|
'Authorization': "Bearer " + getAccessToken(),
|
||||||
return {
|
'tenant-id': getTenantId(),
|
||||||
...item,
|
},
|
||||||
unitLabel: this.getUnitLabel(item.unit)
|
timeout: 30000
|
||||||
};
|
})
|
||||||
});
|
// 4. 处理响应结果
|
||||||
}).catch(err => {
|
if (response.data.code === 0) {
|
||||||
console.error('获取表格数据失败:', err);
|
this.$message.success('文件上传成功!')
|
||||||
this.tableData = [];
|
// 重置表单
|
||||||
});
|
this.upload.fileList = []
|
||||||
|
this.upload.timeDim = 2
|
||||||
|
this.upload.currentFile = null
|
||||||
|
this.upload.open = false
|
||||||
|
this.upload.isUploading = false
|
||||||
|
this.$refs.upload.clearFiles();
|
||||||
|
this.getDataPage();
|
||||||
|
this.$emit('updateLeft')
|
||||||
|
} else {
|
||||||
|
this.$message.error(`上传失败:${response.data.msg || '未知错误'}`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 5. 异常处理
|
||||||
|
console.error('文件上传出错:', error)
|
||||||
|
this.$message.error('上传失败!')
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 查询按钮点击事件(可根据需求扩展逻辑)
|
|
||||||
onSubmit() {
|
|
||||||
// 清空原有表格数据,重新请求
|
|
||||||
this.tableData = [];
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.getData();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,6 +7,15 @@
|
|||||||
|
|
||||||
<!-- 右侧区域:全屏按钮 -->
|
<!-- 右侧区域:全屏按钮 -->
|
||||||
<div class="right-content">
|
<div class="right-content">
|
||||||
|
<el-dropdown trigger="click">
|
||||||
|
<el-button type="text" class="logout-btn" :title="'退出'">
|
||||||
|
<svg-icon style="color: #0B58FF;" icon-class="logout" />
|
||||||
|
</el-button>
|
||||||
|
<el-dropdown-menu slot="dropdown">
|
||||||
|
<el-dropdown-item @click.native='logout'>退出登录</el-dropdown-item>
|
||||||
|
<el-dropdown-item @click.native='handleToggle'>切换账号</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</el-dropdown>
|
||||||
<el-button type="text" class="return-btn" :title="'返回'" @click="handleReturn">
|
<el-button type="text" class="return-btn" :title="'返回'" @click="handleReturn">
|
||||||
<svg-icon style="color: #0B58FF;" icon-class="returnIcon" />
|
<svg-icon style="color: #0B58FF;" icon-class="returnIcon" />
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -35,7 +44,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import moment from 'moment'; // 引入moment
|
import moment from 'moment'; // 引入moment
|
||||||
|
import {getPath} from "@/utils/ruoyi";
|
||||||
export default {
|
export default {
|
||||||
name: 'Header',
|
name: 'Header',
|
||||||
props: {
|
props: {
|
||||||
@@ -75,6 +84,18 @@ export default {
|
|||||||
handleReturn() {
|
handleReturn() {
|
||||||
this.$router.go(-1);
|
this.$router.go(-1);
|
||||||
},
|
},
|
||||||
|
async logout() {
|
||||||
|
this.$modal.confirm('确定注销并退出系统吗?', '提示').then(() => {
|
||||||
|
this.$store.dispatch('LogOut').then(() => {
|
||||||
|
location.href = getPath('/index');
|
||||||
|
})
|
||||||
|
}).catch(() => {});
|
||||||
|
},
|
||||||
|
handleToggle() {
|
||||||
|
this.$store.dispatch('LogOut').then(() => {
|
||||||
|
location.href = getPath('/index');
|
||||||
|
})
|
||||||
|
},
|
||||||
exportPDF() {
|
exportPDF() {
|
||||||
this.$emit('exportPDF');
|
this.$emit('exportPDF');
|
||||||
},
|
},
|
||||||
@@ -300,6 +321,7 @@ export default {
|
|||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
gap: 21px;
|
gap: 21px;
|
||||||
|
height: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// .current-time {
|
// .current-time {
|
||||||
@@ -326,6 +348,7 @@ export default {
|
|||||||
color: #00fff0;
|
color: #00fff0;
|
||||||
font-size: 26px;
|
font-size: 26px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.return-btn {
|
.return-btn {
|
||||||
@@ -336,6 +359,13 @@ export default {
|
|||||||
font-size: 26px;
|
font-size: 26px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logout-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
font-size: 28px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 自定义下拉框样式(替换原有日期选择器样式) */
|
/* 自定义下拉框样式(替换原有日期选择器样式) */
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="changeBase">
|
<div class="changeBase">
|
||||||
<div class="base-item" @click="handleClick(index)" v-for="(item, index) in buttonList" :key="item"
|
<div class="base-item" @click="handleClick(item.id)" v-for="(item) in buttonLevelList" :key="item.id"
|
||||||
:style="{ zIndex: activeButton === index ? 10 : 1 }">
|
:style="{ zIndex: activeButton === item.id ? 10 : 1 }">
|
||||||
<img :src="activeButton === index ? imgMap.bgBase[item] : imgMap.base[item]" :alt="`${item}基地`" :title="item">
|
<img :src="activeButton === item.id ? item.bgImg : item.img" :alt="`${item.name}基地`" :title="item.name">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -33,80 +33,69 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
factory: {
|
factory: {
|
||||||
type: [String, Number],
|
type: [String, Number],
|
||||||
default: 5, // 新映射中“宜兴”对应序号7
|
default: undefined,
|
||||||
validator: (val) => [5, 2, 7, 3, 8, 9, 10].includes(val) // 校验序号范围(匹配新的baseNameToIndexMap)
|
validator: (val) => [5, 2, 7, 3, 8, 9, 10, 6].includes(val) // 校验序号范围(匹配新的baseNameToIndexMap)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 计算属性(响应式,levelList变化时会自动更新)
|
||||||
|
computed: {
|
||||||
|
buttonLevelList() {
|
||||||
|
// 核心:通过$store.getters获取定义的getter
|
||||||
|
let arr = []
|
||||||
|
this.$store.getters.levelList.forEach(item => {
|
||||||
|
this.buttonList.forEach(item2 => {
|
||||||
|
if (item2.id === item.id) {
|
||||||
|
arr.push(item2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.activeButton = arr[0].id
|
||||||
|
return arr
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
activeButton: 5, // 初始化默认选中索引2(对应buttonList中的“宜兴”)
|
activeButton: undefined,
|
||||||
buttonList: ['合肥', '桐城', '宜兴', '自贡', '漳州', '洛阳', '秦皇岛', '宿迁'], // 匹配截图顺序
|
buttonList:[
|
||||||
imgMap: {
|
{id: 5, name: '合肥', img: baseHefei, bgImg: bgBaseHefei},
|
||||||
base: {
|
{id: 2, name: '桐城', img: baseTongcheng, bgImg: bgBaseTongcheng},
|
||||||
合肥: baseHefei,
|
{id: 7, name: '宜兴', img: baseYixing, bgImg: bgBaseYixing},
|
||||||
桐城: baseTongcheng,
|
{id: 3, name: '自贡', img: baseZigong, bgImg: bgBaseZigong},
|
||||||
宜兴: baseYixing,
|
{id: 8, name: '漳州', img: baseZhangzhou, bgImg: bgBaseZhangzhou},
|
||||||
自贡: baseZigong,
|
{id: 9, name: '洛阳', img: baseLuoyang, bgImg: bgBaseLuoyang},
|
||||||
漳州: baseZhangzhou,
|
{id: 10, name: '秦皇岛', img: baseQinhuangdao, bgImg: bgBaseQinhuangdao},
|
||||||
洛阳: baseLuoyang,
|
{id: 6, name: '宿迁', img: baseSuqian, bgImg: bgBaseSuqian}
|
||||||
秦皇岛: baseQinhuangdao,
|
],
|
||||||
宿迁: baseSuqian
|
|
||||||
},
|
|
||||||
bgBase: {
|
|
||||||
合肥: bgBaseHefei,
|
|
||||||
桐城: bgBaseTongcheng,
|
|
||||||
宜兴: bgBaseYixing,
|
|
||||||
自贡: bgBaseZigong,
|
|
||||||
漳州: bgBaseZhangzhou,
|
|
||||||
洛阳: bgBaseLuoyang,
|
|
||||||
秦皇岛: bgBaseQinhuangdao,
|
|
||||||
宿迁: bgBaseSuqian
|
|
||||||
}
|
|
||||||
},
|
|
||||||
baseNameToIndexMap: { // 新的名称→序号映射
|
|
||||||
宜兴: 7,
|
|
||||||
漳州: 8,
|
|
||||||
自贡: 3,
|
|
||||||
桐城: 2,
|
|
||||||
洛阳: 9,
|
|
||||||
合肥: 5,
|
|
||||||
秦皇岛: 10, // 补充
|
|
||||||
宿迁: 6 // 补充
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
// 监听父组件传递的factory变化,同步本地选中索引
|
// 监听父组件传递的factory变化,同步本地选中索引
|
||||||
factory: {
|
factory: {
|
||||||
handler(newVal) {
|
handler(newVal) {
|
||||||
// 根据新的baseNameToIndexMap,找到对应的基地名称
|
console.log('watch factory=======================:', newVal);
|
||||||
const targetName = Object.keys(this.baseNameToIndexMap).find(name => this.baseNameToIndexMap[name] === newVal);
|
if (newVal) {
|
||||||
// 根据名称找到buttonList中的索引
|
this.activeButton = newVal;
|
||||||
const targetIndex = this.buttonList.indexOf(targetName);
|
}
|
||||||
// 合法索引则更新,否则默认选中宜兴
|
// // 根据新的baseNameToIndexMap,找到对应的基地名称
|
||||||
this.activeButton = targetIndex > -1 ? targetIndex : 2;
|
// const targetName = Object.keys(this.baseNameToIndexMap).find(name => this.baseNameToIndexMap[name] === newVal);
|
||||||
console.log('当前选中基地:', this.buttonList[this.activeButton], '序号:', newVal);
|
// // 根据名称找到buttonList中的索引
|
||||||
|
// const targetIndex = this.buttonList.indexOf(targetName);
|
||||||
|
// // 合法索引则更新,否则默认选中宜兴
|
||||||
|
// this.activeButton = targetIndex > -1 ? targetIndex : 2;
|
||||||
|
// console.log('当前选中基地:', this.buttonList[this.activeButton], '序号:', newVal);
|
||||||
},
|
},
|
||||||
immediate: true // 初始化立即执行
|
// immediate: true // 初始化立即执行
|
||||||
},
|
|
||||||
// 监听本地选中索引变化,向父组件发送事件
|
|
||||||
activeButton(newVal) {
|
|
||||||
const selectedName = this.buttonList[newVal];
|
|
||||||
const selectedIndex = this.baseNameToIndexMap[selectedName] || 5; // 默认返回宜兴的序号7
|
|
||||||
this.$emit('baseChange', selectedIndex);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleClick(index) {
|
handleClick(id) {
|
||||||
this.activeButton = index;
|
this.activeButton = id;
|
||||||
},
|
this.$emit('baseChange', id);
|
||||||
getIndexByName(name) {
|
|
||||||
return this.baseNameToIndexMap[name] || 5;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// 初始化时触发事件,传递默认选中的宜兴序号(7)
|
// 初始化时触发事件,传递默认选中的宜兴序号(7)
|
||||||
this.$emit('baseChange', 5);
|
// this.$emit('baseChange', 5);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ export default {
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 5%;
|
right: 5%;
|
||||||
top: 10%;
|
top: 25px;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export default {
|
|||||||
|
|
||||||
return dataMap.map(itemInfo => {
|
return dataMap.map(itemInfo => {
|
||||||
const rawItem = rawData[itemInfo.key] || {};
|
const rawItem = rawData[itemInfo.key] || {};
|
||||||
const progress = Math.max(0, Math.round((rawItem.rate || 0)));
|
const progress = rawItem.rate || 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
unit: itemInfo.unit,
|
unit: itemInfo.unit,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<div class="monthItem" :class="{
|
<div class="monthItem" :class="{
|
||||||
'has-data': month.haveData,
|
'has-data': month.haveData,
|
||||||
'current': index === currentMonthIndex // 本月匹配current样式
|
'current': index === currentMonthIndex // 本月匹配current样式
|
||||||
}" v-for="(month, index) in monthList" :key="index">
|
}" v-for="(month, index) in list" :key="index">
|
||||||
{{ month.name }}
|
{{ month.name }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,21 +41,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
chart: null,
|
chart: null,
|
||||||
// 初始化12个月的列表,可根据实际需求修改haveData默认值
|
list:[]
|
||||||
monthList: [
|
|
||||||
{ name: '1月', haveData: false, isActive: false },
|
|
||||||
{ name: '2月', haveData: false, isActive: false },
|
|
||||||
{ name: '3月', haveData: false, isActive: false },
|
|
||||||
{ name: '4月', haveData: false, isActive: false },
|
|
||||||
{ name: '5月', haveData: false, isActive: false },
|
|
||||||
{ name: '6月', haveData: false, isActive: false },
|
|
||||||
{ name: '7月', haveData: false, isActive: false },
|
|
||||||
{ name: '8月', haveData: false, isActive: false },
|
|
||||||
{ name: '9月', haveData: false, isActive: false },
|
|
||||||
{ name: '10月', haveData: false, isActive: false },
|
|
||||||
{ name: '11月', haveData: false, isActive: false },
|
|
||||||
{ name: '12月', haveData: false, isActive: false }
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -83,19 +69,15 @@ export default {
|
|||||||
// 根据calendarList更新monthList的haveData状态
|
// 根据calendarList更新monthList的haveData状态
|
||||||
updateMonthHaveData(calendarObj) {
|
updateMonthHaveData(calendarObj) {
|
||||||
if (!calendarObj || typeof calendarObj !== 'object') return;
|
if (!calendarObj || typeof calendarObj !== 'object') return;
|
||||||
|
const keys = Object.keys(calendarObj);
|
||||||
// 遍历12个月,匹配对应年月
|
if(keys.length == 0){
|
||||||
this.monthList.forEach((month, index) => {
|
return
|
||||||
// 获取月份数字(索引+1,补两位,如1→01,10→10)
|
}
|
||||||
const monthNum = (index + 1).toString().padStart(2, '0');
|
console.log('calendarObj',calendarObj)
|
||||||
// 拼接成calendarList中的键格式(如2025-01)
|
this.list = []
|
||||||
const yearMonthKey = `2025-${monthNum}`; // 若年份不固定,可改为props传递年份
|
for(let i = 0; i < keys.length; i++) {
|
||||||
|
this.list.push({name:i+1+'月',haveData:calendarObj[keys[i]],isActive:i==new Date().getMonth()})
|
||||||
// 判断calendarObj中该键对应的值:1→true,0→false,无该键则保持默认false
|
}
|
||||||
if (calendarObj.hasOwnProperty(yearMonthKey)) {
|
|
||||||
month.haveData = calendarObj[yearMonthKey] === 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<div style="width: 4px;height: 16px;background: #0B58FF;border-radius: 1px;margin-top: 10px;"></div>
|
<div style="width: 4px;height: 16px;background: #0B58FF;border-radius: 1px;margin-top: 10px;"></div>
|
||||||
<el-form :inline="true" :model="form" class="demo-form-inline">
|
<el-form :inline="true" :model="form" class="demo-form-inline">
|
||||||
<el-form-item label="所属层级">
|
<el-form-item label="所属层级">
|
||||||
<el-select v-model="form.levelId" placeholder="请选择">
|
<el-select v-model="form.levelId" placeholder="请选择" @change='handleLevelChange'>
|
||||||
<el-option v-for="item in levelLList" :key="item.id" :label="item.name" :value="item.id">
|
<el-option v-for="item in levelLList" :key="item.id" :label="item.name" :value="item.id">
|
||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
</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">导入</el-button> -->
|
<el-button type="primary" plain size="medium" @click='importExcel'>导入</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
@@ -64,6 +64,22 @@ 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-upload ref="upload" :limit="1" accept=".xlsx, .xls" action="#" :disabled="upload.isUploading"
|
||||||
|
:on-change="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
|
||||||
|
<i class="el-icon-upload"></i>
|
||||||
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||||
|
<div class="el-upload__tip text-center" slot="tip">
|
||||||
|
<span>仅允许导入xls、xlsx格式文件。</span>
|
||||||
|
</div>
|
||||||
|
<div class="el-upload__tip" slot="tip">
|
||||||
|
</div>
|
||||||
|
</el-upload>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
||||||
|
<el-button @click="upload.open = false">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -72,7 +88,8 @@ font-style: normal;">指标详情</div>
|
|||||||
import Container from './container.vue'
|
import Container from './container.vue'
|
||||||
import { getLevelStruc, getRealMonthPage, updateRealMonthData, getDictListData, } from '@/api/cockpit'
|
import { getLevelStruc, getRealMonthPage, updateRealMonthData, getDictListData, } from '@/api/cockpit'
|
||||||
import inputArea from './inputArea.vue' // 导入输入组件
|
import inputArea from './inputArea.vue' // 导入输入组件
|
||||||
|
import {getAccessToken, getTenantId} from '@/utils/auth'
|
||||||
|
import axios from 'axios';
|
||||||
export default {
|
export default {
|
||||||
name: 'ProductionStatus',
|
name: 'ProductionStatus',
|
||||||
components: {
|
components: {
|
||||||
@@ -85,7 +102,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
form: {
|
form: {
|
||||||
levelId: 1,
|
levelId: undefined,
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 100,
|
pageSize: 100,
|
||||||
month: undefined,
|
month: undefined,
|
||||||
@@ -96,7 +113,17 @@ export default {
|
|||||||
isDetail: false, // 编辑状态标识:false=只读,true=编辑
|
isDetail: false, // 编辑状态标识:false=只读,true=编辑
|
||||||
tableData: [], // 表格数据
|
tableData: [], // 表格数据
|
||||||
levelLList: [], // 所属层级下拉数据
|
levelLList: [], // 所属层级下拉数据
|
||||||
tableProps: [] // 表格列配置
|
tableProps: [], // 表格列配置
|
||||||
|
upload: {
|
||||||
|
// 是否显示弹出层
|
||||||
|
open: false,
|
||||||
|
// 弹出层标题
|
||||||
|
title: "指标填报导入",
|
||||||
|
// 是否禁用上传
|
||||||
|
isUploading: false,
|
||||||
|
fileList:[],
|
||||||
|
currentFile:null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -187,9 +214,10 @@ export default {
|
|||||||
// 清空并重新请求数据,恢复原始状态
|
// 清空并重新请求数据,恢复原始状态
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.tableData = [];
|
this.tableData = [];
|
||||||
this.getData();
|
this.getDataPage();
|
||||||
});
|
});
|
||||||
this.$modal.msgSuccess("保存成功");
|
this.$modal.msgSuccess("保存成功");
|
||||||
|
this.$emit('updateLeft')
|
||||||
}).catch(() => { });
|
}).catch(() => { });
|
||||||
},
|
},
|
||||||
// 取消编辑,恢复只读模式
|
// 取消编辑,恢复只读模式
|
||||||
@@ -200,7 +228,7 @@ export default {
|
|||||||
// 清空并重新请求数据,恢复原始状态
|
// 清空并重新请求数据,恢复原始状态
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.tableData = [];
|
this.tableData = [];
|
||||||
this.getData();
|
this.getDataPage();
|
||||||
});
|
});
|
||||||
console.log('已取消编辑,恢复原始数据');
|
console.log('已取消编辑,恢复原始数据');
|
||||||
},
|
},
|
||||||
@@ -211,7 +239,7 @@ export default {
|
|||||||
// 清空并重新请求数据,恢复原始状态
|
// 清空并重新请求数据,恢复原始状态
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.tableData = [];
|
this.tableData = [];
|
||||||
this.getData();
|
this.getDataPage();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -261,13 +289,17 @@ export default {
|
|||||||
getData() {
|
getData() {
|
||||||
// 1. 请求所属层级下拉数据
|
// 1. 请求所属层级下拉数据
|
||||||
getLevelStruc().then((res) => {
|
getLevelStruc().then((res) => {
|
||||||
console.log('所属层级数据:', res);
|
|
||||||
this.levelLList = res.data || [];
|
this.levelLList = res.data || [];
|
||||||
|
this.form.levelId = this.levelLList[0].id;
|
||||||
|
this.$emit('updateLevel', this.levelLList[0].id)
|
||||||
|
this.getDataPage()
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error('获取所属层级失败:', err);
|
console.error('获取所属层级失败:', err);
|
||||||
this.levelLList = [];
|
this.levelLList = [];
|
||||||
});
|
});
|
||||||
|
this.$emit('updateLeft')
|
||||||
|
},
|
||||||
|
getDataPage() {
|
||||||
// 2. 请求表格分页数据
|
// 2. 请求表格分页数据
|
||||||
getRealMonthPage({
|
getRealMonthPage({
|
||||||
levelId: this.form.levelId,
|
levelId: this.form.levelId,
|
||||||
@@ -288,6 +320,10 @@ export default {
|
|||||||
console.error('获取表格数据失败:', err);
|
console.error('获取表格数据失败:', err);
|
||||||
this.tableData = [];
|
this.tableData = [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
handleLevelChange(id) {
|
||||||
|
this.$emit('updateLevel', id)
|
||||||
},
|
},
|
||||||
|
|
||||||
// 查询按钮点击事件(可根据需求扩展逻辑)
|
// 查询按钮点击事件(可根据需求扩展逻辑)
|
||||||
@@ -295,8 +331,62 @@ export default {
|
|||||||
// 清空原有表格数据,重新请求
|
// 清空原有表格数据,重新请求
|
||||||
this.tableData = [];
|
this.tableData = [];
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.getData();
|
this.getDataPage();
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
// 导入
|
||||||
|
importExcel() {
|
||||||
|
this.upload.open = true
|
||||||
|
},
|
||||||
|
// 文件上传中处理
|
||||||
|
handleFileUploadProgress(file, fileList) {
|
||||||
|
console.log('文件上传中:',file, fileList)
|
||||||
|
this.upload.isUploading = true;
|
||||||
|
this.upload.fileList = fileList;
|
||||||
|
this.upload.currentFile = file.raw;
|
||||||
|
},
|
||||||
|
handleFileSuccess() {},
|
||||||
|
importTemplate() {},
|
||||||
|
// 提交上传文件
|
||||||
|
async submitFileForm() {
|
||||||
|
try {
|
||||||
|
if (!this.upload.currentFile) {
|
||||||
|
return this.$message.error('请先选择要上传的文件!')
|
||||||
|
}
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', this.upload.currentFile) // 文件字段
|
||||||
|
formData.append('reportDate', this.form.endTime) // 时间维度字段
|
||||||
|
formData.append('levelId', this.form.levelId) // 层级维度字段
|
||||||
|
const response = await axios({
|
||||||
|
url: process.env.VUE_APP_BASE_API + '/admin-api/lb/index-real-month/actualIndicatorImport',
|
||||||
|
method: 'post',
|
||||||
|
data: formData,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
'Authorization': "Bearer " + getAccessToken(),
|
||||||
|
'tenant-id': getTenantId(),
|
||||||
|
},
|
||||||
|
timeout: 30000
|
||||||
|
})
|
||||||
|
// 4. 处理响应结果
|
||||||
|
if (response.data.code === 0) {
|
||||||
|
this.$message.success('文件上传成功!')
|
||||||
|
// 重置表单
|
||||||
|
this.upload.fileList = []
|
||||||
|
this.upload.currentFile = null
|
||||||
|
this.upload.open = false
|
||||||
|
this.upload.isUploading = false
|
||||||
|
this.$refs.upload.clearFiles();
|
||||||
|
this.getDataPage();
|
||||||
|
this.$emit('updateLeft')
|
||||||
|
} else {
|
||||||
|
this.$message.error(`上传失败:${response.data.msg || '未知错误'}`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 5. 异常处理
|
||||||
|
console.error('文件上传出错:', error)
|
||||||
|
this.$message.error('上传失败!')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,15 @@
|
|||||||
|
|
||||||
<!-- 右侧区域:全屏按钮 -->
|
<!-- 右侧区域:全屏按钮 -->
|
||||||
<div class="right-content">
|
<div class="right-content">
|
||||||
|
<el-dropdown trigger="click">
|
||||||
|
<el-button type="text" class="logout-btn" :title="'退出'">
|
||||||
|
<svg-icon style="color: #0B58FF;" icon-class="logout" />
|
||||||
|
</el-button>
|
||||||
|
<el-dropdown-menu slot="dropdown">
|
||||||
|
<el-dropdown-item @click.native='logout'>退出登录</el-dropdown-item>
|
||||||
|
<el-dropdown-item @click.native='handleToggle'>切换账号</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</el-dropdown>
|
||||||
<el-button type="text" class="return-btn" :title="'返回'" @click="handleReturn">
|
<el-button type="text" class="return-btn" :title="'返回'" @click="handleReturn">
|
||||||
<svg-icon style="color: #0B58FF;" icon-class="returnIcon" />
|
<svg-icon style="color: #0B58FF;" icon-class="returnIcon" />
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -31,7 +40,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import moment from 'moment'; // 引入moment
|
import moment from 'moment'; // 引入moment
|
||||||
|
import {getPath} from "@/utils/ruoyi";
|
||||||
export default {
|
export default {
|
||||||
name: 'Header',
|
name: 'Header',
|
||||||
props: {
|
props: {
|
||||||
@@ -100,6 +109,18 @@ export default {
|
|||||||
this.$router.go(-1);
|
this.$router.go(-1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async logout() {
|
||||||
|
this.$modal.confirm('确定注销并退出系统吗?', '提示').then(() => {
|
||||||
|
this.$store.dispatch('LogOut').then(() => {
|
||||||
|
location.href = getPath('/index');
|
||||||
|
})
|
||||||
|
}).catch(() => {});
|
||||||
|
},
|
||||||
|
handleToggle() {
|
||||||
|
this.$store.dispatch('LogOut').then(() => {
|
||||||
|
location.href = getPath('/index');
|
||||||
|
})
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* 计算时间范围(时间戳格式)
|
* 计算时间范围(时间戳格式)
|
||||||
* 固定为月维度:当月1日00:00:00 → 当月最后一天23:59:59
|
* 固定为月维度:当月1日00:00:00 → 当月最后一天23:59:59
|
||||||
@@ -297,6 +318,7 @@ export default {
|
|||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
gap: 21px;
|
gap: 21px;
|
||||||
|
height: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.screen-btn {
|
.screen-btn {
|
||||||
@@ -305,6 +327,7 @@ export default {
|
|||||||
color: #00fff0;
|
color: #00fff0;
|
||||||
font-size: 26px;
|
font-size: 26px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-btn {
|
.home-btn {
|
||||||
@@ -322,6 +345,14 @@ export default {
|
|||||||
font-size: 26px;
|
font-size: 26px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logout-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
font-size: 28px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 日期选择器自定义样式 */
|
/* 日期选择器自定义样式 */
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export default {
|
|||||||
grid: {
|
grid: {
|
||||||
top: 35,
|
top: 35,
|
||||||
bottom: 20,
|
bottom: 20,
|
||||||
right: 25,
|
right: 13,
|
||||||
},
|
},
|
||||||
xAxis: [
|
xAxis: [
|
||||||
{
|
{
|
||||||
@@ -222,11 +222,11 @@ export default {
|
|||||||
/* (你的样式代码保持不变) */
|
/* (你的样式代码保持不变) */
|
||||||
.legend {
|
.legend {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 10px;
|
right: 12px;
|
||||||
top: -5px;
|
top: 0px;
|
||||||
display: flex;
|
display: flex;
|
||||||
/* 使用 flex 布局让两个图例项并排且对齐 */
|
/* 使用 flex 布局让两个图例项并排且对齐 */
|
||||||
gap: 20px;
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-item-line {
|
.legend-item-line {
|
||||||
@@ -237,7 +237,7 @@ export default {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 20px;
|
padding-left: 24px;
|
||||||
/* 为圆点和线条留出空间 */
|
/* 为圆点和线条留出空间 */
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export default {
|
|||||||
|
|
||||||
.lineFour {
|
.lineFour {
|
||||||
top: 5px;
|
top: 5px;
|
||||||
left: 268px;
|
left: 252px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-button {
|
.item-button {
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export default {
|
|||||||
unit: mappingItem.unit,
|
unit: mappingItem.unit,
|
||||||
targetValue: indicatorData.target, // 目标值
|
targetValue: indicatorData.target, // 目标值
|
||||||
currentValue: indicatorData.real, // 实际值
|
currentValue: indicatorData.real, // 实际值
|
||||||
progress: indicatorData.rate ? Math.round(indicatorData.rate) : 0, // 完成率(百分比,四舍五入)
|
progress: indicatorData.rate || 0, // 完成率
|
||||||
path: mappingItem.path // 路由路径
|
path: mappingItem.path // 路由路径
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export default {
|
|||||||
// 定义一个映射关系,将后端字段名与前端显示信息关联起来
|
// 定义一个映射关系,将后端字段名与前端显示信息关联起来
|
||||||
const dataMap = [
|
const dataMap = [
|
||||||
{
|
{
|
||||||
key: 'processCost',
|
key: 'totalCost',
|
||||||
unit: '制造成本·元/㎡',
|
unit: '制造成本·元/㎡',
|
||||||
route: '/productionCostAnalysis/productionCostAnalysis'
|
route: '/productionCostAnalysis/productionCostAnalysis'
|
||||||
},
|
},
|
||||||
@@ -101,7 +101,7 @@ export default {
|
|||||||
return dataMap.map(itemInfo => {
|
return dataMap.map(itemInfo => {
|
||||||
const rawItem = rawData[itemInfo.key] || {};
|
const rawItem = rawData[itemInfo.key] || {};
|
||||||
// 计算进度百分比,确保不小于0
|
// 计算进度百分比,确保不小于0
|
||||||
const progress = Math.max(0, Math.round((rawItem.rate || 0)));
|
const progress = rawItem.rate || 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
unit: itemInfo.unit,
|
unit: itemInfo.unit,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default {
|
|||||||
chart: null,
|
chart: null,
|
||||||
// 固定3条默认数据,作为展示模板
|
// 固定3条默认数据,作为展示模板
|
||||||
parentItemList: [
|
parentItemList: [
|
||||||
{ name: "总制造成本", targetValue: 0, value: 0, proportion: 0, flag: 0 },
|
{ name: "制造成本", targetValue: 0, value: 0, proportion: 0, flag: 0 },
|
||||||
{ name: "原片成本", targetValue: 0, value: 0, proportion: 0, flag: 0 },
|
{ name: "原片成本", targetValue: 0, value: 0, proportion: 0, flag: 0 },
|
||||||
{ name: "加工成本", targetValue: 0, value: 0, proportion: 0, flag: 0 },
|
{ name: "加工成本", targetValue: 0, value: 0, proportion: 0, flag: 0 },
|
||||||
]
|
]
|
||||||
|
|||||||
246
src/views/home/depreciationAnalysis/depreciationAnalysis.vue
Normal file
246
src/views/home/depreciationAnalysis/depreciationAnalysis.vue
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
<template>
|
||||||
|
<div id="dayReport" class="dayReport" :style="styles">
|
||||||
|
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||||
|
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||||
|
<ReportHeader :dateData="dateData" top-title="应收账款" :is-full-screen="isFullScreen"
|
||||||
|
@screenfullChange="screenfullChange" @timeRangeChange="handleTimeChange" />
|
||||||
|
<div class="main-body" style="
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
padding: 0px 16px 0 272px;
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div class="top" style="margin-top: -20px; display: flex; gap: 16px">
|
||||||
|
<div class="top-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns:1624px;
|
||||||
|
">
|
||||||
|
<operatingLineChart :dateData="dateData" :monthData="monthData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||||
|
<div class="left-three" style="
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns: 1624px;
|
||||||
|
">
|
||||||
|
<operatingLineChartCumulative :dateData="dateData" :ytdData="ytdData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import ReportHeader from "../components/noRouterHeader.vue";
|
||||||
|
import { Sidebar } from "../../../layout/components";
|
||||||
|
import screenfull from "screenfull";
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
import operatingLineChart from "../depreciationAnalysisComponents/operatingLineChart";
|
||||||
|
import operatingLineChartCumulative from "../depreciationAnalysisComponents/operatingLineChartCumulative.vue";
|
||||||
|
|
||||||
|
import { getSalesRevenueGroupData } from '@/api/cockpit'
|
||||||
|
export default {
|
||||||
|
name: "DepreciationAnalysis",
|
||||||
|
components: {
|
||||||
|
ReportHeader,
|
||||||
|
operatingLineChartCumulative,
|
||||||
|
operatingLineChart,
|
||||||
|
Sidebar,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
weekArr: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
|
||||||
|
isFullScreen: false,
|
||||||
|
timer: null,
|
||||||
|
beilv: 1,
|
||||||
|
value: 100,
|
||||||
|
sort:1,
|
||||||
|
selectDate:{},
|
||||||
|
monthData: {},
|
||||||
|
ytdData:{},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.init();
|
||||||
|
this.windowWidth(document.documentElement.clientWidth);
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
theme: (state) => state.settings.theme,
|
||||||
|
sideTheme: (state) => state.settings.sideTheme,
|
||||||
|
sidebar: (state) => state.app.sidebar,
|
||||||
|
device: (state) => state.app.device,
|
||||||
|
needTagsView: (state) => state.settings.tagsView,
|
||||||
|
fixedHeader: (state) => state.settings.fixedHeader,
|
||||||
|
}),
|
||||||
|
classObj() {
|
||||||
|
return {
|
||||||
|
hideSidebar: !this.sidebar.opened,
|
||||||
|
openSidebar: this.sidebar.opened,
|
||||||
|
withoutAnimation: this.sidebar.withoutAnimation,
|
||||||
|
mobile: this.device === "mobile",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
variables() {
|
||||||
|
return variables;
|
||||||
|
},
|
||||||
|
// ...mapGetters(['sidebar']),
|
||||||
|
styles() {
|
||||||
|
const v = Math.floor(this.value * this.beilv * 100) / 10000;
|
||||||
|
return {
|
||||||
|
transform: `scale(${v})`,
|
||||||
|
transformOrigin: "left top",
|
||||||
|
// overflow: hidden;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
clientWidth(val) {
|
||||||
|
if (!this.timer) {
|
||||||
|
this.clientWidth = val;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
this.timer = true;
|
||||||
|
let _this = this;
|
||||||
|
setTimeout(function () {
|
||||||
|
_this.timer = false;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
// 这里可以添加修改时的方法
|
||||||
|
this.windowWidth(val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.destroy();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const _this = this;
|
||||||
|
_this.beilv = document.documentElement.clientWidth / 1920;
|
||||||
|
window.onresize = () => {
|
||||||
|
return (() => {
|
||||||
|
_this.clientWidth = `${document.documentElement.clientWidth}`;
|
||||||
|
this.beilv = _this.clientWidth / 1920;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// sortChange(value) {
|
||||||
|
// this.sort = value
|
||||||
|
// this.getData()
|
||||||
|
// },
|
||||||
|
getData() {
|
||||||
|
getSalesRevenueGroupData({
|
||||||
|
startTime: this.dateData.startTime,
|
||||||
|
endTime: this.dateData.endTime,
|
||||||
|
sort: this.sort,
|
||||||
|
index: undefined,
|
||||||
|
factory: undefined
|
||||||
|
// timeDim: obj.mode
|
||||||
|
}).then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
this.monthData= res.data.month
|
||||||
|
this.ytdData = res.data.ytd
|
||||||
|
|
||||||
|
// this.saleData = res.data.SaleData
|
||||||
|
// this.premiumProduct = res.data.premiumProduct
|
||||||
|
// this.salesTrendMap = res.data.salesTrendMap
|
||||||
|
// this.grossMarginTrendMap = res.data.grossMarginTrendMap
|
||||||
|
// this.salesProportion = res.data.salesProportion ? res.data.salesProportion : {}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleTimeChange(obj) {
|
||||||
|
console.log(obj, 'obj');
|
||||||
|
this.dateData= obj
|
||||||
|
this.getData()
|
||||||
|
},
|
||||||
|
handleClickOutside() {
|
||||||
|
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
||||||
|
},
|
||||||
|
windowWidth(value) {
|
||||||
|
this.clientWidth = value;
|
||||||
|
this.beilv2 = this.clientWidth / 1920;
|
||||||
|
},
|
||||||
|
change() {
|
||||||
|
this.isFullScreen = screenfull.isFullscreen;
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.on("change", this.change);
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.off("change", this.change);
|
||||||
|
},
|
||||||
|
// 全屏
|
||||||
|
screenfullChange() {
|
||||||
|
console.log("screenfull.enabled", screenfull.isEnabled);
|
||||||
|
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$message({
|
||||||
|
message: "you browser can not work",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
screenfull.toggle(this.$refs.dayReportB);
|
||||||
|
},
|
||||||
|
// 导出
|
||||||
|
// exportPDF() {
|
||||||
|
// this.$message.success('正在导出,请稍等!')
|
||||||
|
// const element = document.getElementById('dayRepDom')
|
||||||
|
// element.style.display = 'block'
|
||||||
|
// const fileName = '株洲碲化镉生产日报' + moment().format('yyMMDD') + '.pdf'
|
||||||
|
// html2canvas(element, {
|
||||||
|
// dpi: 300, // Set to 300 DPI
|
||||||
|
// scale: 3 // Adjusts your resolution
|
||||||
|
// }).then(function(canvas) {
|
||||||
|
// const imgWidth = 595.28
|
||||||
|
// const imgHeight = 841.89
|
||||||
|
// const pageData = canvas.toDataURL('image/jpeg', 1.0)
|
||||||
|
// const PDF = new JsPDF('', 'pt', [imgWidth, imgHeight])
|
||||||
|
// PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
|
||||||
|
// setTimeout(() => {
|
||||||
|
// PDF.save(fileName) // 导出文件名
|
||||||
|
// }, 1000)
|
||||||
|
// })
|
||||||
|
// element.style.display = 'none'
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "~@/assets/styles/mixin.scss";
|
||||||
|
@import "~@/assets/styles/variables.scss";
|
||||||
|
.dayReport {
|
||||||
|
width: 1920px;
|
||||||
|
height: 1080px;
|
||||||
|
background: url("../../../assets/img/backp.png") no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
.hideSidebar .fixed-header {
|
||||||
|
width: calc(100% - 54px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarHide .fixed-header {
|
||||||
|
width: calc(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile .fixed-header {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
419
src/views/home/depreciationAnalysisComponents/Header.vue
Normal file
419
src/views/home/depreciationAnalysisComponents/Header.vue
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
<template>
|
||||||
|
<header class="report-header">
|
||||||
|
<!-- 左侧区域:logo + 标题 -->
|
||||||
|
<div class="left-content">
|
||||||
|
<img style="height: 36px;" src="../../../assets/img/cnbm.png" alt="benmaLogo" >
|
||||||
|
<div class="top-title">{{ topTitle }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="center-content">
|
||||||
|
<!-- 循环 pageRoutes,不再硬编码文字 -->
|
||||||
|
<div class="item" v-for="(page, index) in pageRoutes" :key="index" @click="goToPage(page.path, index)">
|
||||||
|
<span class="item-text">{{ page.text }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- :class="{ 'no-skew': activeIndex === index }
|
||||||
|
" -->
|
||||||
|
<!-- 右侧区域:全屏按钮 -->
|
||||||
|
<div class="right-content">
|
||||||
|
<el-button type="text" class="screen-btn" :title="isFullScreen ? '退出全屏' : '全屏'" @click="changeFullScreen">
|
||||||
|
<svg-icon style="color: #0B58FF;" v-if="isFullScreen" icon-class="unFullScreenView" />
|
||||||
|
<svg-icon style="color: #0B58FF;" v-else icon-class="fullScreenView" />
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 时间选择区域:日/月/年按钮 + label + 日期选择器 -->
|
||||||
|
<div class="timeType">
|
||||||
|
<!-- <div class="item" v-for="(item, index) in timeTypes" :key="index" @click="activeTime = index"
|
||||||
|
:class="{ 'no-skew': activeTime === index }">
|
||||||
|
<span class="item-text">{{ item.text }}</span>
|
||||||
|
</div> -->
|
||||||
|
<div class="dateP">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">月份选择</span>
|
||||||
|
</div>
|
||||||
|
<el-date-picker v-model="date" :type="getPickerType" :placeholder="getPickerPlaceholder"
|
||||||
|
class="custom-date-picker" style="width: 132px;height: 29px;" @change="emitTimeRange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment'
|
||||||
|
export default {
|
||||||
|
name: 'Header',
|
||||||
|
props: {
|
||||||
|
isFullScreen: { type: Boolean, default: false },
|
||||||
|
topTitle: { type: String, default: '' }
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentTime: '',
|
||||||
|
timeTimer: null,
|
||||||
|
date: undefined,
|
||||||
|
activeIndex: -1,
|
||||||
|
activeTime: 1, // 0=日,1=月,2=年(默认选中“日”)
|
||||||
|
pageRoutes: [
|
||||||
|
{ text: '营业收入', path: '/operatingRevenue/operatingRevenueIndex' },
|
||||||
|
{ text: '利润分析', path: '/profitAnalysis' },
|
||||||
|
{ text: '产销率库存分析', path: '/PSIAnal' },
|
||||||
|
{ text: '成本分析', path: '/cost/cost' },
|
||||||
|
{ text: '驾驶舱报表', path: '/cockpit' }
|
||||||
|
],
|
||||||
|
// 定义时间类型配置:text=按钮文字,pickerType=选择器类型,placeholder=占位符
|
||||||
|
timeTypes: [
|
||||||
|
{ text: '日', pickerType: 'date', placeholder: '选择日期' },
|
||||||
|
{ text: '月', pickerType: 'month', placeholder: '选择月份' },
|
||||||
|
{ text: '年', pickerType: 'year', placeholder: '选择年份' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 动态获取日期选择器类型
|
||||||
|
getPickerType() {
|
||||||
|
return this.timeTypes[this.activeTime].pickerType;
|
||||||
|
},
|
||||||
|
// 动态获取日期选择器占位符
|
||||||
|
getPickerPlaceholder() {
|
||||||
|
return this.timeTypes[this.activeTime].placeholder;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goToPage(path, index) {
|
||||||
|
// 1. 跳转到对应路由
|
||||||
|
this.$router.push(path);
|
||||||
|
// 2. 更新activeIndex,让当前点击项高亮
|
||||||
|
this.activeIndex = index;
|
||||||
|
},
|
||||||
|
changeFullScreen() { this.$emit('screenfullChange') },
|
||||||
|
padZero(num) { return num < 10 ? '0' + num : num },
|
||||||
|
/**
|
||||||
|
* 核心方法1:根据维度计算时间区间(首次进入时基于赋值的当月日期,计算“当月第一天0点→次月第一天0点”)
|
||||||
|
* @returns {Object} 包含 start(开始时间)、end(结束时间)、dimension(维度)的区间对象
|
||||||
|
*/
|
||||||
|
calculateTimeRange() {
|
||||||
|
let startTime = 0;
|
||||||
|
let endTime = 0;
|
||||||
|
const mode = this.activeTime + 1; // 1=日,2=月,3=年
|
||||||
|
const defaultMoment = moment(); // 默认当前时间
|
||||||
|
|
||||||
|
const targetMoment = this.date
|
||||||
|
? moment(this.date, this.getPickerType === 'date' ? 'YYYY-MM-DD' : (this.getPickerType === 'month' ? 'YYYY-MM' : 'YYYY'))
|
||||||
|
: defaultMoment;
|
||||||
|
|
||||||
|
if (!targetMoment.isValid()) {
|
||||||
|
console.error('无效日期:', this.date);
|
||||||
|
return { startTime, endTime, mode };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 日维度:00:00:00 → 23:59:59(无毫秒)
|
||||||
|
if (this.activeTime === 0) {
|
||||||
|
startTime = targetMoment.startOf('day').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('day').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 月维度:当月1日00:00:00 → 当月最后一天23:59:59(无毫秒)
|
||||||
|
else if (this.activeTime === 1) {
|
||||||
|
startTime = targetMoment.startOf('month').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('month').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 年维度:当年1月1日00:00:00 → 当年最后一天23:59:59(无毫秒)
|
||||||
|
else if (this.activeTime === 2) {
|
||||||
|
startTime = targetMoment.startOf('year').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('year').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调试输出:验证是否去掉毫秒
|
||||||
|
console.log('时间范围计算结果:', {
|
||||||
|
mode,
|
||||||
|
startTime: moment(startTime * 1000).format('YYYY-MM-DD HH:mm:ss'), // 格式:2025-11-30 00:00:00
|
||||||
|
endTime: moment(endTime * 1000).format('YYYY-MM-DD HH:mm:ss'), // 格式:2025-11-30 23:59:59(无毫秒)
|
||||||
|
startTimeStamp: startTime, // 秒级时间戳(如:1764422400)
|
||||||
|
endTimeStamp: endTime // 秒级时间戳(如:1764508799)
|
||||||
|
});
|
||||||
|
|
||||||
|
return { startTime, endTime, mode };
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 核心方法2:传递时间区间给父组件(首次进入时触发,传递“当月第一天0点→次月第一天0点”)
|
||||||
|
*/
|
||||||
|
emitTimeRange() {
|
||||||
|
const timeRange = this.calculateTimeRange();
|
||||||
|
this.$emit('timeRangeChange', timeRange);
|
||||||
|
// 调试用:查看首次传递的区间(如{start: "2025-10-01T00:00:00", end: "2025-11-01T00:00:00", dimension: "月"})
|
||||||
|
console.log('当前时间区间:', timeRange);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 维度切换时:清空选择的日期,并传递当前维度的默认区间
|
||||||
|
activeTime(newVal, oldVal) {
|
||||||
|
if (newVal !== oldVal) {
|
||||||
|
this.date = undefined;
|
||||||
|
// this.emitTimeRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 核心逻辑:首次进入页面,计算当月默认日期并赋值给选择器,同时传递区间
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = this.padZero(now.getMonth() + 1); // 月份从0开始,+1后补零(如1月→01)
|
||||||
|
// 赋值当月默认日期(格式:YYYY-MM,适配month类型选择器)
|
||||||
|
this.date = `${year}-${month}`;
|
||||||
|
// 确保选择器渲染完成后,传递“当月第一天0点→次月第一天0点”的区间
|
||||||
|
this.$nextTick(() => this.emitTimeRange());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
/* 原有样式不变,仅补充label文字的倾斜抵消样式 */
|
||||||
|
@font-face {
|
||||||
|
font-family: "YouSheBiaoTiHei";
|
||||||
|
src: url('../../../assets/fonts/YouSheBiaoTiHe.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-header {
|
||||||
|
height: 117px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
background: url('../../../assets/img/topTitle.png') no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: 0 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
/* 确保timeType绝对定位生效 */
|
||||||
|
|
||||||
|
.left-content {
|
||||||
|
margin-top: 11px;
|
||||||
|
margin-left: 44px;
|
||||||
|
height: 55px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-title {
|
||||||
|
height: 55px;
|
||||||
|
font-family: "YouSheBiaoTiHei", sans-serif;
|
||||||
|
font-size: 42px;
|
||||||
|
color: #1E1651;
|
||||||
|
line-height: 55px;
|
||||||
|
letter-spacing: 6px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 18px;
|
||||||
|
margin-left: 70px;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
width: 180px;
|
||||||
|
height: 50px;
|
||||||
|
background: #E1EEFC;
|
||||||
|
transform: skew(-20deg);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #1E1651;
|
||||||
|
line-height: 50px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0px 13px 16px 0px rgba(179, 217, 255, 0.43),
|
||||||
|
0px 2px 4px 0px rgba(92, 140, 255, 0.25),
|
||||||
|
inset 0px -43px 13px 0px rgba(255, 255, 255, 0.51);
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(20deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.no-skew {
|
||||||
|
background: none !important;
|
||||||
|
transform: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
color: #1E1651;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 垂直居中,避免元素高低错位 */
|
||||||
|
top: 42px;
|
||||||
|
right:10px;
|
||||||
|
margin-top: 18px;
|
||||||
|
gap: 0;
|
||||||
|
/* 清除间隙,让按钮与选择器紧密连接 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item {
|
||||||
|
width: 40px;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(236, 244, 254, 1);
|
||||||
|
transform: skew(-20deg);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(11, 88, 255, 1);
|
||||||
|
line-height: 28px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
/* 选中按钮与未选中按钮倾斜角度统一,避免切换时跳动 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item .item-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(20deg);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item.no-skew {
|
||||||
|
background: rgba(11, 88, 255, 1);
|
||||||
|
color: rgba(249, 252, 255, 1);
|
||||||
|
transform: skew(-20deg) !important;
|
||||||
|
/* 统一倾斜角度,修复原30deg的错位 */
|
||||||
|
box-shadow: 0 2px 8px rgba(11, 88, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item.no-skew .item-text {
|
||||||
|
transform: skew(20deg) !important;
|
||||||
|
/* 同步统一文字倾斜角度 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateP {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateP .label {
|
||||||
|
width: 165px;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(236, 244, 254, 1);
|
||||||
|
transform: skew(-25deg);
|
||||||
|
/* 与按钮倾斜角度统一(原30deg改为25deg,避免视觉错位) */
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 28px;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 补充:label文字抵消倾斜(原代码遗漏,导致文字倾斜) */
|
||||||
|
.dateP .label-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(25deg);
|
||||||
|
/* 与label倾斜角度相反,确保文字正立 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-right: 4px;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-time {
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 24px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screen-btn {
|
||||||
|
width: 26px;
|
||||||
|
margin-left: 300px;
|
||||||
|
color: #00fff0;
|
||||||
|
font-size: 26px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 日期选择器样式保持不变 */
|
||||||
|
::v-deep .custom-date-picker {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
width: 165px !important;
|
||||||
|
height: 28px !important;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 !important;
|
||||||
|
|
||||||
|
/* 1. 调整输入框文字:确保行高与输入框高度一致,垂直居中 */
|
||||||
|
.el-input__inner {
|
||||||
|
height: 28px !important;
|
||||||
|
width: 165px !important;
|
||||||
|
text-align: center;
|
||||||
|
padding-left: 15px !important;
|
||||||
|
padding-right: 32px !important;
|
||||||
|
/* 给图标留空间,避免文字被遮挡 */
|
||||||
|
font-size: 14px !important;
|
||||||
|
line-height: 28px !important;
|
||||||
|
/* 行高=输入框高度,文字垂直居中 */
|
||||||
|
color: rgba(237, 245, 253, 1) !important;
|
||||||
|
vertical-align: middle !important;
|
||||||
|
/* 强制文字垂直对齐 */
|
||||||
|
clip-path: polygon(18px 0, 100% 0, 100% 100%, 0 100%);
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
background-color: rgba(11, 88, 255, 1) !important;
|
||||||
|
border-left: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. 调整图标容器:让图标与文字在同一水平线上 */
|
||||||
|
.el-input__prefix {
|
||||||
|
left: auto !important;
|
||||||
|
right: 8px !important;
|
||||||
|
top: 50% !important;
|
||||||
|
/* 从40%改为50%,基于输入框垂直居中 */
|
||||||
|
transform: translateY(-50%) !important;
|
||||||
|
/* 向上偏移自身50%,精准居中 */
|
||||||
|
display: inline-flex !important;
|
||||||
|
/* 让容器内图标垂直居中 */
|
||||||
|
align-items: center !important;
|
||||||
|
/* 图标在容器内垂直居中 */
|
||||||
|
height: 28px !important;
|
||||||
|
/* 容器高度=输入框高度,避免偏移 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. 调整图标本身:确保图标大小和对齐方式 */
|
||||||
|
.el-input__icon {
|
||||||
|
color: #ffffff !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
line-height: 28px !important;
|
||||||
|
/* 图标行高=输入框高度,与文字对齐 */
|
||||||
|
vertical-align: middle !important;
|
||||||
|
/* 强制图标垂直对齐 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. 图标伪类:确保颜色和对齐继承 */
|
||||||
|
.el-icon-date::before {
|
||||||
|
color: #ffffff !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
line-height: inherit !important;
|
||||||
|
/* 继承父级行高,避免错位 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
282
src/views/home/depreciationAnalysisComponents/container.vue
Normal file
282
src/views/home/depreciationAnalysisComponents/container.vue
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
<template>
|
||||||
|
<div class="cockpitContainer" :class="['cockpitContainer__' + size]">
|
||||||
|
<div class="content-top" :class="['content-top__' + topSize]">
|
||||||
|
<!-- 使用 flex 容器包裹图标和文字,实现垂直居中 -->
|
||||||
|
<div class="title-wrapper">
|
||||||
|
<svg-icon class="title-icon" :icon-class="icon" />
|
||||||
|
<span class="title-text">
|
||||||
|
{{ name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cockpitContainer-body">
|
||||||
|
<slot>
|
||||||
|
<div class="test-body">something test....</div>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Container',
|
||||||
|
components: {},
|
||||||
|
// eslint-disable-next-line vue/require-prop-types
|
||||||
|
props: ['name', 'size', 'icon', 'topSize'],
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.cockpitContainer {
|
||||||
|
display: inline-block;
|
||||||
|
// width: 100%;
|
||||||
|
// height: 100%;
|
||||||
|
padding: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.content-top {
|
||||||
|
height: 60px;
|
||||||
|
|
||||||
|
.title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 10px;
|
||||||
|
/* 垂直居中关键属性 */
|
||||||
|
height: 100%;
|
||||||
|
/* 继承父容器高度,确保垂直居中范围 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
font-size: 30px;
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
/* 图标和文字之间的间距 */
|
||||||
|
flex-shrink: 0;
|
||||||
|
/* 防止图标被压缩 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 移除固定行高,避免影响垂直对齐
|
||||||
|
// line-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// width: 547px;
|
||||||
|
// background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
&__basic {
|
||||||
|
// width: 547px;
|
||||||
|
background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__middle {
|
||||||
|
background: url(../../../assets/img/topTileMiddle.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__large {
|
||||||
|
background: url(../../../assets/img/topTitleLargeBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__KFAPTopTitle {
|
||||||
|
background: url(../../../assets/img/KFAPTopTitle.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiTopTitleBasic {
|
||||||
|
background: url(../../../assets/img/psiTopTitleBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__rawTopTitleLarge {
|
||||||
|
background: url(../../../assets/img/rawTopTitleLarge.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__topBasic {
|
||||||
|
background: url(../../../assets/img/top-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bottomBasic {
|
||||||
|
background: url(../../../assets/img/bottom-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingBasic {
|
||||||
|
background: url(../../../assets/img/operating-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingLarge {
|
||||||
|
background: url(../../../assets/img/operating-large.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__profitTopBasic {
|
||||||
|
background: url(../../../assets/img/profitTopBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__profitMiddleBasic {
|
||||||
|
background: url(../../../assets/img/profitMiddleBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiBasicBg {
|
||||||
|
background: url(../../../assets/img/psiBasicBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiMiddleBg {
|
||||||
|
background: url(../../../assets/img/psiMiddleBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingRevenueBg {
|
||||||
|
background: url(../../../assets/img/operatingRevenueBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__costBasicBg {
|
||||||
|
background: url(../../../assets/img/costBasicBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__rawTopBg {
|
||||||
|
background: url(../../../assets/img/rawTopBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__opLargeBg {
|
||||||
|
background: url(../../../assets/img/opLargeBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// &__left {
|
||||||
|
// background: url(../../../../../../../assets/img/left.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__energyConsumption {
|
||||||
|
// background: url(../../../../../../../assets/img/energyConsumption.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__left2 {
|
||||||
|
// background: url(../../assets/left_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__left3 {
|
||||||
|
// background: url(../../assets/left_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__mid2 {
|
||||||
|
// background: url(../../assets/mid_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__mid3 {
|
||||||
|
// background: url(../../assets/mid_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right1 {
|
||||||
|
// background: url(../../assets/right_1.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right2 {
|
||||||
|
// background: url(../../assets/right_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right3 {
|
||||||
|
// background: url(../../assets/right_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekRight2 {
|
||||||
|
// background: url(../../assets/week_right_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekMidTop {
|
||||||
|
// background: url(../../assets/week-mid-top.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekMidMid {
|
||||||
|
// background: url(../../assets/week-mid-mid.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: ' ';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
// background: inherit;
|
||||||
|
/* 设置模糊,不用 filter */
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-body {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
298
src/views/home/depreciationAnalysisComponents/dataTrend.vue
Normal file
298
src/views/home/depreciationAnalysisComponents/dataTrend.vue
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="数据趋势" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="right" style="
|
||||||
|
height: 191px;
|
||||||
|
display: flex;
|
||||||
|
width: 1595px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<dataTrendBar @changeItem="handleChange" :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import dataTrendBar from "./dataTrendBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, dataTrendBar },
|
||||||
|
props: {
|
||||||
|
trend: {
|
||||||
|
type: Array,
|
||||||
|
// 默认值与实际数据结构一致(12个月)
|
||||||
|
default: () => [
|
||||||
|
// { title: "2025年01月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年02月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年03月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年04月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年05月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年06月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年07月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年08月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年09月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年10月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年11月", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
// { title: "2025年12月", budget: 0, real: 0, rate: 0, diff: 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: {
|
||||||
|
months: [], // 月份数组(2025年01月 - 2025年12月)
|
||||||
|
rates: [], // 每月完成率(百分比)
|
||||||
|
reals: [], // 每月实际值
|
||||||
|
budgets: [],// 每月预算值
|
||||||
|
diffs: [], // 每月差值
|
||||||
|
flags: [] // 每月达标标识(≥100 → 1,<100 → 0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
trend: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.processTrendData(newVal);
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.processTrendData(this.trend);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleChange(value) {
|
||||||
|
this.$emit("handleChange", value);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 处理趋势数据(适配12个月的数组结构)
|
||||||
|
* @param {Array} trendData - 原始趋势数组(12个月)
|
||||||
|
*/
|
||||||
|
processTrendData(trendData) {
|
||||||
|
// 数据兜底:确保是数组且长度为12
|
||||||
|
const validTrend = Array.isArray(trendData)
|
||||||
|
? trendData
|
||||||
|
: []
|
||||||
|
|
||||||
|
// 初始化空数组
|
||||||
|
const months = [];
|
||||||
|
const rates = [];
|
||||||
|
const reals = [];
|
||||||
|
const budgets = [];
|
||||||
|
const diffs = [];
|
||||||
|
const flags = [];
|
||||||
|
|
||||||
|
// 遍历12个月数据
|
||||||
|
validTrend.forEach(item => {
|
||||||
|
// 基础数据提取(兜底处理)
|
||||||
|
const month = item.title ?? '';
|
||||||
|
const budget = Number(item.budget) || 0;
|
||||||
|
const real = Number(item.real) || 0;
|
||||||
|
const rate = Number(item.rate) || 0;
|
||||||
|
const diff = Number(item.diff) || 0;
|
||||||
|
|
||||||
|
// 计算达标标识(≥100 → 1,<100 → 0)
|
||||||
|
const flag = this.getRateFlag(rate, real, budget);
|
||||||
|
|
||||||
|
// 填充数组
|
||||||
|
months.push(month);
|
||||||
|
rates.push(rate); // 转为百分比并取整
|
||||||
|
reals.push(real);
|
||||||
|
budgets.push(budget);
|
||||||
|
diffs.push(diff);
|
||||||
|
flags.push(flag);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新chartData(响应式)
|
||||||
|
this.chartData = {
|
||||||
|
months,
|
||||||
|
rates,
|
||||||
|
reals,
|
||||||
|
budgets,
|
||||||
|
diffs,
|
||||||
|
flags
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('处理后的趋势数据:', this.chartData);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算达标标识
|
||||||
|
* @param {Number} rate - 完成率(原始值,如1.2 → 120%)
|
||||||
|
* @returns {Number} 1: 达标(≥100%),0: 未达标(<100%)
|
||||||
|
*/
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 滚动容器样式 */
|
||||||
|
.scroll-container {
|
||||||
|
max-height: 210px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 设备项样式优化 */
|
||||||
|
.proBarInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px 27px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
width: 21px;
|
||||||
|
height: 23px;
|
||||||
|
background: rgba(0, 106, 205, 0.22);
|
||||||
|
backdrop-filter: blur(1.5px);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #68b5ff;
|
||||||
|
line-height: 23px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eq-name {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eqStatus {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitLine {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
border: 1px solid #adadad;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #00ffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoLeft {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoRight {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarWrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLine {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #acacac 100%);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLineTop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg,
|
||||||
|
rgba(53, 223, 247, 0) 0%,
|
||||||
|
rgba(54, 220, 246, 0.92) 92%,
|
||||||
|
#36f6e5 100%,
|
||||||
|
#37acf5 100%);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图表相关样式 */
|
||||||
|
.chartImgBottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 45px;
|
||||||
|
left: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
left: 57px;
|
||||||
|
bottom: 42px;
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #00e8ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 全局 tooltip 样式 */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
477
src/views/home/depreciationAnalysisComponents/dataTrendBar.vue
Normal file
477
src/views/home/depreciationAnalysisComponents/dataTrendBar.vue
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<!-- 新增行容器:包裹“各基地情况”和barTop -->
|
||||||
|
<div class="header-row">
|
||||||
|
<div class="barTop">
|
||||||
|
<!-- 关键:新增右侧容器,包裹图例和按钮组,实现整体靠右 -->
|
||||||
|
<div class="right-container">
|
||||||
|
<div class="legend">
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon line yield"></span>
|
||||||
|
完成率
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square target"></span>
|
||||||
|
预算
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square achieved"></span>
|
||||||
|
实际·达标
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square unachieved"></span>
|
||||||
|
实际·未达标
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<div class="item-button category-btn">
|
||||||
|
<span class="item-text">类目选择</span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-container">
|
||||||
|
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||||
|
<span class="item-text profit-text">{{ selectedProfit || '请选择' }}</span>
|
||||||
|
<span class="dropdown-arrow" :class="{ 'rotate': isDropdownShow }"></span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-options" v-if="isDropdownShow">
|
||||||
|
<div class="dropdown-option" v-for="(item, index) in profitOptions" :key="index"
|
||||||
|
@click.stop="selectProfit(item)">
|
||||||
|
{{ item }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||||
|
<operatingLineBar :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSale.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
isDropdownShow: false,
|
||||||
|
selectedProfit: '营业收入', // 选中的名称,初始为null
|
||||||
|
profitOptions: [
|
||||||
|
'营业收入',
|
||||||
|
'单价',
|
||||||
|
'销量',
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// profitOptions() {
|
||||||
|
// return this.categoryData.map(item => item.name) || [];
|
||||||
|
// },
|
||||||
|
currentDataSource() {
|
||||||
|
console.log('yyyy', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
console.log('this.chartData', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData.months
|
||||||
|
},
|
||||||
|
// 根据按钮切换生成对应的 chartData
|
||||||
|
chartD() {
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
console.log(this.currentDataSource, 'currentDataSource');
|
||||||
|
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
// 1. 完成率(折线图)
|
||||||
|
{
|
||||||
|
name: '完成率',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1, // 绑定右侧Y轴(需在子组件启用配置)
|
||||||
|
lineStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, .5)',
|
||||||
|
width: 2
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderColor: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
radius: 4
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.2,
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||||
|
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: data.rates, // 完成率(%)
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 2. 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||||
|
barWidth: 14,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.budgets // 目标销量(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图,含达标状态)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 14,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
// 固定label尺寸:68px×20px
|
||||||
|
width: 68,
|
||||||
|
height: 20,
|
||||||
|
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||||
|
formatter: (params) => {
|
||||||
|
const diff = data.diffs || [];
|
||||||
|
const flags = data.flags || [];
|
||||||
|
const currentDiff = diff[params.dataIndex] || 0;
|
||||||
|
const currentFlag = flags[params.dataIndex] || 0;
|
||||||
|
|
||||||
|
const prefix = currentFlag === 1 ? '+' : '-';
|
||||||
|
|
||||||
|
// 根据标志位选择不同的样式类
|
||||||
|
if (currentFlag === 1) {
|
||||||
|
// 达标 - 使用 rate-achieved 样式
|
||||||
|
return `{achieved|${currentDiff}}{text|差值}`;
|
||||||
|
} else {
|
||||||
|
// 未达标 - 使用 rate-unachieved 样式
|
||||||
|
return `{unachieved|${currentDiff}}{text|差值}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||||
|
{ offset: 0.2, color: '#ffffff' },
|
||||||
|
{ offset: 1, color: '#ffffff' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
shadowColor: 'rgba(191,203,215,0.5)',
|
||||||
|
shadowBlur: 2,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowOffsetY: 2,
|
||||||
|
borderRadius: 4,
|
||||||
|
borderColor: '#BFCBD577',
|
||||||
|
borderWidth: 0,
|
||||||
|
lineHeight: 20,
|
||||||
|
rich: {
|
||||||
|
text: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 10, 5, 0],
|
||||||
|
align: 'center',
|
||||||
|
color: '#464646',
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
},
|
||||||
|
achieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#76DABE', // 与达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
},
|
||||||
|
// 未达标样式
|
||||||
|
unachieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||||
|
const safeFlag = data.flags;
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals // 实际销量(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 根据按钮状态返回对应数据
|
||||||
|
return salesData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectProfit(item) {
|
||||||
|
this.selectedProfit = item;
|
||||||
|
this.isDropdownShow = false;
|
||||||
|
this.$emit("changeItem", item);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
// 新增:头部行容器,实现一行排列
|
||||||
|
.header-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end; // 左右两端对齐
|
||||||
|
align-items: center; // 垂直居中
|
||||||
|
// width: 100%;
|
||||||
|
margin-bottom: 8px; // 与下方图表区保留间距(可根据需求调整)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 各基地情况标题样式
|
||||||
|
.base-title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0 0 0 16px; // 保留原有内边距
|
||||||
|
white-space: nowrap; // 防止文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
// 移除原有flex和justify-content,由header-row控制
|
||||||
|
width: auto; // 自适应宽度
|
||||||
|
// 保留原有align-items,确保内部元素垂直居中
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; // 图例和按钮组垂直居中
|
||||||
|
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||||
|
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 图例:在右侧容器内横向排列
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px; // 图例项之间间距,避免重叠
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap; // 防止图例文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图例颜色
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.dropdown-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-btn {
|
||||||
|
width: 75px;
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-bottom-left-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-btn {
|
||||||
|
width: 123px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 18px 0 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #3071ff;
|
||||||
|
color: rgba(249, 252, 255, .8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-text {
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 6px solid currentColor;
|
||||||
|
border-top: 4px solid transparent;
|
||||||
|
border-bottom: 4px solid transparent;
|
||||||
|
border-right: 4px solid transparent;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
|
||||||
|
&.rotate {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-options {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
right: 0;
|
||||||
|
margin-top: 2px;
|
||||||
|
width: 123px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.dropdown-option {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
color: #3071ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="chartBox" style="width: 100%; height: 108px; position: relative;">
|
||||||
|
<div :id="id" style="width: 100%; height:100%;"></div>
|
||||||
|
<div class="bottomTip">
|
||||||
|
<div class="precent">
|
||||||
|
<span class="precentNum">{{ detailData.rate || 0 }}% </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'EnergyConsumption',
|
||||||
|
// components: { Container },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
detailData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
// electricComu: 0,
|
||||||
|
// steamComu: 20, // 调整为符合max范围的数值(0-8)
|
||||||
|
// // electricity: [120, 150, 130, 180, 160, 200, 190],
|
||||||
|
// // steam: [80, 95, 85, 110, 100, 120, 115],
|
||||||
|
// // dates: ['1日', '2日', '3日', '4日', '5日', '6日', '7日']
|
||||||
|
})
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
default: () => ('monthG')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// electricityChart: null,
|
||||||
|
// steamChart: null,
|
||||||
|
// specialTicks: [2, 4, 6, 8], // 统一的刻度显示
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
detailData: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true, // 初始化时立即执行
|
||||||
|
handler() {
|
||||||
|
this.updateGauges()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initGauges()
|
||||||
|
// window.addEventListener('resize', this.handleResize)
|
||||||
|
this.observeContainerResize()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
observeContainerResize() {
|
||||||
|
// 修复:获取正确的容器(组件内的.gauge-container)
|
||||||
|
const container = this.$el.querySelector('.gauge-container')
|
||||||
|
if (container && window.ResizeObserver) {
|
||||||
|
this.resizeObserver = new ResizeObserver(entries => {
|
||||||
|
if (this.electricityChart) {
|
||||||
|
this.electricityChart.resize() // 直接触发resize,无需防抖
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.resizeObserver.observe(container)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initGauges() {
|
||||||
|
// console.log('this.id',this.id);
|
||||||
|
|
||||||
|
// 初始化电气图表实例
|
||||||
|
const electricityDom = document.getElementById(this.id)
|
||||||
|
if (electricityDom) {
|
||||||
|
// 修复:正确创建并存储图表实例
|
||||||
|
this.electricityChart = echarts.init(electricityDom)
|
||||||
|
// 首次更新数据
|
||||||
|
this.updateGauges()
|
||||||
|
}
|
||||||
|
// 蒸汽图表若未使用,可注释/删除
|
||||||
|
// const steamDom = document.getElementById('steamGauge')
|
||||||
|
// if (steamDom) {
|
||||||
|
// this.steamChart = echarts.init(steamDom)
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
updateGauges() {
|
||||||
|
// 修复:先判断实例是否存在,再更新配置
|
||||||
|
if (!this.electricityChart) return
|
||||||
|
|
||||||
|
// 修复:兜底获取rate值,确保数值有效
|
||||||
|
const rate = Number(this.detailData?.rate) || 0
|
||||||
|
console.log('当前rate值:', rate); // 调试:确认rate值正确
|
||||||
|
|
||||||
|
// 关键:第二个参数传true,清空原有配置,强制更新
|
||||||
|
this.electricityChart.setOption(this.getElectricityGaugeOption(rate), true)
|
||||||
|
},
|
||||||
|
// 用电量仪表盘配置(保留原有样式,优化数值范围)
|
||||||
|
getElectricityGaugeOption(value) {
|
||||||
|
const electricityGradient = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: '#0B58FF' },
|
||||||
|
{ offset: 1, color: '#32FFCD' }
|
||||||
|
])
|
||||||
|
|
||||||
|
return {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '月度',
|
||||||
|
type: 'gauge',
|
||||||
|
radius: '95', // 修复:添加%,避免数值错误
|
||||||
|
center: ['50%', '90%'],
|
||||||
|
startAngle: 180,
|
||||||
|
endAngle: 0,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
splitNumber: 4,
|
||||||
|
label: { show: false },
|
||||||
|
progress: {
|
||||||
|
show: true,
|
||||||
|
overlap: false,
|
||||||
|
roundCap: true,
|
||||||
|
clip: false,
|
||||||
|
width: 14,
|
||||||
|
itemStyle: { color: electricityGradient }
|
||||||
|
},
|
||||||
|
pointer: {
|
||||||
|
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
|
||||||
|
length: '75%',
|
||||||
|
width: 16,
|
||||||
|
itemStyle: { color: '#288AFF' },
|
||||||
|
offsetCenter: [0, '10%']
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
roundCap: true,
|
||||||
|
lineStyle: { width: 12, color: [[1, '#E6EBF7']] }
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
length: 10,
|
||||||
|
lineStyle: { width: 5, color: '#D6DAE5' },
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
splitNumber: 2,
|
||||||
|
length: 6,
|
||||||
|
lineStyle: { width: 2, color: '#D6DAE5' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
detail: { show: false },
|
||||||
|
data: [{ value: value, unit: '' }] // 确保数值正确传入
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 未使用的蒸汽仪表盘可注释/删除
|
||||||
|
// getSteamGaugeOption(value) { ... }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
.chartBox {
|
||||||
|
.bottomTip {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.precent {
|
||||||
|
line-height: 3px;
|
||||||
|
|
||||||
|
&::after,
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 54px;
|
||||||
|
height: 5px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
margin-right: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用电量线条颜色
|
||||||
|
.precent::before {
|
||||||
|
|
||||||
|
background: linear-gradient(90deg, rgba(40, 138, 255, 0) 0%, rgba(12, 125, 254, 0.4) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ::after:从 透明 到 rgba(12, 125, 254, 0.4)(90度渐变,左到右) */
|
||||||
|
.precent::after {
|
||||||
|
background: linear-gradient(90deg, rgba(12, 125, 254, 0.4) 0%, rgba(40, 138, 255, 0) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蒸汽线条颜色
|
||||||
|
// .steam-precent::after,
|
||||||
|
// .steam-precent::before {
|
||||||
|
// background: linear-gradient(90deg, rgba(11, 168, 255, 0.26) 0%, rgba(54, 239, 230, 1) 100%);
|
||||||
|
// }
|
||||||
|
|
||||||
|
.precentNum {
|
||||||
|
display: inline-block;
|
||||||
|
// width: 52px;
|
||||||
|
height: 22px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蒸汽数字颜色
|
||||||
|
.steam-num {
|
||||||
|
color: rgba(54, 239, 230, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||||
|
<div class="dashboard">
|
||||||
|
<div class="title">
|
||||||
|
{{ month }}月完成率
|
||||||
|
</div>
|
||||||
|
<div class="number">
|
||||||
|
<div class="yield">
|
||||||
|
{{ monthData?.rate || 0 }}%
|
||||||
|
</div>
|
||||||
|
<div class="mom">
|
||||||
|
环比{{ monthData?.momRate }}%
|
||||||
|
<img v-if="monthData?.momRate >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="">
|
||||||
|
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="electricityGauge">
|
||||||
|
<electricityGauge :detailData="monthData" id="month"></electricityGauge>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
<div class="line" style="padding: 0px;">
|
||||||
|
<verticalBarChart :detailData="monthData">
|
||||||
|
</verticalBarChart>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import electricityGauge from './electricityGauge.vue'
|
||||||
|
import verticalBarChart from './verticalBarChart.vue'
|
||||||
|
|
||||||
|
// import * as echarts from 'echarts'
|
||||||
|
// import rawItem from './raw-Item.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, electricityGauge, verticalBarChart },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
monthData: { // 接收父组件传递的设备数据数组
|
||||||
|
type: Object,
|
||||||
|
default: () => {} // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
title: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
month: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// itemData: {
|
||||||
|
// handler(newValue, oldValue) {
|
||||||
|
// // this.updateChart()
|
||||||
|
// },
|
||||||
|
// deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
// computed: {
|
||||||
|
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||||
|
// sortedItemData() {
|
||||||
|
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||||
|
// const totalCostItems = this.itemData.filter(item =>
|
||||||
|
// item.name && item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 过滤出不包含“总成本”的项
|
||||||
|
// const otherItems = this.itemData.filter(item =>
|
||||||
|
// !item.name || !item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 合并:总成本项在前,其他项在后
|
||||||
|
// return [...totalCostItems, ...otherItems];
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
mounted() {
|
||||||
|
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||||
|
// this.$nextTick(() => this.updateChart())
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||||
|
.scroll-container {
|
||||||
|
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||||
|
max-height: 210px;
|
||||||
|
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||||
|
overflow-y: auto;
|
||||||
|
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||||
|
/* Chrome/Safari */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* IE/Edge */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 264px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
// width: 190px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 46px;
|
||||||
|
color: #0B58FF;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
width: 500px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .leftTitle {
|
||||||
|
// .item {
|
||||||
|
// width: 67px;
|
||||||
|
// height: 180px;
|
||||||
|
// padding: 37px 23px;
|
||||||
|
// background: #F9FCFF;
|
||||||
|
// font-family: PingFangSC, PingFang SC;
|
||||||
|
// font-weight: 400;
|
||||||
|
// font-size: 18px;
|
||||||
|
// color: #000000;
|
||||||
|
// line-height: 25px;
|
||||||
|
// letter-spacing: 1px;
|
||||||
|
// // text-align: left;
|
||||||
|
// font-style: normal;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- <style>
|
||||||
|
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style> -->
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px; width: 100%;">
|
||||||
|
<!-- 销量模块(直接传递整合了flag的salesData) -->
|
||||||
|
<div class="dashboard left" @click="handleDashboardClick('/salesVolumeAnalysis/salesVolumeAnalysisBase')">
|
||||||
|
<div style='position: relative;'>
|
||||||
|
<div class="title">
|
||||||
|
销量·万㎡
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{monthAnalysis[0].rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:monthAnalysis[0].flags>0?'#30B590':'#FF9423'}" >{{monthAnalysis[0].diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-wrap">
|
||||||
|
<operatingSingleBar :detailData="salesData"></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 单价模块(直接传递整合了flag的unitPriceData) -->
|
||||||
|
<div class="dashboard right" @click="handleDashboardClick('/unitPriceAnalysis/unitPriceAnalysisBase')">
|
||||||
|
<div style='position: relative;'>
|
||||||
|
<div class="title">
|
||||||
|
单价·元/㎡
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{monthAnalysis[1].rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:monthAnalysis[1].flags>0?'#30B590':'#FF9423'}" >{{monthAnalysis[1].diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-wrap">
|
||||||
|
<operatingSingleBar :detailData="unitPriceData"></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import operatingSingleBar from './operatingSingleBar.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, operatingSingleBar },
|
||||||
|
props: {
|
||||||
|
monthAnalysis: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [
|
||||||
|
{ title: "销量", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
{ title: "单价", budget: 0, real: 0, rate: 0, diff: 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
factory: {
|
||||||
|
type: [String,Number],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
month: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
// 初始化数据包含flag字段
|
||||||
|
salesData: { title: "销量", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 },
|
||||||
|
unitPriceData: { title: "单价", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
monthAnalysis: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.updateChart(newVal)
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateChart(this.monthAnalysis)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleDashboardClick(path) {
|
||||||
|
this.$router.push({
|
||||||
|
path: path,
|
||||||
|
query: {
|
||||||
|
factory: this.$route.query.factory ? this.$route.query.factory : this.factory,
|
||||||
|
dateData: this.dateData
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 判断flag的核心方法
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateChart(data) {
|
||||||
|
// 数据兜底
|
||||||
|
const salesItem = Array.isArray(data) && data[0] ? data[0] : { title: "销量", budget: 0, real: 0, rate: 0, diff: 0 };
|
||||||
|
const unitPriceItem = Array.isArray(data) && data[1] ? data[1] : { title: "单价", budget: 0, real: 0, rate: 0, diff: 0 };
|
||||||
|
|
||||||
|
// 核心修改:将flag整合到数据对象中,无需单独定义salesFlag/unitPriceFlag
|
||||||
|
this.salesData = {
|
||||||
|
...salesItem, // 合并原有字段
|
||||||
|
flag: this.getRateFlag(salesItem.rate, salesItem.real, salesItem.budget) // 新增flag字段
|
||||||
|
};
|
||||||
|
|
||||||
|
this.unitPriceData = {
|
||||||
|
...unitPriceItem, // 合并原有字段
|
||||||
|
flag: this.getRateFlag(unitPriceItem.rate, unitPriceItem.real, unitPriceItem.budget) // 新增flag字段
|
||||||
|
};
|
||||||
|
|
||||||
|
// 调试:确认整合后的数据
|
||||||
|
console.log('整合flag后的销量数据:', this.salesData);
|
||||||
|
console.log('整合flag后的单价数据:', this.unitPriceData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
.scroll-container {
|
||||||
|
max-height: 210px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topItem-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 300px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
margin: 0 4px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-wrap {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 30px;
|
||||||
|
height: 32px;
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
width: 97px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard.left {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard.right {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
503
src/views/home/depreciationAnalysisComponents/operatingBar.vue
Normal file
503
src/views/home/depreciationAnalysisComponents/operatingBar.vue
Normal file
@@ -0,0 +1,503 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<div class="header-row">
|
||||||
|
<div class="base-title">各基地情况</div>
|
||||||
|
<div class="barTop">
|
||||||
|
<div class="right-container">
|
||||||
|
<div class="legend">
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon line yield"></span>完成率
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square target"></span>预算
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square achieved"></span>实际·达标
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square unachieved"></span>实际·未达标
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<div class="item-button category-btn">
|
||||||
|
<span class="item-text">展示顺序</span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-container">
|
||||||
|
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||||
|
<span class="item-text profit-text">{{ selectedSort || '请选择' }}</span>
|
||||||
|
<span class="dropdown-arrow" :class="{ 'rotate': isDropdownShow }"></span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-options" v-if="isDropdownShow">
|
||||||
|
<div class="dropdown-option" v-for="(item, index) in profitOptions" :key="index"
|
||||||
|
@click.stop="selectProfit(item)">
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||||
|
<operatingLineBar :dateData="dateData" :chartData="chartD" style="height: 100%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSale.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData", 'dateData'],
|
||||||
|
emits: ['sort-change'], // 声明事件(Vue3 推荐)
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
isDropdownShow: false,
|
||||||
|
selectedSort: '实际值:高~低', // 选中的label
|
||||||
|
selectedSortValue: 1, // 选中的value,用于排序逻辑
|
||||||
|
profitOptions: [
|
||||||
|
{ label: '实际值:高~低', value: 1 },
|
||||||
|
{ label: '实际值:低~高', value: 2 },
|
||||||
|
{ label: '完成率:高~低', value: 3 },
|
||||||
|
{ label: '完成率:低~高', value: 4 },
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 排序后的数据源(核心:根据selectedSortValue重新排序)
|
||||||
|
currentDataSource() {
|
||||||
|
if (!this.chartData?.factory) return {};
|
||||||
|
|
||||||
|
// 深拷贝原始数据,避免修改原数据
|
||||||
|
const factory = JSON.parse(JSON.stringify(this.chartData.factory));
|
||||||
|
if (!factory.locations.length || !this.selectedSortValue) return factory;
|
||||||
|
|
||||||
|
// 构建带索引的数组,方便同步所有字段排序
|
||||||
|
const dataWithIndex = factory.locations.map((name, index) => ({
|
||||||
|
index,
|
||||||
|
name,
|
||||||
|
real: factory.reals[index],
|
||||||
|
target: factory.targets[index],
|
||||||
|
rate: factory.rates[index],
|
||||||
|
diff: factory.diff[index],
|
||||||
|
flag: factory.flags[index]
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 根据选中的排序规则排序
|
||||||
|
switch (this.selectedSortValue) {
|
||||||
|
case 1: // 实际值:高~低
|
||||||
|
dataWithIndex.sort((a, b) => b.real - a.real);
|
||||||
|
break;
|
||||||
|
case 2: // 实际值:低~高
|
||||||
|
dataWithIndex.sort((a, b) => a.real - b.real);
|
||||||
|
break;
|
||||||
|
case 3: // 预算值:高~低
|
||||||
|
dataWithIndex.sort((a, b) => b.rate - a.rate);
|
||||||
|
break;
|
||||||
|
case 4: // 预算值:低~高
|
||||||
|
dataWithIndex.sort((a, b) => a.rate - b.rate);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步更新所有数组
|
||||||
|
factory.locations = dataWithIndex.map(item => item.name);
|
||||||
|
factory.reals = dataWithIndex.map(item => item.real);
|
||||||
|
factory.targets = dataWithIndex.map(item => item.target);
|
||||||
|
factory.rates = dataWithIndex.map(item => item.rate);
|
||||||
|
factory.diff = dataWithIndex.map(item => item.diff);
|
||||||
|
factory.flags = dataWithIndex.map(item => item.flag);
|
||||||
|
|
||||||
|
return factory;
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
return this.currentDataSource.locations || [];
|
||||||
|
},
|
||||||
|
// 最终传递给图表的排序后数据
|
||||||
|
chartD() {
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
// 完成率(折线图)
|
||||||
|
{
|
||||||
|
name: '完成率',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
lineStyle: { color: 'rgba(40, 138, 255, .5)', width: 2 },
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderColor: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
radius: 4
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.2,
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||||
|
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: data.rates || [],
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 40,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.targets || []
|
||||||
|
},
|
||||||
|
// 实际(柱状图)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 40,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [32, 0],
|
||||||
|
width: 100,
|
||||||
|
height: 22,
|
||||||
|
formatter: (params) => {
|
||||||
|
const diff = data.diff || [];
|
||||||
|
const flags = data.flags || [];
|
||||||
|
const currentDiff = diff[params.dataIndex] || 0;
|
||||||
|
const currentFlag = flags[params.dataIndex] || 0;
|
||||||
|
|
||||||
|
const prefix = currentFlag === 1 ? '+' : '-';
|
||||||
|
|
||||||
|
// 根据标志位选择不同的样式类
|
||||||
|
if (currentFlag === 1) {
|
||||||
|
// 达标 - 使用 rate-achieved 样式
|
||||||
|
return `{achieved|${currentDiff}}{text|差值}`;
|
||||||
|
} else {
|
||||||
|
// 未达标 - 使用 rate-unachieved 样式
|
||||||
|
return `{unachieved|${currentDiff}}{text|差值}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||||
|
{ offset: 0.2, color: '#ffffff' },
|
||||||
|
{ offset: 1, color: '#ffffff' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
shadowColor: 'rgba(191,203,215,0.5)',
|
||||||
|
shadowBlur: 2,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowOffsetY: 2,
|
||||||
|
borderRadius: 4,
|
||||||
|
borderColor: '#BFCBD577',
|
||||||
|
borderWidth: 0,
|
||||||
|
lineHeight: 26,
|
||||||
|
rich: {
|
||||||
|
text: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 10, 5, 0],
|
||||||
|
align: 'center',
|
||||||
|
color: '#464646',
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
achieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#76DABE', // 与达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
// 未达标样式
|
||||||
|
unachieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 14,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const safeFlag = data.flags || [];
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals || []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
return salesData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectProfit(item) {
|
||||||
|
// 更新选中的label和value
|
||||||
|
this.selectedSort = item.label;
|
||||||
|
this.selectedSortValue = item.value;
|
||||||
|
this.isDropdownShow = false;
|
||||||
|
// 向父组件传递排序事件(可选,保持原有逻辑)
|
||||||
|
this.$emit('sort-change', item.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 监听父组件传入的chartData变化,重置选中状态(可选)
|
||||||
|
watch: {
|
||||||
|
'chartData.factory': {
|
||||||
|
handler() {
|
||||||
|
// 若需要切换数据源后重置排序,可取消注释
|
||||||
|
// this.selectedSort = null;
|
||||||
|
// this.selectedSortValue = null;
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.header-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0 0 0 16px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
width: auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
margin-right: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.dropdown-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
fontSize: 12px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-btn {
|
||||||
|
width: 75px;
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-bottom-left-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-btn {
|
||||||
|
width: 123px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 18px 0 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #3071ff;
|
||||||
|
color: rgba(249, 252, 255, .8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-text {
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 6px solid currentColor;
|
||||||
|
border-top: 4px solid transparent;
|
||||||
|
border-bottom: 4px solid transparent;
|
||||||
|
border-right: 4px solid transparent;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
|
||||||
|
&.rotate {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-options {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
right: 0;
|
||||||
|
margin-top: 2px;
|
||||||
|
width: 123px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.dropdown-option {
|
||||||
|
padding: 6px 12px;
|
||||||
|
fontSize: 12px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
color: #3071ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChipBottom" id="cockpitEffChipBottom" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
series: [],
|
||||||
|
allPlaceNames: []
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
validator: (value) => {
|
||||||
|
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChipBottom;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatter: (params) => {
|
||||||
|
let html = `${params[0].axisValue}<br/>`;
|
||||||
|
params.forEach(item => {
|
||||||
|
const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
);
|
||||||
|
html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 60,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
splitNumber: 4,
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: (value) => Math.ceil((value.max || 0) * 1.1),
|
||||||
|
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
scale:true,
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
series: [],
|
||||||
|
allPlaceNames: []
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
validator: (value) => {
|
||||||
|
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 60,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
// {
|
||||||
|
// type: 'value',
|
||||||
|
// nameTextStyle: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// align: 'left'
|
||||||
|
// },
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
// axisTick: { show: false },
|
||||||
|
// axisLabel: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// formatter: '{value}%'
|
||||||
|
// },
|
||||||
|
// splitLine: { show: false },
|
||||||
|
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
// splitNumber: 4
|
||||||
|
// }
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null, // 存储图表实例
|
||||||
|
resizeHandler: null, // 存储resize事件处理函数
|
||||||
|
// 核心:基地名称与序号的映射表(固定顺序)
|
||||||
|
baseNameToIndexMap: {
|
||||||
|
'宜兴': 7,
|
||||||
|
'漳州': 8,
|
||||||
|
'自贡': 3,
|
||||||
|
'桐城': 2,
|
||||||
|
'洛阳': 9,
|
||||||
|
'合肥': 5,
|
||||||
|
'宿迁': 6,
|
||||||
|
'秦皇岛': 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.initChart(); // 初始化图表(只执行一次)
|
||||||
|
this.updateChart(); // 更新图表数据
|
||||||
|
});
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
this.updateChart(); // 仅更新数据,不重新创建实例
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 组件销毁时清理资源
|
||||||
|
this.destroyChart();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 初始化图表(只在mounted中执行一次)
|
||||||
|
initChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只创建一次图表实例
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
// 绑定点击事件(只绑定一次,永久生效)
|
||||||
|
this.myChart.getZr().on('click', (params) => {
|
||||||
|
console.log('params', params);
|
||||||
|
|
||||||
|
// 提取点击的基地名称
|
||||||
|
// const itemName = params.name;
|
||||||
|
let itemName = undefined
|
||||||
|
// 根据映射表获取对应的序号(未匹配到则返回0或其他默认值)
|
||||||
|
let pointInPixel = [params.offsetX, params.offsetY];
|
||||||
|
if (this.myChart.containPixel('grid', pointInPixel)) {
|
||||||
|
let pointInGrid = this.myChart.convertFromPixel({
|
||||||
|
seriesIndex: 0
|
||||||
|
}, pointInPixel);
|
||||||
|
let xIndex = pointInGrid[0]; //索引
|
||||||
|
let handleIndex = Number(xIndex);
|
||||||
|
let seriesObj = this.myChart.getOption(); //图表object对象
|
||||||
|
var op = this.myChart.getOption();
|
||||||
|
//获得图表中点击的列
|
||||||
|
itemName = op.xAxis[0].data[handleIndex]; //获取点击的列名
|
||||||
|
console.log(itemName, 'monthmonthmonth');
|
||||||
|
console.log(handleIndex, seriesObj);
|
||||||
|
};
|
||||||
|
const baseIndex = this.baseNameToIndexMap[itemName] || 0;
|
||||||
|
|
||||||
|
console.log(`你点击了【${itemName}】(序号:${baseIndex})`);
|
||||||
|
if (itemName === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 路由跳转时携带序号(或名称+序号)
|
||||||
|
this.$router.push({
|
||||||
|
path: 'operatingRevenueBase',
|
||||||
|
query: { // 使用query传递参数(推荐),也可使用params
|
||||||
|
// baseName: itemName,
|
||||||
|
factory: baseIndex,
|
||||||
|
dateData: this.dateData
|
||||||
|
}
|
||||||
|
// 若仍需用base作为参数:
|
||||||
|
// base: itemName,
|
||||||
|
// params: { baseIndex: baseIndex }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定义resize处理函数(命名函数,方便移除)
|
||||||
|
this.resizeHandler = () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
};
|
||||||
|
// 绑定resize事件(只绑定一次)
|
||||||
|
window.addEventListener('resize', this.resizeHandler);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新图表数据(数据变化时执行)
|
||||||
|
updateChart() {
|
||||||
|
if (!this.myChart) {
|
||||||
|
return; // 实例未初始化则返回
|
||||||
|
}
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || [];
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 5,
|
||||||
|
right: 20,
|
||||||
|
left: 25,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0],
|
||||||
|
// 可选:X轴标签显示“序号+名称”(如“1 宜兴”)
|
||||||
|
// formatter: (value) => {
|
||||||
|
// const index = this.baseNameToIndexMap[value] || '';
|
||||||
|
// return index ? `${index} ${value}` : value;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries
|
||||||
|
};
|
||||||
|
|
||||||
|
// 只更新配置,不重新创建实例
|
||||||
|
this.myChart.setOption(option, true); // 第二个参数true表示清空原有配置,避免数据残留
|
||||||
|
},
|
||||||
|
|
||||||
|
// 销毁图表资源
|
||||||
|
destroyChart() {
|
||||||
|
// 移除resize事件
|
||||||
|
if (this.resizeHandler) {
|
||||||
|
window.removeEventListener('resize', this.resizeHandler);
|
||||||
|
}
|
||||||
|
// 销毁图表实例
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
this.myChart = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 380px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
console.log('chartData', this.chartData);
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
console.log('xData', xData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom:20,
|
||||||
|
right: 10,
|
||||||
|
left: 25,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
//
|
||||||
|
splitNumber: 4,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 200px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
refName: {
|
||||||
|
type: String,
|
||||||
|
default: () => 'cockpitEffChip',
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs[this.refName];
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
console.log('chartData', this.chartData);
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
console.log('xData', xData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
// axisPointer: {
|
||||||
|
// type: 'cross',
|
||||||
|
// label: {
|
||||||
|
// backgroundColor: '#6a7985'
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 20,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 5,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
// name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true, // 显示Y轴轴线(关键)
|
||||||
|
lineStyle: {
|
||||||
|
color: '#E5E6EB', // 轴线颜色(浅灰色,可自定义)
|
||||||
|
width: 1, // 轴线宽度
|
||||||
|
type: 'solid' // 实线(可选:dashed虚线、dotted点线)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 2,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
},
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,349 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="当月数据对比" icon="cockpitItemIcon" size="operatingLarge" topSize="large">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="left" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 348px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
">
|
||||||
|
集团情况
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{chartData.group.rate[0]}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.group.flags>0?'#30B590':'#FF9423'}" >{{chartData.group.diff[0]}}</span></span>
|
||||||
|
</div>
|
||||||
|
<operatingTopBar :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
<div class="right" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 1220px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<!-- <top-item /> -->
|
||||||
|
<operatingBar :dateData="dateData" :chartData="chartData" @sort-change="sortChange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import operatingBar from "./operatingBar.vue";
|
||||||
|
import operatingTopBar from "./operatingTopBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, operatingBar, operatingTopBar },
|
||||||
|
props: {
|
||||||
|
monthData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
group: {
|
||||||
|
rate: 0,
|
||||||
|
diff: 0,
|
||||||
|
real: 0,
|
||||||
|
target: 0
|
||||||
|
},
|
||||||
|
factory: []
|
||||||
|
}),
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: null, // 初始化 chartData 为 null
|
||||||
|
groupData: {}, // 集团数据
|
||||||
|
factoryData: [] // 工厂数据
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
monthData: {
|
||||||
|
handler() {
|
||||||
|
this.processChartData();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
sortChange(value) {
|
||||||
|
this.$emit('sort-change', value);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 判断rate对应的flag值(<1为0,>1为1)
|
||||||
|
* @param {number} rate 处理后的rate值(已*100)
|
||||||
|
* @returns {0|1} flag值
|
||||||
|
*/
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 核心处理函数:在所有数据都准备好后,才组装 chartData
|
||||||
|
*/
|
||||||
|
processChartData() {
|
||||||
|
// 1. 处理集团数据 - 提取各字段到对应数组
|
||||||
|
this.groupData = this.monthData.group || { rate: 0, diff: 0, real: 0, target: 0 };
|
||||||
|
|
||||||
|
// 集团各维度数据数组(单条数据,对应凯盛新能)
|
||||||
|
const groupTarget = [this.groupData.target]; // 预算值数组
|
||||||
|
const groupDiff = [this.groupData.diff]; // 差值数组
|
||||||
|
const groupReal = [this.groupData.real]; // 实际值数组
|
||||||
|
const groupRate = [this.groupData.rate]; // 完成率数组
|
||||||
|
// 新增:集团rate对应的flag
|
||||||
|
const groupFlag = [this.getRateFlag(groupRate[0], groupReal[0], groupTarget[0])];
|
||||||
|
|
||||||
|
console.log('集团数据数组:', {
|
||||||
|
groupTarget,
|
||||||
|
groupDiff,
|
||||||
|
groupReal,
|
||||||
|
groupRate,
|
||||||
|
groupFlag,
|
||||||
|
rawGroupData: this.groupData
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 处理工厂数据 - 提取每个工厂的对应字段到数组
|
||||||
|
this.factoryData = this.monthData.factory || [];
|
||||||
|
// 提取工厂名称数组
|
||||||
|
const factoryNames = this.factoryData.map(item => item.title || '');
|
||||||
|
// 提取工厂各维度数据数组
|
||||||
|
const factoryBudget = this.factoryData.map(item => item.budget || 0);
|
||||||
|
const factoryReal = this.factoryData.map(item => item.real || 0);
|
||||||
|
const factoryRate = this.factoryData.map(item => item.rate || 0);
|
||||||
|
const factoryDiff = this.factoryData.map(item => item.diff || 0);
|
||||||
|
// 新增:每个工厂rate对应的flag数组
|
||||||
|
const factoryFlags = this.factoryData.map(item => this.getRateFlag(item.rate, item.real, item.budget));
|
||||||
|
|
||||||
|
// 3. 组装最终的chartData(供子组件使用)
|
||||||
|
this.chartData = {
|
||||||
|
// 集团数据(对应凯盛新能)
|
||||||
|
group: {
|
||||||
|
locations: ['凯盛新能'], // 集团名称
|
||||||
|
targets: groupTarget, // 集团预算值数组
|
||||||
|
diff: groupDiff, // 集团差值数组
|
||||||
|
reals: groupReal, // 集团实际值数组
|
||||||
|
rate: groupRate, // 集团完成率数组
|
||||||
|
flags: groupFlag // 新增:集团rate对应的flag
|
||||||
|
},
|
||||||
|
// 工厂数据
|
||||||
|
factory: {
|
||||||
|
locations: factoryNames, // 工厂名称数组
|
||||||
|
targets: factoryBudget, // 工厂预算数组
|
||||||
|
reals: factoryReal, // 工厂实际值数组
|
||||||
|
rates: factoryRate, // 工厂完成率数组
|
||||||
|
diff: factoryDiff, // 工厂差值数组
|
||||||
|
flags: factoryFlags // 新增:工厂rate对应的flags数组
|
||||||
|
},
|
||||||
|
// 原始数据备份(方便后续使用)
|
||||||
|
rawData: {
|
||||||
|
group: this.groupData,
|
||||||
|
factory: this.factoryData
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('最终处理后的图表数据:', this.chartData);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||||
|
.scroll-container {
|
||||||
|
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||||
|
max-height: 210px;
|
||||||
|
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||||
|
overflow-y: auto;
|
||||||
|
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||||
|
/* Chrome/Safari */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* IE/Edge */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||||
|
.proBarInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px 27px;
|
||||||
|
/* 调整内边距,优化排版 */
|
||||||
|
margin-bottom: 10px;
|
||||||
|
/* 设备项之间的垂直间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 原有样式保留,优化细节 */
|
||||||
|
.proBarInfoEqInfo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
/* 垂直居中,避免序号/文字错位 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
width: 21px;
|
||||||
|
height: 23px;
|
||||||
|
background: rgba(0, 106, 205, 0.22);
|
||||||
|
backdrop-filter: blur(1.5px);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #68b5ff;
|
||||||
|
line-height: 23px;
|
||||||
|
/* 垂直居中文字 */
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eq-name {
|
||||||
|
margin-left: 8px;
|
||||||
|
/* 增加与序号的间距 */
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eqStatus {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitLine {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
border: 1px solid #adadad;
|
||||||
|
margin: 0 8px;
|
||||||
|
/* 优化分割线间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #00ffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoLeft {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 序号和设备名垂直居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoRight {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 状态/分割线/百分比垂直居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarWrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
/* 进度条与上方信息的间距 */
|
||||||
|
border-radius: 5px;
|
||||||
|
/* 进度条圆角,优化视觉 */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLine {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #acacac 100%);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLineTop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg,
|
||||||
|
rgba(53, 223, 247, 0) 0%,
|
||||||
|
rgba(54, 220, 246, 0.92) 92%,
|
||||||
|
#36f6e5 100%,
|
||||||
|
#37acf5 100%);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
/* 进度变化时添加过渡动画,更流畅 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图表相关样式保留 */
|
||||||
|
.chartImgBottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 45px;
|
||||||
|
left: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
left: 57px;
|
||||||
|
bottom: 42px;
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #00e8ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="累计数据对比" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="left" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 348px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
">
|
||||||
|
集团情况
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{chartData.group.rate[0]}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.group.flags>0?'#30B590':'#FF9423'}" >{{chartData.group.diff[0]}}</span></span>
|
||||||
|
</div>
|
||||||
|
<operatingTopBar :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
<div class="right" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 1220px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<!-- <top-item /> -->
|
||||||
|
<operatingBar :dateData="dateData" @sort-change="sortChange" :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import operatingBar from "./operatingBar.vue";
|
||||||
|
import operatingTopBar from "./operatingTopBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, operatingBar, operatingTopBar },
|
||||||
|
props: {
|
||||||
|
salesTrendMap: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
ytdData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: null, // 初始化 chartData 为 null
|
||||||
|
groupData: {}, // 集团数据
|
||||||
|
factoryData: [] // 工厂数据
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
ytdData: {
|
||||||
|
handler() {
|
||||||
|
this.processChartData();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
sortChange(value) {
|
||||||
|
this.$emit('sort-change', value);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 判断rate对应的flag值(<1为0,>1为1)
|
||||||
|
* @param {number} rate 处理后的rate值(已*100)
|
||||||
|
* @returns {0|1} flag值
|
||||||
|
*/
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心处理函数:在所有数据都准备好后,才组装 chartData
|
||||||
|
*/
|
||||||
|
processChartData() {
|
||||||
|
// 1. 处理集团数据 - 提取各字段到对应数组
|
||||||
|
this.groupData = this.ytdData.group || { rate: 0, diff: 0, real: 0, target: 0 };
|
||||||
|
|
||||||
|
// 集团各维度数据数组(单条数据,对应凯盛新能)
|
||||||
|
const groupTarget = [this.groupData.target]; // 预算值数组
|
||||||
|
const groupDiff = [this.groupData.diff]; // 差值数组
|
||||||
|
const groupReal = [this.groupData.real]; // 实际值数组
|
||||||
|
const groupRate = [this.groupData.rate]; // 完成率数组
|
||||||
|
// 新增:集团rate对应的flag
|
||||||
|
const groupFlag = [this.getRateFlag(groupRate[0], groupReal[0], groupTarget[0])];
|
||||||
|
|
||||||
|
console.log('集团数据数组:', {
|
||||||
|
groupTarget,
|
||||||
|
groupDiff,
|
||||||
|
groupReal,
|
||||||
|
groupRate,
|
||||||
|
groupFlag,
|
||||||
|
rawGroupData: this.groupData
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 处理工厂数据 - 提取每个工厂的对应字段到数组
|
||||||
|
this.factoryData = this.ytdData.factory || [];
|
||||||
|
// 提取工厂名称数组
|
||||||
|
const factoryNames = this.factoryData.map(item => item.title || '');
|
||||||
|
// 提取工厂各维度数据数组
|
||||||
|
const factoryBudget = this.factoryData.map(item => item.budget || 0);
|
||||||
|
const factoryReal = this.factoryData.map(item => item.real || 0);
|
||||||
|
const factoryRate = this.factoryData.map(item => item.rate || 0);
|
||||||
|
const factoryDiff = this.factoryData.map(item => item.diff || 0);
|
||||||
|
// 新增:每个工厂rate对应的flag数组
|
||||||
|
const factoryFlags = this.factoryData.map(item => this.getRateFlag(item.rate, item.real, item.budget));
|
||||||
|
|
||||||
|
// 3. 组装最终的chartData(供子组件使用)
|
||||||
|
this.chartData = {
|
||||||
|
// 集团数据(对应凯盛新能)
|
||||||
|
group: {
|
||||||
|
locations: ['凯盛新能'], // 集团名称
|
||||||
|
targets: groupTarget, // 集团预算值数组
|
||||||
|
diff: groupDiff, // 集团差值数组
|
||||||
|
reals: groupReal, // 集团实际值数组
|
||||||
|
rate: groupRate, // 集团完成率数组
|
||||||
|
flags: groupFlag // 新增:集团rate对应的flag
|
||||||
|
},
|
||||||
|
// 工厂数据
|
||||||
|
factory: {
|
||||||
|
locations: factoryNames, // 工厂名称数组
|
||||||
|
targets: factoryBudget, // 工厂预算数组
|
||||||
|
reals: factoryReal, // 工厂实际值数组
|
||||||
|
rates: factoryRate, // 工厂完成率数组
|
||||||
|
diff: factoryDiff, // 工厂差值数组
|
||||||
|
flags: factoryFlags // 新增:工厂rate对应的flags数组
|
||||||
|
},
|
||||||
|
// 原始数据备份(方便后续使用)
|
||||||
|
rawData: {
|
||||||
|
group: this.groupData,
|
||||||
|
factory: this.factoryData
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('最终处理后的图表数据:', this.chartData);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||||
|
.scroll-container {
|
||||||
|
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||||
|
max-height: 210px;
|
||||||
|
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||||
|
overflow-y: auto;
|
||||||
|
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||||
|
/* Chrome/Safari */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* IE/Edge */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 设备项样式优化:增加间距,避免拥挤 */
|
||||||
|
.proBarInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px 27px;
|
||||||
|
/* 调整内边距,优化排版 */
|
||||||
|
margin-bottom: 10px;
|
||||||
|
/* 设备项之间的垂直间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 原有样式保留,优化细节 */
|
||||||
|
.proBarInfoEqInfo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
/* 垂直居中,避免序号/文字错位 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
width: 21px;
|
||||||
|
height: 23px;
|
||||||
|
background: rgba(0, 106, 205, 0.22);
|
||||||
|
backdrop-filter: blur(1.5px);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #68b5ff;
|
||||||
|
line-height: 23px;
|
||||||
|
/* 垂直居中文字 */
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eq-name {
|
||||||
|
margin-left: 8px;
|
||||||
|
/* 增加与序号的间距 */
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eqStatus {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitLine {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
border: 1px solid #adadad;
|
||||||
|
margin: 0 8px;
|
||||||
|
/* 优化分割线间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #00ffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoLeft {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 序号和设备名垂直居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoRight {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 状态/分割线/百分比垂直居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarWrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
/* 进度条与上方信息的间距 */
|
||||||
|
border-radius: 5px;
|
||||||
|
/* 进度条圆角,优化视觉 */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLine {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #acacac 100%);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLineTop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(
|
||||||
|
65deg,
|
||||||
|
rgba(53, 223, 247, 0) 0%,
|
||||||
|
rgba(54, 220, 246, 0.92) 92%,
|
||||||
|
#36f6e5 100%,
|
||||||
|
#37acf5 100%
|
||||||
|
);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
/* 进度变化时添加过渡动画,更流畅 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图表相关样式保留 */
|
||||||
|
.chartImgBottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 45px;
|
||||||
|
left: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
left: 57px;
|
||||||
|
bottom: 42px;
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #00e8ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
<template>
|
||||||
|
<div class="lineBottom" style="height: 180px; width: 100%">
|
||||||
|
<operatingLineBarSaleSingle :refName=" 'totalOperating' " :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBarSaleSingle from './operatingLineBarSaleSingle.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBarSaleSingle },
|
||||||
|
props: ["detailData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
locations() {
|
||||||
|
return ['预算', '实际'];
|
||||||
|
},
|
||||||
|
chartD() {
|
||||||
|
// 背景图片路径(若不需要可注释)
|
||||||
|
// const bgImageUrl = require('@/assets/img/labelBg.png');
|
||||||
|
console.log('this.detailData', this.detailData);
|
||||||
|
const rate = this.detailData?.rate || 0
|
||||||
|
const diff = this.detailData?.diff || 0
|
||||||
|
console.log('diff', diff);
|
||||||
|
|
||||||
|
const seriesData = [
|
||||||
|
{
|
||||||
|
value: this.detailData?.budget || 0,
|
||||||
|
flag: 1, // 实际项:达标(绿色)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: this.detailData?.real || 0,
|
||||||
|
flag: this.detailData?.flag, // 实际项:达标(绿色)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
allPlaceNames: ['预算', '实际'],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 60,
|
||||||
|
barCategoryGap: '50%',
|
||||||
|
data: seriesData,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const currentFlag = params.data.flag || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
console.log('data', data);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFangSC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
margin-right: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
width: 283px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0b58ff;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 8px;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button.active {
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
background: #3071ff;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,337 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<div class="lineBottom" style="height: 320px; width: 100%">
|
||||||
|
<operatingLineBar :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSaleGroup.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentDataSource() {
|
||||||
|
console.log('yyyy', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData.group
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
console.log('this.chartData', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData.group.locations
|
||||||
|
},
|
||||||
|
// 根据按钮切换生成对应的 chartData
|
||||||
|
chartD() {
|
||||||
|
// 销量场景数据
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
const diff = this.currentDataSource.diff[0]
|
||||||
|
const rate = this.currentDataSource.rate[0]
|
||||||
|
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.targets // 目标销量(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图,含达标状态)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||||
|
const safeFlag = data.flags;
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals // 实际销量(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 毛利率场景数据
|
||||||
|
const grossProfitData = {
|
||||||
|
series: [
|
||||||
|
// 1. 完成率(折线图)
|
||||||
|
{
|
||||||
|
name: '完成率',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
lineStyle: { color: 'rgba(40, 138, 255, .5)', width: 2 },
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderColor: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
radius: 4
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.2,
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||||
|
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: [106.7, 96.9, 106.5, 106.1, 93.8, 105.9], // 毛利率完成率(%)
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 2. 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: [30, 32, 31, 33, 32, 34] // 目标毛利率(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const safeFlag = [1, 0, 1, 1, 0, 1]; // 达标状态
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: [32, 31, 33, 35, 30, 36] // 实际毛利率(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据按钮状态返回对应数据
|
||||||
|
return this.activeButton === 0 ? salesData : grossProfitData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end; // 标题左、右侧容器右,整体两端对齐
|
||||||
|
align-items: center; // 垂直居中,避免上下错位
|
||||||
|
gap: 16px; // 标题与右侧容器的最小间距,防止拥挤
|
||||||
|
width: 100%; // 确保占满父容器,实现两端对齐
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 标题固定在左侧,不挤压右侧空间
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; // 图例和按钮组垂直居中
|
||||||
|
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||||
|
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 图例:在右侧容器内横向排列
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px; // 图例项之间间距,避免重叠
|
||||||
|
align-items: center;
|
||||||
|
// 移除原margin-left,避免位置偏移
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap; // 防止图例文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图例颜色
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
width: 283px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
border-radius: 12px;
|
||||||
|
// 移除原margin-right,由右侧容器统一控制
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0b58ff;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 8px; // 确保文字间距生效
|
||||||
|
padding-left: 8px; // 抵消letter-spacing导致的文字左偏
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button.active {
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
background: #3071ff;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
202
src/views/home/depreciationAnalysisComponents/totalOverview.vue
Normal file
202
src/views/home/depreciationAnalysisComponents/totalOverview.vue
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||||
|
<div class="dashboard">
|
||||||
|
<div class="title">
|
||||||
|
累计完成率
|
||||||
|
</div>
|
||||||
|
<div class="number">
|
||||||
|
<div class="yield">
|
||||||
|
{{ ytdData?.rate || 0}}%
|
||||||
|
</div>
|
||||||
|
<div class="mom">
|
||||||
|
同比{{ ytdData?.yoyRate || 0}}%
|
||||||
|
<img v-if="ytdData?.yoyRate >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="">
|
||||||
|
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="electricityGauge">
|
||||||
|
<electricityGauge :id=" 'totalG' " :detailData="ytdData" id="totalGauge"></electricityGauge>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
<div class="line" style="padding: 0px;">
|
||||||
|
<verticalBarChart :refName=" 'totalVerticalBarChart' " :detailData="ytdData">
|
||||||
|
</verticalBarChart>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import electricityGauge from './electricityGauge.vue'
|
||||||
|
import verticalBarChart from './verticalBarChart.vue'
|
||||||
|
|
||||||
|
// import * as echarts from 'echarts'
|
||||||
|
// import rawItem from './raw-Item.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, electricityGauge, verticalBarChart },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
ytdData: { // 接收父组件传递的设备数据数组
|
||||||
|
type: Object,
|
||||||
|
default: () => {} // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
title: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
month: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
itemData: {
|
||||||
|
handler(newValue, oldValue) {
|
||||||
|
// this.updateChart()
|
||||||
|
},
|
||||||
|
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// computed: {
|
||||||
|
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||||
|
// sortedItemData() {
|
||||||
|
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||||
|
// const totalCostItems = this.itemData.filter(item =>
|
||||||
|
// item.name && item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 过滤出不包含“总成本”的项
|
||||||
|
// const otherItems = this.itemData.filter(item =>
|
||||||
|
// !item.name || !item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 合并:总成本项在前,其他项在后
|
||||||
|
// return [...totalCostItems, ...otherItems];
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
mounted() {
|
||||||
|
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||||
|
// this.$nextTick(() => this.updateChart())
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||||
|
.scroll-container {
|
||||||
|
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||||
|
max-height: 210px;
|
||||||
|
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||||
|
overflow-y: auto;
|
||||||
|
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||||
|
/* Chrome/Safari */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* IE/Edge */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 264px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
.title {
|
||||||
|
// width: 190px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 46px;
|
||||||
|
color: #0B58FF;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
width: 500px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .leftTitle {
|
||||||
|
// .item {
|
||||||
|
// width: 67px;
|
||||||
|
// height: 180px;
|
||||||
|
// padding: 37px 23px;
|
||||||
|
// background: #F9FCFF;
|
||||||
|
// font-family: PingFangSC, PingFang SC;
|
||||||
|
// font-weight: 400;
|
||||||
|
// font-size: 18px;
|
||||||
|
// color: #000000;
|
||||||
|
// line-height: 25px;
|
||||||
|
// letter-spacing: 1px;
|
||||||
|
// // text-align: left;
|
||||||
|
// font-style: normal;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- <style>
|
||||||
|
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style> -->
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
<template>
|
||||||
|
<div style="width: 100%; height: 210px;position: relative;">
|
||||||
|
<div style='font-size: 16px;position: absolute;right: 20px;top:10px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{detailData.rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:detailData.flags>0?'#30B590':'#FF9423'}" >{{detailData.diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 210px;"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
refName: {
|
||||||
|
type: String,
|
||||||
|
default: () => 'verticalBarChart',
|
||||||
|
},
|
||||||
|
detailData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
detailData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs[this.refName];
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const diff = this.detailData.diff || 0
|
||||||
|
const rate = this.detailData.rate || 0
|
||||||
|
const flagValue = this.getRateFlag(this.detailData.rate, this.detailData.real, this.detailData.target) || 0
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 40,
|
||||||
|
bottom: 15,
|
||||||
|
right: 80,
|
||||||
|
left: 10,
|
||||||
|
containLabel: true,
|
||||||
|
show: false // 隐藏grid背景,避免干扰
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
// 横向柱状图的x轴必须设为数值轴,否则无法正常展示数值
|
||||||
|
type: 'value',
|
||||||
|
// offset: 0,
|
||||||
|
// boundaryGap: true ,
|
||||||
|
// boundaryGap: [10, 0], // 可根据需要开启,控制轴的留白
|
||||||
|
axisTick: { show: false },
|
||||||
|
min: 0,
|
||||||
|
//
|
||||||
|
splitNumber: 4,
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
// data: xData // 数值轴不需要手动设置data,由series的数据自动生成
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'category',
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.75)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true, // 显示Y轴轴线(关键)
|
||||||
|
lineStyle: {
|
||||||
|
color: '#E5E6EB', // 轴线颜色(浅灰色,可自定义)
|
||||||
|
width: 1, // 轴线宽度
|
||||||
|
type: 'solid' // 实线(可选:dashed虚线、dotted点线)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisTick: { show: false },
|
||||||
|
// padding: [300, 100, 100, 100],
|
||||||
|
data: ['实际', '预算'] // y轴分类:实际、预算
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
// name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 24,
|
||||||
|
// barCategoryGap: '50', // 柱子之间的间距(相对于柱子宽度)
|
||||||
|
// 数据长度与yAxis的分类数量匹配(实际、预算各一个值)
|
||||||
|
data: [{
|
||||||
|
value: this.detailData.real,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'right',
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: flagValue === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0]
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
value: this.detailData.target,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'right',
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
// 预算的渐变颜色(蓝系渐变)
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 1, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: '#82CCFF' }, // 浅蓝
|
||||||
|
{ offset: 1, color: '#4B9DFF' } // 深蓝
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
},],
|
||||||
|
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px; width: 100%;">
|
||||||
|
<!-- 销量模块(直接传递整合了flag的salesData) -->
|
||||||
|
<div class="dashboard left" @click="handleDashboardClick('/salesVolumeAnalysis/salesVolumeAnalysisBase')">
|
||||||
|
<div style='position: relative;'>
|
||||||
|
<div class="title">
|
||||||
|
销量·万㎡
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{ytdAnalysis[0].rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:ytdAnalysis[0].flags>0?'#30B590':'#FF9423'}" >{{ytdAnalysis[0].diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-wrap">
|
||||||
|
<operatingSingleBar :detailData="salesData"></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 单价模块(直接传递整合了flag的unitPriceData) -->
|
||||||
|
<div class="dashboard right" @click="handleDashboardClick('/unitPriceAnalysis/unitPriceAnalysisBase')">
|
||||||
|
<div style='position: relative;'>
|
||||||
|
<div class="title">
|
||||||
|
单价·元/㎡
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;position: absolute;top:-4px;right:15px'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{ytdAnalysis[1].rate}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:ytdAnalysis[1].flags>0?'#30B590':'#FF9423'}" >{{ytdAnalysis[1].diff}}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-wrap">
|
||||||
|
<operatingSingleBar :detailData="unitPriceData"></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import operatingSingleBar from './operatingSingleBar.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, operatingSingleBar },
|
||||||
|
props: {
|
||||||
|
ytdAnalysis: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [
|
||||||
|
{ title: "销量", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||||
|
{ title: "单价", budget: 0, real: 0, rate: 0, diff: 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
month: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
// 初始化数据包含flag字段
|
||||||
|
salesData: { title: "销量", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 },
|
||||||
|
unitPriceData: { title: "单价", budget: 0, real: 0, rate: 0, diff: 0, flag: 0 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
ytdAnalysis: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.updateChart(newVal)
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateChart(this.ytdAnalysis)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleDashboardClick(path) {
|
||||||
|
this.$router.push({
|
||||||
|
path: path,
|
||||||
|
query: {
|
||||||
|
factory: this.$route.query.factory ? this.$route.query.factory : 5,
|
||||||
|
dateData: this.dateData
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 判断flag的核心方法
|
||||||
|
getRateFlag(rate, real, target) {
|
||||||
|
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||||
|
|
||||||
|
// 1. 完成率 >= 100 => 达标
|
||||||
|
if (rate >= 100) return 1;
|
||||||
|
|
||||||
|
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||||
|
if (rate === 0 && target === 0) return 1;
|
||||||
|
|
||||||
|
// 其他情况 => 未达标
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateChart(data) {
|
||||||
|
// 数据兜底
|
||||||
|
const salesItem = Array.isArray(data) && data[0] ? data[0] : { title: "销量", budget: 0, real: 0, rate: 0, diff: 0 };
|
||||||
|
const unitPriceItem = Array.isArray(data) && data[1] ? data[1] : { title: "单价", budget: 0, real: 0, rate: 0, diff: 0 };
|
||||||
|
|
||||||
|
// 核心修改:将flag整合到数据对象中,无需单独定义salesFlag/unitPriceFlag
|
||||||
|
this.salesData = {
|
||||||
|
...salesItem, // 合并原有字段
|
||||||
|
flag: this.getRateFlag(salesItem.rate, salesItem.real, salesItem.budget) // 新增flag字段
|
||||||
|
};
|
||||||
|
|
||||||
|
this.unitPriceData = {
|
||||||
|
...unitPriceItem, // 合并原有字段
|
||||||
|
flag: this.getRateFlag(unitPriceItem.rate, unitPriceItem.real, unitPriceItem.budget) // 新增flag字段
|
||||||
|
};
|
||||||
|
|
||||||
|
// 调试:确认整合后的数据
|
||||||
|
console.log('整合flag后的销量数据:', this.salesData);
|
||||||
|
console.log('整合flag后的单价数据:', this.unitPriceData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
.scroll-container {
|
||||||
|
max-height: 210px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topItem-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 300px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
margin: 0 4px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-wrap {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 30px;
|
||||||
|
height: 32px;
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
width: 97px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard.left {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard.right {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
<div id="dayReport" class="dayReport" :style="styles">
|
<div id="dayReport" class="dayReport" :style="styles">
|
||||||
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||||
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||||
<ReportHeader top-title="营业收入" :is-full-screen="isFullScreen" @screenfullChange="screenfullChange"
|
<ReportHeader :dateData="dateData" top-title="电费分析" :is-full-screen="isFullScreen"
|
||||||
@timeRangeChange="handleTimeChange" />
|
@screenfullChange="screenfullChange" @timeRangeChange="handleTimeChange" />
|
||||||
<div class="main-body" style="
|
<div class="main-body" style="
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
grid-template-columns:1624px;
|
grid-template-columns:1624px;
|
||||||
">
|
">
|
||||||
<operatingLineChart :salesTrendMap="salesTrendMap" :grossMarginTrendMap="grossMarginTrendMap" />
|
<operatingLineChart :dateData="dateData" :monData="monData" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
|
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
grid-template-columns: 1624px;
|
grid-template-columns: 1624px;
|
||||||
">
|
">
|
||||||
<operatingLineChartCumulative :salesTrendMap="salesTrendMap" :grossMarginTrendMap="grossMarginTrendMap" />
|
<operatingLineChartCumulative :dateData="dateData" :totalData="totalData" />
|
||||||
<!-- <keyWork /> -->
|
<!-- <keyWork /> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,16 +41,16 @@
|
|||||||
</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 operatingSalesRevenue from "./operatingComponents/operatingSalesRevenue";
|
||||||
// import premProdStatus from "./components/premProdStatus.vue";
|
// import premProdStatus from "./components/premProdStatus.vue";
|
||||||
import { mapState } from "vuex";
|
import { mapState } from "vuex";
|
||||||
import operatingLineChart from "./operatingComponents/operatingLineChart";
|
import operatingLineChart from "../electricityCostAnalysisComponents/operatingLineChart";
|
||||||
import operatingLineChartCumulative from "./operatingComponents/operatingLineChartCumulative.vue";
|
import operatingLineChartCumulative from "../electricityCostAnalysisComponents/operatingLineChartCumulative.vue";
|
||||||
|
|
||||||
import { getSalesRevenueData } from '@/api/cockpit'
|
import { getProfitAnalysisTotalList } from '@/api/cockpit'
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
export default {
|
export default {
|
||||||
name: "DayReport",
|
name: "DayReport",
|
||||||
@@ -68,11 +68,9 @@ export default {
|
|||||||
timer: null,
|
timer: null,
|
||||||
beilv: 1,
|
beilv: 1,
|
||||||
value: 100,
|
value: 100,
|
||||||
saleData: {},
|
dateData: {},
|
||||||
premiumProduct: {},
|
monData: [],
|
||||||
salesTrendMap: {},
|
totalData: [],
|
||||||
grossMarginTrendMap: {},
|
|
||||||
salesProportion:{},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -138,25 +136,34 @@ export default {
|
|||||||
this.beilv = _this.clientWidth / 1920;
|
this.beilv = _this.clientWidth / 1920;
|
||||||
})();
|
})();
|
||||||
};
|
};
|
||||||
|
this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getData(obj) {
|
getData() {
|
||||||
getSalesRevenueData({
|
getProfitAnalysisTotalList({
|
||||||
startTime: obj.startTime,
|
startTime: this.dateData.startTime,
|
||||||
endTime: obj.endTime,
|
endTime: this.dateData.endTime,
|
||||||
timeDim: obj.mode
|
analysisObject: [
|
||||||
|
"利润总额"
|
||||||
|
],
|
||||||
|
levelId: 1,
|
||||||
|
// timeDim: this.dateData.mode
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
console.log(res);
|
console.log(res);
|
||||||
this.saleData = res.data.SaleData
|
this.monData = res.data.currentMonthData
|
||||||
this.premiumProduct = res.data.premiumProduct
|
this.totalData = res.data.totalMonthData
|
||||||
this.salesTrendMap = res.data.salesTrendMap
|
// this.totalData = res.data.totalData
|
||||||
this.grossMarginTrendMap = res.data.grossMarginTrendMap
|
// this.saleData = res.data.SaleData
|
||||||
this.salesProportion = res.data.salesProportion ? res.data.salesProportion : {}
|
// this.premiumProduct = res.data.premiumProduct
|
||||||
|
// this.salesTrendMap = res.data.salesTrendMap
|
||||||
|
// this.grossMarginTrendMap = res.data.grossMarginTrendMap
|
||||||
|
// this.salesProportion = res.data.salesProportion ? res.data.salesProportion : {}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
handleTimeChange(obj) {
|
handleTimeChange(obj) {
|
||||||
console.log(obj, 'obj');
|
// console.log(obj, 'obj');
|
||||||
this.getData(obj)
|
this.dateData= obj
|
||||||
|
this.getData()
|
||||||
},
|
},
|
||||||
handleClickOutside() {
|
handleClickOutside() {
|
||||||
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
||||||
@@ -228,12 +235,14 @@ export default {
|
|||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import "~@/assets/styles/mixin.scss";
|
@import "~@/assets/styles/mixin.scss";
|
||||||
@import "~@/assets/styles/variables.scss";
|
@import "~@/assets/styles/variables.scss";
|
||||||
|
|
||||||
.dayReport {
|
.dayReport {
|
||||||
width: 1920px;
|
width: 1920px;
|
||||||
height: 1080px;
|
height: 1080px;
|
||||||
background: url("../../assets/img/backp.png") no-repeat;
|
background: url("../../../assets/img/backp.png") no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hideSidebar .fixed-header {
|
.hideSidebar .fixed-header {
|
||||||
width: calc(100% - 54px);
|
width: calc(100% - 54px);
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
<div id="dayReport" class="dayReport" :style="styles">
|
<div id="dayReport" class="dayReport" :style="styles">
|
||||||
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||||
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||||
<ReportHeader size="psi" @timeRangeChange="handleTimeChange" top-title="基地营业收入" :is-full-screen="isFullScreen"
|
<ReportHeader :dateData="dateData" size="psi" @timeRangeChange="handleTimeChange" top-title="基地电费分析"
|
||||||
@screenfullChange="screenfullChange" />
|
:is-full-screen="isFullScreen" @screenfullChange="screenfullChange" />
|
||||||
<div class="main-body" style="
|
<div class="main-body" style="
|
||||||
margin-top: -20px;
|
margin-top: -20px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -17,25 +17,38 @@
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
grid-template-columns: 1624px;
|
grid-template-columns: 1624px;
|
||||||
">
|
">
|
||||||
<changeBase @selectChange="selectChange" />
|
<changeBase :factory="factory" @baseChange="selectChange" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="top" style="display: flex; gap: 16px;margin-top: -20px;">
|
<div class="top" style="display: flex; gap: 16px;margin-top: -20px;">
|
||||||
<div class="left-three" style="
|
<div class="left-three" style="
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
grid-template-columns: 1624px;
|
grid-template-columns: 804px 804px;
|
||||||
">
|
">
|
||||||
<costItemOverviewItem :itemData="renderList" :title="'燃料成本概述·元/m²'" />
|
<monthlyOverview :month="month" :monData="monData" :title="'月度概览'" />
|
||||||
|
<totalOverview :totalData="totalData" :title="'累计概览'" />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
|
<div class="middle" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||||
<div class="left-three" style="
|
<div class="left-three" style="
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
grid-template-columns: 1624px;
|
grid-template-columns: 1624px;
|
||||||
">
|
">
|
||||||
<profitLineChart :yName="'元/㎡'" :trendData="trendData" :dateData="dateData" />
|
<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>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,14 +63,17 @@
|
|||||||
</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 changeBase from "./components/changeBase.vue";
|
import changeBase from "../components/changeBase.vue";
|
||||||
import costItemOverviewItem from "./costComponents/costItemOverviewItem.vue";
|
import monthlyOverview from "../electricityCostAnalysisComponents/monthlyOverview.vue";
|
||||||
import profitLineChart from "./costComponents/profitLineChart.vue";
|
import totalOverview from "../electricityCostAnalysisComponents/totalOverview.vue";
|
||||||
|
// import totalOverview from "../operatingComponents/totalOverview.vue";
|
||||||
|
import relatedIndicatorsAnalysis from "../electricityCostAnalysisComponents/relatedIndicatorsAnalysis.vue";
|
||||||
|
import dataTrend from "../electricityCostAnalysisComponents/dataTrend.vue";
|
||||||
import { mapState } from "vuex";
|
import { mapState } from "vuex";
|
||||||
import { getCostAnalysisXXCostList } from '@/api/cockpit'
|
import { getProfitAnalysisTotalList } from '@/api/cockpit'
|
||||||
// import PSDO from "./components/PSDO.vue";
|
// import PSDO from "./components/PSDO.vue";
|
||||||
// import psiLineChart from "./components/psiLineChart.vue";
|
// import psiLineChart from "./components/psiLineChart.vue";
|
||||||
|
|
||||||
@@ -72,9 +88,11 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
ReportHeader,
|
ReportHeader,
|
||||||
changeBase,
|
changeBase,
|
||||||
profitLineChart,
|
monthlyOverview,
|
||||||
costItemOverviewItem,
|
|
||||||
Sidebar,
|
Sidebar,
|
||||||
|
totalOverview,
|
||||||
|
dataTrend,
|
||||||
|
relatedIndicatorsAnalysis
|
||||||
// psiLineChart
|
// psiLineChart
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -82,15 +100,16 @@ export default {
|
|||||||
isFullScreen: false,
|
isFullScreen: false,
|
||||||
timer: null,
|
timer: null,
|
||||||
beilv: 1,
|
beilv: 1,
|
||||||
|
month: '',
|
||||||
value: 100,
|
value: 100,
|
||||||
dateData:{},
|
factory: 5,
|
||||||
levelId:undefined,
|
dateData: {},
|
||||||
itemData: [],
|
monData: {},
|
||||||
trendData: [],
|
totalData: {},
|
||||||
parentItemList: [
|
trend: [],
|
||||||
{ name: "燃料成本", target: 0, value: 0, proportion: 0, flag: 1 },
|
relatedData: [],
|
||||||
{ name: "天然气", target: 0, value: 0, proportion: 0, flag: 1 }
|
trendName: '利润总额',
|
||||||
],
|
// cusProData: {},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -107,12 +126,6 @@ export default {
|
|||||||
needTagsView: (state) => state.settings.tagsView,
|
needTagsView: (state) => state.settings.tagsView,
|
||||||
fixedHeader: (state) => state.settings.fixedHeader,
|
fixedHeader: (state) => state.settings.fixedHeader,
|
||||||
}),
|
}),
|
||||||
renderList() {
|
|
||||||
if (this.itemData && this.itemData.length > 0) {
|
|
||||||
return this.itemData;
|
|
||||||
}
|
|
||||||
return this.parentItemList;
|
|
||||||
},
|
|
||||||
classObj() {
|
classObj() {
|
||||||
return {
|
return {
|
||||||
hideSidebar: !this.sidebar.opened,
|
hideSidebar: !this.sidebar.opened,
|
||||||
@@ -162,44 +175,76 @@ export default {
|
|||||||
this.beilv = _this.clientWidth / 1920;
|
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: {
|
methods: {
|
||||||
|
handleChange(value) {
|
||||||
|
this.index = value
|
||||||
|
this.getData()
|
||||||
|
},
|
||||||
|
changeItem(item) {
|
||||||
|
console.log('item', item);
|
||||||
|
|
||||||
|
this.trendName = item
|
||||||
|
this.getData()
|
||||||
|
},
|
||||||
getData() {
|
getData() {
|
||||||
const requestParams = {
|
const requestParams = {
|
||||||
// startTime: this.startTime,
|
|
||||||
// endTime: this.endTime,
|
|
||||||
// mode: this.mode,
|
|
||||||
startTime: this.dateData.startTime,
|
startTime: this.dateData.startTime,
|
||||||
endTime: this.dateData.endTime,
|
endTime: this.dateData.endTime,
|
||||||
mode: this.dateData.mode,
|
// index: this.index,
|
||||||
trendName: "燃料成本",
|
// sort: 1,
|
||||||
levelId: this.levelId ? this.levelId :1
|
trendName: this.trendName,
|
||||||
|
analysisObject: [
|
||||||
|
"利润总额",
|
||||||
|
],
|
||||||
|
// paramList: ['制造成本', '财务费用', '销售费用', '管理费用', '运费'],
|
||||||
|
levelId: this.factory,
|
||||||
|
// baseId: Number(this.factory),
|
||||||
};
|
};
|
||||||
// 调用接口
|
// 调用接口
|
||||||
getCostAnalysisXXCostList(requestParams).then((res) => {
|
getProfitAnalysisTotalList(requestParams).then((res) => {
|
||||||
this.itemData = res.data[0].map((item) => {
|
this.monData = res.data.currentMonthData.find(item => {
|
||||||
return {
|
return item.name === "利润总额";
|
||||||
...item,
|
});
|
||||||
route: 'singleFuelAnalysis'
|
console.log('this.monData', this.monData);
|
||||||
}
|
|
||||||
})
|
this.totalData = res.data.totalMonthData.find(item => {
|
||||||
this.trendData= res.data[1]
|
return item.name === "利润总额";
|
||||||
|
});
|
||||||
|
// this.relatedMon = res.data.relatedMon
|
||||||
|
this.relatedData = {
|
||||||
|
relatedMon: res.data.currentMonthData.filter(item => {
|
||||||
|
return item.name !== "利润总额";
|
||||||
|
}), // 兜底月度数据
|
||||||
|
relatedTotal: res.data.totalMonthData.filter(item => {
|
||||||
|
return item.name !== "利润总额";
|
||||||
|
}) // 兜底累计数据
|
||||||
|
}
|
||||||
|
this.trend = res.data.dataTrend
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleTimeChange(obj) {
|
handleTimeChange(obj) {
|
||||||
console.log(obj, 'obj');
|
this.month = obj.targetMonth
|
||||||
this.dateData = {
|
this.dateData = {
|
||||||
startTime: obj.startTime,
|
startTime: obj.startTime,
|
||||||
endTime: obj.endTime,
|
endTime: obj.endTime,
|
||||||
mode: obj.mode,
|
// mode: obj.mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getData()
|
this.getData()
|
||||||
},
|
},
|
||||||
selectChange(data) {
|
selectChange(data) {
|
||||||
console.log('选中的数据:', data);
|
console.log('选中的数据:', data);
|
||||||
this.levelId = data
|
this.factory = data
|
||||||
if (this.dateData.startTime && this.dateData.endTime) {
|
if (this.dateData.startTime && this.dateData.endTime) {
|
||||||
this.getData();
|
this.getData();
|
||||||
}
|
}
|
||||||
@@ -284,12 +329,14 @@ export default {
|
|||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import "~@/assets/styles/mixin.scss";
|
@import "~@/assets/styles/mixin.scss";
|
||||||
@import "~@/assets/styles/variables.scss";
|
@import "~@/assets/styles/variables.scss";
|
||||||
|
|
||||||
.dayReport {
|
.dayReport {
|
||||||
width: 1920px;
|
width: 1920px;
|
||||||
height: 1080px;
|
height: 1080px;
|
||||||
background: url("../../assets/img/backp.png") no-repeat;
|
background: url("../../../assets/img/backp.png") no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hideSidebar .fixed-header {
|
.hideSidebar .fixed-header {
|
||||||
width: calc(100% - 54px);
|
width: calc(100% - 54px);
|
||||||
}
|
}
|
||||||
419
src/views/home/electricityCostAnalysisComponents/Header.vue
Normal file
419
src/views/home/electricityCostAnalysisComponents/Header.vue
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
<template>
|
||||||
|
<header class="report-header">
|
||||||
|
<!-- 左侧区域:logo + 标题 -->
|
||||||
|
<div class="left-content">
|
||||||
|
<img style="height: 36px;" src="../../../assets/img/cnbm.png" alt="benmaLogo" >
|
||||||
|
<div class="top-title">{{ topTitle }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="center-content">
|
||||||
|
<!-- 循环 pageRoutes,不再硬编码文字 -->
|
||||||
|
<div class="item" v-for="(page, index) in pageRoutes" :key="index" @click="goToPage(page.path, index)">
|
||||||
|
<span class="item-text">{{ page.text }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- :class="{ 'no-skew': activeIndex === index }
|
||||||
|
" -->
|
||||||
|
<!-- 右侧区域:全屏按钮 -->
|
||||||
|
<div class="right-content">
|
||||||
|
<el-button type="text" class="screen-btn" :title="isFullScreen ? '退出全屏' : '全屏'" @click="changeFullScreen">
|
||||||
|
<svg-icon style="color: #0B58FF;" v-if="isFullScreen" icon-class="unFullScreenView" />
|
||||||
|
<svg-icon style="color: #0B58FF;" v-else icon-class="fullScreenView" />
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 时间选择区域:日/月/年按钮 + label + 日期选择器 -->
|
||||||
|
<div class="timeType">
|
||||||
|
<!-- <div class="item" v-for="(item, index) in timeTypes" :key="index" @click="activeTime = index"
|
||||||
|
:class="{ 'no-skew': activeTime === index }">
|
||||||
|
<span class="item-text">{{ item.text }}</span>
|
||||||
|
</div> -->
|
||||||
|
<div class="dateP">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">月份选择</span>
|
||||||
|
</div>
|
||||||
|
<el-date-picker v-model="date" :type="getPickerType" :placeholder="getPickerPlaceholder"
|
||||||
|
class="custom-date-picker" style="width: 132px;height: 29px;" @change="emitTimeRange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment'
|
||||||
|
export default {
|
||||||
|
name: 'Header',
|
||||||
|
props: {
|
||||||
|
isFullScreen: { type: Boolean, default: false },
|
||||||
|
topTitle: { type: String, default: '' }
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentTime: '',
|
||||||
|
timeTimer: null,
|
||||||
|
date: undefined,
|
||||||
|
activeIndex: -1,
|
||||||
|
activeTime: 1, // 0=日,1=月,2=年(默认选中“日”)
|
||||||
|
pageRoutes: [
|
||||||
|
{ text: '营业收入', path: '/operatingRevenue/operatingRevenueIndex' },
|
||||||
|
{ text: '利润分析', path: '/profitAnalysis' },
|
||||||
|
{ text: '产销率库存分析', path: '/PSIAnal' },
|
||||||
|
{ text: '成本分析', path: '/cost/cost' },
|
||||||
|
{ text: '驾驶舱报表', path: '/cockpit' }
|
||||||
|
],
|
||||||
|
// 定义时间类型配置:text=按钮文字,pickerType=选择器类型,placeholder=占位符
|
||||||
|
timeTypes: [
|
||||||
|
{ text: '日', pickerType: 'date', placeholder: '选择日期' },
|
||||||
|
{ text: '月', pickerType: 'month', placeholder: '选择月份' },
|
||||||
|
{ text: '年', pickerType: 'year', placeholder: '选择年份' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 动态获取日期选择器类型
|
||||||
|
getPickerType() {
|
||||||
|
return this.timeTypes[this.activeTime].pickerType;
|
||||||
|
},
|
||||||
|
// 动态获取日期选择器占位符
|
||||||
|
getPickerPlaceholder() {
|
||||||
|
return this.timeTypes[this.activeTime].placeholder;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goToPage(path, index) {
|
||||||
|
// 1. 跳转到对应路由
|
||||||
|
this.$router.push(path);
|
||||||
|
// 2. 更新activeIndex,让当前点击项高亮
|
||||||
|
this.activeIndex = index;
|
||||||
|
},
|
||||||
|
changeFullScreen() { this.$emit('screenfullChange') },
|
||||||
|
padZero(num) { return num < 10 ? '0' + num : num },
|
||||||
|
/**
|
||||||
|
* 核心方法1:根据维度计算时间区间(首次进入时基于赋值的当月日期,计算“当月第一天0点→次月第一天0点”)
|
||||||
|
* @returns {Object} 包含 start(开始时间)、end(结束时间)、dimension(维度)的区间对象
|
||||||
|
*/
|
||||||
|
calculateTimeRange() {
|
||||||
|
let startTime = 0;
|
||||||
|
let endTime = 0;
|
||||||
|
const mode = this.activeTime + 1; // 1=日,2=月,3=年
|
||||||
|
const defaultMoment = moment(); // 默认当前时间
|
||||||
|
|
||||||
|
const targetMoment = this.date
|
||||||
|
? moment(this.date, this.getPickerType === 'date' ? 'YYYY-MM-DD' : (this.getPickerType === 'month' ? 'YYYY-MM' : 'YYYY'))
|
||||||
|
: defaultMoment;
|
||||||
|
|
||||||
|
if (!targetMoment.isValid()) {
|
||||||
|
console.error('无效日期:', this.date);
|
||||||
|
return { startTime, endTime, mode };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 日维度:00:00:00 → 23:59:59(无毫秒)
|
||||||
|
if (this.activeTime === 0) {
|
||||||
|
startTime = targetMoment.startOf('day').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('day').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 月维度:当月1日00:00:00 → 当月最后一天23:59:59(无毫秒)
|
||||||
|
else if (this.activeTime === 1) {
|
||||||
|
startTime = targetMoment.startOf('month').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('month').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 年维度:当年1月1日00:00:00 → 当年最后一天23:59:59(无毫秒)
|
||||||
|
else if (this.activeTime === 2) {
|
||||||
|
startTime = targetMoment.startOf('year').millisecond(0).valueOf();
|
||||||
|
endTime = targetMoment.endOf('year').millisecond(0).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调试输出:验证是否去掉毫秒
|
||||||
|
console.log('时间范围计算结果:', {
|
||||||
|
mode,
|
||||||
|
startTime: moment(startTime * 1000).format('YYYY-MM-DD HH:mm:ss'), // 格式:2025-11-30 00:00:00
|
||||||
|
endTime: moment(endTime * 1000).format('YYYY-MM-DD HH:mm:ss'), // 格式:2025-11-30 23:59:59(无毫秒)
|
||||||
|
startTimeStamp: startTime, // 秒级时间戳(如:1764422400)
|
||||||
|
endTimeStamp: endTime // 秒级时间戳(如:1764508799)
|
||||||
|
});
|
||||||
|
|
||||||
|
return { startTime, endTime, mode };
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 核心方法2:传递时间区间给父组件(首次进入时触发,传递“当月第一天0点→次月第一天0点”)
|
||||||
|
*/
|
||||||
|
emitTimeRange() {
|
||||||
|
const timeRange = this.calculateTimeRange();
|
||||||
|
this.$emit('timeRangeChange', timeRange);
|
||||||
|
// 调试用:查看首次传递的区间(如{start: "2025-10-01T00:00:00", end: "2025-11-01T00:00:00", dimension: "月"})
|
||||||
|
console.log('当前时间区间:', timeRange);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 维度切换时:清空选择的日期,并传递当前维度的默认区间
|
||||||
|
activeTime(newVal, oldVal) {
|
||||||
|
if (newVal !== oldVal) {
|
||||||
|
this.date = undefined;
|
||||||
|
// this.emitTimeRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 核心逻辑:首次进入页面,计算当月默认日期并赋值给选择器,同时传递区间
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = this.padZero(now.getMonth() + 1); // 月份从0开始,+1后补零(如1月→01)
|
||||||
|
// 赋值当月默认日期(格式:YYYY-MM,适配month类型选择器)
|
||||||
|
this.date = `${year}-${month}`;
|
||||||
|
// 确保选择器渲染完成后,传递“当月第一天0点→次月第一天0点”的区间
|
||||||
|
this.$nextTick(() => this.emitTimeRange());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
/* 原有样式不变,仅补充label文字的倾斜抵消样式 */
|
||||||
|
@font-face {
|
||||||
|
font-family: "YouSheBiaoTiHei";
|
||||||
|
src: url('../../../assets/fonts/YouSheBiaoTiHe.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-header {
|
||||||
|
height: 117px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
background: url('../../../assets/img/topTitle.png') no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: 0 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
/* 确保timeType绝对定位生效 */
|
||||||
|
|
||||||
|
.left-content {
|
||||||
|
margin-top: 11px;
|
||||||
|
margin-left: 44px;
|
||||||
|
height: 55px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-title {
|
||||||
|
height: 55px;
|
||||||
|
font-family: "YouSheBiaoTiHei", sans-serif;
|
||||||
|
font-size: 42px;
|
||||||
|
color: #1E1651;
|
||||||
|
line-height: 55px;
|
||||||
|
letter-spacing: 6px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 18px;
|
||||||
|
margin-left: 70px;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
width: 180px;
|
||||||
|
height: 50px;
|
||||||
|
background: #E1EEFC;
|
||||||
|
transform: skew(-20deg);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #1E1651;
|
||||||
|
line-height: 50px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0px 13px 16px 0px rgba(179, 217, 255, 0.43),
|
||||||
|
0px 2px 4px 0px rgba(92, 140, 255, 0.25),
|
||||||
|
inset 0px -43px 13px 0px rgba(255, 255, 255, 0.51);
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(20deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.no-skew {
|
||||||
|
background: none !important;
|
||||||
|
transform: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
color: #1E1651;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 垂直居中,避免元素高低错位 */
|
||||||
|
top: 42px;
|
||||||
|
right:10px;
|
||||||
|
margin-top: 18px;
|
||||||
|
gap: 0;
|
||||||
|
/* 清除间隙,让按钮与选择器紧密连接 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item {
|
||||||
|
width: 40px;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(236, 244, 254, 1);
|
||||||
|
transform: skew(-20deg);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(11, 88, 255, 1);
|
||||||
|
line-height: 28px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
/* 选中按钮与未选中按钮倾斜角度统一,避免切换时跳动 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item .item-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(20deg);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item.no-skew {
|
||||||
|
background: rgba(11, 88, 255, 1);
|
||||||
|
color: rgba(249, 252, 255, 1);
|
||||||
|
transform: skew(-20deg) !important;
|
||||||
|
/* 统一倾斜角度,修复原30deg的错位 */
|
||||||
|
box-shadow: 0 2px 8px rgba(11, 88, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeType .item.no-skew .item-text {
|
||||||
|
transform: skew(20deg) !important;
|
||||||
|
/* 同步统一文字倾斜角度 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateP {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateP .label {
|
||||||
|
width: 165px;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(236, 244, 254, 1);
|
||||||
|
transform: skew(-25deg);
|
||||||
|
/* 与按钮倾斜角度统一(原30deg改为25deg,避免视觉错位) */
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 28px;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 补充:label文字抵消倾斜(原代码遗漏,导致文字倾斜) */
|
||||||
|
.dateP .label-text {
|
||||||
|
display: inline-block;
|
||||||
|
transform: skew(25deg);
|
||||||
|
/* 与label倾斜角度相反,确保文字正立 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-right: 4px;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-time {
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 24px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screen-btn {
|
||||||
|
width: 26px;
|
||||||
|
margin-left: 300px;
|
||||||
|
color: #00fff0;
|
||||||
|
font-size: 26px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 日期选择器样式保持不变 */
|
||||||
|
::v-deep .custom-date-picker {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
width: 165px !important;
|
||||||
|
height: 28px !important;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 !important;
|
||||||
|
|
||||||
|
/* 1. 调整输入框文字:确保行高与输入框高度一致,垂直居中 */
|
||||||
|
.el-input__inner {
|
||||||
|
height: 28px !important;
|
||||||
|
width: 165px !important;
|
||||||
|
text-align: center;
|
||||||
|
padding-left: 15px !important;
|
||||||
|
padding-right: 32px !important;
|
||||||
|
/* 给图标留空间,避免文字被遮挡 */
|
||||||
|
font-size: 14px !important;
|
||||||
|
line-height: 28px !important;
|
||||||
|
/* 行高=输入框高度,文字垂直居中 */
|
||||||
|
color: rgba(237, 245, 253, 1) !important;
|
||||||
|
vertical-align: middle !important;
|
||||||
|
/* 强制文字垂直对齐 */
|
||||||
|
clip-path: polygon(18px 0, 100% 0, 100% 100%, 0 100%);
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
background-color: rgba(11, 88, 255, 1) !important;
|
||||||
|
border-left: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. 调整图标容器:让图标与文字在同一水平线上 */
|
||||||
|
.el-input__prefix {
|
||||||
|
left: auto !important;
|
||||||
|
right: 8px !important;
|
||||||
|
top: 50% !important;
|
||||||
|
/* 从40%改为50%,基于输入框垂直居中 */
|
||||||
|
transform: translateY(-50%) !important;
|
||||||
|
/* 向上偏移自身50%,精准居中 */
|
||||||
|
display: inline-flex !important;
|
||||||
|
/* 让容器内图标垂直居中 */
|
||||||
|
align-items: center !important;
|
||||||
|
/* 图标在容器内垂直居中 */
|
||||||
|
height: 28px !important;
|
||||||
|
/* 容器高度=输入框高度,避免偏移 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. 调整图标本身:确保图标大小和对齐方式 */
|
||||||
|
.el-input__icon {
|
||||||
|
color: #ffffff !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
line-height: 28px !important;
|
||||||
|
/* 图标行高=输入框高度,与文字对齐 */
|
||||||
|
vertical-align: middle !important;
|
||||||
|
/* 强制图标垂直对齐 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. 图标伪类:确保颜色和对齐继承 */
|
||||||
|
.el-icon-date::before {
|
||||||
|
color: #ffffff !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
line-height: inherit !important;
|
||||||
|
/* 继承父级行高,避免错位 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
271
src/views/home/electricityCostAnalysisComponents/container.vue
Normal file
271
src/views/home/electricityCostAnalysisComponents/container.vue
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
<template>
|
||||||
|
<div class="cockpitContainer" :class="['cockpitContainer__' + size]">
|
||||||
|
<div class="content-top" :class="['content-top__' + topSize]">
|
||||||
|
<!-- 使用 flex 容器包裹图标和文字,实现垂直居中 -->
|
||||||
|
<div class="title-wrapper">
|
||||||
|
<svg-icon class="title-icon" :icon-class="icon" />
|
||||||
|
<span class="title-text">
|
||||||
|
{{ name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cockpitContainer-body">
|
||||||
|
<slot>
|
||||||
|
<div class="test-body">something test....</div>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Container',
|
||||||
|
components: {},
|
||||||
|
// eslint-disable-next-line vue/require-prop-types
|
||||||
|
props: ['name', 'size', 'icon', 'topSize'],
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.cockpitContainer {
|
||||||
|
display: inline-block;
|
||||||
|
// width: 100%;
|
||||||
|
// height: 100%;
|
||||||
|
padding: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.content-top {
|
||||||
|
height: 60px;
|
||||||
|
.title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 10px;
|
||||||
|
/* 垂直居中关键属性 */
|
||||||
|
height: 100%;
|
||||||
|
/* 继承父容器高度,确保垂直居中范围 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
font-size: 30px;
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
/* 图标和文字之间的间距 */
|
||||||
|
flex-shrink: 0;
|
||||||
|
/* 防止图标被压缩 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 移除固定行高,避免影响垂直对齐
|
||||||
|
// line-height: 60px;
|
||||||
|
}
|
||||||
|
// width: 547px;
|
||||||
|
// background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
&__basic {
|
||||||
|
// width: 547px;
|
||||||
|
background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__middle {
|
||||||
|
background: url(../../../assets/img/topTileMiddle.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__large {
|
||||||
|
background: url(../../../assets/img/topTitleLargeBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__KFAPTopTitle {
|
||||||
|
background: url(../../../assets/img/KFAPTopTitle.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiTopTitleBasic {
|
||||||
|
background: url(../../../assets/img/psiTopTitleBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
&__rawTopTitleLarge {
|
||||||
|
background: url(../../../assets/img/rawTopTitleLarge.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__topBasic {
|
||||||
|
background: url(../../../assets/img/top-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bottomBasic {
|
||||||
|
background: url(../../../assets/img/bottom-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingBasic {
|
||||||
|
background: url(../../../assets/img/operating-basic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingLarge {
|
||||||
|
background: url(../../../assets/img/operating-large.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__profitTopBasic {
|
||||||
|
background: url(../../../assets/img/profitTopBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__profitMiddleBasic {
|
||||||
|
background: url(../../../assets/img/profitMiddleBasic.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiBasicBg {
|
||||||
|
background: url(../../../assets/img/psiBasicBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__psiMiddleBg {
|
||||||
|
background: url(../../../assets/img/psiMiddleBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operatingRevenueBg {
|
||||||
|
background: url(../../../assets/img/operatingRevenueBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__costBasicBg {
|
||||||
|
background: url(../../../assets/img/costBasicBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
&__rawTopBg {
|
||||||
|
background: url(../../../assets/img/rawTopBg.png) no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
// &__left {
|
||||||
|
// background: url(../../../../../../../assets/img/left.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__energyConsumption {
|
||||||
|
// background: url(../../../../../../../assets/img/energyConsumption.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__left2 {
|
||||||
|
// background: url(../../assets/left_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__left3 {
|
||||||
|
// background: url(../../assets/left_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__mid2 {
|
||||||
|
// background: url(../../assets/mid_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__mid3 {
|
||||||
|
// background: url(../../assets/mid_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right1 {
|
||||||
|
// background: url(../../assets/right_1.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right2 {
|
||||||
|
// background: url(../../assets/right_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__right3 {
|
||||||
|
// background: url(../../assets/right_3.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekRight2 {
|
||||||
|
// background: url(../../assets/week_right_2.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekMidTop {
|
||||||
|
// background: url(../../assets/week-mid-top.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &__weekMidMid {
|
||||||
|
// background: url(../../assets/week-mid-mid.png) no-repeat;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-position: 0 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: ' ';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
// background: inherit;
|
||||||
|
/* 设置模糊,不用 filter */
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-body {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
250
src/views/home/electricityCostAnalysisComponents/dataTrend.vue
Normal file
250
src/views/home/electricityCostAnalysisComponents/dataTrend.vue
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="数据趋势" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="right" style="
|
||||||
|
height: 191px;
|
||||||
|
display: flex;
|
||||||
|
width: 1595px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<!-- 直接使用计算属性 chartData,无需手动更新 -->
|
||||||
|
<dataTrendBar @handleGetItemData="getData" :chartData="chartData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import dataTrendBar from "./dataTrendBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, dataTrendBar },
|
||||||
|
props: {
|
||||||
|
trendData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 移除:原 chartData 定义,改为计算属性
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// 移除:原 watch 监听配置,计算属性自动响应 trendData 变化
|
||||||
|
computed: {
|
||||||
|
/**
|
||||||
|
* chartData 计算属性:自动响应 trendData 变化,格式化并提取各字段数组
|
||||||
|
* @returns {Object} 包含6个独立数组的格式化数据
|
||||||
|
*/
|
||||||
|
chartData() {
|
||||||
|
// 初始化6个独立数组
|
||||||
|
const timeArr = []; // 格式化后的年月数组
|
||||||
|
const valueArr = []; // 实际值数组
|
||||||
|
const diffValueArr = []; // 差异值数组
|
||||||
|
const targetValueArr = []; // 预算值数组
|
||||||
|
const proportionArr = []; // 占比数组
|
||||||
|
const completedArr = []; // 完成率数组
|
||||||
|
|
||||||
|
// 遍历传入的 trendData 数组(响应式依赖,变化时自动重算)
|
||||||
|
this.trendData.forEach((item) => {
|
||||||
|
// 1. 格式化时间并推入时间数组
|
||||||
|
const yearMonth = this.formatTimeToYearMonth(item.time);
|
||||||
|
timeArr.push(yearMonth);
|
||||||
|
|
||||||
|
// 2. 提取其他字段,兜底为0(防止null/undefined影响图表渲染)
|
||||||
|
valueArr.push(item.value ?? 0);
|
||||||
|
diffValueArr.push(item.diffValue ?? 0);
|
||||||
|
targetValueArr.push(item.targetValue ?? 0);
|
||||||
|
proportionArr.push(item.proportion ?? 0);
|
||||||
|
completedArr.push(item.completed ?? 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 组装并返回格式化后的数据(结构与原一致)
|
||||||
|
return {
|
||||||
|
time: timeArr,
|
||||||
|
value: valueArr,
|
||||||
|
diffValue: diffValueArr,
|
||||||
|
targetValue: targetValueArr,
|
||||||
|
proportion: proportionArr,
|
||||||
|
completed: completedArr,
|
||||||
|
rawData: this.trendData, // 透传原始数据,方便子组件使用
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 格式化时间戳为年月格式(YYYY-MM)
|
||||||
|
* @param {Number} timestamp 13位毫秒级时间戳
|
||||||
|
* @returns {String} 格式化后的年月字符串(如:2025-10)
|
||||||
|
*/
|
||||||
|
formatTimeToYearMonth(timestamp) {
|
||||||
|
if (!timestamp || isNaN(timestamp)) {
|
||||||
|
return ""; // 容错:非有效时间戳返回空字符串
|
||||||
|
}
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, "0"); // 月份从0开始,补0至2位
|
||||||
|
return `${year}-${month}`;
|
||||||
|
},
|
||||||
|
getData(value) {
|
||||||
|
this.$emit('getData', value)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 原有样式保持不变 */
|
||||||
|
.scroll-container {
|
||||||
|
max-height: 210px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px 27px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
width: 21px;
|
||||||
|
height: 23px;
|
||||||
|
background: rgba(0, 106, 205, 0.22);
|
||||||
|
backdrop-filter: blur(1.5px);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #68b5ff;
|
||||||
|
line-height: 23px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eq-name {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eqStatus {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitLine {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
border: 1px solid #adadad;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #00ffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoLeft {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoRight {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarWrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLine {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #acacac 100%);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLineTop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg,
|
||||||
|
rgba(53, 223, 247, 0) 0%,
|
||||||
|
rgba(54, 220, 246, 0.92) 92%,
|
||||||
|
#36f6e5 100%,
|
||||||
|
#37acf5 100%);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chartImgBottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 45px;
|
||||||
|
left: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
left: 57px;
|
||||||
|
bottom: 42px;
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #00e8ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,479 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<!-- 新增行容器:包裹“各基地情况”和barTop -->
|
||||||
|
<div class="header-row">
|
||||||
|
<div class="barTop">
|
||||||
|
<!-- 关键:新增右侧容器,包裹图例和按钮组,实现整体靠右 -->
|
||||||
|
<div class="right-container">
|
||||||
|
<div class="legend">
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon line yield"></span>
|
||||||
|
完成率
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square target"></span>
|
||||||
|
目标
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square achieved"></span>
|
||||||
|
实际·达标
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square unachieved"></span>
|
||||||
|
实际·未达标
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<div class="item-button category-btn">
|
||||||
|
<span class="item-text">类目选择</span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-container">
|
||||||
|
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||||
|
<span class="item-text profit-text">{{ selectedProfit || '请选择' }}</span>
|
||||||
|
<span class="dropdown-arrow" :class="{ 'rotate': isDropdownShow }"></span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-options" v-if="isDropdownShow">
|
||||||
|
<div class="dropdown-option" v-for="(item, index) in profitOptions" :key="index"
|
||||||
|
@click.stop="selectProfit(item)">
|
||||||
|
{{ item }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||||
|
<operatingLineBar :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSale.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isDropdownShow: false,
|
||||||
|
selectedProfit: '利润总额', // 选中的名称,初始为null
|
||||||
|
profitOptions: [
|
||||||
|
'利润总额',
|
||||||
|
'销量',
|
||||||
|
'单价',
|
||||||
|
'制造成本',
|
||||||
|
'管理费用',
|
||||||
|
'销售费用',
|
||||||
|
'财务费用',
|
||||||
|
'非经营性利润',
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// profitOptions() {
|
||||||
|
// return this.categoryData.map(item => item.name) || [];
|
||||||
|
// },
|
||||||
|
currentDataSource() {
|
||||||
|
return this.chartData
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
return this.chartData.time
|
||||||
|
},
|
||||||
|
// 根据按钮切换生成对应的 chartData
|
||||||
|
chartD() {
|
||||||
|
// 销量场景数据
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
console.log(this.currentDataSource, 'currentDataSource');
|
||||||
|
console.log('this.currentDataSource', data);
|
||||||
|
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
// 1. 完成率(折线图)
|
||||||
|
{
|
||||||
|
name: '完成率',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1, // 绑定右侧Y轴(需在子组件启用配置)
|
||||||
|
lineStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, .5)',
|
||||||
|
width: 2
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderColor: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
radius: 4
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.2,
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||||
|
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: data.proportion || [], // 完成率(%)
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 2. 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||||
|
barWidth: 14,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.targetValue || [] // 目标销量(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图,含达标状态)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 14,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
// 固定label尺寸:68px×20px
|
||||||
|
width: 68,
|
||||||
|
height: 20,
|
||||||
|
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||||
|
formatter: (params) => {
|
||||||
|
const diff = data.diffValue || [];
|
||||||
|
const flags = data.completed || [];
|
||||||
|
const currentDiff = diff[params.dataIndex] || 0;
|
||||||
|
const currentFlag = flags[params.dataIndex] || 0;
|
||||||
|
|
||||||
|
const prefix = currentFlag === 1 ? '+' : '-';
|
||||||
|
|
||||||
|
// 根据标志位选择不同的样式类
|
||||||
|
if (currentFlag === 1) {
|
||||||
|
// 达标 - 使用 rate-achieved 样式
|
||||||
|
return `{achieved|${currentDiff}}{text|差值}`;
|
||||||
|
} else {
|
||||||
|
// 未达标 - 使用 rate-unachieved 样式
|
||||||
|
return `{unachieved|${currentDiff}}{text|差值}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||||
|
{ offset: 0.2, color: '#ffffff' },
|
||||||
|
{ offset: 1, color: '#ffffff' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
shadowColor: 'rgba(191,203,215,0.5)',
|
||||||
|
shadowBlur: 2,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowOffsetY: 2,
|
||||||
|
borderRadius: 4,
|
||||||
|
borderColor: '#BFCBD577',
|
||||||
|
borderWidth: 0,
|
||||||
|
lineHeight: 20,
|
||||||
|
rich: {
|
||||||
|
text: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 10, 5, 0],
|
||||||
|
align: 'center',
|
||||||
|
color: '#464646',
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
},
|
||||||
|
achieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#76DABE', // 与达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
},
|
||||||
|
// 未达标样式
|
||||||
|
unachieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||||
|
const safeFlag = data.completed || [];
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.value || [] // 实际销量(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
return salesData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectProfit(item) {
|
||||||
|
this.selectedProfit = item;
|
||||||
|
this.isDropdownShow = false;
|
||||||
|
this.$emit('handleGetItemData', item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
// 新增:头部行容器,实现一行排列
|
||||||
|
.header-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end; // 左右两端对齐
|
||||||
|
align-items: center; // 垂直居中
|
||||||
|
// width: 100%;
|
||||||
|
margin-bottom: 8px; // 与下方图表区保留间距(可根据需求调整)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 各基地情况标题样式
|
||||||
|
.base-title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0 0 0 16px; // 保留原有内边距
|
||||||
|
white-space: nowrap; // 防止文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
// 移除原有flex和justify-content,由header-row控制
|
||||||
|
width: auto; // 自适应宽度
|
||||||
|
// 保留原有align-items,确保内部元素垂直居中
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; // 图例和按钮组垂直居中
|
||||||
|
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||||
|
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 图例:在右侧容器内横向排列
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px; // 图例项之间间距,避免重叠
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap; // 防止图例文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图例颜色
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.dropdown-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 999; // 提高z-index,确保菜单不被遮挡
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-btn {
|
||||||
|
width: 75px;
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-bottom-left-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-btn {
|
||||||
|
width: 123px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 18px 0 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #3071ff;
|
||||||
|
color: rgba(249, 252, 255, .8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-text {
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 6px solid currentColor;
|
||||||
|
border-top: 4px solid transparent;
|
||||||
|
border-bottom: 4px solid transparent;
|
||||||
|
border-right: 4px solid transparent;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
|
||||||
|
&.rotate {
|
||||||
|
transform: rotate(90deg); // 箭头旋转方向可根据需求调整,比如改为rotate(-90deg)更符合向上展开的视觉
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-options {
|
||||||
|
position: absolute;
|
||||||
|
// 关键修改1:调整top值,让菜单显示在选择框上方,calc(-100% - 2px)表示向上偏移自身100%再加2px间距
|
||||||
|
bottom: 100%;
|
||||||
|
right: 0;
|
||||||
|
// 移除多余的margin-top,避免额外间距
|
||||||
|
// margin-top: 2px;
|
||||||
|
width: 123px;
|
||||||
|
background: #ffffff;
|
||||||
|
// 关键修改2:调整border-radius,让菜单顶部圆角匹配选择框的右上角,底部圆角为0(更美观)
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.dropdown-option {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
color: #3071ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="chartBox" style="width: 100%; height: 108px; position: relative;">
|
||||||
|
<div :id="id" style="width: 100%; height:100%;"></div>
|
||||||
|
<div class="bottomTip">
|
||||||
|
<div class="precent">
|
||||||
|
<span class="precentNum">{{ detailData.completeRate ? detailData.completeRate : 0 }}% </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'EnergyConsumption',
|
||||||
|
// components: { Container },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
detailData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
// electricComu: 0,
|
||||||
|
// steamComu: 20, // 调整为符合max范围的数值(0-8)
|
||||||
|
// // electricity: [120, 150, 130, 180, 160, 200, 190],
|
||||||
|
// // steam: [80, 95, 85, 110, 100, 120, 115],
|
||||||
|
// // dates: ['1日', '2日', '3日', '4日', '5日', '6日', '7日']
|
||||||
|
})
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
default: () => ('monthG')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// electricityChart: null,
|
||||||
|
// steamChart: null,
|
||||||
|
// specialTicks: [2, 4, 6, 8], // 统一的刻度显示
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
detailData: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true, // 初始化时立即执行
|
||||||
|
handler() {
|
||||||
|
this.updateGauges()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initGauges()
|
||||||
|
// window.addEventListener('resize', this.handleResize)
|
||||||
|
this.observeContainerResize()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
observeContainerResize() {
|
||||||
|
// 修复:获取正确的容器(组件内的.gauge-container)
|
||||||
|
const container = this.$el.querySelector('.gauge-container')
|
||||||
|
if (container && window.ResizeObserver) {
|
||||||
|
this.resizeObserver = new ResizeObserver(entries => {
|
||||||
|
if (this.electricityChart) {
|
||||||
|
this.electricityChart.resize() // 直接触发resize,无需防抖
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.resizeObserver.observe(container)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initGauges() {
|
||||||
|
// console.log('this.id',this.id);
|
||||||
|
|
||||||
|
// 初始化电气图表实例
|
||||||
|
const electricityDom = document.getElementById(this.id)
|
||||||
|
if (electricityDom) {
|
||||||
|
// 修复:正确创建并存储图表实例
|
||||||
|
this.electricityChart = echarts.init(electricityDom)
|
||||||
|
// 首次更新数据
|
||||||
|
this.updateGauges()
|
||||||
|
}
|
||||||
|
// 蒸汽图表若未使用,可注释/删除
|
||||||
|
// const steamDom = document.getElementById('steamGauge')
|
||||||
|
// if (steamDom) {
|
||||||
|
// this.steamChart = echarts.init(steamDom)
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
updateGauges() {
|
||||||
|
// 修复:先判断实例是否存在,再更新配置
|
||||||
|
if (!this.electricityChart) return
|
||||||
|
|
||||||
|
// 修复:兜底获取rate值,确保数值有效
|
||||||
|
const rate = Number(this.detailData?.completeRate) || 0
|
||||||
|
console.log('当前rate值:', rate); // 调试:确认rate值正确
|
||||||
|
|
||||||
|
// 关键:第二个参数传true,清空原有配置,强制更新
|
||||||
|
this.electricityChart.setOption(this.getElectricityGaugeOption(rate), true)
|
||||||
|
},
|
||||||
|
// 用电量仪表盘配置(保留原有样式,优化数值范围)
|
||||||
|
getElectricityGaugeOption(value) {
|
||||||
|
const electricityGradient = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: '#0B58FF' },
|
||||||
|
{ offset: 1, color: '#32FFCD' }
|
||||||
|
])
|
||||||
|
|
||||||
|
return {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '月度',
|
||||||
|
type: 'gauge',
|
||||||
|
radius: '95', // 修复:添加%,避免数值错误
|
||||||
|
center: ['50%', '90%'],
|
||||||
|
startAngle: 180,
|
||||||
|
endAngle: 0,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
splitNumber: 4,
|
||||||
|
label: { show: false },
|
||||||
|
progress: {
|
||||||
|
show: true,
|
||||||
|
overlap: false,
|
||||||
|
roundCap: true,
|
||||||
|
clip: false,
|
||||||
|
width: 14,
|
||||||
|
itemStyle: { color: electricityGradient }
|
||||||
|
},
|
||||||
|
pointer: {
|
||||||
|
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
|
||||||
|
length: '75%',
|
||||||
|
width: 16,
|
||||||
|
itemStyle: { color: '#288AFF' },
|
||||||
|
offsetCenter: [0, '10%']
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
roundCap: true,
|
||||||
|
lineStyle: { width: 12, color: [[1, '#E6EBF7']] }
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
length: 10,
|
||||||
|
lineStyle: { width: 5, color: '#D6DAE5' },
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
splitNumber: 2,
|
||||||
|
length: 6,
|
||||||
|
lineStyle: { width: 2, color: '#D6DAE5' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
detail: { show: false },
|
||||||
|
data: [{ value: value, unit: '' }] // 确保数值正确传入
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 未使用的蒸汽仪表盘可注释/删除
|
||||||
|
// getSteamGaugeOption(value) { ... }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
.chartBox {
|
||||||
|
.bottomTip {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.precent {
|
||||||
|
line-height: 3px;
|
||||||
|
|
||||||
|
&::after,
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 54px;
|
||||||
|
height: 5px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
margin-right: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用电量线条颜色
|
||||||
|
.precent::before {
|
||||||
|
|
||||||
|
background: linear-gradient(90deg, rgba(40, 138, 255, 0) 0%, rgba(12, 125, 254, 0.4) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ::after:从 透明 到 rgba(12, 125, 254, 0.4)(90度渐变,左到右) */
|
||||||
|
.precent::after {
|
||||||
|
background: linear-gradient(90deg, rgba(12, 125, 254, 0.4) 0%, rgba(40, 138, 255, 0) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蒸汽线条颜色
|
||||||
|
// .steam-precent::after,
|
||||||
|
// .steam-precent::before {
|
||||||
|
// background: linear-gradient(90deg, rgba(11, 168, 255, 0.26) 0%, rgba(54, 239, 230, 1) 100%);
|
||||||
|
// }
|
||||||
|
|
||||||
|
.precentNum {
|
||||||
|
display: inline-block;
|
||||||
|
// width: 52px;
|
||||||
|
height: 22px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蒸汽数字颜色
|
||||||
|
.steam-num {
|
||||||
|
color: rgba(54, 239, 230, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||||
|
<div class="dashboard">
|
||||||
|
<div class="title">
|
||||||
|
{{ month }}月完成率
|
||||||
|
</div>
|
||||||
|
<div class="number">
|
||||||
|
<div class="yield">
|
||||||
|
{{ formatRate(factoryData?.completeRate) }}%
|
||||||
|
</div>
|
||||||
|
<div class="mom">
|
||||||
|
环比{{ formatRate(factoryData?.thb) }}%
|
||||||
|
<img v-if="factoryData?.thb >= 0" class="arrow" src="../../../assets/img/topArrow.png" alt="上升">
|
||||||
|
<img v-else class="arrow" src="../../../assets/img/downArrow.png" alt="下降">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="electricityGauge">
|
||||||
|
<electricityGauge id="month" :detailData="factoryData"></electricityGauge>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
<div class="line" style="padding: 0px;">
|
||||||
|
<!-- 传递包含flag的factoryData给柱状图组件 -->
|
||||||
|
<verticalBarChart :detailData="factoryData"></verticalBarChart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import electricityGauge from './electricityGauge.vue'
|
||||||
|
import verticalBarChart from './verticalBarChart.vue'
|
||||||
|
// 引入箭头图片(根据实际路径调整,若模板中直接用路径可注释)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, electricityGauge, verticalBarChart },
|
||||||
|
props: {
|
||||||
|
monData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
month: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
/**
|
||||||
|
* 自动提取monData中的工厂数据,并新增flag字段
|
||||||
|
*/
|
||||||
|
factoryData() { // 整合原始数据 + 计算flag
|
||||||
|
return {
|
||||||
|
completeRate: this.monData.proportion ? Number(this.monData.proportion) : 0,
|
||||||
|
diff: this.monData.diffValue,
|
||||||
|
real: this.monData.value,
|
||||||
|
target: this.monData.targetValue,
|
||||||
|
thb: this.monData.thb,
|
||||||
|
// ...rawData,
|
||||||
|
flag: this.monData.completed // 新增flag字段
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 格式化百分比数值:处理空值/非数字,兜底为0
|
||||||
|
*/
|
||||||
|
formatRate(value) {
|
||||||
|
if (isNaN(value) || value === null || value === undefined) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 判断完成率对应的flag值(<100为0,≥100为1)
|
||||||
|
* @param {number} rate 完成率(原始值,如89代表89%)
|
||||||
|
* @returns {0|1} flag值
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
/* 原有样式保持不变 */
|
||||||
|
.scroll-container {
|
||||||
|
max-height: 210px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 264px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 46px;
|
||||||
|
color: #0B58FF;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 箭头样式优化
|
||||||
|
.arrow {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
width: 500px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container :name="title" icon="cockpitItemIcon" size="operatingRevenueBg" topSize="middle">
|
||||||
|
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||||
|
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||||
|
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||||
|
<div class="dashboard left">
|
||||||
|
<div class="title">
|
||||||
|
销量·万元
|
||||||
|
</div>
|
||||||
|
<div class="line">
|
||||||
|
<operatingSingleBar></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dashboard right">
|
||||||
|
<div class="title">
|
||||||
|
单价·万元
|
||||||
|
</div>
|
||||||
|
<div class="line">
|
||||||
|
<operatingSingleBar></operatingSingleBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Container from './container.vue'
|
||||||
|
import operatingSingleBar from './operatingSingleBar.vue'
|
||||||
|
import verticalBarChart from './verticalBarChart.vue'
|
||||||
|
|
||||||
|
// import * as echarts from 'echarts'
|
||||||
|
// import rawItem from './raw-Item.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProductionStatus',
|
||||||
|
components: { Container, operatingSingleBar, verticalBarChart },
|
||||||
|
// mixins: [resize],
|
||||||
|
props: {
|
||||||
|
itemData: { // 接收父组件传递的设备数据数组
|
||||||
|
type: Array,
|
||||||
|
default: () => [] // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
title: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
month: { // 接收父组件传递的设备数据数组
|
||||||
|
type: String,
|
||||||
|
default: () => '' // 默认空数组,避免报错
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
itemData: {
|
||||||
|
handler(newValue, oldValue) {
|
||||||
|
// this.updateChart()
|
||||||
|
},
|
||||||
|
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// computed: {
|
||||||
|
// // 处理排序:包含“总成本”的项放前面,其余项按原顺序排列
|
||||||
|
// sortedItemData() {
|
||||||
|
// // 过滤出包含“总成本”的项(不区分大小写)
|
||||||
|
// const totalCostItems = this.itemData.filter(item =>
|
||||||
|
// item.name && item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 过滤出不包含“总成本”的项
|
||||||
|
// const otherItems = this.itemData.filter(item =>
|
||||||
|
// !item.name || !item.name.includes('总成本')
|
||||||
|
// );
|
||||||
|
// // 合并:总成本项在前,其他项在后
|
||||||
|
// return [...totalCostItems, ...otherItems];
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
mounted() {
|
||||||
|
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||||
|
// this.$nextTick(() => this.updateChart())
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||||
|
.scroll-container {
|
||||||
|
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||||
|
max-height: 210px;
|
||||||
|
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||||
|
overflow-y: auto;
|
||||||
|
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||||
|
/* Chrome/Safari */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* IE/Edge */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
width: 382px;
|
||||||
|
height: 205px;
|
||||||
|
background: #F9FCFF;
|
||||||
|
padding: 16px 0 0 10px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
// width: 190px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
// width: 190px;
|
||||||
|
height: 32px;
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #0B58FF;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mom {
|
||||||
|
width: 120px;
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// .line {
|
||||||
|
// width: 500px;
|
||||||
|
// height: 205px;
|
||||||
|
// background: #F9FCFF;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .leftTitle {
|
||||||
|
// .item {
|
||||||
|
// width: 67px;
|
||||||
|
// height: 180px;
|
||||||
|
// padding: 37px 23px;
|
||||||
|
// background: #F9FCFF;
|
||||||
|
// font-family: PingFangSC, PingFang SC;
|
||||||
|
// font-weight: 400;
|
||||||
|
// font-size: 18px;
|
||||||
|
// color: #000000;
|
||||||
|
// line-height: 25px;
|
||||||
|
// letter-spacing: 1px;
|
||||||
|
// // text-align: left;
|
||||||
|
// font-style: normal;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- <style>
|
||||||
|
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style> -->
|
||||||
@@ -0,0 +1,503 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<div class="header-row">
|
||||||
|
<div class="base-title">各基地情况</div>
|
||||||
|
<div class="barTop">
|
||||||
|
<div class="right-container">
|
||||||
|
<div class="legend">
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon line yield"></span>完成率
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square target"></span>预算
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square achieved"></span>实际·达标
|
||||||
|
</span>
|
||||||
|
<span class="legend-item">
|
||||||
|
<span class="legend-icon square unachieved"></span>实际·未达标
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<div class="item-button category-btn">
|
||||||
|
<span class="item-text">展示顺序</span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-container">
|
||||||
|
<div class="item-button profit-btn active" @click.stop="isDropdownShow = !isDropdownShow">
|
||||||
|
<span class="item-text profit-text">{{ selectedSort || '请选择' }}</span>
|
||||||
|
<span class="dropdown-arrow" :class="{ 'rotate': isDropdownShow }"></span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-options" v-if="isDropdownShow">
|
||||||
|
<div class="dropdown-option" v-for="(item, index) in profitOptions" :key="index"
|
||||||
|
@click.stop="selectProfit(item)">
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="lineBottom" style="height: 100%; width: 100%">
|
||||||
|
<operatingLineBar :dateData="dateData" :chartData="chartD" style="height: 100%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSale.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData",'dateData'],
|
||||||
|
emits: ['sort-change'], // 声明事件(Vue3 推荐)
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
isDropdownShow: false,
|
||||||
|
selectedSort: '实际值:高~低', // 选中的label
|
||||||
|
selectedSortValue: 1, // 选中的value,用于排序逻辑
|
||||||
|
profitOptions: [
|
||||||
|
{ label: '实际值:高~低', value: 1 },
|
||||||
|
{ label: '实际值:低~高', value: 2 },
|
||||||
|
{ label: '完成率:高~低', value: 3 },
|
||||||
|
{ label: '完成率:低~高', value: 4 },
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 排序后的数据源(核心:根据selectedSortValue重新排序)
|
||||||
|
currentDataSource() {
|
||||||
|
if (!this.chartData) return {};
|
||||||
|
|
||||||
|
// 深拷贝原始数据,避免修改原数据
|
||||||
|
const factory = JSON.parse(JSON.stringify(this.chartData));
|
||||||
|
if (!factory.locations.length || !this.selectedSortValue) return factory;
|
||||||
|
|
||||||
|
// 构建带索引的数组,方便同步所有字段排序
|
||||||
|
const dataWithIndex = factory.locations.map((name, index) => ({
|
||||||
|
index,
|
||||||
|
name,
|
||||||
|
real: factory.reals[index],
|
||||||
|
target: factory.targets[index],
|
||||||
|
rate: factory.rate[index],
|
||||||
|
diff: factory.diff[index],
|
||||||
|
flag: factory.flags[index]
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 根据选中的排序规则排序
|
||||||
|
switch (this.selectedSortValue) {
|
||||||
|
case 1: // 实际值:高~低
|
||||||
|
dataWithIndex.sort((a, b) => b.real - a.real);
|
||||||
|
break;
|
||||||
|
case 2: // 实际值:低~高
|
||||||
|
dataWithIndex.sort((a, b) => a.real - b.real);
|
||||||
|
break;
|
||||||
|
case 3: // 预算值:高~低
|
||||||
|
dataWithIndex.sort((a, b) => b.rate - a.rate);
|
||||||
|
break;
|
||||||
|
case 4: // 预算值:低~高
|
||||||
|
dataWithIndex.sort((a, b) => a.rate - b.rate);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步更新所有数组
|
||||||
|
factory.locations = dataWithIndex.map(item => item.name);
|
||||||
|
factory.reals = dataWithIndex.map(item => item.real);
|
||||||
|
factory.targets = dataWithIndex.map(item => item.target);
|
||||||
|
factory.rates = dataWithIndex.map(item => item.rate);
|
||||||
|
factory.diff = dataWithIndex.map(item => item.diff);
|
||||||
|
factory.flags = dataWithIndex.map(item => item.flag);
|
||||||
|
|
||||||
|
return factory;
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
return this.currentDataSource.locations || [];
|
||||||
|
},
|
||||||
|
// 最终传递给图表的排序后数据
|
||||||
|
chartD() {
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
// 完成率(折线图)
|
||||||
|
{
|
||||||
|
name: '完成率',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
lineStyle: { color: 'rgba(40, 138, 255, .5)', width: 2 },
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderColor: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
radius: 4
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.2,
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||||
|
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: data.rate || [],
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 40,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.targets || []
|
||||||
|
},
|
||||||
|
// 实际(柱状图)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 40,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [32, 0],
|
||||||
|
width: 100,
|
||||||
|
height: 22,
|
||||||
|
formatter: (params) => {
|
||||||
|
const diff = data.diff || [];
|
||||||
|
const flags = data.flags || [];
|
||||||
|
const currentDiff = diff[params.dataIndex] || 0;
|
||||||
|
const currentFlag = flags[params.dataIndex] || 0;
|
||||||
|
|
||||||
|
const prefix = currentFlag === 1 ? '+' : '-';
|
||||||
|
|
||||||
|
// 根据标志位选择不同的样式类
|
||||||
|
if (currentFlag === 1) {
|
||||||
|
// 达标 - 使用 rate-achieved 样式
|
||||||
|
return `{achieved|${currentDiff}}{text|差值}`;
|
||||||
|
} else {
|
||||||
|
// 未达标 - 使用 rate-unachieved 样式
|
||||||
|
return `{unachieved|${currentDiff}}{text|差值}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' },
|
||||||
|
{ offset: 0.2, color: '#ffffff' },
|
||||||
|
{ offset: 1, color: '#ffffff' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
shadowColor: 'rgba(191,203,215,0.5)',
|
||||||
|
shadowBlur: 2,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowOffsetY: 2,
|
||||||
|
borderRadius: 4,
|
||||||
|
borderColor: '#BFCBD577',
|
||||||
|
borderWidth: 0,
|
||||||
|
lineHeight: 26,
|
||||||
|
rich: {
|
||||||
|
text: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 10, 5, 0],
|
||||||
|
align: 'center',
|
||||||
|
color: '#464646',
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
achieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#76DABE', // 与达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
// 未达标样式
|
||||||
|
unachieved: {
|
||||||
|
width: 'auto',
|
||||||
|
padding: [5, 0, 5, 10],
|
||||||
|
align: 'center',
|
||||||
|
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
|
||||||
|
fontSize: 14
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const safeFlag = data.flags || [];
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals || []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
return salesData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectProfit(item) {
|
||||||
|
// 更新选中的label和value
|
||||||
|
this.selectedSort = item.label;
|
||||||
|
this.selectedSortValue = item.value;
|
||||||
|
this.isDropdownShow = false;
|
||||||
|
// 向父组件传递排序事件(可选,保持原有逻辑)
|
||||||
|
this.$emit('sort-change', item.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 监听父组件传入的chartData变化,重置选中状态(可选)
|
||||||
|
watch: {
|
||||||
|
'chartData.factory': {
|
||||||
|
handler() {
|
||||||
|
// 若需要切换数据源后重置排序,可取消注释
|
||||||
|
// this.selectedSort = null;
|
||||||
|
// this.selectedSortValue = null;
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.header-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0 0 0 16px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
width: auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
margin-right: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.dropdown-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-btn {
|
||||||
|
width: 75px;
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-bottom-left-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-btn {
|
||||||
|
width: 123px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 18px 0 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0b58ff;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #3071ff;
|
||||||
|
color: rgba(249, 252, 255, .8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-text {
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 6px solid currentColor;
|
||||||
|
border-top: 4px solid transparent;
|
||||||
|
border-bottom: 4px solid transparent;
|
||||||
|
border-right: 4px solid transparent;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
|
||||||
|
&.rotate {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-options {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
right: 0;
|
||||||
|
margin-top: 2px;
|
||||||
|
width: 123px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.dropdown-option {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
color: #3071ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChipBottom" id="cockpitEffChipBottom" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
series: [],
|
||||||
|
allPlaceNames: []
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
validator: (value) => {
|
||||||
|
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChipBottom;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatter: (params) => {
|
||||||
|
let html = `${params[0].axisValue}<br/>`;
|
||||||
|
params.forEach(item => {
|
||||||
|
const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
);
|
||||||
|
html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 60,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
splitNumber: 4,
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: (value) => Math.ceil((value.max || 0) * 1.1),
|
||||||
|
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
scale:true,
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
series: [],
|
||||||
|
allPlaceNames: []
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
validator: (value) => {
|
||||||
|
return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 60,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
// {
|
||||||
|
// type: 'value',
|
||||||
|
// nameTextStyle: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// align: 'left'
|
||||||
|
// },
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
// axisTick: { show: false },
|
||||||
|
// axisLabel: {
|
||||||
|
// color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
// fontSize: 12,
|
||||||
|
// formatter: '{value}%'
|
||||||
|
// },
|
||||||
|
// splitLine: { show: false },
|
||||||
|
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
// splitNumber: 4
|
||||||
|
// }
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 400px;"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null, // 存储图表实例
|
||||||
|
resizeHandler: null, // 存储resize事件处理函数
|
||||||
|
// 核心:基地名称与序号的映射表(固定顺序)
|
||||||
|
baseNameToIndexMap: {
|
||||||
|
'宜兴': 7,
|
||||||
|
'漳州': 8,
|
||||||
|
'自贡': 3,
|
||||||
|
'桐城': 2,
|
||||||
|
'洛阳': 9,
|
||||||
|
'合肥': 5,
|
||||||
|
'宿迁': 6,
|
||||||
|
'秦皇岛': 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.initChart(); // 初始化图表(只执行一次)
|
||||||
|
this.updateChart(); // 更新图表数据
|
||||||
|
});
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
this.updateChart(); // 仅更新数据,不重新创建实例
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 组件销毁时清理资源
|
||||||
|
this.destroyChart();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 初始化图表(只在mounted中执行一次)
|
||||||
|
initChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
this.myChart.getZr().on('click', (params) => {
|
||||||
|
console.log('params', params);
|
||||||
|
|
||||||
|
// 提取点击的基地名称
|
||||||
|
// const itemName = params.name;
|
||||||
|
let itemName = undefined
|
||||||
|
// 根据映射表获取对应的序号(未匹配到则返回0或其他默认值)
|
||||||
|
let pointInPixel = [params.offsetX, params.offsetY];
|
||||||
|
if (this.myChart.containPixel('grid', pointInPixel)) {
|
||||||
|
let pointInGrid = this.myChart.convertFromPixel({
|
||||||
|
seriesIndex: 0
|
||||||
|
}, pointInPixel);
|
||||||
|
let xIndex = pointInGrid[0]; //索引
|
||||||
|
let handleIndex = Number(xIndex);
|
||||||
|
let seriesObj = this.myChart.getOption(); //图表object对象
|
||||||
|
var op = this.myChart.getOption();
|
||||||
|
//获得图表中点击的列
|
||||||
|
itemName = op.xAxis[0].data[handleIndex]; //获取点击的列名
|
||||||
|
console.log(itemName, 'monthmonthmonth');
|
||||||
|
console.log(handleIndex, seriesObj);
|
||||||
|
};
|
||||||
|
const baseIndex = this.baseNameToIndexMap[itemName] || 0;
|
||||||
|
|
||||||
|
console.log(`你点击了【${itemName}】(序号:${baseIndex})`);
|
||||||
|
if (itemName === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 路由跳转时携带序号(或名称+序号)
|
||||||
|
this.$router.push({
|
||||||
|
path: 'totalProfitBase',
|
||||||
|
query: { // 使用query传递参数(推荐),也可使用params
|
||||||
|
// baseName: itemName,
|
||||||
|
factory: baseIndex,
|
||||||
|
dateData: this.dateData
|
||||||
|
}
|
||||||
|
// 若仍需用base作为参数:
|
||||||
|
// base: itemName,
|
||||||
|
// params: { baseIndex: baseIndex }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定义resize处理函数(命名函数,方便移除)
|
||||||
|
this.resizeHandler = () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
};
|
||||||
|
// 绑定resize事件(只绑定一次)
|
||||||
|
window.addEventListener('resize', this.resizeHandler);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新图表数据(数据变化时执行)
|
||||||
|
updateChart() {
|
||||||
|
if (!this.myChart) {
|
||||||
|
return; // 实例未初始化则返回
|
||||||
|
}
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || [];
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 5,
|
||||||
|
right: 20,
|
||||||
|
left: 25,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0],
|
||||||
|
// 可选:X轴标签显示“序号+名称”(如“1 宜兴”)
|
||||||
|
// formatter: (value) => {
|
||||||
|
// const index = this.baseNameToIndexMap[value] || '';
|
||||||
|
// return index ? `${index} ${value}` : value;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
splitNumber: 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries
|
||||||
|
};
|
||||||
|
|
||||||
|
// 只更新配置,不重新创建实例
|
||||||
|
this.myChart.setOption(option, true); // 第二个参数true表示清空原有配置,避免数据残留
|
||||||
|
},
|
||||||
|
|
||||||
|
// 销毁图表资源
|
||||||
|
destroyChart() {
|
||||||
|
// 移除resize事件
|
||||||
|
if (this.resizeHandler) {
|
||||||
|
window.removeEventListener('resize', this.resizeHandler);
|
||||||
|
}
|
||||||
|
// 销毁图表实例
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
this.myChart = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 380px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData, 'chartData');
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
console.log('chartData', this.chartData);
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
console.log('xData', xData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 20,
|
||||||
|
right: 10,
|
||||||
|
left: 25,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
// 左侧Y轴:营业收入、成本(单位万元)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 4,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
},
|
||||||
|
// 右侧Y轴:利润占比(百分比)
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
// min: 0,
|
||||||
|
// max: 100,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}%'
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
//
|
||||||
|
splitNumber: 4,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 200px;"></div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
myChart: null // 存储图表实例,避免重复创建
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 明确接收的props结构,增强可读性
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
}),
|
||||||
|
// 校验数据格式
|
||||||
|
// validator: (value) => {
|
||||||
|
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateChart();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:监听 chartData 变化
|
||||||
|
watch: {
|
||||||
|
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||||
|
chartData: {
|
||||||
|
handler() {
|
||||||
|
console.log(this.chartData,'chartData');
|
||||||
|
this.updateChart();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true // 初始化时立即执行
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart() {
|
||||||
|
const chartDom = this.$refs.cockpitEffChip;
|
||||||
|
if (!chartDom) {
|
||||||
|
console.error('图表容器未找到!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.myChart) {
|
||||||
|
this.myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
const { allPlaceNames, series } = this.chartData || {};
|
||||||
|
console.log('chartData', this.chartData);
|
||||||
|
|
||||||
|
// 处理空数据
|
||||||
|
const xData = allPlaceNames || [];
|
||||||
|
const chartSeries = series || []; // 父组件传递的 series
|
||||||
|
console.log('xData', xData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
// axisPointer: {
|
||||||
|
// type: 'cross',
|
||||||
|
// label: {
|
||||||
|
// backgroundColor: '#6a7985'
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// formatter: (params) => {
|
||||||
|
// let html = `${params[0].axisValue}<br/>`;
|
||||||
|
// params.forEach(item => {
|
||||||
|
// const unit = item.seriesName === '完成率' ? '%' : (
|
||||||
|
// ['产量', '销量'].includes(this.$parent.selectedProfit) ? '片' : '万元'
|
||||||
|
// );
|
||||||
|
// html += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>`;
|
||||||
|
// });
|
||||||
|
// return html;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 25,
|
||||||
|
bottom: 25,
|
||||||
|
right: 10,
|
||||||
|
left: 2,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
// offset: 10
|
||||||
|
// boundaryGap: ['50%', '50%'],
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.15)' }
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
padding: [5, 0, 0, 0]
|
||||||
|
},
|
||||||
|
data: xData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
// name: '万元',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true, // 显示Y轴轴线(关键)
|
||||||
|
lineStyle: {
|
||||||
|
color: '#E5E6EB', // 轴线颜色(浅灰色,可自定义)
|
||||||
|
width: 1, // 轴线宽度
|
||||||
|
type: 'solid' // 实线(可选:dashed虚线、dotted点线)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
splitNumber: 2,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
// axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||||
|
},
|
||||||
|
series: chartSeries // 直接使用父组件传递的 series
|
||||||
|
};
|
||||||
|
|
||||||
|
option && this.myChart.setOption(option);
|
||||||
|
|
||||||
|
// 窗口缩放适配和销毁逻辑保持不变
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
this.myChart && this.myChart.resize();
|
||||||
|
});
|
||||||
|
this.myChart && this.myChart.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,313 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="当月数据对比" icon="cockpitItemIcon" size="operatingLarge" topSize="large">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="left" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 348px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
">
|
||||||
|
集团情况
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{chartData.topBarData.rate[0]}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.topBarData.flags>0?'#30B590':'#FF9423'}" >{{chartData.topBarData.diff[0]}}</span></span>
|
||||||
|
</div>
|
||||||
|
<operatingTopBar :chartData="chartData?.topBarData || {}" />
|
||||||
|
</div>
|
||||||
|
<div class="right" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 1220px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<operatingBar :dateData="dateData" :chartData="chartData?.barData || {}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import operatingBar from "./operatingBar.vue";
|
||||||
|
import operatingTopBar from "./operatingTopBar.vue";
|
||||||
|
|
||||||
|
// 序号→地名映射表(levelId=序号)
|
||||||
|
const baseIndexToNameMap = {
|
||||||
|
7: "宜兴",
|
||||||
|
8: "漳州",
|
||||||
|
3: "自贡",
|
||||||
|
2: "桐城",
|
||||||
|
9: "洛阳",
|
||||||
|
5: "合肥",
|
||||||
|
10: "秦皇岛",
|
||||||
|
6: "宿迁"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, operatingBar, operatingTopBar },
|
||||||
|
props: {
|
||||||
|
monData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: {
|
||||||
|
topBarData: { // levelId=1的整合数据
|
||||||
|
locations: [], // 固定为["凯盛新能"]
|
||||||
|
diff: [], // 差值数组
|
||||||
|
targets: [], // 预算值数组
|
||||||
|
reals: [], // 实际值数组
|
||||||
|
rate: [], // 完成率数组
|
||||||
|
flags: [] // 完成状态数组(0/1)
|
||||||
|
},
|
||||||
|
barData: { // levelId≠1的整合数据
|
||||||
|
locations: [], // levelId对应的baseIndexToNameMap中的地名
|
||||||
|
diff: [], // 对应差值数组
|
||||||
|
targets: [], // 预算值数组
|
||||||
|
reals: [], // 实际值数组
|
||||||
|
rate: [], // 完成率数组
|
||||||
|
flags: [] // 完成状态数组(0/1)
|
||||||
|
// baseIndexes: []// 对应levelId(序号)数组
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
monData: {
|
||||||
|
handler() {
|
||||||
|
this.processChartData();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 核心方法:按levelId匹配地名生成locations
|
||||||
|
*/
|
||||||
|
processChartData() {
|
||||||
|
// 初始化空数据结构
|
||||||
|
const initTopBarData = {
|
||||||
|
locations: [], diff: [], targets: [], reals: [], rate: [], flags: []
|
||||||
|
};
|
||||||
|
const initBarData = { locations: [], diff: [], targets: [], reals: [], rate: [], flags: [] };
|
||||||
|
|
||||||
|
if (!Array.isArray(this.monData) || this.monData.length === 0) {
|
||||||
|
this.chartData = { topBarData: initTopBarData, barData: initBarData };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 处理levelId=1的整合数据(逻辑不变)
|
||||||
|
const level1Data = this.monData.filter(item => item.levelId === 1);
|
||||||
|
const topBarData = { ...initTopBarData };
|
||||||
|
level1Data.forEach(item => {
|
||||||
|
if (!item.name) return;
|
||||||
|
topBarData.locations = ["凯盛新能"]; // levelId=1固定为凯盛新能
|
||||||
|
topBarData.diff.push(item.diffValue || 0);
|
||||||
|
topBarData.targets.push(item.targetValue || 0);
|
||||||
|
topBarData.reals.push(item.value || 0);
|
||||||
|
topBarData.rate.push(item.proportion || 0);
|
||||||
|
topBarData.flags.push(item.completed ? 1 : 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 处理levelId≠1的整合数据(核心:levelId匹配地名)
|
||||||
|
const barData = { ...initBarData };
|
||||||
|
// 筛选有效数据:levelId≠1 且 levelId在baseIndexToNameMap中
|
||||||
|
const validOtherData = this.monData.filter(item => {
|
||||||
|
return item.levelId !== 1 && baseIndexToNameMap.hasOwnProperty(item.levelId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 遍历有效数据,填充locations(levelId→地名)
|
||||||
|
validOtherData.forEach(item => {
|
||||||
|
// 根据levelId(序号)从映射表获取对应地名
|
||||||
|
const baseName = baseIndexToNameMap[item.levelId];
|
||||||
|
if (baseName) { // 确保地名和原始名称有效
|
||||||
|
// barData.names.push(item.name); // 保留monData中的原始名称
|
||||||
|
barData.locations.push(baseName); // locations=levelId对应的地名(如levelId=7→宜兴)
|
||||||
|
barData.diff.push(item.diffValue || 0);
|
||||||
|
barData.targets.push(item.targetValue || 0);
|
||||||
|
barData.reals.push(item.value || 0);
|
||||||
|
barData.rate.push(item.proportion || 0);
|
||||||
|
barData.flags.push(item.completed ? 1 : 0);
|
||||||
|
// barData.baseIndexes.push(Number(item.levelId) || 0); // 序号转数字
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. 更新chartData
|
||||||
|
this.chartData = { topBarData, barData };
|
||||||
|
console.log('levelId=1数据:', this.chartData.topBarData);
|
||||||
|
console.log('levelId≠1数据(locations=levelId对应地名):', this.chartData.barData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 原有样式保持不变 */
|
||||||
|
.scroll-container {
|
||||||
|
max-height: 210px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px 27px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
width: 21px;
|
||||||
|
height: 23px;
|
||||||
|
background: rgba(0, 106, 205, 0.22);
|
||||||
|
backdrop-filter: blur(1.5px);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #68b5ff;
|
||||||
|
line-height: 23px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eq-name {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eqStatus {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitLine {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
border: 1px solid #adadad;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #00ffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoLeft {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoRight {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarWrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLine {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #acacac 100%);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLineTop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg,
|
||||||
|
rgba(53, 223, 247, 0) 0%,
|
||||||
|
rgba(54, 220, 246, 0.92) 92%,
|
||||||
|
#36f6e5 100%,
|
||||||
|
#37acf5 100%);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chartImgBottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 45px;
|
||||||
|
left: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
left: 57px;
|
||||||
|
bottom: 42px;
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #00e8ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,313 @@
|
|||||||
|
<template>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<Container name="累计数据对比" icon="cockpitItemIcon" size="operatingLarge" topSize="large">
|
||||||
|
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%; gap: 16px">
|
||||||
|
<div class="left" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 348px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
padding: 16px 16px 5px 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-style: normal;
|
||||||
|
">
|
||||||
|
集团情况
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 16px;line-height: 16px;text-align: right;padding-right: 16px;'>
|
||||||
|
<span>完成率:<span style='color: #0B58FF;'>{{chartData.topBarData.rate[0]}}%</span></span>
|
||||||
|
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:chartData.topBarData.flags>0?'#30B590':'#FF9423'}" >{{chartData.topBarData.diff[0]}}</span></span>
|
||||||
|
</div>
|
||||||
|
<operatingTopBar :chartData="chartData?.topBarData || {}" />
|
||||||
|
</div>
|
||||||
|
<div class="right" style="
|
||||||
|
height: 380px;
|
||||||
|
display: flex;
|
||||||
|
width: 1220px;
|
||||||
|
background-color: rgba(249, 252, 255, 1);
|
||||||
|
">
|
||||||
|
<operatingBar :dateData="dateData" :chartData="chartData?.barData || {}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Container from "../components/container.vue";
|
||||||
|
import operatingBar from "./operatingBar.vue";
|
||||||
|
import operatingTopBar from "./operatingTopBar.vue";
|
||||||
|
|
||||||
|
// 序号→地名映射表(levelId=序号)
|
||||||
|
const baseIndexToNameMap = {
|
||||||
|
7: "宜兴",
|
||||||
|
8: "漳州",
|
||||||
|
3: "自贡",
|
||||||
|
2: "桐城",
|
||||||
|
9: "洛阳",
|
||||||
|
5: "合肥",
|
||||||
|
10: "秦皇岛",
|
||||||
|
6: "宿迁"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductionStatus",
|
||||||
|
components: { Container, operatingBar, operatingTopBar },
|
||||||
|
props: {
|
||||||
|
totalData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
dateData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: {
|
||||||
|
topBarData: { // levelId=1的整合数据
|
||||||
|
locations: [], // 固定为["凯盛新能"]
|
||||||
|
diff: [], // 差值数组
|
||||||
|
targets: [], // 预算值数组
|
||||||
|
reals: [], // 实际值数组
|
||||||
|
rate: [], // 完成率数组
|
||||||
|
flags: [] // 完成状态数组(0/1)
|
||||||
|
},
|
||||||
|
barData: { // levelId≠1的整合数据
|
||||||
|
locations: [], // levelId对应的baseIndexToNameMap中的地名
|
||||||
|
diff: [], // 对应差值数组
|
||||||
|
targets: [], // 预算值数组
|
||||||
|
reals: [], // 实际值数组
|
||||||
|
rate: [], // 完成率数组
|
||||||
|
flags: [] // 完成状态数组(0/1)
|
||||||
|
// baseIndexes: []// 对应levelId(序号)数组
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
totalData: {
|
||||||
|
handler() {
|
||||||
|
this.processChartData();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 核心方法:按levelId匹配地名生成locations
|
||||||
|
*/
|
||||||
|
processChartData() {
|
||||||
|
// 初始化空数据结构
|
||||||
|
const initTopBarData = {
|
||||||
|
locations: [], diff: [], targets: [], reals: [], rate: [], flags: []
|
||||||
|
};
|
||||||
|
const initBarData = { locations: [], diff: [], targets: [], reals: [], rate: [], flags: [] };
|
||||||
|
|
||||||
|
if (!Array.isArray(this.totalData) || this.totalData.length === 0) {
|
||||||
|
this.chartData = { topBarData: initTopBarData, barData: initBarData };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 处理levelId=1的整合数据(逻辑不变)
|
||||||
|
const level1Data = this.totalData.filter(item => item.levelId === 1);
|
||||||
|
const topBarData = { ...initTopBarData };
|
||||||
|
level1Data.forEach(item => {
|
||||||
|
if (!item.name) return;
|
||||||
|
topBarData.locations = ["凯盛新能"]; // levelId=1固定为凯盛新能
|
||||||
|
topBarData.diff.push(item.diffValue || 0);
|
||||||
|
topBarData.targets.push(item.targetValue || 0);
|
||||||
|
topBarData.reals.push(item.value || 0);
|
||||||
|
topBarData.rate.push(item.proportion || 0);
|
||||||
|
topBarData.flags.push(item.completed ? 1 : 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 处理levelId≠1的整合数据(核心:levelId匹配地名)
|
||||||
|
const barData = { ...initBarData };
|
||||||
|
// 筛选有效数据:levelId≠1 且 levelId在baseIndexToNameMap中
|
||||||
|
const validOtherData = this.totalData.filter(item => {
|
||||||
|
return item.levelId !== 1 && baseIndexToNameMap.hasOwnProperty(item.levelId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 遍历有效数据,填充locations(levelId→地名)
|
||||||
|
validOtherData.forEach(item => {
|
||||||
|
// 根据levelId(序号)从映射表获取对应地名
|
||||||
|
const baseName = baseIndexToNameMap[item.levelId];
|
||||||
|
if (baseName) { // 确保地名和原始名称有效
|
||||||
|
// barData.names.push(item.name); // 保留monData中的原始名称
|
||||||
|
barData.locations.push(baseName); // locations=levelId对应的地名(如levelId=7→宜兴)
|
||||||
|
barData.diff.push(item.diffValue || 0);
|
||||||
|
barData.targets.push(item.targetValue || 0);
|
||||||
|
barData.reals.push(item.value || 0);
|
||||||
|
barData.rate.push(item.proportion || 0);
|
||||||
|
barData.flags.push(item.completed ? 1 : 0);
|
||||||
|
// barData.baseIndexes.push(Number(item.levelId) || 0); // 序号转数字
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. 更新chartData
|
||||||
|
this.chartData = { topBarData, barData };
|
||||||
|
console.log('levelId=1数据:', this.chartData.topBarData);
|
||||||
|
console.log('levelId≠1数据(locations=levelId对应地名):', this.chartData.barData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 原有样式保持不变 */
|
||||||
|
.scroll-container {
|
||||||
|
max-height: 210px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px 27px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
width: 21px;
|
||||||
|
height: 23px;
|
||||||
|
background: rgba(0, 106, 205, 0.22);
|
||||||
|
backdrop-filter: blur(1.5px);
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #68b5ff;
|
||||||
|
line-height: 23px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eq-name {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eqStatus {
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitLine {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
border: 1px solid #adadad;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #00ffff;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoLeft {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarInfoEqInfoRight {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarWrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLine {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg, rgba(82, 82, 82, 0) 0%, #acacac 100%);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proBarLineTop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(65deg,
|
||||||
|
rgba(53, 223, 247, 0) 0%,
|
||||||
|
rgba(54, 220, 246, 0.92) 92%,
|
||||||
|
#36f6e5 100%,
|
||||||
|
#37acf5 100%);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chartImgBottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 45px;
|
||||||
|
left: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
left: 57px;
|
||||||
|
bottom: 42px;
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #00e8ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.production-status-chart-tooltip {
|
||||||
|
background: #0a2b4f77 !important;
|
||||||
|
border: none !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.production-status-chart-tooltip * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<template>
|
||||||
|
<div class="lineBottom" style="height: 160px; width: 100%">
|
||||||
|
<operatingLineBarSaleSingle :refName="'totalOperating'" :chartData="chartD" style="height: 100%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBarSaleSingle from './operatingLineBarSaleSingle.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBarSaleSingle },
|
||||||
|
props: ["detailData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
locations() {
|
||||||
|
return ['预算', '实际'];
|
||||||
|
},
|
||||||
|
chartD() {
|
||||||
|
// 背景图片路径(若不需要可注释)
|
||||||
|
// const bgImageUrl = require('@/assets/img/labelBg.png');
|
||||||
|
const rate = this.detailData?.proportion? Number(this.detailData?.proportion) : 0
|
||||||
|
const diff = this.detailData?.diffValue || 0
|
||||||
|
console.log('diff', diff);
|
||||||
|
|
||||||
|
const seriesData = [
|
||||||
|
{
|
||||||
|
value: this.detailData?.targetValue || 0,
|
||||||
|
flag: 1, // 实际项:达标(绿色)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: this.detailData?.value || 0,
|
||||||
|
flag: this.detailData?.completed, // 实际项:达标(绿色)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
offset: [0, 0],
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
allPlaceNames: ['预算', '实际'],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 60,
|
||||||
|
barCategoryGap: '50%',
|
||||||
|
data: seriesData,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const currentFlag = params.data.flag || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
console.log('data', data);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFangSC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
margin-right: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
width: 283px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0b58ff;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 8px;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button.active {
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
background: #3071ff;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,365 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coreBar">
|
||||||
|
<div class="lineBottom" style="height: 320px; width: 100%">
|
||||||
|
<operatingLineBar :chartData="chartD" style="height: 99%; width: 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import operatingLineBar from './operatingLineBarSaleGroup.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Container",
|
||||||
|
components: { operatingLineBar },
|
||||||
|
props: ["chartData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeButton: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentDataSource() {
|
||||||
|
console.log('yyyy', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData
|
||||||
|
},
|
||||||
|
locations() {
|
||||||
|
console.log('this.1111', this.chartData);
|
||||||
|
|
||||||
|
return this.chartData.locations
|
||||||
|
},
|
||||||
|
// 根据按钮切换生成对应的 chartData
|
||||||
|
chartD() {
|
||||||
|
// 销量场景数据
|
||||||
|
const data = this.currentDataSource;
|
||||||
|
const diff = data.diff[0]
|
||||||
|
const rate = data.rate[0]
|
||||||
|
console.log(this.currentDataSource, 'currentDataSource');
|
||||||
|
|
||||||
|
const salesData = {
|
||||||
|
allPlaceNames: this.locations,
|
||||||
|
series: [
|
||||||
|
// 1. 完成率(折线图)
|
||||||
|
// {
|
||||||
|
// name: '完成率',
|
||||||
|
// type: 'line',
|
||||||
|
// yAxisIndex: 1, // 绑定右侧Y轴(需在子组件启用配置)
|
||||||
|
// lineStyle: {
|
||||||
|
// color: 'rgba(40, 138, 255, .5)',
|
||||||
|
// width: 2
|
||||||
|
// },
|
||||||
|
// itemStyle: {
|
||||||
|
// color: 'rgba(40, 138, 255, 1)',
|
||||||
|
// borderColor: 'rgba(40, 138, 255, 1)',
|
||||||
|
// borderWidth: 2,
|
||||||
|
// radius: 4
|
||||||
|
// },
|
||||||
|
// areaStyle: {
|
||||||
|
// opacity: 0.2,
|
||||||
|
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
// { offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||||
|
// { offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||||
|
// ])
|
||||||
|
// },
|
||||||
|
// data: data.rates, // 完成率(%)
|
||||||
|
// symbol: 'circle',
|
||||||
|
// symbolSize: 6
|
||||||
|
// },
|
||||||
|
// 2. 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0, // 左侧Y轴(万元)
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.targets // 目标销量(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图,含达标状态)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
barWidth: 65,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||||
|
const safeFlag = data.flags;
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: data.reals // 实际销量(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 毛利率场景数据
|
||||||
|
const grossProfitData = {
|
||||||
|
series: [
|
||||||
|
// 1. 完成率(折线图)
|
||||||
|
{
|
||||||
|
name: '完成率',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
lineStyle: { color: 'rgba(40, 138, 255, .5)', width: 2 },
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderColor: 'rgba(40, 138, 255, 1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
radius: 4
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.2,
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(40, 138, 255, .9)' },
|
||||||
|
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: [106.7, 96.9, 106.5, 106.1, 93.8, 105.9], // 毛利率完成率(%)
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
// 2. 目标(柱状图)
|
||||||
|
{
|
||||||
|
name: '预算',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 14,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(130, 204, 255, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 157, 255, 1)' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: [30, 32, 31, 33, 32, 34] // 目标毛利率(万元)
|
||||||
|
},
|
||||||
|
// 3. 实际(柱状图)
|
||||||
|
{
|
||||||
|
name: '实际',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: 14,
|
||||||
|
itemStyle: {
|
||||||
|
color: (params) => {
|
||||||
|
const safeFlag = [1, 0, 1, 1, 0, 1]; // 达标状态
|
||||||
|
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||||
|
return currentFlag === 1
|
||||||
|
? {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(174, 239, 224, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(118, 218, 190, 1)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(253, 209, 129, 1)' },
|
||||||
|
{ offset: 1, color: 'rgba(249, 164, 74, 1)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
data: [32, 31, 33, 35, 30, 36] // 实际毛利率(万元)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据按钮状态返回对应数据
|
||||||
|
return this.activeButton === 0 ? salesData : grossProfitData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.coreBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.barTop {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end; // 标题左、右侧容器右,整体两端对齐
|
||||||
|
align-items: center; // 垂直居中,避免上下错位
|
||||||
|
gap: 16px; // 标题与右侧容器的最小间距,防止拥挤
|
||||||
|
width: 100%; // 确保占满父容器,实现两端对齐
|
||||||
|
|
||||||
|
.title {
|
||||||
|
height: 18px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
// 标题固定在左侧,不挤压右侧空间
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 右侧容器:包裹图例和按钮组,整体靠右
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; // 图例和按钮组垂直居中
|
||||||
|
gap: 24px; // 图例与按钮组的间距,避免贴紧
|
||||||
|
margin-right: 46px; // 右侧整体留边,与原按钮组边距一致
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 图例:在右侧容器内横向排列
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px; // 图例项之间间距,避免重叠
|
||||||
|
align-items: center;
|
||||||
|
// 移除原margin-left,避免位置偏移
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: left;
|
||||||
|
font-style: normal;
|
||||||
|
white-space: nowrap; // 防止图例文字换行
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.line {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图例颜色
|
||||||
|
.yield {
|
||||||
|
background: rgba(40, 138, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
background: #2889FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achieved {
|
||||||
|
background: rgba(40, 203, 151, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unachieved {
|
||||||
|
background: rgba(255, 132, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 按钮组:在右侧容器内,保留原有样式
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
gap: 2px;
|
||||||
|
width: 283px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
background: #ecf4fe;
|
||||||
|
border-radius: 12px;
|
||||||
|
// 移除原margin-right,由右侧容器统一控制
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.item-button {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0b58ff;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 8px; // 确保文字间距生效
|
||||||
|
padding-left: 8px; // 抵消letter-spacing导致的文字左偏
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-button.active {
|
||||||
|
width: 142px;
|
||||||
|
height: 24px;
|
||||||
|
background: #3071ff;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user