制造成本分析修改
This commit is contained in:
4
.env.dev
4
.env.dev
@@ -12,9 +12,9 @@ VUE_APP_TITLE = 洛玻集团驾驶舱
|
||||
# 杨姗姗
|
||||
# VUE_APP_BASE_API = 'http://172.16.20.218:7070'
|
||||
# 小田
|
||||
# VUE_APP_BASE_API = 'http://172.16.19.232:7070'
|
||||
VUE_APP_BASE_API = 'http://172.16.19.232:7070'
|
||||
# 测试
|
||||
VUE_APP_BASE_API = 'http://192.168.0.35:8080'
|
||||
# VUE_APP_BASE_API = 'http://192.168.0.35:8080'
|
||||
|
||||
|
||||
# 路由懒加载
|
||||
|
||||
@@ -8,11 +8,8 @@ import { isRelogin } from '@/utils/request'
|
||||
|
||||
NProgress.configure({ showSpinner: false })
|
||||
|
||||
/**
|
||||
* 从菜单树中查找 jumpFlag===1 的默认跳转路径
|
||||
* 若打标在父节点则取其下第一个可见叶子路径;若在叶子则直接返回该路径
|
||||
*/
|
||||
function findDefaultPathFromMenus(menus) {
|
||||
// 从菜单树中查找第一个可见叶子菜单路径(按接口返回顺序)
|
||||
function findFirstLeafPathFromMenus(menus) {
|
||||
if (!Array.isArray(menus) || menus.length === 0) return null
|
||||
|
||||
function dfs(list, parentPath = '') {
|
||||
@@ -23,16 +20,11 @@ function findDefaultPathFromMenus(menus) {
|
||||
? rawPath
|
||||
: `${parentPath}/${rawPath}`.replace(/\/+/g, '/')
|
||||
|
||||
if (item.jumpFlag === 1) {
|
||||
if (item.children && item.children.length > 0) {
|
||||
const childPath = dfs(item.children, currentPath)
|
||||
return childPath != null ? childPath : currentPath
|
||||
}
|
||||
return currentPath
|
||||
}
|
||||
if (item.children && item.children.length > 0) {
|
||||
const found = dfs(item.children, currentPath)
|
||||
if (found != null) return found
|
||||
} else {
|
||||
return currentPath
|
||||
}
|
||||
}
|
||||
return null
|
||||
@@ -40,6 +32,24 @@ function findDefaultPathFromMenus(menus) {
|
||||
return dfs(menus)
|
||||
}
|
||||
|
||||
function findFirstLeafPathFromRoutes(routes) {
|
||||
if (!Array.isArray(routes) || routes.length === 0) return null
|
||||
const stack = [...routes]
|
||||
while (stack.length) {
|
||||
const route = stack.shift()
|
||||
if (!route || route.hidden === true) continue
|
||||
if (Array.isArray(route.children) && route.children.length > 0) {
|
||||
stack.unshift(...route.children)
|
||||
continue
|
||||
}
|
||||
const p = route.path || ''
|
||||
if (typeof p === 'string' && p.startsWith('/') && p !== '/404') {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 增加三方登陆 update by 芋艿
|
||||
const whiteList = ['/login', '/social-login', '/auth-redirect', '/bind', '/register', '/oauthLogin/gitee']
|
||||
|
||||
@@ -65,11 +75,16 @@ router.beforeEach((to, from, next) => {
|
||||
store.dispatch('GenerateRoutes', userInfo.menus).then(accessRoutes => {
|
||||
// 根据 roles 权限生成可访问的路由表
|
||||
router.addRoutes(accessRoutes) // 动态添加可访问路由表
|
||||
const defaultPath = findDefaultPathFromMenus(userInfo.menus) || '/'
|
||||
const menuDefaultPath = findFirstLeafPathFromMenus(userInfo.menus)
|
||||
const routeFallbackPath = findFirstLeafPathFromRoutes(accessRoutes)
|
||||
const defaultPath = menuDefaultPath || routeFallbackPath || '/'
|
||||
store.dispatch('SetDefaultPath', defaultPath)
|
||||
// 仅当目标为根路径 '/' 时跳默认页;否则保持当前路径(含刷新场景),避免刷新被误判为“未匹配”而跳到默认页
|
||||
if (to.path === '/') {
|
||||
next({ path: defaultPath, replace: true })
|
||||
const matched = router.match(defaultPath)
|
||||
const hasMatch = matched && Array.isArray(matched.matched) && matched.matched.length > 0
|
||||
const safePath = hasMatch ? defaultPath : (routeFallbackPath || '/')
|
||||
next({ path: safePath, replace: true })
|
||||
} else {
|
||||
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成,当前页刷新时保留 to 的路径
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
<div class="bullshit__info">
|
||||
对不起,您正在寻找的页面不存在。尝试检查URL的错误,然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容。
|
||||
</div>
|
||||
<router-link to="/" class="bullshit__return-home">
|
||||
返回首页
|
||||
<router-link to="/login" class="bullshit__return-home">
|
||||
返回登录页
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
@@ -7,10 +7,21 @@
|
||||
@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',
|
||||
@@ -27,6 +38,7 @@ const tableProps = [
|
||||
];
|
||||
export default {
|
||||
name: 'CustomerInfoConfiguration',
|
||||
components:{CustomerInfoAdd},
|
||||
data () {
|
||||
return {
|
||||
formConfig: [
|
||||
@@ -64,7 +76,7 @@ export default {
|
||||
pageNo:1
|
||||
},
|
||||
total:2,
|
||||
tableData:[{name:'111'}],
|
||||
tableData:[{name:'111',id:1}],
|
||||
tableProps,
|
||||
tableBtn:[
|
||||
{
|
||||
@@ -75,19 +87,65 @@ export default {
|
||||
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() {}
|
||||
|
||||
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>
|
||||
|
||||
@@ -7,15 +7,30 @@
|
||||
@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: '所属年份'
|
||||
@@ -27,6 +42,7 @@ const tableProps = [
|
||||
];
|
||||
export default {
|
||||
name: 'GroupKeyTaskConfiguration',
|
||||
components:{GroupKeyAdd},
|
||||
data () {
|
||||
return {
|
||||
formConfig: [
|
||||
@@ -79,19 +95,66 @@ export default {
|
||||
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() {}
|
||||
|
||||
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>
|
||||
|
||||
@@ -7,10 +7,21 @@
|
||||
@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',
|
||||
@@ -27,6 +38,7 @@ const tableProps = [
|
||||
];
|
||||
export default {
|
||||
name: 'ProductInfoConfiguration',
|
||||
components:{ProductInfoAdd},
|
||||
data () {
|
||||
return {
|
||||
formConfig: [
|
||||
@@ -69,19 +81,66 @@ export default {
|
||||
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() {}
|
||||
|
||||
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>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
gap: 12px;
|
||||
grid-template-columns:1624px;
|
||||
">
|
||||
<operatingLineChart :dateData="dateData" :monthData="monthData" />
|
||||
<operatingLineChart :dateData="dateData" :monData="monData" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||
@@ -25,27 +25,40 @@
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<operatingLineChartCumulative :dateData="dateData" :ytdData="ytdData" />
|
||||
<operatingLineChartCumulative :dateData="dateData" :totalData="totalData" />
|
||||
<!-- <keyWork /> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="centerImg" style="
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1; /* 确保在 backp 之上、内容之下 */
|
||||
"></div> -->
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ReportHeader from "../components/noRouterHeader.vue";
|
||||
import { Sidebar } from "../../../layout/components";
|
||||
import screenfull from "screenfull";
|
||||
// import operatingSalesRevenue from "./operatingComponents/operatingSalesRevenue";
|
||||
// import premProdStatus from "./components/premProdStatus.vue";
|
||||
import { mapState } from "vuex";
|
||||
import operatingLineChart from "../electricityCostAnalysisComponents/operatingLineChart";
|
||||
import operatingLineChartCumulative from "../electricityCostAnalysisComponents/operatingLineChartCumulative.vue";
|
||||
|
||||
import { getSalesRevenueGroupData } from '@/api/cockpit'
|
||||
import { getProfitAnalysisTotalList } from '@/api/cockpit'
|
||||
import moment from "moment";
|
||||
export default {
|
||||
name: "ElectricityCostAnalysis",
|
||||
name: "DayReport",
|
||||
components: {
|
||||
ReportHeader,
|
||||
operatingLineChartCumulative,
|
||||
operatingLineChart,
|
||||
// premProdStatus,
|
||||
Sidebar,
|
||||
},
|
||||
data() {
|
||||
@@ -55,10 +68,9 @@ export default {
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
value: 100,
|
||||
sort:1,
|
||||
selectDate:{},
|
||||
monthData: {},
|
||||
ytdData:{},
|
||||
dateData: {},
|
||||
monData: [],
|
||||
totalData: [],
|
||||
};
|
||||
},
|
||||
|
||||
@@ -127,23 +139,20 @@ export default {
|
||||
this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined
|
||||
},
|
||||
methods: {
|
||||
// sortChange(value) {
|
||||
// this.sort = value
|
||||
// this.getData()
|
||||
// },
|
||||
getData() {
|
||||
getSalesRevenueGroupData({
|
||||
getProfitAnalysisTotalList({
|
||||
startTime: this.dateData.startTime,
|
||||
endTime: this.dateData.endTime,
|
||||
sort: this.sort,
|
||||
index: undefined,
|
||||
factory: undefined
|
||||
// timeDim: obj.mode
|
||||
analysisObject: [
|
||||
"利润总额"
|
||||
],
|
||||
levelId: 1,
|
||||
// timeDim: this.dateData.mode
|
||||
}).then((res) => {
|
||||
console.log(res);
|
||||
this.monthData= res.data.month
|
||||
this.ytdData = res.data.ytd
|
||||
|
||||
this.monData = res.data.currentMonthData
|
||||
this.totalData = res.data.totalMonthData
|
||||
// this.totalData = res.data.totalData
|
||||
// this.saleData = res.data.SaleData
|
||||
// this.premiumProduct = res.data.premiumProduct
|
||||
// this.salesTrendMap = res.data.salesTrendMap
|
||||
@@ -152,7 +161,7 @@ export default {
|
||||
})
|
||||
},
|
||||
handleTimeChange(obj) {
|
||||
console.log(obj, 'obj');
|
||||
// console.log(obj, 'obj');
|
||||
this.dateData= obj
|
||||
this.getData()
|
||||
},
|
||||
@@ -226,12 +235,14 @@ export default {
|
||||
<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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
<template>
|
||||
<div id="dayReport" class="dayReport" :style="styles">
|
||||
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||
<ReportHeader :dateData="dateData" size="psi" @timeRangeChange="handleTimeChange" top-title="基地电费分析"
|
||||
:is-full-screen="isFullScreen" @screenfullChange="screenfullChange" />
|
||||
<div class="main-body" style="
|
||||
margin-top: -20px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
padding: 0px 16px 0 272px;
|
||||
flex-direction: column;
|
||||
">
|
||||
<div class="top" style="display: flex; gap: 16px">
|
||||
<div class="top-three" style="
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<changeBase :factory="factory" @baseChange="selectChange" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px;margin-top: -20px;">
|
||||
<div class="left-three" style="
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
grid-template-columns: 804px 804px;
|
||||
">
|
||||
<monthlyOverview :month="month" :monData="monData" :title="'月度概览'" />
|
||||
<totalOverview :totalData="totalData" :title="'累计概览'" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="middle" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||
<div class="left-three" style="
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<relatedIndicatorsAnalysis :dateData="dateData" :factory="factory" :relatedData="relatedData"
|
||||
:title="'相关指标分析'" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||
<div class="left-three" style="
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<dataTrend @getData="changeItem" :trendData="trend" :title="'数据趋势'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="centerImg" style="
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1; /* 确保在 backp 之上、内容之下 */
|
||||
"></div> -->
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ReportHeader from "../components/noRouterHeader.vue";
|
||||
import { Sidebar } from "../../../layout/components";
|
||||
import screenfull from "screenfull";
|
||||
import changeBase from "../components/changeBase.vue";
|
||||
import monthlyOverview from "../electricityCostAnalysisComponents/monthlyOverview.vue";
|
||||
import totalOverview from "../electricityCostAnalysisComponents/totalOverview.vue";
|
||||
// import totalOverview from "../operatingComponents/totalOverview.vue";
|
||||
import relatedIndicatorsAnalysis from "../electricityCostAnalysisComponents/relatedIndicatorsAnalysis.vue";
|
||||
import dataTrend from "../electricityCostAnalysisComponents/dataTrend.vue";
|
||||
import { mapState } from "vuex";
|
||||
import { getProfitAnalysisTotalList } from '@/api/cockpit'
|
||||
// import PSDO from "./components/PSDO.vue";
|
||||
// import psiLineChart from "./components/psiLineChart.vue";
|
||||
|
||||
// import coreBottomLeft from "./components/coreBottomLeft.vue";
|
||||
// import orderProgress from "./components/orderProgress.vue";
|
||||
// import keyWork from "./components/keyWork.vue";
|
||||
import moment from "moment";
|
||||
// import html2canvas from 'html2canvas'
|
||||
// import JsPDF from 'jspdf'
|
||||
export default {
|
||||
name: "DayReport",
|
||||
components: {
|
||||
ReportHeader,
|
||||
changeBase,
|
||||
monthlyOverview,
|
||||
Sidebar,
|
||||
totalOverview,
|
||||
dataTrend,
|
||||
relatedIndicatorsAnalysis
|
||||
// psiLineChart
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isFullScreen: false,
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
month: '',
|
||||
value: 100,
|
||||
factory: 5,
|
||||
dateData: {},
|
||||
monData: {},
|
||||
totalData: {},
|
||||
trend: [],
|
||||
relatedData: [],
|
||||
trendName: '利润总额',
|
||||
// cusProData: {},
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
this.init();
|
||||
this.windowWidth(document.documentElement.clientWidth);
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
theme: (state) => state.settings.theme,
|
||||
sideTheme: (state) => state.settings.sideTheme,
|
||||
sidebar: (state) => state.app.sidebar,
|
||||
device: (state) => state.app.device,
|
||||
needTagsView: (state) => state.settings.tagsView,
|
||||
fixedHeader: (state) => state.settings.fixedHeader,
|
||||
}),
|
||||
classObj() {
|
||||
return {
|
||||
hideSidebar: !this.sidebar.opened,
|
||||
openSidebar: this.sidebar.opened,
|
||||
withoutAnimation: this.sidebar.withoutAnimation,
|
||||
mobile: this.device === "mobile",
|
||||
};
|
||||
},
|
||||
variables() {
|
||||
return variables;
|
||||
},
|
||||
// ...mapGetters(['sidebar']),
|
||||
styles() {
|
||||
const v = Math.floor(this.value * this.beilv * 100) / 10000;
|
||||
return {
|
||||
transform: `scale(${v})`,
|
||||
transformOrigin: "left top",
|
||||
// overflow: hidden;
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
clientWidth(val) {
|
||||
if (!this.timer) {
|
||||
this.clientWidth = val;
|
||||
this.beilv2 = this.clientWidth / 1920;
|
||||
this.timer = true;
|
||||
let _this = this;
|
||||
setTimeout(function () {
|
||||
_this.timer = false;
|
||||
}, 500);
|
||||
}
|
||||
// 这里可以添加修改时的方法
|
||||
this.windowWidth(val);
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.timer);
|
||||
this.destroy();
|
||||
},
|
||||
mounted() {
|
||||
const _this = this;
|
||||
_this.beilv = document.documentElement.clientWidth / 1920;
|
||||
window.onresize = () => {
|
||||
return (() => {
|
||||
_this.clientWidth = `${document.documentElement.clientWidth}`;
|
||||
this.beilv = _this.clientWidth / 1920;
|
||||
})();
|
||||
};
|
||||
if(this.$route.query.factory){
|
||||
this.factory =Number(this.$route.query.factory)
|
||||
}else if(this.$store.getters.levelList.length > 0 && this.$store.getters.levelList[0].id !== 1) {
|
||||
this.factory = this.$store.getters.levelList[0].id
|
||||
}else{
|
||||
this.factory = this.$store.getters.levelList[1].id
|
||||
}
|
||||
this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined
|
||||
},
|
||||
methods: {
|
||||
handleChange(value) {
|
||||
this.index = value
|
||||
this.getData()
|
||||
},
|
||||
changeItem(item) {
|
||||
console.log('item', item);
|
||||
|
||||
this.trendName = item
|
||||
this.getData()
|
||||
},
|
||||
getData() {
|
||||
const requestParams = {
|
||||
startTime: this.dateData.startTime,
|
||||
endTime: this.dateData.endTime,
|
||||
// index: this.index,
|
||||
// sort: 1,
|
||||
trendName: this.trendName,
|
||||
analysisObject: [
|
||||
"利润总额",
|
||||
],
|
||||
// paramList: ['制造成本', '财务费用', '销售费用', '管理费用', '运费'],
|
||||
levelId: this.factory,
|
||||
// baseId: Number(this.factory),
|
||||
};
|
||||
// 调用接口
|
||||
getProfitAnalysisTotalList(requestParams).then((res) => {
|
||||
this.monData = res.data.currentMonthData.find(item => {
|
||||
return item.name === "利润总额";
|
||||
});
|
||||
console.log('this.monData', this.monData);
|
||||
|
||||
this.totalData = res.data.totalMonthData.find(item => {
|
||||
return item.name === "利润总额";
|
||||
});
|
||||
// this.relatedMon = res.data.relatedMon
|
||||
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) {
|
||||
this.month = obj.targetMonth
|
||||
this.dateData = {
|
||||
startTime: obj.startTime,
|
||||
endTime: obj.endTime,
|
||||
// mode: obj.mode,
|
||||
}
|
||||
|
||||
this.getData()
|
||||
},
|
||||
selectChange(data) {
|
||||
console.log('选中的数据:', data);
|
||||
this.factory = data
|
||||
if (this.dateData.startTime && this.dateData.endTime) {
|
||||
this.getData();
|
||||
}
|
||||
},
|
||||
handleClickOutside() {
|
||||
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
||||
},
|
||||
windowWidth(value) {
|
||||
this.clientWidth = value;
|
||||
this.beilv2 = this.clientWidth / 1920;
|
||||
},
|
||||
change() {
|
||||
this.isFullScreen = screenfull.isFullscreen;
|
||||
},
|
||||
init() {
|
||||
if (!screenfull.isEnabled) {
|
||||
this.$message({
|
||||
message: "you browser can not work",
|
||||
type: "warning",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
screenfull.on("change", this.change);
|
||||
},
|
||||
destroy() {
|
||||
if (!screenfull.isEnabled) {
|
||||
this.$message({
|
||||
message: "you browser can not work",
|
||||
type: "warning",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
screenfull.off("change", this.change);
|
||||
},
|
||||
// 全屏
|
||||
screenfullChange() {
|
||||
console.log("screenfull.enabled", screenfull.isEnabled);
|
||||
|
||||
if (!screenfull.isEnabled) {
|
||||
this.$message({
|
||||
message: "you browser can not work",
|
||||
type: "warning",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
screenfull.toggle(this.$refs.dayReportB);
|
||||
},
|
||||
changeDate(val) {
|
||||
this.date = val;
|
||||
// this.weekDay = this.weekArr[moment(this.date).format('e')]
|
||||
// this.getData()
|
||||
if (this.date === moment().format("yyyy-MM-DD")) {
|
||||
this.loopTime();
|
||||
} else {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
},
|
||||
// 导出
|
||||
// () {
|
||||
// this.$message.success('正在导出,请稍等!')
|
||||
// const element = document.getElementById('dayRepDom')
|
||||
// element.style.display = 'block'
|
||||
// const fileName = '株洲碲化镉生产日报' + moment().format('yyMMDD') + '.pdf'
|
||||
// html2canvas(element, {
|
||||
// dpi: 300, // Set to 300 DPI
|
||||
// scale: 3 // Adjusts your resolution
|
||||
// }).then(function(canvas) {
|
||||
// const imgWidth = 595.28
|
||||
// const imgHeight = 841.89
|
||||
// const pageData = canvas.toDataURL('image/jpeg', 1.0)
|
||||
// const PDF = new JsPDF('', 'pt', [imgWidth, imgHeight])
|
||||
// PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
|
||||
// setTimeout(() => {
|
||||
// PDF.save(fileName) // 导出文件名
|
||||
// }, 1000)
|
||||
// })
|
||||
// element.style.display = 'none'
|
||||
// }
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<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>
|
||||
@@ -43,7 +43,6 @@ export default {
|
||||
|
||||
.content-top {
|
||||
height: 60px;
|
||||
|
||||
.title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -73,7 +72,6 @@ export default {
|
||||
// 移除固定行高,避免影响垂直对齐
|
||||
// line-height: 60px;
|
||||
}
|
||||
|
||||
// width: 547px;
|
||||
// background: url(../../../assets/img/contentTopBasic.png) no-repeat;
|
||||
// background-size: 100% 100%;
|
||||
@@ -108,7 +106,6 @@ export default {
|
||||
background-size: 100% 100%;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&__rawTopTitleLarge {
|
||||
background: url(../../../assets/img/rawTopTitleLarge.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
@@ -176,19 +173,11 @@ export default {
|
||||
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%;
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
width: 1595px;
|
||||
background-color: rgba(249, 252, 255, 1);
|
||||
">
|
||||
<dataTrendBar @changeItem="handleChange" :chartData="chartData" />
|
||||
<!-- 直接使用计算属性 chartData,无需手动更新 -->
|
||||
<dataTrendBar @handleGetItemData="getData" :chartData="chartData" />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
@@ -23,128 +24,81 @@ export default {
|
||||
name: "ProductionStatus",
|
||||
components: { Container, dataTrendBar },
|
||||
props: {
|
||||
trend: {
|
||||
trendData: {
|
||||
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 }
|
||||
]
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartData: {
|
||||
months: [], // 月份数组(2025年01月 - 2025年12月)
|
||||
rates: [], // 每月完成率(百分比)
|
||||
reals: [], // 每月实际值
|
||||
budgets: [],// 每月预算值
|
||||
diffs: [], // 每月差值
|
||||
flags: [] // 每月达标标识(≥100 → 1,<100 → 0)
|
||||
}
|
||||
// 移除:原 chartData 定义,改为计算属性
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
trend: {
|
||||
handler(newVal) {
|
||||
this.processTrendData(newVal);
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.processTrendData(this.trend);
|
||||
},
|
||||
methods: {
|
||||
handleChange(value) {
|
||||
this.$emit("handleChange", value);
|
||||
},
|
||||
// 移除:原 watch 监听配置,计算属性自动响应 trendData 变化
|
||||
computed: {
|
||||
/**
|
||||
* 处理趋势数据(适配12个月的数组结构)
|
||||
* @param {Array} trendData - 原始趋势数组(12个月)
|
||||
* chartData 计算属性:自动响应 trendData 变化,格式化并提取各字段数组
|
||||
* @returns {Object} 包含6个独立数组的格式化数据
|
||||
*/
|
||||
processTrendData(trendData) {
|
||||
// 数据兜底:确保是数组且长度为12
|
||||
const validTrend = Array.isArray(trendData)
|
||||
? trendData
|
||||
: []
|
||||
chartData() {
|
||||
// 初始化6个独立数组
|
||||
const timeArr = []; // 格式化后的年月数组
|
||||
const valueArr = []; // 实际值数组
|
||||
const diffValueArr = []; // 差异值数组
|
||||
const targetValueArr = []; // 预算值数组
|
||||
const proportionArr = []; // 占比数组
|
||||
const completedArr = []; // 完成率数组
|
||||
|
||||
// 初始化空数组
|
||||
const months = [];
|
||||
const rates = [];
|
||||
const reals = [];
|
||||
const budgets = [];
|
||||
const diffs = [];
|
||||
const flags = [];
|
||||
// 遍历传入的 trendData 数组(响应式依赖,变化时自动重算)
|
||||
this.trendData.forEach((item) => {
|
||||
// 1. 格式化时间并推入时间数组
|
||||
const yearMonth = this.formatTimeToYearMonth(item.time);
|
||||
timeArr.push(yearMonth);
|
||||
|
||||
// 遍历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);
|
||||
// 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);
|
||||
});
|
||||
|
||||
// 更新chartData(响应式)
|
||||
this.chartData = {
|
||||
months,
|
||||
rates,
|
||||
reals,
|
||||
budgets,
|
||||
diffs,
|
||||
flags
|
||||
// 组装并返回格式化后的数据(结构与原一致)
|
||||
return {
|
||||
time: timeArr,
|
||||
value: valueArr,
|
||||
diffValue: diffValueArr,
|
||||
targetValue: targetValueArr,
|
||||
proportion: proportionArr,
|
||||
completed: completedArr,
|
||||
rawData: this.trendData, // 透传原始数据,方便子组件使用
|
||||
};
|
||||
|
||||
console.log('处理后的趋势数据:', this.chartData);
|
||||
},
|
||||
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 计算达标标识
|
||||
* @param {Number} rate - 完成率(原始值,如1.2 → 120%)
|
||||
* @returns {Number} 1: 达标(≥100%),0: 未达标(<100%)
|
||||
* 格式化时间戳为年月格式(YYYY-MM)
|
||||
* @param {Number} timestamp 13位毫秒级时间戳
|
||||
* @returns {String} 格式化后的年月字符串(如:2025-10)
|
||||
*/
|
||||
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;
|
||||
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;
|
||||
@@ -159,7 +113,6 @@ getRateFlag(rate, real, target) {
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
/* 设备项样式优化 */
|
||||
.proBarInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -266,7 +219,6 @@ getRateFlag(rate, real, target) {
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
/* 图表相关样式 */
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
@@ -285,7 +237,7 @@ getRateFlag(rate, real, target) {
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式 */
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square target"></span>
|
||||
预算
|
||||
目标
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-icon square achieved"></span>
|
||||
@@ -59,13 +59,17 @@ export default {
|
||||
props: ["chartData"],
|
||||
data() {
|
||||
return {
|
||||
activeButton: 0,
|
||||
isDropdownShow: false,
|
||||
selectedProfit: '营业收入', // 选中的名称,初始为null
|
||||
selectedProfit: '利润总额', // 选中的名称,初始为null
|
||||
profitOptions: [
|
||||
'营业收入',
|
||||
'单价',
|
||||
'利润总额',
|
||||
'销量',
|
||||
'单价',
|
||||
'制造成本',
|
||||
'管理费用',
|
||||
'销售费用',
|
||||
'财务费用',
|
||||
'非经营性利润',
|
||||
]
|
||||
};
|
||||
},
|
||||
@@ -74,19 +78,17 @@ export default {
|
||||
// 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
|
||||
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,
|
||||
@@ -113,7 +115,7 @@ export default {
|
||||
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||
])
|
||||
},
|
||||
data: data.rates, // 完成率(%)
|
||||
data: data.proportion || [], // 完成率(%)
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
@@ -135,7 +137,7 @@ export default {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.budgets // 目标销量(万元)
|
||||
data: data.targetValue || [] // 目标销量(万元)
|
||||
},
|
||||
// 3. 实际(柱状图,含达标状态)
|
||||
{
|
||||
@@ -152,8 +154,8 @@ export default {
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: (params) => {
|
||||
const diff = data.diffs || [];
|
||||
const flags = data.flags || [];
|
||||
const diff = data.diffValue || [];
|
||||
const flags = data.completed || [];
|
||||
const currentDiff = diff[params.dataIndex] || 0;
|
||||
const currentFlag = flags[params.dataIndex] || 0;
|
||||
|
||||
@@ -216,7 +218,7 @@ export default {
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
// 达标状态:1=达标(绿色),0=未达标(橙色)
|
||||
const safeFlag = data.flags;
|
||||
const safeFlag = data.completed || [];
|
||||
const currentFlag = safeFlag[params.dataIndex] || 0;
|
||||
return currentFlag === 1
|
||||
? {
|
||||
@@ -239,13 +241,10 @@ export default {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
data: data.reals // 实际销量(万元)
|
||||
data: data.value || [] // 实际销量(万元)
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
// 根据按钮状态返回对应数据
|
||||
return salesData;
|
||||
}
|
||||
},
|
||||
@@ -253,7 +252,7 @@ export default {
|
||||
selectProfit(item) {
|
||||
this.selectedProfit = item;
|
||||
this.isDropdownShow = false;
|
||||
this.$emit("changeItem", item);
|
||||
this.$emit('handleGetItemData', item)
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -378,7 +377,7 @@ export default {
|
||||
|
||||
.dropdown-container {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
z-index: 999; // 提高z-index,确保菜单不被遮挡
|
||||
}
|
||||
|
||||
.item-button {
|
||||
@@ -442,18 +441,21 @@ export default {
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&.rotate {
|
||||
transform: rotate(90deg);
|
||||
transform: rotate(90deg); // 箭头旋转方向可根据需求调整,比如改为rotate(-90deg)更符合向上展开的视觉
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-options {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
// 关键修改1:调整top值,让菜单显示在选择框上方,calc(-100% - 2px)表示向上偏移自身100%再加2px间距
|
||||
bottom: 100%;
|
||||
right: 0;
|
||||
margin-top: 2px;
|
||||
// 移除多余的margin-top,避免额外间距
|
||||
// margin-top: 2px;
|
||||
width: 123px;
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
// 关键修改2:调整border-radius,让菜单顶部圆角匹配选择框的右上角,底部圆角为0(更美观)
|
||||
border-radius: 8px 8px 0 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div :id="id" style="width: 100%; height:100%;"></div>
|
||||
<div class="bottomTip">
|
||||
<div class="precent">
|
||||
<span class="precentNum">{{ detailData.rate || 0 }}% </span>
|
||||
<span class="precentNum">{{ detailData.completeRate ? detailData.completeRate : 0 }}% </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -89,7 +89,7 @@ export default {
|
||||
if (!this.electricityChart) return
|
||||
|
||||
// 修复:兜底获取rate值,确保数值有效
|
||||
const rate = Number(this.detailData?.rate) || 0
|
||||
const rate = Number(this.detailData?.completeRate) || 0
|
||||
console.log('当前rate值:', rate); // 调试:确认rate值正确
|
||||
|
||||
// 关键:第二个参数传true,清空原有配置,强制更新
|
||||
@@ -221,6 +221,4 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<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">
|
||||
@@ -11,113 +9,104 @@
|
||||
</div>
|
||||
<div class="number">
|
||||
<div class="yield">
|
||||
{{ monthData?.rate || 0 }}%
|
||||
{{ formatRate(factoryData?.completeRate) }}%
|
||||
</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="">
|
||||
环比{{ 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 :detailData="monthData" id="month"></electricityGauge>
|
||||
<electricityGauge id="month" :detailData="factoryData"></electricityGauge>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="line" style="padding: 0px;">
|
||||
<verticalBarChart :detailData="monthData">
|
||||
</verticalBarChart>
|
||||
|
||||
<!-- 传递包含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'
|
||||
|
||||
// import * as echarts from 'echarts'
|
||||
// import rawItem from './raw-Item.vue'
|
||||
// 引入箭头图片(根据实际路径调整,若模板中直接用路径可注释)
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, electricityGauge, verticalBarChart },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
monthData: { // 接收父组件传递的设备数据数组
|
||||
monData: {
|
||||
type: Object,
|
||||
default: () => {} // 默认空数组,避免报错
|
||||
default: () => ({})
|
||||
},
|
||||
title: { // 接收父组件传递的设备数据数组
|
||||
title: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
month: { // 接收父组件传递的设备数据数组
|
||||
month: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
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())
|
||||
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>
|
||||
/* 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;
|
||||
}
|
||||
|
||||
@@ -126,18 +115,13 @@ export default {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -164,6 +148,13 @@ export default {
|
||||
font-style: normal;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
// 箭头样式优化
|
||||
.arrow {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.line {
|
||||
@@ -171,34 +162,4 @@ export default {
|
||||
height: 205px;
|
||||
background: #F9FCFF;
|
||||
}
|
||||
|
||||
// .leftTitle {
|
||||
// .item {
|
||||
// width: 67px;
|
||||
// height: 180px;
|
||||
// padding: 37px 23px;
|
||||
// background: #F9FCFF;
|
||||
// font-family: PingFangSC, PingFang SC;
|
||||
// font-weight: 400;
|
||||
// font-size: 18px;
|
||||
// color: #000000;
|
||||
// line-height: 25px;
|
||||
// letter-spacing: 1px;
|
||||
// // text-align: left;
|
||||
// font-style: normal;
|
||||
// }
|
||||
// }
|
||||
</style>
|
||||
|
||||
<!-- <style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style> -->
|
||||
|
||||
@@ -1,36 +1,24 @@
|
||||
<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%;">
|
||||
<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;'>
|
||||
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||
<div class="dashboard left">
|
||||
<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 class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
</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="dashboard right">
|
||||
<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 class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -38,167 +26,102 @@
|
||||
</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 },
|
||||
components: { Container, operatingSingleBar, verticalBarChart },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
monthAnalysis: {
|
||||
itemData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [
|
||||
{ title: "销量", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||
{ title: "单价", budget: 0, real: 0, rate: 0, diff: 0 }
|
||||
]
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
dateData: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
title: {
|
||||
title: { // 接收父组件传递的设备数据数组
|
||||
type: String,
|
||||
default: ''
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
},
|
||||
factory: {
|
||||
type: [String,Number],
|
||||
default: ''
|
||||
},
|
||||
month: {
|
||||
month: { // 接收父组件传递的设备数据数组
|
||||
type: String,
|
||||
default: ''
|
||||
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)
|
||||
itemData: {
|
||||
handler(newValue, oldValue) {
|
||||
// this.updateChart()
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
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() {
|
||||
this.updateChart(this.monthAnalysis)
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
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>
|
||||
/* 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;
|
||||
}
|
||||
|
||||
.topItem-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
width: 382px;
|
||||
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;
|
||||
// width: 190px;
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
@@ -207,15 +130,74 @@ getRateFlag(rate, real, target) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard.left {
|
||||
margin-left: 0;
|
||||
// .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);
|
||||
}
|
||||
|
||||
.dashboard.right {
|
||||
margin-right: 0;
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
</style> -->
|
||||
|
||||
@@ -70,10 +70,10 @@ export default {
|
||||
computed: {
|
||||
// 排序后的数据源(核心:根据selectedSortValue重新排序)
|
||||
currentDataSource() {
|
||||
if (!this.chartData?.factory) return {};
|
||||
if (!this.chartData) return {};
|
||||
|
||||
// 深拷贝原始数据,避免修改原数据
|
||||
const factory = JSON.parse(JSON.stringify(this.chartData.factory));
|
||||
const factory = JSON.parse(JSON.stringify(this.chartData));
|
||||
if (!factory.locations.length || !this.selectedSortValue) return factory;
|
||||
|
||||
// 构建带索引的数组,方便同步所有字段排序
|
||||
@@ -82,7 +82,7 @@ export default {
|
||||
name,
|
||||
real: factory.reals[index],
|
||||
target: factory.targets[index],
|
||||
rate: factory.rates[index],
|
||||
rate: factory.rate[index],
|
||||
diff: factory.diff[index],
|
||||
flag: factory.flags[index]
|
||||
}));
|
||||
@@ -143,7 +143,7 @@ export default {
|
||||
{ offset: 1, color: 'rgba(40, 138, 255, 0)' }
|
||||
])
|
||||
},
|
||||
data: data.rates || [],
|
||||
data: data.rate || [],
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
@@ -219,14 +219,14 @@ export default {
|
||||
padding: [5, 10, 5, 0],
|
||||
align: 'center',
|
||||
color: '#464646',
|
||||
fontSize: 14,
|
||||
fontSize: 14
|
||||
},
|
||||
achieved: {
|
||||
width: 'auto',
|
||||
padding: [5, 0, 5, 10],
|
||||
align: 'center',
|
||||
color: '#76DABE', // 与达标的 offset: 1 颜色一致
|
||||
fontSize: 14,
|
||||
fontSize: 14
|
||||
},
|
||||
// 未达标样式
|
||||
unachieved: {
|
||||
@@ -234,7 +234,7 @@ export default {
|
||||
padding: [5, 0, 5, 10],
|
||||
align: 'center',
|
||||
color: '#F9A44A', // 与未达标的 offset: 1 颜色一致
|
||||
fontSize: 14,
|
||||
fontSize: 14
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -413,7 +413,7 @@ export default {
|
||||
height: 24px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
fontSize: 12px;
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
font-style: normal;
|
||||
letter-spacing: 2px;
|
||||
@@ -485,7 +485,7 @@ export default {
|
||||
|
||||
.dropdown-option {
|
||||
padding: 6px 12px;
|
||||
fontSize: 12px;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
|
||||
@@ -62,11 +62,7 @@ export default {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
}
|
||||
|
||||
// 只创建一次图表实例
|
||||
this.myChart = echarts.init(chartDom);
|
||||
|
||||
// 绑定点击事件(只绑定一次,永久生效)
|
||||
this.myChart.getZr().on('click', (params) => {
|
||||
console.log('params', params);
|
||||
|
||||
@@ -94,9 +90,10 @@ export default {
|
||||
if (itemName === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 路由跳转时携带序号(或名称+序号)
|
||||
this.$router.push({
|
||||
path: 'operatingRevenueBase',
|
||||
path: 'totalProfitBase',
|
||||
query: { // 使用query传递参数(推荐),也可使用params
|
||||
// baseName: itemName,
|
||||
factory: baseIndex,
|
||||
@@ -175,6 +172,7 @@ export default {
|
||||
fontSize: 12,
|
||||
align: 'right'
|
||||
},
|
||||
|
||||
splitNumber: 4,
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
@@ -183,7 +181,7 @@ export default {
|
||||
formatter: '{value}'
|
||||
},
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }
|
||||
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } }
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
@@ -199,7 +197,7 @@ export default {
|
||||
formatter: '{value}%'
|
||||
},
|
||||
splitLine: { show: false },
|
||||
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
splitNumber: 4
|
||||
}
|
||||
],
|
||||
|
||||
@@ -118,6 +118,7 @@ export default {
|
||||
fontSize: 12,
|
||||
align: 'right'
|
||||
},
|
||||
|
||||
splitNumber: 4,
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
@@ -126,7 +127,7 @@ export default {
|
||||
formatter: '{value}'
|
||||
},
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { show: true, show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
},
|
||||
// 右侧Y轴:利润占比(百分比)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 200px;"></div>
|
||||
<div ref="cockpitEffChip" id="coreLineChart" style="width: 100%; height: 200px;"></div>
|
||||
</template>
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
@@ -13,14 +13,6 @@ export default {
|
||||
},
|
||||
props: {
|
||||
// 明确接收的props结构,增强可读性
|
||||
refName: {
|
||||
type: String,
|
||||
default: () => 'cockpitEffChip',
|
||||
// 校验数据格式
|
||||
// validator: (value) => {
|
||||
// return Array.isArray(value.series) && Array.isArray(value.allPlaceNames);
|
||||
// }
|
||||
},
|
||||
chartData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
@@ -51,7 +43,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
updateChart() {
|
||||
const chartDom = this.$refs[this.refName];
|
||||
const chartDom = this.$refs.cockpitEffChip;
|
||||
if (!chartDom) {
|
||||
console.error('图表容器未找到!');
|
||||
return;
|
||||
@@ -92,16 +84,18 @@ export default {
|
||||
// }
|
||||
},
|
||||
grid: {
|
||||
top: 20,
|
||||
bottom: 30,
|
||||
right: 20,
|
||||
left: 5,
|
||||
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,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<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;
|
||||
@@ -21,10 +20,10 @@
|
||||
集团情况
|
||||
</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>
|
||||
<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" />
|
||||
<operatingTopBar :chartData="chartData?.topBarData || {}" />
|
||||
</div>
|
||||
<div class="right" style="
|
||||
height: 380px;
|
||||
@@ -32,8 +31,7 @@
|
||||
width: 1220px;
|
||||
background-color: rgba(249, 252, 255, 1);
|
||||
">
|
||||
<!-- <top-item /> -->
|
||||
<operatingBar :dateData="dateData" :chartData="chartData" @sort-change="sortChange" />
|
||||
<operatingBar :dateData="dateData" :chartData="chartData?.barData || {}" />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
@@ -45,38 +43,56 @@ import Container from "../components/container.vue";
|
||||
import operatingBar from "./operatingBar.vue";
|
||||
import operatingTopBar from "./operatingTopBar.vue";
|
||||
|
||||
// 序号→地名映射表(levelId=序号)
|
||||
const baseIndexToNameMap = {
|
||||
7: "宜兴",
|
||||
8: "漳州",
|
||||
3: "自贡",
|
||||
2: "桐城",
|
||||
9: "洛阳",
|
||||
5: "合肥",
|
||||
10: "秦皇岛",
|
||||
6: "宿迁"
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "ProductionStatus",
|
||||
components: { Container, operatingBar, operatingTopBar },
|
||||
props: {
|
||||
monthData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
group: {
|
||||
rate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0
|
||||
},
|
||||
factory: []
|
||||
}),
|
||||
required: true
|
||||
monData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
dateData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
}),
|
||||
},
|
||||
default: () => {},
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartData: null, // 初始化 chartData 为 null
|
||||
groupData: {}, // 集团数据
|
||||
factoryData: [] // 工厂数据
|
||||
chartData: {
|
||||
topBarData: { // levelId=1的整合数据
|
||||
locations: [], // 固定为["凯盛新能"]
|
||||
diff: [], // 差值数组
|
||||
targets: [], // 预算值数组
|
||||
reals: [], // 实际值数组
|
||||
rate: [], // 完成率数组
|
||||
flags: [] // 完成状态数组(0/1)
|
||||
},
|
||||
barData: { // levelId≠1的整合数据
|
||||
locations: [], // levelId对应的baseIndexToNameMap中的地名
|
||||
diff: [], // 对应差值数组
|
||||
targets: [], // 预算值数组
|
||||
reals: [], // 实际值数组
|
||||
rate: [], // 完成率数组
|
||||
flags: [] // 完成状态数组(0/1)
|
||||
// baseIndexes: []// 对应levelId(序号)数组
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
monthData: {
|
||||
monData: {
|
||||
handler() {
|
||||
this.processChartData();
|
||||
},
|
||||
@@ -85,135 +101,93 @@ export default {
|
||||
},
|
||||
},
|
||||
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
|
||||
* 核心方法:按levelId匹配地名生成locations
|
||||
*/
|
||||
processChartData() {
|
||||
// 1. 处理集团数据 - 提取各字段到对应数组
|
||||
this.groupData = this.monthData.group || { rate: 0, diff: 0, real: 0, target: 0 };
|
||||
// 初始化空数据结构
|
||||
const initTopBarData = {
|
||||
locations: [], diff: [], targets: [], reals: [], rate: [], flags: []
|
||||
};
|
||||
const initBarData = { locations: [], diff: [], targets: [], reals: [], rate: [], flags: [] };
|
||||
|
||||
// 集团各维度数据数组(单条数据,对应凯盛新能)
|
||||
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])];
|
||||
if (!Array.isArray(this.monData) || this.monData.length === 0) {
|
||||
this.chartData = { topBarData: initTopBarData, barData: initBarData };
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('集团数据数组:', {
|
||||
groupTarget,
|
||||
groupDiff,
|
||||
groupReal,
|
||||
groupRate,
|
||||
groupFlag,
|
||||
rawGroupData: this.groupData
|
||||
// 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. 处理工厂数据 - 提取每个工厂的对应字段到数组
|
||||
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));
|
||||
// 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);
|
||||
});
|
||||
|
||||
// 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
|
||||
// 遍历有效数据,填充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); // 序号转数字
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
console.log('最终处理后的图表数据:', this.chartData);
|
||||
},
|
||||
// 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>
|
||||
/* 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 {
|
||||
@@ -226,14 +200,12 @@ getRateFlag(rate, real, target) {
|
||||
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;
|
||||
@@ -259,7 +231,6 @@ getRateFlag(rate, real, target) {
|
||||
height: 14px;
|
||||
border: 1px solid #adadad;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
@@ -276,22 +247,18 @@ getRateFlag(rate, real, target) {
|
||||
.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;
|
||||
}
|
||||
|
||||
@@ -314,10 +281,8 @@ getRateFlag(rate, real, target) {
|
||||
#37acf5 100%);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
/* 进度变化时添加过渡动画,更流畅 */
|
||||
}
|
||||
|
||||
/* 图表相关样式保留 */
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
@@ -336,7 +301,6 @@ getRateFlag(rate, real, target) {
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container name="累计数据对比" icon="cockpitItemIcon" size="opLargeBg" topSize="large">
|
||||
<!-- 1. 移除 .kpi-content 的固定高度,改为自适应 -->
|
||||
<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;
|
||||
@@ -21,10 +20,10 @@
|
||||
集团情况
|
||||
</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>
|
||||
<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" />
|
||||
<operatingTopBar :chartData="chartData?.topBarData || {}" />
|
||||
</div>
|
||||
<div class="right" style="
|
||||
height: 380px;
|
||||
@@ -32,46 +31,68 @@
|
||||
width: 1220px;
|
||||
background-color: rgba(249, 252, 255, 1);
|
||||
">
|
||||
<!-- <top-item /> -->
|
||||
<operatingBar :dateData="dateData" @sort-change="sortChange" :chartData="chartData" />
|
||||
<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: {
|
||||
salesTrendMap: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
ytdData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
totalData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
dateData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
}),
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
chartData: null, // 初始化 chartData 为 null
|
||||
groupData: {}, // 集团数据
|
||||
factoryData: [] // 工厂数据
|
||||
chartData: {
|
||||
topBarData: { // levelId=1的整合数据
|
||||
locations: [], // 固定为["凯盛新能"]
|
||||
diff: [], // 差值数组
|
||||
targets: [], // 预算值数组
|
||||
reals: [], // 实际值数组
|
||||
rate: [], // 完成率数组
|
||||
flags: [] // 完成状态数组(0/1)
|
||||
},
|
||||
barData: { // levelId≠1的整合数据
|
||||
locations: [], // levelId对应的baseIndexToNameMap中的地名
|
||||
diff: [], // 对应差值数组
|
||||
targets: [], // 预算值数组
|
||||
reals: [], // 实际值数组
|
||||
rate: [], // 完成率数组
|
||||
flags: [] // 完成状态数组(0/1)
|
||||
// baseIndexes: []// 对应levelId(序号)数组
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
ytdData: {
|
||||
totalData: {
|
||||
handler() {
|
||||
this.processChartData();
|
||||
},
|
||||
@@ -80,136 +101,93 @@ export default {
|
||||
},
|
||||
},
|
||||
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
|
||||
* 核心方法:按levelId匹配地名生成locations
|
||||
*/
|
||||
processChartData() {
|
||||
// 1. 处理集团数据 - 提取各字段到对应数组
|
||||
this.groupData = this.ytdData.group || { rate: 0, diff: 0, real: 0, target: 0 };
|
||||
// 初始化空数据结构
|
||||
const initTopBarData = {
|
||||
locations: [], diff: [], targets: [], reals: [], rate: [], flags: []
|
||||
};
|
||||
const initBarData = { locations: [], diff: [], targets: [], reals: [], rate: [], flags: [] };
|
||||
|
||||
// 集团各维度数据数组(单条数据,对应凯盛新能)
|
||||
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])];
|
||||
if (!Array.isArray(this.totalData) || this.totalData.length === 0) {
|
||||
this.chartData = { topBarData: initTopBarData, barData: initBarData };
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('集团数据数组:', {
|
||||
groupTarget,
|
||||
groupDiff,
|
||||
groupReal,
|
||||
groupRate,
|
||||
groupFlag,
|
||||
rawGroupData: this.groupData
|
||||
// 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. 处理工厂数据 - 提取每个工厂的对应字段到数组
|
||||
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));
|
||||
// 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);
|
||||
});
|
||||
|
||||
// 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
|
||||
// 遍历有效数据,填充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); // 序号转数字
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
console.log('最终处理后的图表数据:', this.chartData);
|
||||
},
|
||||
// 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>
|
||||
/* 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 {
|
||||
@@ -222,14 +200,12 @@ getRateFlag(rate, real, target) {
|
||||
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;
|
||||
@@ -255,7 +231,6 @@ getRateFlag(rate, real, target) {
|
||||
height: 14px;
|
||||
border: 1px solid #adadad;
|
||||
margin: 0 8px;
|
||||
/* 优化分割线间距 */
|
||||
}
|
||||
|
||||
.yield {
|
||||
@@ -272,22 +247,18 @@ getRateFlag(rate, real, target) {
|
||||
.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;
|
||||
}
|
||||
|
||||
@@ -303,19 +274,15 @@ getRateFlag(rate, real, target) {
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
65deg,
|
||||
background: linear-gradient(65deg,
|
||||
rgba(53, 223, 247, 0) 0%,
|
||||
rgba(54, 220, 246, 0.92) 92%,
|
||||
#36f6e5 100%,
|
||||
#37acf5 100%
|
||||
);
|
||||
#37acf5 100%);
|
||||
border-radius: 5px;
|
||||
transition: width 0.3s ease;
|
||||
/* 进度变化时添加过渡动画,更流畅 */
|
||||
}
|
||||
|
||||
/* 图表相关样式保留 */
|
||||
.chartImgBottom {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
@@ -334,7 +301,6 @@ getRateFlag(rate, real, target) {
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局 tooltip 样式(不使用 scoped,确保生效) */
|
||||
.production-status-chart-tooltip {
|
||||
background: #0a2b4f77 !important;
|
||||
border: none !important;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<template>
|
||||
<div class="lineBottom" style="height: 180px; width: 100%">
|
||||
<operatingLineBarSaleSingle :refName=" 'totalOperating' " :chartData="chartD" style="height: 99%; width: 100%" />
|
||||
<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';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: "Container",
|
||||
@@ -23,20 +22,18 @@ export default {
|
||||
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
|
||||
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?.budget || 0,
|
||||
value: this.detailData?.targetValue || 0,
|
||||
flag: 1, // 实际项:达标(绿色)
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
fontSize: 14,
|
||||
},
|
||||
itemStyle: {
|
||||
color: {
|
||||
@@ -52,13 +49,12 @@ export default {
|
||||
},
|
||||
},
|
||||
{
|
||||
value: this.detailData?.real || 0,
|
||||
flag: this.detailData?.flag, // 实际项:达标(绿色)
|
||||
value: this.detailData?.value || 0,
|
||||
flag: this.detailData?.completed, // 实际项:达标(绿色)
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
fontSize: 14,
|
||||
},
|
||||
itemStyle: {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
|
||||
@@ -23,23 +23,51 @@ export default {
|
||||
currentDataSource() {
|
||||
console.log('yyyy', this.chartData);
|
||||
|
||||
return this.chartData.group
|
||||
return this.chartData
|
||||
},
|
||||
locations() {
|
||||
console.log('this.chartData', this.chartData);
|
||||
console.log('this.1111', this.chartData);
|
||||
|
||||
return this.chartData.group.locations
|
||||
return this.chartData.locations
|
||||
},
|
||||
// 根据按钮切换生成对应的 chartData
|
||||
chartD() {
|
||||
// 销量场景数据
|
||||
const data = this.currentDataSource;
|
||||
const diff = this.currentDataSource.diff[0]
|
||||
const rate = this.currentDataSource.rate[0]
|
||||
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',
|
||||
@@ -135,7 +163,7 @@ export default {
|
||||
name: '预算',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 65,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
@@ -155,7 +183,7 @@ export default {
|
||||
name: '实际',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: 65,
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
const safeFlag = [1, 0, 1, 1, 0, 1]; // 达标状态
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<Container :isShowTab="true" :name="title" icon="cockpitItemIcon" size="opLargeBg" topSize="large"
|
||||
@tabChange="handleChange">
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||
<div class="topItem-container" style="display: flex; gap: 8px">
|
||||
<div
|
||||
v-for="item in sortedIndicators"
|
||||
:key="item.key"
|
||||
class="dashboard"
|
||||
@click="item.route && handleDashboardClick(item.route)"
|
||||
>
|
||||
<div class="title">
|
||||
{{ item.name }}·{{ item.unit }}
|
||||
</div>
|
||||
<div style='font-size: 16px;text-align: right;padding-right: 5px;'>
|
||||
<span>完成率:<span style='color: #0B58FF;'>{{item.data.proportion}}%</span></span>
|
||||
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:item.data.completed>0?'#30B590':'#FF9423'}" >{{item.data.diffValue}}</span></span>
|
||||
</div>
|
||||
<operatingSingleBar :detailData="item.data"></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from '../components/container.vue'
|
||||
import operatingSingleBar from './operatingSingleBar.vue'
|
||||
|
||||
// import * as echarts from 'echarts'
|
||||
// import rawItem from './raw-Item.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, operatingSingleBar },
|
||||
props: {
|
||||
// 接收父组件传递的 月度+累计 组合数据
|
||||
relatedData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
relatedMon: [], // 月度数据(数组格式,存储销量/单价等数据)
|
||||
relatedTotal: [] // 累计数据(数组格式,存储销量/单价等数据)
|
||||
})
|
||||
},
|
||||
dateData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
factory: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 可选:动态标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
// 核心:当前激活的数据集(月度/累计),默认初始化月度数据
|
||||
activeData: this.relatedData.relatedMon || []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
indicatorDefs() {
|
||||
return [
|
||||
{ key: 'sales', name: '销量', unit: '万㎡', route: '/salesVolumeAnalysis/salesVolumeAnalysisBase'},
|
||||
{ key: 'price', name: '单价', unit: '元/㎡', route: '/unitPriceAnalysis/unitPriceAnalysisBase'},
|
||||
{ key: 'mfgCost', name: '制造成本', unit: '元/㎡', route: '/productionCostAnalysis/productionCostAnalysisBase'},
|
||||
{ key: 'mgmtFee', name: '管理费用', unit: '万元', route: '/expenseAnalysis/expenseAnalysisBase'},
|
||||
{ key: 'salesFee', name: '销售费用', unit: '万元', route: '/expenseAnalysis/expenseAnalysisBase'},
|
||||
{ key: 'finFee', name: '财务费用', unit: '万元', route: '/expenseAnalysis/expenseAnalysisBase'},
|
||||
{ key: 'nonOpProfit', name: '非经营性利润', unit: '万元', route: null}
|
||||
]
|
||||
},
|
||||
indicators() {
|
||||
const fallback = { targetValue: 0, value: 0, completed: 0, diffValue: 0 }
|
||||
const list = (Array.isArray(this.activeData) ? this.activeData : [])
|
||||
|
||||
return this.indicatorDefs.map(def => {
|
||||
const data = list.find(item => item && item.name === def.name) || fallback
|
||||
return {
|
||||
...def,
|
||||
data,
|
||||
sortValue: Number((data && data.value) ?? 0)
|
||||
}
|
||||
})
|
||||
},
|
||||
sortedIndicators() {
|
||||
const unitOrder = ['万㎡', '元/㎡', '万元']
|
||||
const unitRank = (u) => {
|
||||
const idx = unitOrder.indexOf(u)
|
||||
return idx === -1 ? 999 : idx
|
||||
}
|
||||
|
||||
return this.indicators.slice().sort((a, b) => {
|
||||
const ur = unitRank(a.unit) - unitRank(b.unit)
|
||||
if (ur !== 0) return ur
|
||||
const vr = (b.sortValue ?? -Infinity) - (a.sortValue ?? -Infinity)
|
||||
if (vr !== 0) return vr
|
||||
return String(a.key).localeCompare(String(b.key))
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 可选:监听 relatedData 初始变化(若父组件异步传递数据,确保 activeData 同步更新)
|
||||
relatedData: {
|
||||
handler(newVal) {
|
||||
this.activeData = newVal.relatedMon || [];
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log('组件挂载时的激活数据:', this.activeData);
|
||||
},
|
||||
methods: {
|
||||
handleDashboardClick(path) {
|
||||
this.$router.push({
|
||||
path: path,
|
||||
query: {
|
||||
factory: this.$route.query.factory ? this.$route.query.factory : this.factory,
|
||||
dateData: this.dateData
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Tab 切换处理函数
|
||||
* @param {String} value 切换值('month' = 月度,'total' = 累计,可根据实际Tab值调整)
|
||||
*/
|
||||
handleChange(value) {
|
||||
console.log('Tab 切换值:', value);
|
||||
// 根据 Tab 值更新当前激活的数据集
|
||||
if (value === 'month') {
|
||||
// 切换为月度数据
|
||||
this.activeData = this.relatedData.relatedMon || [];
|
||||
} else {
|
||||
// 切换为累计数据(非 month 均视为累计,可根据实际需求调整判断条件)
|
||||
this.activeData = this.relatedData.relatedTotal || [];
|
||||
}
|
||||
console.log('当前激活数据集:', this.activeData);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* 3. 核心:滚动容器样式(固定高度+溢出滚动+隐藏滚动条) */
|
||||
.scroll-container {
|
||||
/* 1. 固定容器高度:根据页面布局调整(示例300px,超出则滚动) */
|
||||
max-height: 210px;
|
||||
/* 2. 溢出滚动:内容超出高度时显示滚动功能 */
|
||||
overflow-y: auto;
|
||||
/* 3. 隐藏横向滚动条(防止设备名过长导致横向滚动) */
|
||||
overflow-x: hidden;
|
||||
/* 4. 内边距:与标题栏和容器边缘对齐 */
|
||||
padding: 10px 0;
|
||||
|
||||
/* 5. 隐藏滚动条(兼容主流浏览器) */
|
||||
/* Chrome/Safari */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
scrollbar-width: none;
|
||||
/* IE/Edge */
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
width: 220px;
|
||||
height: 205px;
|
||||
background: #F9FCFF;
|
||||
padding: 16px 0 0 10px;
|
||||
|
||||
.title {
|
||||
// width: 190px;
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
color: #000000;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.number {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
// width: 190px;
|
||||
height: 32px;
|
||||
font-family: YouSheBiaoTiHei;
|
||||
font-size: 32px;
|
||||
color: #0B58FF;
|
||||
line-height: 32px;
|
||||
letter-spacing: 2px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mom {
|
||||
width: 120px;
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
color: #000000;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
z-index: 1000;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -11,22 +11,21 @@
|
||||
</div>
|
||||
<div class="number">
|
||||
<div class="yield">
|
||||
{{ ytdData?.rate || 0}}%
|
||||
{{ formatRate(factoryData?.completeRate) }}%
|
||||
</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="">
|
||||
同比{{ 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=" 'totalG' " :detailData="ytdData" id="totalGauge"></electricityGauge>
|
||||
<electricityGauge id="year" :detailData="factoryData"></electricityGauge>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="line" style="padding: 0px;">
|
||||
<verticalBarChart :refName=" 'totalVerticalBarChart' " :detailData="ytdData">
|
||||
</verticalBarChart>
|
||||
|
||||
<!-- 传递包含flag的factoryData给柱状图组件 -->
|
||||
<verticalBarChart :detailData="factoryData"></verticalBarChart>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,55 +43,56 @@ import verticalBarChart from './verticalBarChart.vue'
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, electricityGauge, verticalBarChart },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
ytdData: { // 接收父组件传递的设备数据数组
|
||||
totalData: {
|
||||
type: Object,
|
||||
default: () => {} // 默认空数组,避免报错
|
||||
default: () => ({})
|
||||
},
|
||||
title: { // 接收父组件传递的设备数据数组
|
||||
title: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
month: { // 接收父组件传递的设备数据数组
|
||||
month: {
|
||||
type: String,
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
itemData: {
|
||||
handler(newValue, oldValue) {
|
||||
// this.updateChart()
|
||||
},
|
||||
deep: true // 若对象内属性变化需触发,需加 deep: true
|
||||
computed: {
|
||||
/**
|
||||
* 自动提取monData中的工厂数据,并新增flag字段
|
||||
*/
|
||||
factoryData() { // 整合原始数据 + 计算flag
|
||||
return {
|
||||
completeRate: this.totalData.proportion ? Number(this.totalData.proportion) : 0,
|
||||
diff: this.totalData.diffValue,
|
||||
real: this.totalData.value,
|
||||
target: this.totalData.targetValue,
|
||||
thb: this.totalData.thb,
|
||||
// ...rawData,
|
||||
flag: this.totalData.completed// 新增flag字段
|
||||
};
|
||||
}
|
||||
},
|
||||
// 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: {
|
||||
/**
|
||||
* 格式化百分比数值:处理空值/非数字,兜底为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>
|
||||
@@ -126,6 +126,7 @@ export default {
|
||||
height: 205px;
|
||||
background: #F9FCFF;
|
||||
padding: 16px 0 0 10px;
|
||||
|
||||
.title {
|
||||
// width: 190px;
|
||||
height: 18px;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<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>
|
||||
<span>完成率:<span style='color: #0B58FF;'>{{detailData.completeRate}}%</span></span>
|
||||
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:detailData.flag>0?'#30B590':'#FF9423'}" >{{detailData.diff}}</span></span>
|
||||
</div>
|
||||
<div :ref="refName" id="coreLineChart" style="width: 100%; height: 210px;"></div>
|
||||
</div>
|
||||
@@ -14,153 +14,109 @@ export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
myChart: null // 存储图表实例,避免重复创建
|
||||
myChart: null
|
||||
};
|
||||
},
|
||||
props: {
|
||||
// 明确接收的props结构,增强可读性
|
||||
refName: {
|
||||
type: String,
|
||||
default: () => 'verticalBarChart',
|
||||
default: 'verticalBarChart',
|
||||
},
|
||||
detailData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
completeRate: 0,
|
||||
diff: 0,
|
||||
real: 0,
|
||||
target: 0,
|
||||
thb: 0,
|
||||
flag: 0
|
||||
}),
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
this.$nextTick(() => this.updateChart());
|
||||
},
|
||||
|
||||
// 新增:监听 chartData 变化
|
||||
watch: {
|
||||
// 深度监听数据变化,仅更新图表配置(不销毁实例)
|
||||
detailData: {
|
||||
handler() {
|
||||
console.log(this.chartData, 'chartData');
|
||||
|
||||
this.updateChart();
|
||||
},
|
||||
deep: true,
|
||||
immediate: 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;
|
||||
}
|
||||
console.log('this.detailData', this.detailData);
|
||||
|
||||
// 修复:优化实例销毁逻辑,避免重复dispose
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
this.myChart.clear(); // 先清空,再重新渲染
|
||||
} else {
|
||||
this.myChart = echarts.init(chartDom);
|
||||
}
|
||||
|
||||
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 { diff, completeRate, real, target, flag } = this.detailData;
|
||||
// 确保数值为数字类型
|
||||
const realValue = Number(real) || 0;
|
||||
const targetValue = Number(target) || 0;
|
||||
const diffValue = Number(diff) || 0;
|
||||
const rateValue = Number(completeRate) || 0;
|
||||
const flagValue = Number(flag) || 0;
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
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背景,避免干扰
|
||||
show: false
|
||||
},
|
||||
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的数据自动生成
|
||||
axisLine: { show: true, lineStyle: { color: 'rgba(0, 0, 0, 0.15)' } },
|
||||
axisLabel: { color: 'rgba(0, 0, 0, 0.45)', fontSize: 12, interval: 0, padding: [5, 0, 0, 0] }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
axisLabel: {
|
||||
color: 'rgba(0, 0, 0, 0.75)',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
padding: [5, 0, 0, 0]
|
||||
},
|
||||
axisLine: {
|
||||
show: true, // 显示Y轴轴线(关键)
|
||||
lineStyle: {
|
||||
color: '#E5E6EB', // 轴线颜色(浅灰色,可自定义)
|
||||
width: 1, // 轴线宽度
|
||||
type: 'solid' // 实线(可选:dashed虚线、dotted点线)
|
||||
}
|
||||
},
|
||||
axisLabel: { color: 'rgba(0, 0, 0, 0.75)', fontSize: 12, interval: 0, padding: [5, 0, 0, 0] },
|
||||
axisLine: { show: true, lineStyle: { color: '#E5E6EB', width: 1, type: 'solid' } },
|
||||
axisTick: { show: false },
|
||||
// padding: [300, 100, 100, 100],
|
||||
data: ['实际', '预算'] // y轴分类:实际、预算
|
||||
data: ['实际', '预算']
|
||||
},
|
||||
series: [
|
||||
{
|
||||
// name: '预算',
|
||||
type: 'bar',
|
||||
barWidth: 24,
|
||||
// barCategoryGap: '50', // 柱子之间的间距(相对于柱子宽度)
|
||||
// 数据长度与yAxis的分类数量匹配(实际、预算各一个值)
|
||||
data: [{
|
||||
value: this.detailData.real,
|
||||
// 修复:拆分数据项,确保每个柱子的样式独立生效
|
||||
data: [
|
||||
// 实际值柱子(核心:绑定flag颜色)
|
||||
{
|
||||
value: realValue,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
fontSize: 14,
|
||||
fontSize: 14
|
||||
},
|
||||
// 修复:flag颜色判断独立绑定到实际值柱子
|
||||
itemStyle: {
|
||||
color: flagValue === 1
|
||||
? {
|
||||
@@ -181,44 +137,46 @@ export default {
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
}
|
||||
}, {
|
||||
value: this.detailData.target,
|
||||
},
|
||||
// 预算值柱子(固定蓝色渐变)
|
||||
{
|
||||
value: targetValue,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
fontSize: 14,
|
||||
fontSize: 14
|
||||
},
|
||||
itemStyle: {
|
||||
// 预算的渐变颜色(蓝系渐变)
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 1, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#82CCFF' }, // 浅蓝
|
||||
{ offset: 1, color: '#4B9DFF' } // 深蓝
|
||||
{ offset: 0, color: '#82CCFF' },
|
||||
{ offset: 1, color: '#4B9DFF' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0
|
||||
},
|
||||
},],
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
option && this.myChart.setOption(option);
|
||||
this.myChart.setOption(option, true); // 新增:true表示替换所有配置,避免缓存
|
||||
|
||||
// 窗口缩放适配和销毁逻辑保持不变
|
||||
window.addEventListener('resize', () => {
|
||||
// 优化:防抖resize,避免频繁触发
|
||||
const resizeHandler = () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
};
|
||||
window.removeEventListener('resize', resizeHandler); // 先移除再添加,避免重复绑定
|
||||
window.addEventListener('resize', resizeHandler);
|
||||
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
this.myChart && this.myChart.resize();
|
||||
});
|
||||
window.removeEventListener('resize', resizeHandler);
|
||||
this.myChart && this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,36 +1,24 @@
|
||||
<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%;">
|
||||
<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;'>
|
||||
<!-- 新增:topItem 专属包裹容器,统一控制样式和布局 -->
|
||||
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||
<div class="dashboard left">
|
||||
<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 class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
</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="dashboard right">
|
||||
<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 class="line">
|
||||
<operatingSingleBar></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -38,163 +26,102 @@
|
||||
</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 },
|
||||
components: { Container, operatingSingleBar, verticalBarChart },
|
||||
// mixins: [resize],
|
||||
props: {
|
||||
ytdAnalysis: {
|
||||
itemData: { // 接收父组件传递的设备数据数组
|
||||
type: Array,
|
||||
default: () => [
|
||||
{ title: "销量", budget: 0, real: 0, rate: 0, diff: 0 },
|
||||
{ title: "单价", budget: 0, real: 0, rate: 0, diff: 0 }
|
||||
]
|
||||
default: () => [] // 默认空数组,避免报错
|
||||
},
|
||||
dateData: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
title: {
|
||||
title: { // 接收父组件传递的设备数据数组
|
||||
type: String,
|
||||
default: ''
|
||||
default: () => '' // 默认空数组,避免报错
|
||||
},
|
||||
month: {
|
||||
month: { // 接收父组件传递的设备数据数组
|
||||
type: String,
|
||||
default: ''
|
||||
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)
|
||||
itemData: {
|
||||
handler(newValue, oldValue) {
|
||||
// this.updateChart()
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
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() {
|
||||
this.updateChart(this.ytdAnalysis)
|
||||
// 初始化图表(若需展示图表,需在模板中添加对应 DOM)
|
||||
// this.$nextTick(() => this.updateChart())
|
||||
},
|
||||
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>
|
||||
/* 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;
|
||||
}
|
||||
|
||||
.topItem-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
width: 382px;
|
||||
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;
|
||||
// width: 190px;
|
||||
height: 18px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
@@ -203,15 +130,74 @@ getRateFlag(rate, real, target) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard.left {
|
||||
margin-left: 0;
|
||||
// .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);
|
||||
}
|
||||
|
||||
.dashboard.right {
|
||||
margin-right: 0;
|
||||
.production-status-chart-tooltip * {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
</style> -->
|
||||
|
||||
@@ -85,7 +85,7 @@ export default {
|
||||
},
|
||||
grid: {
|
||||
top: 25,
|
||||
bottom: 20,
|
||||
bottom: 25,
|
||||
right: 10,
|
||||
left: 2,
|
||||
containLabel: true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="lineBottom" style="height: 180px; width: 100%">
|
||||
<operatingLineBarSaleSingle :refName="'totalOperating'" :chartData="chartD" style="height: 99%; width: 100%" />
|
||||
<div class="lineBottom" style="height: 160px; width: 100%">
|
||||
<operatingLineBarSaleSingle :refName="'totalOperating'" :chartData="chartD" style="height: 100%; width: 100%" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -35,59 +35,6 @@ export default {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: function (params) {
|
||||
return `{value|完成率}{rate|${rate}%}`;
|
||||
},
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' }, // 顶部0px位置:阴影最强
|
||||
// { offset: 0.1, color: 'rgba(205, 215, 224, 0.4)' }, // 1px位置:阴影减弱(对应1px)
|
||||
// { offset: 0.15, color: 'rgba(205, 215, 224, 0.6)' }, // 3px位置:阴影几乎消失(对应3px扩散)
|
||||
{ offset: 0.2, color: '#ffffff' }, // 主体白色
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
// 外阴影:0px 2px 2px 0px rgba(191,203,215,0.5)
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
// 圆角:4px
|
||||
borderRadius: 4,
|
||||
// 移除边框
|
||||
borderColor: '#BFCBD577',
|
||||
borderWidth: 0,
|
||||
// 文字垂直居中(针对富文本)
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
value: {
|
||||
// 缩小宽度和内边距,适配68px容器
|
||||
width: 'auto', // 自动宽度,替代固定40px
|
||||
padding: [5, 0, 5, 10], // 缩小内边距
|
||||
align: 'center',
|
||||
color: '#464646', // 文字灰色
|
||||
fontSize: 11, // 缩小字体,适配小尺寸
|
||||
lineHeight: 20 // 垂直居中
|
||||
},
|
||||
rate: {
|
||||
width: 'auto',
|
||||
padding: [5, 10, 5, 0],
|
||||
align: 'center',
|
||||
color: '#0B58FF', // 数字蓝色
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
}
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
color: {
|
||||
@@ -109,69 +56,6 @@ export default {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: (params) => {
|
||||
|
||||
// const flags = flags || [];
|
||||
const currentDiff = diff || 0;
|
||||
const currentFlag = this.detailData?.flag || 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: {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
@@ -185,7 +69,7 @@ export default {
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
barWidth: 24,
|
||||
barWidth: 60,
|
||||
barCategoryGap: '50%',
|
||||
data: seriesData,
|
||||
itemStyle: {
|
||||
|
||||
@@ -2,65 +2,22 @@
|
||||
<div style="flex: 1">
|
||||
<Container :isShowTab="true" :name="title" icon="cockpitItemIcon" size="opLargeBg" topSize="large"
|
||||
@tabChange="handleChange">
|
||||
<!-- 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"
|
||||
@click="handleDashboardClick('/productionCostAnalysis/productionCostAnalysisBase')">
|
||||
<div class="topItem-container" style="display: flex; gap: 8px">
|
||||
<div
|
||||
v-for="item in sortedIndicators"
|
||||
:key="item.key"
|
||||
class="dashboard"
|
||||
@click="item.route && handleDashboardClick(item.route)"
|
||||
>
|
||||
<div class="title">
|
||||
制造成本·元/㎡
|
||||
{{ item.name }}·{{ item.unit }}
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.制造成本 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.制造成本 || defaultData).completeRate, (relatedDetailData.制造成本 || defaultData).real, (relatedDetailData.制造成本 || defaultData).target)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right" @click="handleDashboardClick('/expenseAnalysis/expenseAnalysisBase')">
|
||||
<div class="title">
|
||||
财务费用·元/㎡
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.财务费用 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.财务费用 || defaultData).completeRate, (relatedDetailData.财务费用 || defaultData).real, (relatedDetailData.财务费用 || defaultData).target)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right" @click="handleDashboardClick('/expenseAnalysis/expenseAnalysisBase')">
|
||||
<div class="title">
|
||||
销售费用·元/㎡
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.销售费用 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.销售费用 || defaultData).completeRate, (relatedDetailData.销售费用 || defaultData).real, (relatedDetailData.销售费用 || defaultData).target,)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right" @click="handleDashboardClick('/expenseAnalysis/expenseAnalysisBase')">
|
||||
<div class="title">
|
||||
管理费用·元/㎡
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.管理费用 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.管理费用 || defaultData).completeRate, (relatedDetailData.管理费用 || defaultData).real, (relatedDetailData.管理费用 || defaultData).target)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
运费·元/㎡
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.运费 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.运费 || defaultData).completeRate, (relatedDetailData.运费 || defaultData).reat, (relatedDetailData.运费 || defaultData).target)
|
||||
}" />
|
||||
<div style='font-size: 16px;text-align: right;padding-right: 5px;'>
|
||||
<span>完成率:<span style='color: #0B58FF;'>{{item.detailData.completeRate}}%</span></span>
|
||||
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:item.detailData.flag>0?'#30B590':'#FF9423'}" >{{item.detailData.diff}}</span></span>
|
||||
</div>
|
||||
<operatingSingleBar :detailData="item.detailData"></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,6 +73,57 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
indicatorDefs() {
|
||||
return [
|
||||
{ key: 'productionCost', name: '制造成本', unit: '元/㎡', route:'/productionCostAnalysis/productionCostAnalysisBase'},
|
||||
{ key: 'financialCost', name: '财务费用', unit: '元/㎡',route:'/expenseAnalysis/expenseAnalysisBase' },
|
||||
{ key: 'saleCost', name: '销售费用', unit: '元/㎡',route:'/expenseAnalysis/expenseAnalysisBase'},
|
||||
{ key: 'manageCost', name: '管理费用', unit: '元/㎡',route:'/expenseAnalysis/expenseAnalysisBase' },
|
||||
{ key: 'freight', name: '运费', unit: '元/㎡',route:null },
|
||||
]
|
||||
},
|
||||
indicators() {
|
||||
let _this = this
|
||||
const fallback = { target: 0, real: 0, completeRate: 0, diff: 0, flag: 0 }
|
||||
const list = Object.entries(_this.relatedDetailData).map(([title, data]) => {
|
||||
return {
|
||||
title: title,
|
||||
target: data.target,
|
||||
real: data.real,
|
||||
completeRate: data.completeRate,
|
||||
diff: data.diff
|
||||
};
|
||||
});
|
||||
return _this.indicatorDefs.map(def => {
|
||||
const data = list.find(item => item && item.title === def.name) || fallback
|
||||
const detailData = {
|
||||
...data,
|
||||
flag: _this.getRateFlag((data || _this.defaultData).completeRate, (data || _this.defaultData).real, (data || _this.defaultData).target),
|
||||
}
|
||||
return {
|
||||
...def,
|
||||
detailData,
|
||||
sortValue: Number((data && data.real) ?? 0)
|
||||
}
|
||||
})
|
||||
},
|
||||
sortedIndicators() {
|
||||
const unitOrder = ['元/㎡']
|
||||
const unitRank = (u) => {
|
||||
const idx = unitOrder.indexOf(u)
|
||||
return idx === -1 ? 999 : idx
|
||||
}
|
||||
|
||||
return this.indicators.slice().sort((a, b) => {
|
||||
const ur = unitRank(a.unit) - unitRank(b.unit)
|
||||
if (ur !== 0) return ur
|
||||
const vr = (b.sortValue ?? -Infinity) - (a.sortValue ?? -Infinity)
|
||||
if (vr !== 0) return vr
|
||||
return String(a.key).localeCompare(String(b.key))
|
||||
})
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// 监听 relatedData 变化(异步加载场景),同步更新月度数据
|
||||
relatedData: {
|
||||
@@ -145,11 +153,6 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
handleRoute(path) {
|
||||
this.$router.push({
|
||||
path: path
|
||||
})
|
||||
},
|
||||
getRateFlag(rate, real, target) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ export default {
|
||||
},
|
||||
grid: {
|
||||
top: 25,
|
||||
bottom: 20,
|
||||
bottom: 25,
|
||||
right: 10,
|
||||
left: 2,
|
||||
containLabel: true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="lineBottom" style="height: 180px; width: 100%">
|
||||
<operatingLineBarSaleSingle :refName="'totalOperating'" :chartData="chartD" style="height: 99%; width: 100%" />
|
||||
<div class="lineBottom" style="height: 160px; width: 100%">
|
||||
<operatingLineBarSaleSingle :refName="'totalOperating'" :chartData="chartD" style="height: 100%; width: 100%" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -35,59 +35,6 @@ export default {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: function (params) {
|
||||
return `{value|完成率}{rate|${rate}%}`;
|
||||
},
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' }, // 顶部0px位置:阴影最强
|
||||
// { offset: 0.1, color: 'rgba(205, 215, 224, 0.4)' }, // 1px位置:阴影减弱(对应1px)
|
||||
// { offset: 0.15, color: 'rgba(205, 215, 224, 0.6)' }, // 3px位置:阴影几乎消失(对应3px扩散)
|
||||
{ offset: 0.2, color: '#ffffff' }, // 主体白色
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
// 外阴影:0px 2px 2px 0px rgba(191,203,215,0.5)
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
// 圆角:4px
|
||||
borderRadius: 4,
|
||||
// 移除边框
|
||||
borderColor: '#BFCBD577',
|
||||
borderWidth: 0,
|
||||
// 文字垂直居中(针对富文本)
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
value: {
|
||||
// 缩小宽度和内边距,适配68px容器
|
||||
width: 'auto', // 自动宽度,替代固定40px
|
||||
padding: [5, 0, 5, 10], // 缩小内边距
|
||||
align: 'center',
|
||||
color: '#464646', // 文字灰色
|
||||
fontSize: 11, // 缩小字体,适配小尺寸
|
||||
lineHeight: 20 // 垂直居中
|
||||
},
|
||||
rate: {
|
||||
width: 'auto',
|
||||
padding: [5, 10, 5, 0],
|
||||
align: 'center',
|
||||
color: '#0B58FF', // 数字蓝色
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
}
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
color: {
|
||||
@@ -109,72 +56,6 @@ export default {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: (params) => {
|
||||
|
||||
// const flags = flags || [];
|
||||
const currentDiff = diff || 0;
|
||||
const currentFlag = this.detailData?.flag || 0;
|
||||
// console.log('flags[params.dataIndex]', flags);
|
||||
|
||||
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: {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
@@ -188,7 +69,7 @@ export default {
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
barWidth: 24,
|
||||
barWidth: 60,
|
||||
barCategoryGap: '50%',
|
||||
data: seriesData,
|
||||
itemStyle: {
|
||||
|
||||
@@ -3,61 +3,21 @@
|
||||
<Container :isShowTab="true" :name="title" icon="cockpitItemIcon" size="opLargeBg" topSize="large"
|
||||
@tabChange="handleChange">
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||
<div class="dashboard left">
|
||||
<div class="topItem-container" style="display: flex; gap: 8px">
|
||||
<div
|
||||
v-for="item in sortedIndicators"
|
||||
:key="item.key"
|
||||
class="dashboard"
|
||||
@click="item.route && handleDashboardClick(item.route)"
|
||||
>
|
||||
<div class="title">
|
||||
大宗类·万元
|
||||
{{ item.name }}·{{ item.unit }}
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.大宗增效额 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.大宗增效额 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
石料类·万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.石料增效额 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.石料增效额 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
材料类·万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.材料增效额 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.材料增效额 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
动力类·万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.动力增效额 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.动力增效额 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right" @click="handleRoute('/salesVolumeAnalysis/doublePlatedBase')">
|
||||
<div class="title">
|
||||
技术服务类·万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.技术服务类 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.技术服务类 || defaultData).completeRate)
|
||||
}" />
|
||||
<div style='font-size: 16px;text-align: right;padding-right: 5px;'>
|
||||
<span>完成率:<span style='color: #0B58FF;'>{{item.detailData.completeRate}}%</span></span>
|
||||
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:item.detailData.flag>0?'#30B590':'#FF9423'}" >{{item.detailData.diff}}</span></span>
|
||||
</div>
|
||||
<operatingSingleBar :detailData="item.detailData"></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,6 +66,57 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
indicatorDefs() {
|
||||
return [
|
||||
{ key: 'grossProfit', name: '大宗类', unit: '万元', route:null},
|
||||
{ key: 'stoneProfit', name: '石料类', unit: '万元',route:null},
|
||||
{ key: 'materialProfit', name: '材料类', unit: '万元',route:null},
|
||||
{ key: 'powerProfit', name: '动力类', unit: '万元',route:null},
|
||||
{ key: 'technicalServiceProfit', name: '技术服务类', unit: '万元',route:'/salesVolumeAnalysis/doublePlatedBase' },
|
||||
]
|
||||
},
|
||||
indicators() {
|
||||
let _this = this
|
||||
const fallback = { target: 0, real: 0, completeRate: 0, diff: 0, flag: 0 }
|
||||
const list = Object.entries(_this.relatedDetailData).map(([title, data]) => {
|
||||
return {
|
||||
title: title,
|
||||
target: data.target,
|
||||
real: data.real,
|
||||
completeRate: data.completeRate,
|
||||
diff: data.diff
|
||||
};
|
||||
});
|
||||
return _this.indicatorDefs.map(def => {
|
||||
const data = list.find(item => item && item.title.includes(def.name.slice(0, -1))) || fallback
|
||||
const detailData = {
|
||||
...data,
|
||||
flag: _this.getRateFlag((data || _this.defaultData).completeRate, (data || _this.defaultData).real, (data || _this.defaultData).target),
|
||||
}
|
||||
return {
|
||||
...def,
|
||||
detailData,
|
||||
sortValue: Number((data && data.real) ?? 0)
|
||||
}
|
||||
})
|
||||
},
|
||||
sortedIndicators() {
|
||||
const unitOrder = ['万元']
|
||||
const unitRank = (u) => {
|
||||
const idx = unitOrder.indexOf(u)
|
||||
return idx === -1 ? 999 : idx
|
||||
}
|
||||
|
||||
return this.indicators.slice().sort((a, b) => {
|
||||
const ur = unitRank(a.unit) - unitRank(b.unit)
|
||||
if (ur !== 0) return ur
|
||||
const vr = (b.sortValue ?? -Infinity) - (a.sortValue ?? -Infinity)
|
||||
if (vr !== 0) return vr
|
||||
return String(a.key).localeCompare(String(b.key))
|
||||
})
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// 监听 relatedData 变化(异步加载场景),同步更新月度数据
|
||||
relatedData: {
|
||||
@@ -131,9 +142,17 @@ export default {
|
||||
path: path
|
||||
})
|
||||
},
|
||||
getRateFlag(rate) {
|
||||
getRateFlag(rate, real, target) {
|
||||
if (isNaN(rate) || rate === null || rate === undefined) return 0;
|
||||
return +(rate >= 100 || rate === 0);
|
||||
|
||||
// 1. 完成率 >= 100 => 达标
|
||||
if (rate >= 100) return 1;
|
||||
|
||||
// 2. 完成率 = 0 且 (目标值=0 或 实际值=目标值=0) => 达标
|
||||
if (rate === 0 && target === 0) return 1;
|
||||
|
||||
// 其他情况 => 未达标
|
||||
return 0;
|
||||
},
|
||||
handleChange(value) {
|
||||
console.log('value', value, this.relatedData);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div id="dayReport" class="dayReport" :style="styles">
|
||||
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||
<ReportHeader :dateData="dateData" size="psi" @timeRangeChange="handleTimeChange" top-title="原片燃料成本分析"
|
||||
<ReportHeader :dateData="dateData" size="psi" @timeRangeChange="handleTimeChange" top-title="原片燃动力成本分析"
|
||||
:is-full-screen="isFullScreen" @screenfullChange="screenfullChange" />
|
||||
<div class="main-body" style="
|
||||
margin-top: -20px;
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
<template>
|
||||
<div id="dayReport" class="dayReport" :style="styles">
|
||||
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||
<ReportHeader :dateData="dateData" size="psi" @timeRangeChange="handleTimeChange" top-title="复合澄清剂成本分析"
|
||||
:is-full-screen="isFullScreen" @screenfullChange="screenfullChange" />
|
||||
<div class="main-body" style="
|
||||
margin-top: -20px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
padding: 0px 16px 0 272px;
|
||||
flex-direction: column;
|
||||
">
|
||||
<div class="top" style="display: flex; gap: 16px">
|
||||
<div class="top-three" style="
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<changeBase :factory="factory" @baseChange="selectChange" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px;margin-top: -20px;">
|
||||
<div class="left-three" style="
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
grid-template-columns: 804px 804px;
|
||||
">
|
||||
<monthlyOverview :month="month" :monData="monData" :title="'月度概览'" />
|
||||
<totalOverview :totalData="totalData" :title="'累计概览'" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="middle" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||
<div class="left-three" style="
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
grid-template-columns: 804px 804px;
|
||||
">
|
||||
<monthlyRelatedMetrics :dateData="dateData" :factory="factory" :relatedData="monthRelatedData"
|
||||
:title="'月度·相关指标分析'" />
|
||||
<yearRelatedMetrics :dateData="dateData" :factory="factory" :relatedData="totalRelatedData" :title="'累计·相关指标分析'" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||
<div class="left-three" style="
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<dataTrend @getData="changeItem" :trendData="trend" :title="'数据趋势'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ReportHeader from "../components/noRouterHeader.vue";
|
||||
import { Sidebar } from "../../../layout/components";
|
||||
import screenfull from "screenfull";
|
||||
import changeBase from "../components/changeBase.vue";
|
||||
import monthlyOverview from "../productionCostAnalysisComponents/monthlyOverview.vue";
|
||||
import totalOverview from "../productionCostAnalysisComponents/totalOverview.vue";
|
||||
import monthlyRelatedMetrics from "../productionCostAnalysisComponents/monthlyThreeRelatedMetricsCCA.vue";
|
||||
import yearRelatedMetrics from "../productionCostAnalysisComponents/yearThreeRelatedMetricsCCA.vue";
|
||||
import dataTrend from "../productionCostAnalysisComponents/dataTrendSingleCCA.vue";
|
||||
import { mapState } from "vuex";
|
||||
import { getSingleMaterialAnalysis } from '@/api/cockpit'
|
||||
import moment from "moment";
|
||||
export default {
|
||||
name: "DayReport",
|
||||
components: {
|
||||
ReportHeader,
|
||||
changeBase,
|
||||
monthlyOverview,
|
||||
Sidebar,
|
||||
totalOverview,
|
||||
monthlyRelatedMetrics,
|
||||
yearRelatedMetrics,
|
||||
dataTrend
|
||||
// psiLineChart
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isFullScreen: false,
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
month: '',
|
||||
value: 100,
|
||||
factory: null,
|
||||
dateData: {},
|
||||
monData: {},
|
||||
totalData: {},
|
||||
trend: [],
|
||||
monthRelatedData: [],
|
||||
totalRelatedData: [],
|
||||
trendName: '复合澄清剂',
|
||||
meterialName:'复合澄清剂',
|
||||
};
|
||||
},
|
||||
|
||||
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,
|
||||
}),
|
||||
renderList() {
|
||||
if (this.itemData && this.itemData.length > 0) {
|
||||
return this.itemData;
|
||||
}
|
||||
return this.parentItemList;
|
||||
},
|
||||
classObj() {
|
||||
return {
|
||||
hideSidebar: !this.sidebar.opened,
|
||||
openSidebar: this.sidebar.opened,
|
||||
withoutAnimation: this.sidebar.withoutAnimation,
|
||||
mobile: this.device === "mobile",
|
||||
};
|
||||
},
|
||||
variables() {
|
||||
return variables;
|
||||
},
|
||||
styles() {
|
||||
const v = Math.floor(this.value * this.beilv * 100) / 10000;
|
||||
return {
|
||||
transform: `scale(${v})`,
|
||||
transformOrigin: "left top",
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
clientWidth(val) {
|
||||
if (!this.timer) {
|
||||
this.clientWidth = val;
|
||||
this.beilv2 = this.clientWidth / 1920;
|
||||
this.timer = true;
|
||||
let _this = this;
|
||||
setTimeout(function () {
|
||||
_this.timer = false;
|
||||
}, 500);
|
||||
}
|
||||
// 这里可以添加修改时的方法
|
||||
this.windowWidth(val);
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.timer);
|
||||
this.destroy();
|
||||
},
|
||||
mounted() {
|
||||
const _this = this;
|
||||
_this.beilv = document.documentElement.clientWidth / 1920;
|
||||
window.onresize = () => {
|
||||
return (() => {
|
||||
_this.clientWidth = `${document.documentElement.clientWidth}`;
|
||||
this.beilv = _this.clientWidth / 1920;
|
||||
})();
|
||||
};
|
||||
if(this.$route.query.factory){
|
||||
this.factory =Number(this.$route.query.factory)
|
||||
}else if(this.$store.getters.levelList.length > 0 && this.$store.getters.levelList[0].id !== 1) {
|
||||
this.factory = this.$store.getters.levelList[0].id
|
||||
}else{
|
||||
this.factory = this.$store.getters.levelList[1].id
|
||||
}
|
||||
this.dateData = this.$route.query.dateData ? this.$route.query.dateData : undefined
|
||||
},
|
||||
methods: {
|
||||
changeItem(item) {
|
||||
this.trendName = item
|
||||
this.getData()
|
||||
},
|
||||
getData() {
|
||||
const requestParams = {
|
||||
startTime: this.dateData.startTime,
|
||||
endTime: this.dateData.endTime,
|
||||
trendName: this.trendName+'成本',
|
||||
analysisObject: ['复合澄清剂成本'],
|
||||
levelId: this.factory,
|
||||
};
|
||||
// 调用接口
|
||||
getSingleMaterialAnalysis(requestParams).then((res) => {
|
||||
this.monData = res.data.currentMonthData.find(item => {
|
||||
return item.name === '复合澄清剂成本';
|
||||
});
|
||||
this.totalData = res.data.totalMonthData.find(item => {
|
||||
return item.name === '复合澄清剂成本';
|
||||
});
|
||||
this.monthRelatedData = res.data.currentMonthData
|
||||
this.totalRelatedData = res.data.totalMonthData
|
||||
this.trend = res.data.dataTrend
|
||||
});
|
||||
},
|
||||
|
||||
handleTimeChange(obj) {
|
||||
this.month = obj.targetMonth
|
||||
this.dateData = {
|
||||
startTime: obj.startTime,
|
||||
endTime: obj.endTime,
|
||||
mode: obj.mode,
|
||||
}
|
||||
|
||||
this.getData()
|
||||
},
|
||||
selectChange(data) {
|
||||
this.factory = data
|
||||
if (this.dateData.startTime && this.dateData.endTime) {
|
||||
this.getData();
|
||||
}
|
||||
},
|
||||
handleClickOutside() {
|
||||
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
|
||||
},
|
||||
windowWidth(value) {
|
||||
this.clientWidth = value;
|
||||
this.beilv2 = this.clientWidth / 1920;
|
||||
},
|
||||
change() {
|
||||
this.isFullScreen = screenfull.isFullscreen;
|
||||
},
|
||||
init() {
|
||||
if (!screenfull.isEnabled) {
|
||||
this.$message({
|
||||
message: "you browser can not work",
|
||||
type: "warning",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
screenfull.on("change", this.change);
|
||||
},
|
||||
destroy() {
|
||||
if (!screenfull.isEnabled) {
|
||||
this.$message({
|
||||
message: "you browser can not work",
|
||||
type: "warning",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
screenfull.off("change", this.change);
|
||||
},
|
||||
// 全屏
|
||||
screenfullChange() {
|
||||
console.log("screenfull.enabled", screenfull.isEnabled);
|
||||
|
||||
if (!screenfull.isEnabled) {
|
||||
this.$message({
|
||||
message: "you browser can not work",
|
||||
type: "warning",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
screenfull.toggle(this.$refs.dayReportB);
|
||||
},
|
||||
changeDate(val) {
|
||||
this.date = val;
|
||||
// this.weekDay = this.weekArr[moment(this.date).format('e')]
|
||||
// this.getData()
|
||||
if (this.date === moment().format("yyyy-MM-DD")) {
|
||||
this.loopTime();
|
||||
} else {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</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>
|
||||
@@ -33,7 +33,7 @@
|
||||
<div class="middle" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||
<div class="left-three" style="
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
gap: 6px;
|
||||
grid-template-columns: 1624px;
|
||||
">
|
||||
<!-- <monthlyRelatedMetrics :itemData="renderList" :title="'月度·相关指标分析'" /> -->
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
grid-template-columns: 804px 804px;
|
||||
">
|
||||
<monthlyRelatedMetrics :dateData="dateData" :factory="factory" :relatedData="monthRelatedData"
|
||||
:title="'月度概览'" />
|
||||
<yearRelatedMetrics :dateData="dateData" :factory="factory" :relatedData="totalRelatedData" :title="'累计概览'" />
|
||||
:title="'月度·相关指标分析'" />
|
||||
<yearRelatedMetrics :dateData="dateData" :factory="factory" :relatedData="totalRelatedData" :title="'累计·相关指标分析'" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom" style="display: flex; gap: 16px;margin-top: 6px;">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div id="dayReport" class="dayReport" :style="styles">
|
||||
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||
<ReportHeader :dateData="dateData" size="psi" @timeRangeChange="handleTimeChange" top-title="加工燃料成本分析"
|
||||
<ReportHeader :dateData="dateData" size="psi" @timeRangeChange="handleTimeChange" top-title="加工燃动力成本分析"
|
||||
:is-full-screen="isFullScreen" @screenfullChange="screenfullChange" />
|
||||
<div class="main-body" style="
|
||||
margin-top: -20px;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||
<ReportHeader :dateData="dateData" size="psi" @timeRangeChange="handleTimeChange"
|
||||
@selectChange='handlefuelChange' :selectName='fuelName' :selectOptions='fuelOptions'
|
||||
top-title="单项原片燃料成本分析" :is-full-screen="isFullScreen"
|
||||
top-title="单项原片燃动力成本分析" :is-full-screen="isFullScreen"
|
||||
@screenfullChange="screenfullChange" :leftMargin="'280px'" />
|
||||
<div class="main-body" style="
|
||||
margin-top: -20px;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||
<ReportHeader :dateData="dateData" size="psi" @timeRangeChange="handleTimeChange"
|
||||
@selectChange='handleFuelChange' :selectName='fuelName' :selectOptions='fuelOptions'
|
||||
top-title="单项加工燃料成本分析" :is-full-screen="isFullScreen"
|
||||
top-title="单项加工燃动力成本分析" :is-full-screen="isFullScreen"
|
||||
@screenfullChange="screenfullChange" :leftMargin="'280px'" />
|
||||
<div class="main-body" style="
|
||||
margin-top: -20px;
|
||||
|
||||
@@ -0,0 +1,475 @@
|
||||
<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>
|
||||
@@ -64,10 +64,13 @@ export default {
|
||||
profitOptions: [
|
||||
'原片原料成本',
|
||||
'硅砂',
|
||||
'海砂',
|
||||
'纯碱',
|
||||
'白云石',
|
||||
'石灰石',
|
||||
'复合澄清剂',
|
||||
'氢氧化铝',
|
||||
'助溶剂',
|
||||
'碎玻璃'
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
@@ -66,6 +66,7 @@ export default {
|
||||
'产量',
|
||||
'单耗',
|
||||
'消耗量',
|
||||
'日均消耗量'
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
@@ -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 "./dataTrendBarCCA.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,239 @@
|
||||
<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
|
||||
v-for="item in sortedIndicators"
|
||||
:key="item.key"
|
||||
class="dashboard"
|
||||
@click="handleDashboardClick(item.name)"
|
||||
>
|
||||
<div class="title">
|
||||
{{ item.name }}·{{ item.unit }}
|
||||
</div>
|
||||
<div style='font-size: 16px;text-align: right;padding-right: 5px;'>
|
||||
<span>完成率:<span style='color: #0B58FF;'>{{item.data.proportion}}%</span></span>
|
||||
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:item.data.completed>0?'#30B590':'#FF9423'}" >{{item.data.diffValue}}</span></span>
|
||||
</div>
|
||||
<operatingSingleBar :detailData="item.data"></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
import operatingSingleBar from './operatingSingleBar.vue'
|
||||
|
||||
// import * as echarts from 'echarts'
|
||||
// import rawItem from './raw-Item.vue'
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, operatingSingleBar },
|
||||
props: {
|
||||
// 接收父组件传递的 月度+累计 组合数据(原有配置保留,仅优化注释)
|
||||
relatedData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
factory: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
dateData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 可选:动态标题(原有配置保留)
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
// 注释无效的activeData,保持数据简洁(样式不变)
|
||||
// activeData: this.relatedData || []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
indicatorDefs() {
|
||||
return [
|
||||
{ key: 'mx', name: '芒硝', unit: '元/㎡'},
|
||||
{ key: 'xsn', name: '硝酸钠', unit: '元/㎡'},
|
||||
{ key: 'jtsn', name: '焦锑酸钠', unit: '元/㎡'},
|
||||
]
|
||||
},
|
||||
indicators() {
|
||||
const fallback = { targetValue: 0, value: 0, completed: 0, diffValue: 0 }
|
||||
const list = (Array.isArray(this.relatedData) ? this.relatedData : [])
|
||||
|
||||
return this.indicatorDefs.map(def => {
|
||||
const data = list.find(item => item && item.name.includes(def.name)) || fallback
|
||||
return {
|
||||
...def,
|
||||
data,
|
||||
sortValue: Number((data && data.value) ?? 0)
|
||||
}
|
||||
})
|
||||
},
|
||||
sortedIndicators() {
|
||||
const unitOrder = ['元/㎡']
|
||||
const unitRank = (u) => {
|
||||
const idx = unitOrder.indexOf(u)
|
||||
return idx === -1 ? 999 : idx
|
||||
}
|
||||
|
||||
return this.indicators.slice().sort((a, b) => {
|
||||
const ur = unitRank(a.unit) - unitRank(b.unit)
|
||||
if (ur !== 0) return ur
|
||||
const vr = (b.sortValue ?? -Infinity) - (a.sortValue ?? -Infinity)
|
||||
if (vr !== 0) return vr
|
||||
return String(a.key).localeCompare(String(b.key))
|
||||
})
|
||||
}
|
||||
},
|
||||
// 保留原有watch注释(若需恢复可直接启用,样式不变)
|
||||
// watch: {
|
||||
// // 可选:监听 relatedData 初始变化(若父组件异步传递数据,确保 activeData 同步更新)
|
||||
// relatedData: {
|
||||
// handler(newVal) {
|
||||
// this.activeData = newVal.relatedMon || [];
|
||||
// },
|
||||
// immediate: true,
|
||||
// deep: true
|
||||
// }
|
||||
// },
|
||||
mounted() {
|
||||
// 优化打印日志,输出有效数据(移除无效的activeData打印,样式不变)
|
||||
console.log('组件挂载时的相关数据:', this.relatedData);
|
||||
},
|
||||
methods: {
|
||||
handleDashboardClick(name) {
|
||||
this.$router.push({
|
||||
path: 'singleProcMfgOverheadCost',
|
||||
query: {
|
||||
name,
|
||||
factory: this.$route.query.factory ? this.$route.query.factory : this.factory,
|
||||
dateData: this.dateData
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</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: 250px;
|
||||
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> -->
|
||||
@@ -140,7 +140,7 @@ export default {
|
||||
top: 30,
|
||||
bottom: 5,
|
||||
right: 20,
|
||||
left: 25,
|
||||
left: 35,
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: [
|
||||
|
||||
@@ -71,7 +71,7 @@ export default {
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
barWidth: 60,
|
||||
barWidth: 40,
|
||||
barCategoryGap: '50%',
|
||||
data: seriesData,
|
||||
itemStyle: {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="title">
|
||||
{{ item.name }}·{{ item.unit }}
|
||||
</div>
|
||||
<div style='font-size: 16px;text-align: right;padding-right: 5px;'>
|
||||
<div style='font-size: 14px;text-align: right;padding-right: 5px;'>
|
||||
<span>完成率:<span style='color: #0B58FF;'>{{item.data.proportion}}%</span></span>
|
||||
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:item.data.completed>0?'#30B590':'#FF9423'}" >{{item.data.diffValue}}</span></span>
|
||||
</div>
|
||||
@@ -65,10 +65,14 @@ export default {
|
||||
indicatorDefs() {
|
||||
return [
|
||||
{ key: 'silicaSand', name: '硅砂', unit: '元/㎡', route: 'SIMFRMCostAnalysis'},
|
||||
{ key: 'seaSand', name: '海砂', unit: '元/㎡', route: 'SIMFRMCostAnalysis'},
|
||||
// { key: 'seaSand', name: '海砂', unit: '元/㎡', route: 'SIMFRMCostAnalysis'},
|
||||
{ key: 'sodaAsh', name: '纯碱', unit: '元/㎡', route: 'SIMFRMCostAnalysis'},
|
||||
{ key: 'dolomite', name: '白云石', unit: '元/㎡', route: 'SIMFRMCostAnalysis'},
|
||||
{ key: 'limestone', name: '石灰石', unit: '元/㎡', route: 'SIMFRMCostAnalysis'},
|
||||
{ key: 'compoundClarifyingAgent', name: '复合澄清剂', unit: '元/㎡', route: 'compositeClarifyingAgentCostAnalysis'},
|
||||
{ key: 'ATH', name: '氢氧化铝', unit: '元/㎡', route: null},
|
||||
{ key: 'cosolvent', name: '助溶剂', unit: '元/㎡', route: null},
|
||||
{ key: 'brokenGlass', name: '碎玻璃', unit: '元/㎡', route: null},
|
||||
]
|
||||
},
|
||||
indicators() {
|
||||
@@ -166,7 +170,7 @@ export default {
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
width: 310px;
|
||||
width: 194px;
|
||||
height: 205px;
|
||||
background: #F9FCFF;
|
||||
padding: 16px 0 0 10px;
|
||||
|
||||
@@ -68,7 +68,8 @@ export default {
|
||||
{ key: 'rawMaterialCost', name: '采购单价', unit: '元/吨'},
|
||||
{ key: 'fuelCost', name: '产量', unit: '吨'},
|
||||
{ key: 'electricityCost', name: '单耗', unit: '吨'},
|
||||
{ key: 'laborCost', name: '消耗量', unit: '吨'}
|
||||
{ key: 'laborCost', name: '消耗量', unit: '吨'},
|
||||
{ key: 'laborCostDay', name: '日均消耗量', unit: '吨'}
|
||||
]
|
||||
},
|
||||
indicators() {
|
||||
@@ -156,7 +157,7 @@ export default {
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
width: 390px;
|
||||
width: 312px;
|
||||
height: 205px;
|
||||
background: #F9FCFF;
|
||||
padding: 16px 0 0 10px;
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
<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
|
||||
v-for="item in sortedIndicators"
|
||||
:key="item.key"
|
||||
class="dashboard"
|
||||
@click="handleDashboardClick(item.name)"
|
||||
>
|
||||
<div class="title">
|
||||
{{ item.name }}·{{ item.unit }}
|
||||
</div>
|
||||
<div style='font-size: 16px;text-align: right;padding-right: 5px;'>
|
||||
<span>完成率:<span style='color: #0B58FF;'>{{item.data.proportion}}%</span></span>
|
||||
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:item.data.completed>0?'#30B590':'#FF9423'}" >{{item.data.diffValue}}</span></span>
|
||||
</div>
|
||||
<operatingSingleBar :detailData="item.data"></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from './container.vue'
|
||||
import operatingSingleBar from './operatingSingleBar.vue'
|
||||
|
||||
// import * as echarts from 'echarts'
|
||||
// import rawItem from './raw-Item.vue'
|
||||
export default {
|
||||
name: 'ProductionStatus',
|
||||
components: { Container, operatingSingleBar },
|
||||
props: {
|
||||
// 接收父组件传递的 月度+累计 组合数据(原有配置保留,仅优化注释)
|
||||
relatedData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
factory: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
dateData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 可选:动态标题(原有配置保留)
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
// 注释无效的activeData,保持数据简洁(样式不变)
|
||||
// activeData: this.relatedData || []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
indicatorDefs() {
|
||||
return [
|
||||
{ key: 'mx', name: '芒硝', unit: '元/㎡'},
|
||||
{ key: 'xsn', name: '硝酸钠', unit: '元/㎡'},
|
||||
{ key: 'jtsn', name: '焦锑酸钠', unit: '元/㎡'},
|
||||
]
|
||||
},
|
||||
indicators() {
|
||||
const fallback = { targetValue: 0, value: 0, completed: 0, diffValue: 0 }
|
||||
const list = (Array.isArray(this.relatedData) ? this.relatedData : [])
|
||||
|
||||
return this.indicatorDefs.map(def => {
|
||||
const data = list.find(item => item && item.name.includes(def.name)) || fallback
|
||||
return {
|
||||
...def,
|
||||
data,
|
||||
sortValue: Number((data && data.value) ?? 0)
|
||||
}
|
||||
})
|
||||
},
|
||||
sortedIndicators() {
|
||||
const unitOrder = ['元/㎡']
|
||||
const unitRank = (u) => {
|
||||
const idx = unitOrder.indexOf(u)
|
||||
return idx === -1 ? 999 : idx
|
||||
}
|
||||
|
||||
return this.indicators.slice().sort((a, b) => {
|
||||
const ur = unitRank(a.unit) - unitRank(b.unit)
|
||||
if (ur !== 0) return ur
|
||||
const vr = (b.sortValue ?? -Infinity) - (a.sortValue ?? -Infinity)
|
||||
if (vr !== 0) return vr
|
||||
return String(a.key).localeCompare(String(b.key))
|
||||
})
|
||||
}
|
||||
},
|
||||
// 保留原有watch注释(若需恢复可直接启用,样式不变)
|
||||
// watch: {
|
||||
// // 可选:监听 relatedData 初始变化(若父组件异步传递数据,确保 activeData 同步更新)
|
||||
// relatedData: {
|
||||
// handler(newVal) {
|
||||
// this.activeData = newVal.relatedMon || [];
|
||||
// },
|
||||
// immediate: true,
|
||||
// deep: true
|
||||
// }
|
||||
// },
|
||||
mounted() {
|
||||
// 优化打印日志,输出有效数据(移除无效的activeData打印,样式不变)
|
||||
console.log('组件挂载时的相关数据:', this.relatedData);
|
||||
},
|
||||
methods: {
|
||||
handleDashboardClick(name) {
|
||||
this.$router.push({
|
||||
path: 'singleProcMfgOverheadCost',
|
||||
query: {
|
||||
name,
|
||||
factory: this.$route.query.factory ? this.$route.query.factory : this.factory,
|
||||
dateData: this.dateData
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</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: 250px;
|
||||
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> -->
|
||||
@@ -1,31 +1,21 @@
|
||||
<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="topItem-container" style="display: flex; gap: 8px">
|
||||
<div
|
||||
v-for="item in sortedIndicators"
|
||||
:key="item.key"
|
||||
class="dashboard"
|
||||
>
|
||||
<div class="title">
|
||||
产量·万㎡
|
||||
{{ item.name }}·{{ item.unit }}
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedMon['产量(深加工)'] || defaultData),
|
||||
flag: getRateFlag((relatedMon['产量(深加工)'] || defaultData).completeRate, (relatedMon['产量(深加工)'] || defaultData).real, (relatedMon['产量(深加工)'] || defaultData).target)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
销量·万㎡
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedMon.销量 || defaultData),
|
||||
flag: getRateFlag((relatedMon.销量 || defaultData).completeRate, (relatedMon.销量 || defaultData).real, (relatedMon.销量 || defaultData).target)
|
||||
}" />
|
||||
<div style='font-size: 16px;text-align: right;padding-right: 5px;'>
|
||||
<span>完成率:<span style='color: #0B58FF;'>{{item.detailData.completeRate}}%</span></span>
|
||||
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:item.detailData.flag>0?'#30B590':'#FF9423'}" >{{item.detailData.diff}}</span></span>
|
||||
</div>
|
||||
<operatingSingleBar :detailData="item.detailData"></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -35,7 +25,6 @@
|
||||
<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'
|
||||
@@ -69,6 +58,54 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
indicatorDefs() {
|
||||
return [
|
||||
{ key: 'production', name: '产量', unit: '万㎡'},
|
||||
{ key: 'financialCost', name: '销量', unit: '万㎡'},
|
||||
]
|
||||
},
|
||||
indicators() {
|
||||
let _this = this
|
||||
const fallback = { target: 0, real: 0, completeRate: 0, diff: 0, flag: 0 }
|
||||
const list = Object.entries(_this.relatedMon).map(([title, data]) => {
|
||||
return {
|
||||
title: title,
|
||||
target: data.target,
|
||||
real: data.real,
|
||||
completeRate: data.completeRate,
|
||||
diff: data.diff
|
||||
};
|
||||
});
|
||||
return _this.indicatorDefs.map(def => {
|
||||
const data = list.find(item => item && item.title === def.name) || fallback
|
||||
const detailData = {
|
||||
...data,
|
||||
flag: _this.getRateFlag((data || _this.defaultData).completeRate, (data || _this.defaultData).real, (data || _this.defaultData).target),
|
||||
}
|
||||
return {
|
||||
...def,
|
||||
detailData,
|
||||
sortValue: Number((data && data.real) ?? 0)
|
||||
}
|
||||
})
|
||||
},
|
||||
sortedIndicators() {
|
||||
const unitOrder = ['万㎡']
|
||||
const unitRank = (u) => {
|
||||
const idx = unitOrder.indexOf(u)
|
||||
return idx === -1 ? 999 : idx
|
||||
}
|
||||
|
||||
return this.indicators.slice().sort((a, b) => {
|
||||
const ur = unitRank(a.unit) - unitRank(b.unit)
|
||||
if (ur !== 0) return ur
|
||||
const vr = (b.sortValue ?? -Infinity) - (a.sortValue ?? -Infinity)
|
||||
if (vr !== 0) return vr
|
||||
return String(a.key).localeCompare(String(b.key))
|
||||
})
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
relatedMon: {
|
||||
handler(newValue) {
|
||||
|
||||
@@ -1,42 +1,21 @@
|
||||
<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="topItem-container" style="display: flex; gap: 8px">
|
||||
<div
|
||||
v-for="item in sortedIndicators"
|
||||
:key="item.key"
|
||||
class="dashboard"
|
||||
>
|
||||
<div class="title">
|
||||
双镀成本·元/㎡
|
||||
{{ item.name }}·{{ item.unit }}
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedMon.双镀成本 || defaultData),
|
||||
flag: getRateFlag((relatedMon.双镀成本 || defaultData).completeRate, (relatedMon.双镀成本 || defaultData).real, (relatedMon.双镀成本 || defaultData).target)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
双镀均价·元/㎡
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedMon.双镀均价 || defaultData),
|
||||
flag: getRateFlag((relatedMon.双镀均价 || defaultData).completeRate, (relatedMon.双镀均价 || defaultData).real, (relatedMon.双镀均价 || defaultData).target)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
双镀毛利·万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedMon.双镀毛利 || defaultData),
|
||||
flag: getRateFlag((relatedMon.双镀毛利 || defaultData).completeRate, (relatedMon.双镀毛利 || defaultData).real, (relatedMon.双镀毛利 || defaultData).target)
|
||||
}" />
|
||||
<div style='font-size: 16px;text-align: right;padding-right: 5px;'>
|
||||
<span>完成率:<span style='color: #0B58FF;'>{{item.detailData.completeRate}}%</span></span>
|
||||
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:item.detailData.flag>0?'#30B590':'#FF9423'}" >{{item.detailData.diff}}</span></span>
|
||||
</div>
|
||||
<operatingSingleBar :detailData="item.detailData"></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -46,8 +25,6 @@
|
||||
<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'
|
||||
|
||||
@@ -81,6 +58,55 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
indicatorDefs() {
|
||||
return [
|
||||
{ key: 'financialCost', name: '双镀成本', unit: '元/㎡'},
|
||||
{ key: 'financialPrice', name: '双镀均价', unit: '元/㎡'},
|
||||
{ key: 'financialProfit', name: '双镀毛利', unit: '万元'},
|
||||
]
|
||||
},
|
||||
indicators() {
|
||||
let _this = this
|
||||
const fallback = { target: 0, real: 0, completeRate: 0, diff: 0, flag: 0 }
|
||||
const list = Object.entries(_this.relatedMon).map(([title, data]) => {
|
||||
return {
|
||||
title: title,
|
||||
target: data.target,
|
||||
real: data.real,
|
||||
completeRate: data.completeRate,
|
||||
diff: data.diff
|
||||
};
|
||||
});
|
||||
return _this.indicatorDefs.map(def => {
|
||||
const data = list.find(item => item && item.title === def.name) || fallback
|
||||
const detailData = {
|
||||
...data,
|
||||
flag: _this.getRateFlag((data || _this.defaultData).completeRate, (data || _this.defaultData).real, (data || _this.defaultData).target),
|
||||
}
|
||||
return {
|
||||
...def,
|
||||
detailData,
|
||||
sortValue: Number((data && data.real) ?? 0)
|
||||
}
|
||||
})
|
||||
},
|
||||
sortedIndicators() {
|
||||
const unitOrder = ['元/㎡','万元']
|
||||
const unitRank = (u) => {
|
||||
const idx = unitOrder.indexOf(u)
|
||||
return idx === -1 ? 999 : idx
|
||||
}
|
||||
|
||||
return this.indicators.slice().sort((a, b) => {
|
||||
const ur = unitRank(a.unit) - unitRank(b.unit)
|
||||
if (ur !== 0) return ur
|
||||
const vr = (b.sortValue ?? -Infinity) - (a.sortValue ?? -Infinity)
|
||||
if (vr !== 0) return vr
|
||||
return String(a.key).localeCompare(String(b.key))
|
||||
})
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
relatedMon: {
|
||||
handler(newValue) {
|
||||
|
||||
@@ -85,7 +85,7 @@ export default {
|
||||
},
|
||||
grid: {
|
||||
top: 25,
|
||||
bottom: 20,
|
||||
bottom: 25,
|
||||
right: 10,
|
||||
left: 2,
|
||||
containLabel: true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="lineBottom" style="height: 180px; width: 100%">
|
||||
<operatingLineBarSaleSingle :refName="'totalOperating'" :chartData="chartD" style="height: 99%; width: 100%" />
|
||||
<div class="lineBottom" style="height: 160px; width: 100%">
|
||||
<operatingLineBarSaleSingle :refName="'totalOperating'" :chartData="chartD" style="height: 100%; width: 100%" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -35,59 +35,6 @@ export default {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: function (params) {
|
||||
return `{value|完成率}{rate|${rate}%}`;
|
||||
},
|
||||
// 核心样式:匹配CSS需求
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(205, 215, 224, 0.6)' }, // 顶部0px位置:阴影最强
|
||||
// { offset: 0.1, color: 'rgba(205, 215, 224, 0.4)' }, // 1px位置:阴影减弱(对应1px)
|
||||
// { offset: 0.15, color: 'rgba(205, 215, 224, 0.6)' }, // 3px位置:阴影几乎消失(对应3px扩散)
|
||||
{ offset: 0.2, color: '#ffffff' }, // 主体白色
|
||||
{ offset: 1, color: '#ffffff' }
|
||||
]
|
||||
},
|
||||
// 外阴影:0px 2px 2px 0px rgba(191,203,215,0.5)
|
||||
shadowColor: 'rgba(191,203,215,0.5)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 2,
|
||||
// 圆角:4px
|
||||
borderRadius: 4,
|
||||
// 移除边框
|
||||
borderColor: '#BFCBD577',
|
||||
borderWidth: 0,
|
||||
// 文字垂直居中(针对富文本)
|
||||
lineHeight: 20,
|
||||
rich: {
|
||||
value: {
|
||||
// 缩小宽度和内边距,适配68px容器
|
||||
width: 'auto', // 自动宽度,替代固定40px
|
||||
padding: [5, 0, 5, 10], // 缩小内边距
|
||||
align: 'center',
|
||||
color: '#464646', // 文字灰色
|
||||
fontSize: 11, // 缩小字体,适配小尺寸
|
||||
lineHeight: 20 // 垂直居中
|
||||
},
|
||||
rate: {
|
||||
width: 'auto',
|
||||
padding: [5, 10, 5, 0],
|
||||
align: 'center',
|
||||
color: '#0B58FF', // 数字蓝色
|
||||
fontSize: 11,
|
||||
lineHeight: 20
|
||||
}
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
color: {
|
||||
@@ -109,72 +56,6 @@ export default {
|
||||
show: true,
|
||||
position: 'top',
|
||||
offset: [0, 0],
|
||||
// 固定label尺寸:68px×20px
|
||||
width: 68,
|
||||
height: 20,
|
||||
// 关键:去掉换行,让文字在一行显示,适配小尺寸
|
||||
formatter: (params) => {
|
||||
|
||||
// const flags = flags || [];
|
||||
const currentDiff = diff || 0;
|
||||
const currentFlag = this.detailData?.flag || 0;
|
||||
// console.log('flags[params.dataIndex]', flags);
|
||||
|
||||
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: {
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
@@ -188,7 +69,7 @@ export default {
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
barWidth: 24,
|
||||
barWidth: 60,
|
||||
barCategoryGap: '50%',
|
||||
data: seriesData,
|
||||
itemStyle: {
|
||||
|
||||
@@ -3,83 +3,21 @@
|
||||
<Container :isShowTab="true" :name="title" icon="cockpitItemIcon" size="opLargeBg" topSize="large"
|
||||
@tabChange="handleChange">
|
||||
<div class="kpi-content" style="padding: 14px 16px; display: flex; width: 100%;">
|
||||
<div class="topItem-container" style="display: flex; gap: 8px;">
|
||||
<div class="dashboard left" @click="handleRoute('/netPriceAnalysis/netPriceAnalysisBase')">
|
||||
<div class="topItem-container" style="display: flex; gap: 8px">
|
||||
<div
|
||||
v-for="item in sortedIndicators"
|
||||
:key="item.key"
|
||||
class="dashboard"
|
||||
@click="item.route && handleDashboardClick(item.route)"
|
||||
>
|
||||
<div class="title">
|
||||
净价·万㎡
|
||||
{{ item.name }}·{{ item.unit }}
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.净价 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.净价 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right" @click="handleRoute('/unitPriceAnalysis/unitPriceAnalysisBase')">
|
||||
<div class="title">
|
||||
单价·元/㎡
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.单价 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.单价 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right" @click="handleRoute('/salesVolumeAnalysis/productionSalesBase')">
|
||||
<div class="title">
|
||||
产销率·%
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.产销率 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.产销率 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right" @click="handleRoute('/salesVolumeAnalysis/doublePlatedBase')">
|
||||
<div class="title">
|
||||
双镀销量·万㎡
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.双镀销量 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.双镀销量 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right" @click="handleRoute('/salesVolumeAnalysis/doublePlatedBase')">
|
||||
<div class="title">
|
||||
双镀占比·%
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.双镀占比 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.双镀占比 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
溢价产品销量·万㎡
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.溢价产品销量 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.溢价产品销量 || defaultData).completeRate)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
溢价产品毛利·万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedDetailData.溢价产品毛利 || defaultData),
|
||||
flag: getRateFlag((relatedDetailData.溢价产品毛利 || defaultData).completeRate)
|
||||
}" />
|
||||
<div style='font-size: 16px;text-align: right;padding-right: 5px;'>
|
||||
<span>完成率:<span style='color: #0B58FF;'>{{item.detailData.completeRate}}%</span></span>
|
||||
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:item.detailData.flag>0?'#30B590':'#FF9423'}" >{{item.detailData.diff}}</span></span>
|
||||
</div>
|
||||
<operatingSingleBar :detailData="item.detailData"></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,6 +66,59 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
indicatorDefs() {
|
||||
return [
|
||||
{ key: 'netPrice', name: '净价', unit: '万㎡', route:'/netPriceAnalysis/netPriceAnalysisBase'},
|
||||
{ key: 'unitPrice', name: '单价', unit: '元/㎡',route:'/unitPriceAnalysis/unitPriceAnalysisBase' },
|
||||
{ key: 'productionSales', name: '产销率', unit: '%',route:'/salesVolumeAnalysis/productionSalesBase'},
|
||||
{ key: 'manageCost', name: '双镀销量', unit: '万㎡',route:'/salesVolumeAnalysis/doublePlatedBase' },
|
||||
{ key: 'freight', name: '双镀占比', unit: '%',route:'/salesVolumeAnalysis/doublePlatedBase' },
|
||||
{ key: 'manageCost', name: '溢价产品销量', unit: '万㎡',route:null },
|
||||
{ key: 'premiumProducts', name: '溢价产品毛利', unit: '万元',route:null },
|
||||
]
|
||||
},
|
||||
indicators() {
|
||||
let _this = this
|
||||
const fallback = { target: 0, real: 0, completeRate: 0, diff: 0, flag: 0 }
|
||||
const list = Object.entries(_this.relatedDetailData).map(([title, data]) => {
|
||||
return {
|
||||
title: title,
|
||||
target: data.target,
|
||||
real: data.real,
|
||||
completeRate: data.completeRate,
|
||||
diff: data.diff
|
||||
};
|
||||
});
|
||||
return _this.indicatorDefs.map(def => {
|
||||
const data = list.find(item => item && item.title === def.name) || fallback
|
||||
const detailData = {
|
||||
...data,
|
||||
flag: _this.getRateFlag((data || _this.defaultData).completeRate, (data || _this.defaultData).real, (data || _this.defaultData).target),
|
||||
}
|
||||
return {
|
||||
...def,
|
||||
detailData,
|
||||
sortValue: Number((data && data.real) ?? 0)
|
||||
}
|
||||
})
|
||||
},
|
||||
sortedIndicators() {
|
||||
const unitOrder = ['万㎡', '元/㎡', '%','万元']
|
||||
const unitRank = (u) => {
|
||||
const idx = unitOrder.indexOf(u)
|
||||
return idx === -1 ? 999 : idx
|
||||
}
|
||||
|
||||
return this.indicators.slice().sort((a, b) => {
|
||||
const ur = unitRank(a.unit) - unitRank(b.unit)
|
||||
if (ur !== 0) return ur
|
||||
const vr = (b.sortValue ?? -Infinity) - (a.sortValue ?? -Infinity)
|
||||
if (vr !== 0) return vr
|
||||
return String(a.key).localeCompare(String(b.key))
|
||||
})
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// 监听 relatedData 变化(异步加载场景),同步更新月度数据
|
||||
relatedData: {
|
||||
|
||||
@@ -1,31 +1,21 @@
|
||||
<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="topItem-container" style="display: flex; gap: 8px">
|
||||
<div
|
||||
v-for="item in sortedIndicators"
|
||||
:key="item.key"
|
||||
class="dashboard"
|
||||
>
|
||||
<div class="title">
|
||||
产量·万㎡
|
||||
{{ item.name }}·{{ item.unit }}
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedTotal['产量(深加工)'] || defaultData),
|
||||
flag: getRateFlag((relatedTotal['产量(深加工)'] || defaultData).completeRate, (relatedTotal['产量(深加工)'] || defaultData).real, (relatedTotal['产量(深加工)'] || defaultData).target)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
销量·万㎡
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedTotal.销量 || defaultData),
|
||||
flag: getRateFlag((relatedTotal.销量 || defaultData).completeRate, (relatedTotal.销量 || defaultData).real, (relatedTotal.销量 || defaultData).target)
|
||||
}" />
|
||||
<div style='font-size: 16px;text-align: right;padding-right: 5px;'>
|
||||
<span>完成率:<span style='color: #0B58FF;'>{{item.detailData.completeRate}}%</span></span>
|
||||
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:item.detailData.flag>0?'#30B590':'#FF9423'}" >{{item.detailData.diff}}</span></span>
|
||||
</div>
|
||||
<operatingSingleBar :detailData="item.detailData"></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,6 +59,54 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
indicatorDefs() {
|
||||
return [
|
||||
{ key: 'production', name: '产量', unit: '万㎡'},
|
||||
{ key: 'financialCost', name: '销量', unit: '万㎡'},
|
||||
]
|
||||
},
|
||||
indicators() {
|
||||
let _this = this
|
||||
const fallback = { target: 0, real: 0, completeRate: 0, diff: 0, flag: 0 }
|
||||
const list = Object.entries(_this.relatedTotal).map(([title, data]) => {
|
||||
return {
|
||||
title: title,
|
||||
target: data.target,
|
||||
real: data.real,
|
||||
completeRate: data.completeRate,
|
||||
diff: data.diff
|
||||
};
|
||||
});
|
||||
return _this.indicatorDefs.map(def => {
|
||||
const data = list.find(item => item && item.title === def.name) || fallback
|
||||
const detailData = {
|
||||
...data,
|
||||
flag: _this.getRateFlag((data || _this.defaultData).completeRate, (data || _this.defaultData).real, (data || _this.defaultData).target),
|
||||
}
|
||||
return {
|
||||
...def,
|
||||
detailData,
|
||||
sortValue: Number((data && data.real) ?? 0)
|
||||
}
|
||||
})
|
||||
},
|
||||
sortedIndicators() {
|
||||
const unitOrder = ['万㎡']
|
||||
const unitRank = (u) => {
|
||||
const idx = unitOrder.indexOf(u)
|
||||
return idx === -1 ? 999 : idx
|
||||
}
|
||||
|
||||
return this.indicators.slice().sort((a, b) => {
|
||||
const ur = unitRank(a.unit) - unitRank(b.unit)
|
||||
if (ur !== 0) return ur
|
||||
const vr = (b.sortValue ?? -Infinity) - (a.sortValue ?? -Infinity)
|
||||
if (vr !== 0) return vr
|
||||
return String(a.key).localeCompare(String(b.key))
|
||||
})
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
relatedTotal: {
|
||||
handler(newValue) {
|
||||
|
||||
@@ -1,42 +1,22 @@
|
||||
<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="topItem-container" style="display: flex; gap: 8px">
|
||||
<div
|
||||
v-for="item in sortedIndicators"
|
||||
:key="item.key"
|
||||
class="dashboard"
|
||||
@click="item.route && handleDashboardClick(item.route)"
|
||||
>
|
||||
<div class="title">
|
||||
双镀成本·元/㎡
|
||||
{{ item.name }}·{{ item.unit }}
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedTotal.双镀成本 || defaultData),
|
||||
flag: getRateFlag((relatedTotal.双镀成本 || defaultData).completeRate, (relatedTotal.双镀成本 || defaultData).real, (relatedTotal.双镀成本 || defaultData).target)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
双镀均价·元/㎡
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedTotal.双镀均价 || defaultData),
|
||||
flag: getRateFlag((relatedTotal.双镀均价 || defaultData).completeRate, (relatedTotal.双镀均价 || defaultData).real, (relatedTotal.双镀均价 || defaultData).target)
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard right">
|
||||
<div class="title">
|
||||
双镀毛利·万元
|
||||
</div>
|
||||
<div class="line">
|
||||
<operatingSingleBar :detailData="{
|
||||
...(relatedTotal.双镀毛利 || defaultData),
|
||||
flag: getRateFlag((relatedTotal.双镀毛利 || defaultData).completeRate, (relatedTotal.双镀毛利 || defaultData).real, (relatedTotal.双镀毛利 || defaultData).target)
|
||||
}" />
|
||||
<div style='font-size: 16px;text-align: right;padding-right: 5px;'>
|
||||
<span>完成率:<span style='color: #0B58FF;'>{{item.detailData.completeRate}}%</span></span>
|
||||
<span style='display: inline-block;margin-left: 10px;'>差值:<span :style="{color:item.detailData.flag>0?'#30B590':'#FF9423'}" >{{item.detailData.diff}}</span></span>
|
||||
</div>
|
||||
<operatingSingleBar :detailData="item.detailData"></operatingSingleBar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -81,6 +61,55 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
indicatorDefs() {
|
||||
return [
|
||||
{ key: 'financialCost', name: '双镀成本', unit: '元/㎡'},
|
||||
{ key: 'financialPrice', name: '双镀均价', unit: '元/㎡'},
|
||||
{ key: 'financialProfit', name: '双镀毛利', unit: '万元'},
|
||||
]
|
||||
},
|
||||
indicators() {
|
||||
let _this = this
|
||||
const fallback = { target: 0, real: 0, completeRate: 0, diff: 0, flag: 0 }
|
||||
const list = Object.entries(_this.relatedTotal).map(([title, data]) => {
|
||||
return {
|
||||
title: title,
|
||||
target: data.target,
|
||||
real: data.real,
|
||||
completeRate: data.completeRate,
|
||||
diff: data.diff
|
||||
};
|
||||
});
|
||||
return _this.indicatorDefs.map(def => {
|
||||
const data = list.find(item => item && item.title === def.name) || fallback
|
||||
const detailData = {
|
||||
...data,
|
||||
flag: _this.getRateFlag((data || _this.defaultData).completeRate, (data || _this.defaultData).real, (data || _this.defaultData).target),
|
||||
}
|
||||
return {
|
||||
...def,
|
||||
detailData,
|
||||
sortValue: Number((data && data.real) ?? 0)
|
||||
}
|
||||
})
|
||||
},
|
||||
sortedIndicators() {
|
||||
const unitOrder = ['元/㎡','万元']
|
||||
const unitRank = (u) => {
|
||||
const idx = unitOrder.indexOf(u)
|
||||
return idx === -1 ? 999 : idx
|
||||
}
|
||||
|
||||
return this.indicators.slice().sort((a, b) => {
|
||||
const ur = unitRank(a.unit) - unitRank(b.unit)
|
||||
if (ur !== 0) return ur
|
||||
const vr = (b.sortValue ?? -Infinity) - (a.sortValue ?? -Infinity)
|
||||
if (vr !== 0) return vr
|
||||
return String(a.key).localeCompare(String(b.key))
|
||||
})
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
relatedTotal: {
|
||||
handler(newValue) {
|
||||
|
||||
Reference in New Issue
Block a user