rtgk-screen-web/pages/reports/homePage/index.js

982 lines
39 KiB
JavaScript
Raw Normal View History

2024-06-20 11:26:44 +08:00
import React, { useEffect, useState, useMemo } from 'react';
import dynamic from 'next/dynamic';
const Liquid = dynamic(() => import('@ant-design/plots').then(({ Liquid }) => Liquid),
{ ssr: false }
);
const RingProgress = dynamic(() => import('@ant-design/plots').then(({ RingProgress }) => RingProgress),
{ ssr: false }
);
const Progress = dynamic(() => import('@ant-design/plots').then(({ Progress }) => Progress),
{ ssr: false }
);
const Line = dynamic(() => import('@ant-design/plots').then(({ Line }) => Line),
{ ssr: false }
);
import { List, Carousel, Cascader, Skeleton, Spin } from 'antd';
import _ from 'lodash';
import {
getTimeRate,
getAbnlException,
getSevenQualifiedRate,
getOpByRouterLine,
getOrganization,
} from '../../../reportUtils/getHomePageData';
import homecss from '../../../styles/homecss.module.css';
function HomePage() {
let [loading, setLoading] = useState(false);
// 工厂选择
let [workShops, setWorkShops] = useState([]);
// 级联选择框的数据存储
let [workShopValue, setWorkShopValue] = useState([]);
// 级联选择工作中心
let [caWorkCenterCode, setCaWorkCenterCode] = useState('');
// 级联选择产线
let [caRouterLineCode, setCaRouterLineCode] = useState('');
// 水波图,柱形图,环形图
let [timeRate, setTimeRate] = useState({});
// 异常消息
let [abnlException, setAbnlException] = useState({
equip: [],
quality: [],
prodexec: [],
});
let [exceptions, setExceptions] = useState([]);
let [currentExceptions, setCurrentExceptions] = useState([]);
//存储工序消息
let [opData, setOpdata] = useState([]);
// 质量合格率
let [qualifiedRate, setQualifiedRate] = useState([]);
// 页面初始化加载
useEffect(() => {
getOrganization().then(data => {
if (data.length > 0) {
let defaultOrg = {};
for (let i = 0; i < data.length; i++) {
if (
data[i]['children'] &&
data[i]['children'].length === 0
) {
data[i]['isLeaf'] = false;
}
if (data[i]['defaultWorkCenter']) {
defaultOrg = data[i];
}
}
let defaultOrgValue = [];
if (defaultOrg['defaultWorkCenter']) {
defaultOrgValue.push(defaultOrg['value']);
let child = defaultOrg['children'];
let flag = true;
for (let i = 0; i < child.length; i++) {
if (child[i]['defaultRoute'] && flag) {
flag = false;
defaultOrgValue.push(child[i]['value']);
}
}
}
setWorkShops(data);
setWorkShopValue(defaultOrgValue);
}
});
}, []);
useEffect(() => {
let workCenterCode = '';
let routerLineCode = '';
if (workShopValue.length > 0) {
workCenterCode = workShopValue[0];
setOpdata([]);
getTimeRate(workCenterCode).then(data => {
if (data) {
for (let key in data) {
if (data[key] == 1 || data[key] == 0) {
data[key] = parseInt(data[key]);
} else {
data[key] = parseFloat(data[key].toFixed(3));
}
}
setTimeRate(data);
}
});
getAbnlException(workCenterCode).then(data => {
if (data) {
let exceptions = {
equip: [],
quality: [],
prodexec: [],
};
for (let i = 0; i < data.length; i++) {
if (data[i].typeException === 'equip') {
exceptions['equip'].push(data[i]);
} else if (data[i].typeException === 'quality') {
exceptions['quality'].push(data[i]);
} else {
exceptions['prodexec'].push(data[i]);
}
}
setExceptions(data);
setCurrentExceptions(data);
setAbnlException(exceptions);
}
});
getSevenQualifiedRate(workCenterCode).then(data => {
if (data) {
let rates = [];
for (let key in data) {
rates.push({
month: key,
value: data[key],
});
}
setQualifiedRate(rates);
}
});
if (workShopValue.length === 2) {
setLoading(true);
routerLineCode = workShopValue[1];
getOpByRouterLine(routerLineCode).then(data => {
setLoading(false);
if (data && data.length > 0) {
setOpdata(data);
} else {
setOpdata([]);
}
});
}
} else {
setQualifiedRate([]);
setOpdata([]);
setCurrentExceptions([]);
setExceptions([]);
setTimeRate({});
setAbnlException({
equip: [],
quality: [],
prodexec: [],
});
}
}, [workShopValue]);
const clickException = type => {
let exceptionData = [];
if (type === 'all') {
exceptionData = exceptions;
} else if (type === 'equip') {
exceptionData = abnlException['equip'];
} else if (type === 'quality') {
exceptionData = abnlException['quality'];
} else {
exceptionData = abnlException['prodexec'];
}
setCurrentExceptions(exceptionData);
};
const factoryChange = (value, selectedOptions) => {
let currentValue = [];
if (value) {
currentValue = value;
}
console.log(currentValue);
setWorkShopValue(currentValue);
};
// 用来进行异步加载
/* const loadData = selectedOptions => {
const targetOption = selectedOptions[selectedOptions.length - 1];
const { value = '' } = targetOption;
targetOption.loading = true;
getRouterLineByFactory(value).then(data => {
targetOption.loading = false;
let children = [];
if (data.length > 0) {
for (let i = 0; i < data.length; i++) {
children.push({
value: data[i].code,
label: data[i].name,
});
}
}
targetOption.children = children;
setWorkShops([...workShops]);
});
}; */
return (
<div className={homecss['home_page']}>
<div className={homecss['header']}>
<div className={homecss['header_title']}>
<span className={homecss['title']}>
<Cascader
className={homecss['header_workshop']}
bordered={false}
value={workShopValue}
suffixIcon={<Triangle />}
options={workShops}
onChange={factoryChange}
changeOnSelect
placeholder={'请选择产线'}
/>
</span>
<ul className={homecss['equip_state_mark']}>
<li>
<span style={{ backgroundColor: '#28A3FF' }}></span>
生产
</li>
<li>
<span style={{ backgroundColor: '#FAAB0C' }}></span>
待产
</li>
<li>
<span style={{ backgroundColor: '#F5222D' }}></span>
停产
</li>
</ul>
</div>
<div className={homecss['equip_state_card']}>
<Spin tip="Loading..." spinning={loading}>
<Skeleton loading={loading} active>
<ProcessCard
data={opData}
rowKey={'code'}
workCenterCode={workShopValue}
Content={OperationCard}
/>
</Skeleton>
</Spin>
</div>
</div>
<div className={homecss['content']}>
<div className={homecss['content_row']}>
<div>
<span className={homecss['title']}>时间稼动率</span>
<div className={homecss['liquid_parent']}>
<div>
<WaterWave
color="#5487f3"
stroke="#5487f3"
percent={
timeRate['BY_TIME']
? timeRate['BY_TIME']
: 0
}
/>
<span className="echart_title">本月</span>
</div>
<div>
<WaterWave
color="#f8ae3c"
stroke="#f8ae3c"
percent={
timeRate['BZ_TIME']
? timeRate['BZ_TIME']
: 0
}
/>
<span className="echart_title">本周</span>
</div>
<div>
<WaterWave
color="#3ad18c"
stroke="#3ad18c"
percent={
timeRate['JR_TIME']
? timeRate['JR_TIME']
: 0
}
/>
<span className="echart_title">今日</span>
</div>
</div>
</div>
<div>
<span className={homecss['title']}>性能稼动率</span>
<div className={homecss['progress_parent']}>
<div className={homecss['progress_child']}>
<span
className={homecss['progress_child_title']}
>
本月
</span>
<div className={homecss['progress_child_bar']}>
<ProgressBar
config={{
autoFit: true,
percent: timeRate['BY_PERFORMANCE']
? timeRate['BY_PERFORMANCE']
: 0,
barWidthRatio: 0.3,
color: ['#28A3FF', '#E4E4E4'],
}}
/>
</div>
<span className={homecss['progress_child_val']}>
{timeRate['BY_PERFORMANCE']
? parseFloat(
(
timeRate['BY_PERFORMANCE'] *
100
).toFixed(2)
) + '%'
: '0%'}
</span>
</div>
<div className={homecss['progress_child']}>
<span
className={homecss['progress_child_title']}
>
本周
</span>
<div className={homecss['progress_child_bar']}>
<ProgressBar
config={{
percent: timeRate['BZ_PERFORMANCE']
? timeRate['BZ_PERFORMANCE']
: 0,
barWidthRatio: 0.3,
color: ['#FFC760', '#E4E4E4'],
}}
/>
</div>
<span className={homecss['progress_child_val']}>
{timeRate['BZ_PERFORMANCE']
? parseFloat(
(
timeRate['BZ_PERFORMANCE'] *
100
).toFixed(2)
) + '%'
: '0%'}
</span>
</div>
<div className={homecss['progress_child']}>
<span
className={homecss['progress_child_title']}
>
今日
</span>
<div className={homecss['progress_child_bar']}>
<ProgressBar
config={{
percent: timeRate['JR_PERFORMANCE']
? timeRate['JR_PERFORMANCE']
: 0,
barWidthRatio: 0.3,
color: ['#17C082', '#E4E4E4'],
}}
/>
</div>
<span className={homecss['progress_child_val']}>
{timeRate['BZ_PERFORMANCE']
? parseFloat(
(
timeRate['JR_PERFORMANCE'] *
100
).toFixed(2)
) + '%'
: '0%'}
</span>
</div>
</div>
</div>
<div className={homecss['content_last_col']}>
<span className={homecss['title']}>合格率</span>
<div className={homecss['ringgraph_parent']}>
<div className={homecss['ringgraph_child']}>
<CircleProgress
percent={
timeRate['JR_GOODPRODUCT']
? timeRate['JR_GOODPRODUCT']
: 0
}
/>
</div>
</div>
</div>
</div>
</div>
<div className={homecss['footer']}>
<div className={homecss['footer_row']}>
<div>
<span
className={homecss['title']}
style={{ cursor: 'pointer' }}
onClick={() => clickException('all')}
>
今日预警通知
</span>
<div className={homecss['footer_content']}>
<ul className={homecss['footer_statistics']}>
<li onClick={() => clickException('equip')}>
<span
className={
homecss['footer_statistics_title']
}
>
设备预警
</span>
<span
className={
homecss['footer_statistics_val']
}
style={{ color: '#f5222d' }}
>
{abnlException['equip'].length}
</span>
</li>
<li onClick={() => clickException('quality')}>
<span
className={
homecss['footer_statistics_title']
}
>
质量预警
</span>
<span
className={
homecss['footer_statistics_val']
}
style={{ color: '#28A3FF' }}
>
{abnlException['quality'].length}
</span>
</li>
<li
className={
homecss['footer_statistics_last']
}
onClick={() => clickException('prodexec')}
>
<span
className={
homecss['footer_statistics_title']
}
>
生产预警
</span>
<span
className={
homecss['footer_statistics_val']
}
style={{ color: '#17C082' }}
>
{abnlException['prodexec'].length}
</span>
</li>
</ul>
<div className={homecss['footer_tips']}>
<List
itemLayout="horizontal"
split={false}
dataSource={currentExceptions}
renderItem={item => (
<List.Item
extra={
<div
className={
homecss[
'footer_tips_time'
]
}
>
{item.time}
</div>
}
>
<List.Item.Meta
avatar={
<div
className={
homecss[
'footer_tips_title'
]
}
style={{
color:
item.typeException ===
'equip'
? '#F5222D'
: item.typeException ===
'quality'
? '#28A3FF'
: '#17C082',
}}
>
{item.typeException ===
'equip'
? '【设备】'
: item.typeException ===
'quality'
? '【质量】'
: '【生产】'}
</div>
}
description={
<div
className={
homecss[
'footer_tips_content'
]
}
>
{item.phenomenonName}
</div>
}
/>
</List.Item>
)}
/>
</div>
</div>
</div>
<div>
<span className={homecss['title']}>合格率走势图</span>
<div className={homecss['footer_qualified_rate']}>
<LineChart data={qualifiedRate} />
</div>
</div>
</div>
</div>
</div>
);
}
const WaterWave = React.memo(props => {
const { color, stroke, percent } = props;
let config = {
autoFit: true,
liquidStyle: { fill: color },
percent: percent,
outline: {
border: 3,
distance: 1,
style: {
stroke: stroke,
},
},
wave: {
length: 80,
count: 3,
},
statistic: {
title: {
style: {
fontFamily: 'PingFangSC-Medium',
fontSize: '28px',
color: 'rgba(0,0,0,0.85)',
textAlign: 'center',
lineHeight: '26px',
fontWeight: '500',
},
formatter: data => {
return `${parseFloat((data.percent * 100).toFixed(1))}%`;
},
offsetX: 3,
offsetY: -20,
},
content: false,
},
};
return <Liquid {...config} />;
});
const CircleProgress = React.memo(props => {
const { percent } = props;
let config = {
autoFit: true,
percent: percent,
color: ['#28a3ff', '#f1f1f1'],
statistic: {
title: {
style: {
fontFamily: 'PingFangSC-Medium',
fontSize: '14px',
color: 'rgba(0,0,0,0.85)',
textAlign: 'center',
lineHeight: '20px',
fontWeight: '400',
},
content: '合格率',
offsetX: 1,
offsetY: 35,
},
content: {
style: {
fontFamily: 'PingFangSC-Medium',
fontSize: '24px',
color: 'rgba(0,0,0,0.85)',
textAlign: 'center',
lineHeight: '18px',
fontWeight: '500',
},
formatter: data => {
return `${data.percent * 100}%`;
},
offsetX: 3,
offsetY: -18,
},
},
};
return <RingProgress {...config} />;
});
const ProgressBar = React.memo(props => {
const { percent, barWidthRatio, color } = props.config;
let config = {
autoFit: true,
percent: percent,
barWidthRatio: barWidthRatio,
color: color,
};
return <Progress {...config} />;
});
const LineChart = React.memo(props => {
let { data = [] } = props;
const config = {
autoFit: true,
smooth: true,
color: '#2D99FF',
lineStyle: {
fill: '#cae5ff',
},
yAxis: {
line: {
style: {
stroke: '#bfbfbf',
lineWidth: 1,
opacity: 0.75,
},
},
label: {
formatter: axisValue => {
if (axisValue > 0) {
return axisValue * 100 + '%';
}
return axisValue;
},
},
},
data,
xField: 'month',
yField: 'value',
point: {
size: 4,
shape: 'circle',
style: {
fill: '#2D99FF',
stroke: '#2D99FF',
lineWidth: 1,
},
},
tooltip: {
formatter: datum => {
return { name: '合格率', value: datum.value * 100 + '%' };
},
},
};
return <Line {...config} />;
});
const Triangle = React.memo(() => {
return (
<div className="triangle">
<style jsx>{`
.triangle {
margin-top: 3px;
width: 0;
height: 0;
border-top: 5px solid #000000;
border-right: 5px solid transparent;
border-left: 5px solid transparent;
}
`}</style>
</div>
);
});
const ProcessCard = React.memo(props => {
const spacing = [24, 24];
const { Content, data, rowKey, workCenterCode } = props;
const [ruleLength] = useState(5);
const memoData = useMemo(() => {
let res = [];
data.forEach((item, index) => {
let row = Math.ceil((index + 1) / ruleLength);
let rowIndex = index % ruleLength;
if (!res[row]) res[row] = [];
res[row][rowIndex] = item;
});
return res;
}, [data, ruleLength]);
return (
<div className="list-flex">
{memoData.map((row, rowIndex) => {
let reverse = rowIndex % 2 > 0;
return (
<div
className={`list-flex-row ${reverse ? '' : 'reverse'}`}
key={rowIndex}
>
{row.map(item => {
return (
<div
className="list-flex-item"
key={item[rowKey]}
>
<Content
workCenterCode={workCenterCode}
equipList={item.iotEquipmentVos || []}
title={`${item['code']}-${item['name']}`}
/>
</div>
);
})}
</div>
);
})}
<style jsx>{`
.list-flex {
overflow: hidden;
}
.list-flex-row {
display: flex;
margin: 0 -${spacing[0] / 2}px;
}
.list-flex-row.reverse {
flex-direction: row-reverse;
}
.list-flex-item {
width: ${100 / ruleLength}%;
padding: 0 ${spacing[0] / 2}px;
margin-bottom: ${spacing[1]}px;
position: relative;
}
.list-flex-row:last-child .list-flex-item {
margin-bottom: 0;
}
.list-flex-item::after {
content: '';
display: block;
position: absolute;
}
// 右箭头
.list-flex-row:not(.reverse) .list-flex-item::after {
width: 16px;
height: 14px;
right: 0;
left: unset;
bottom: unset;
top: 50%;
transform: translate(50%, -50%);
background: url('/img/jt.png') center center no-repeat;
}
// 左箭头
.list-flex-row.reverse .list-flex-item::after {
width: 16px;
height: 14px;
left: 0;
right: unset;
top: 50%;
bottom: unset;
transform: translate(-50%, -50%) rotate(180deg);
transform-origin: 50% 50%;
background: url('/img/jt.png') center center no-repeat;
}
// 下箭头
.list-flex-row .list-flex-item:last-child::after {
width: 16px;
height: 14px;
left: unset;
right: 50%;
top: unset;
bottom: -${(spacing[1] - 14) / 2}px;
top: unset;
transform: translate(-50%, 100%) rotate(90deg);
transform-origin: 50% 50%;
background: url('/img/jt.png') center center no-repeat;
}
// 无箭头
.list-flex-row:last-child .list-flex-item:last-child::after {
display: none;
}
`}</style>
</div>
);
});
const OperationCard = React.memo(props => {
let { equipList = [], title = '', workCenterCode = '' } = props;
return (
<div className="operation_card">
<div className="operation_card_title">
<span>{title}</span>
</div>
<div className="operation_card_content">
<Carousel autoplay dots={false} autoplaySpeed={10000}>
{equipList.map(item => {
return (
<div key={`${item.equipCode}-${item.equipName}`}>
<EquipCard
config={{
status: parseInt(item.runningstatus),
equipCode: item.equipCode,
equipName: item.equipName,
workCenterCode: workCenterCode,
}}
/>
</div>
);
})}
</Carousel>
</div>
<style jsx>{`
.operation_card {
width: 100%;
height: 116px;
background: #ffffff;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 4px;
box-sizing: border-box;
}
.operation_card_title {
width: 100%;
height: 35px;
}
.operation_card_title > span {
display: inline-block;
padding: 6px 0 0 16px;
fontfamily: PingFangSC-Semibold;
fontsize: 14px;
color: #000000;
fontweight: 600;
}
.operation_card_content {
width: 100%;
height: 81px;
box-sizing: border-box;
overflow: hidden;
}
`}</style>
</div>
);
});
const EquipCard = React.memo(props => {
let { status, equipCode, equipName, workCenterCode } = props.config;
let title = '';
let background = '';
let img = '';
if (status === 1) {
title = '待产中';
background = 'linear-gradient(270deg, #FFDB93 0%, #FAAB0C 100%)';
img = '/img/dc.png';
} else if (status === 2) {
title = '生产中';
background = 'linear-gradient(270deg, #7AC7FF 0%, #28A3FF 100%)';
img = '/img/sz.png';
} else if (status === 3) {
title = '停产中';
background = 'linear-gradient(270deg, #FF735F 0%, #F5222D 100%)';
img = '/img/tc.png';
}
return (
<div
style={{
display: 'flex',
width: '100%',
height: '81px',
backgroundImage: background,
boxSizing: 'border-box',
cursor: 'pointer',
borderRadius: '0px 0px 4px 4px',
}}
onClick={() => {
setTimeout(() => {
window.parent.postMessage(
{
path: `/mdgeneric/neusoft_web/runtime/f4c2f3083ec84daca1b6bea7985194cb?workCenterCode=${workCenterCode[0]}`,
},
'*'
);
}, 0);
localStorage.setItem('workCenterCode', workCenterCode[0]);
console.log(workCenterCode[0], '+++++');
}}
>
<div
style={{
position: 'relative',
height: '81px',
marginLeft: '4%',
marginRight: '6%',
textAlign: 'center',
lineHeight: '75px',
}}
>
<span
style={{
position: 'absolute',
fontFamily: 'PingFangSC-Semibold',
fontSize: '12px',
color: '#FFFFFF',
fontWeight: '600',
left: '25%',
}}
>
{title}
</span>
<img
src={img}
style={{
display: 'inline-block',
verticalAlign: 'middle',
lineHeight: 'initial',
}}
/>
</div>
<div
style={{
display: 'flex',
flexDirection: 'column',
minWidth: '25%',
height: '81px',
textAlign: 'center',
}}
>
<span
style={{
display: 'inline-block',
padding: '16px 0 6px 0',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
minWidth: '25%',
fontFamily: 'PingFangSC-Medium',
fontSize: '14px',
color: '#FFFFFF',
fontWeight: '500',
}}
>
{equipName}
</span>
<span
style={{
display: 'inline-block',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
minWidth: '25%',
fontFamily: 'PingFangSC-Medium',
fontSize: '14px',
color: '#FFFFFF',
fontWeight: '500',
}}
>
{equipCode}
</span>
</div>
</div>
);
});
export default HomePage;