first commit

This commit is contained in:
RTGK 2024-06-20 11:26:44 +08:00
commit b810251a0c
140 changed files with 35146 additions and 0 deletions

19
.eslintrc.json Normal file
View File

@ -0,0 +1,19 @@
{
"extends": "next/core-web-vitals",
"rules": {
"react-hooks/exhaustive-deps": "off",
"react/display-name": "off",
"react/jsx-key": "off",
"@next/next/no-sync-scripts": "off",
"react/no-unknown-property": [
2,
{
"ignore": [
"jsx",
"global"
]
}
]
}
}

35
.gitignore vendored Normal file
View File

@ -0,0 +1,35 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel

34
README.md Normal file
View File

@ -0,0 +1,34 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

124
components/client/Button.js Normal file
View File

@ -0,0 +1,124 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Spin } from "antd";
import { LoadingOutlined } from "@ant-design/icons";
import { useRule } from "./PackagingOperation/hooks"
const btnTyps = [
{ type: "primary", color: "#0185FF" },
{ type: "danger", color: "#F5222D" },
{ type: "warning", color: "#FAAB0C" },
{ type: "success", color: "#85C700" },
{ type: "info", color: "#26c1e1" },
{ type: "default", color: "rgba(0,0,0,.85)" },
];
export const Button = React.memo((props) => {
const {
visible = true,
enable = true,
title,
type = "primary",
ghost = false,
size,
onClick = () => { },
ruleCode,
} = props;
const rule = useRule(ruleCode);
// useEffect(() => {
// console.log("rule", rule)
// }, [rule])
const types = useRef(btnTyps.map(({ type }) => type));
const sizes = useRef(["large"]);
const className = useMemo(() => {
let res = "client-btn";
if (type && types.current.includes(type)) res += ` btn-${type}`;
if (ghost === true) res += ` btn-ghost`;
if (size && sizes.current.includes(size)) res += ` btn-${size}`;
if (enable === false) res += ` btn-disabled`;
return res;
}, [type, ghost, size, enable]);
const [loading, setLoading] = useState(false);
const handleClick = useCallback(() => {
if (enable && !loading) {
let res = onClick();
if (res && typeof res.then == "function" && typeof res.finally == "function") {
setLoading(true);
res.finally(() => setLoading(false))
}
}
}, [enable, onClick, loading]);
if (!rule) return null;
if (!visible) return null;
return (
<div className={className} onClick={() => enable && handleClick()}>
{title}
<Spin
spinning={loading}
indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />}
/>
<style jsx>{`
.client-btn {
border-radius: 2px;
cursor: pointer;
display: inline-block;
color: white;
line-height: 44px;
padding: 0 24px;
font-size: 20px;
user-select: none;
position: relative;
}
.client-btn.btn-large {
line-height: 56px;
font-size: 24px;
padding: 0 36px;
}
.client-btn.btn-disabled {
opacity: 0.4;
cursor: not-allowed;
}
${btnTyps
.map((item) => {
let { type, color } = item;
return `
.client-btn.btn-${type} {
background: ${color};
}
.client-btn.btn-ghost.btn-${type} {
background: transparent;
border: 1px solid;
color: ${color};
border-color: ${color};
}
`;
})
.join("") + "\n"}
.client-btn.btn-ghost {
}
.client-btn.btn-ghost.btn-default {
border-color: #d9d9d9;
}
`}</style>
<style jsx global>{`
.client-btn ~ .client-btn {
margin-left: 16px;
}
.client-btn .ant-spin {
width: 100%;
position: absolute;
height: 100%;
background: #ffffff87;
left: 0;
top: 0;
line-height: inherit;
}
.client-btn .ant-spin-dot {
}
`}</style>
</div>
);
});

View File

@ -0,0 +1,148 @@
import React, {
useMemo,
} from "react";
export const FormItem = React.memo((props) => {
const {
title,
value = "",
readonly = true,
horizontal = true,
type,
...otherProps
} = props;
const valueMemo = useMemo(() => {
if (readonly) {
if (["", undefined, null].includes(value)) return "-";
return value;
}
if (type === "number") {
return <FormItemNumber value={value + ""} {...otherProps} />;
}
return value;
}, [readonly, value, type, otherProps]);
return (
<div className={"form-item " + (horizontal ? "horizontal" : "vertical")}>
<div className="label">{title}</div>
<div className="value">{valueMemo}</div>
<style jsx>{`
.form-item {
line-height: 36px;
margin-bottom: 32px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.form-item.horizontal {
height: 36px;
}
.form-item.horizontal .label,
.form-item.horizontal .value {
display: inline;
vertical-align: middle;
}
.form-item.horizontal .label {
margin-right: 16px;
}
.form-item.vertical {
height: 88px;
}
.form-item.vertical .label,
.form-item.vertical .value {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.form-item.vertical .label {
margin-bottom: 8px;
}
.form-item .label {
font-size: 20px;
color: rgb(0 0 0 / 45%);
}
.form-item .value {
font-size: 24px;
color: rgb(0 0 0 / 85%);
font-weight: bold;
}
`}</style>
</div>
);
});
const FormItemNumber = React.memo((props) => {
const { value, onChange = () => {}, min } = props;
const onInputChange = (e) => {
onChange(e.target.value);
};
const onInputBlur = (e) => {
let res = parseFloat(e.target.value);
if (isNaN(res)) res = "-";
onChange(res + "");
};
const onBtnClick = (add) => {
let res = parseFloat(value);
if (isNaN(res)) res = add;
res = res + add;
if (min !== undefined && res < min) res = min;
onChange(res + "");
};
const btnDisabled = useMemo(() => [value <= min], [value, min]);
return (
<div className="input-number">
<div
className={"btn" + (btnDisabled[0] ? " disabled" : "")}
onClick={() => value > min && onBtnClick(-1)}
>
-
</div>
<input
className="input-number-input"
type="text"
value={value}
onChange={onInputChange}
onBlur={onInputBlur}
/>
<div className="btn" onClick={() => onBtnClick(1)}>
+
</div>
<style jsx>{`
.btn,
.input-number-input {
height: 36px;
line-height: 30px;
display: inline-block;
vertical-align: middle;
text-align: center;
background: #f5f5f5;
}
.input-number-input {
outline: none;
border: 1px solid transparent;
box-shadow: none;
font-size: 24px;
padding: 0 12px;
width: 120px;
margin: 0 3px;
}
.input-number-input:focus {
border-color: #0285ff;
}
.btn {
width: 36px;
font-size: 32px;
color: #999;
cursor: pointer;
user-select: none;
}
.btn.disabled {
color: #ccc;
cursor: not-allowed;
}
`}</style>
</div>
);
});

View File

@ -0,0 +1,555 @@
import React, {
useCallback,
useEffect,
useImperativeHandle,
useRef,
useState,
} from "react";
import { Modal, Row, Col, Form, Input, Select, message } from "antd";
import { Button } from "../Button";
import { request } from "../utils";
const ModalClient = React.memo((props) => {
const {
visible = false,
title,
onOk = () => {},
onCancel = () => {},
children,
} = props;
return (
<Modal
className="modalClient"
destroyOnClose
visible={visible}
closable={false}
footer={null}
width={536}
bodyStyle={{ padding: "32px" }}
>
<div className="modalClient-header">{title}</div>
<div className="modalClient-body">{children}</div>
<div className="modalClient-footer">
<Button title="确认" size="large" onClick={onOk} />
<Button
title="取消"
type="default"
size="large"
ghost
onClick={onCancel}
/>
</div>
<style jsx>{`
.modalClient-header {
height: 32px;
font-size: 28px;
color: rgb(0 0 0/0.85);
letter-spacing: 0px;
line-height: 32px;
margin-bottom: 42px;
}
.modalClient-footer {
text-align: right;
padding-top: 32px;
}
`}</style>
<style jsx global>{`
.modalClient .ant-form-item-label {
text-align: left;
}
.modalClient .ant-form-item-label > label {
font-size: 24px;
}
.modalClient .ant-form-large .ant-form-item-control-input {
min-height: 56px;
}
.modalClient
.ant-select-single.ant-select-lg:not(.ant-select-customize-input)
.ant-select-selector::after,
.modalClient
.ant-select-single.ant-select-lg:not(.ant-select-customize-input)
.ant-select-selector
.ant-select-selection-item,
.modalClient
.ant-select-single.ant-select-lg:not(.ant-select-customize-input)
.ant-select-selector
.ant-select-selection-placeholder {
line-height: 56px;
}
.modalClient .ant-form-large .ant-form-item-label > label,
.modalClient
.ant-select-single.ant-select-lg:not(.ant-select-customize-input)
.ant-select-selector {
height: 56px;
}
.modalClient .ant-input-lg {
line-height: 54px;
padding: 0 11px;
}
.modalClient p,
.modalClient .ant-input-lg,
.modalClient .ant-select-lg {
font-size: 24px;
}
.modalClient .item-line {
text-align: center;
line-height: 56px;
}
.modalClient.ant-select-item {
font-size: 18px;
line-height: 32px;
}
`}</style>
</Modal>
);
});
// 设置
export const ModelSetting = React.forwardRef((props, ref) => {
const {} = props;
const callbackRef = useRef(() => {});
const [form] = Form.useForm();
const [visible, setVisible] = useState(false);
const handleCancel = useCallback(() => setVisible(false), []);
const [equipList, setEquipList] = useState([]);
const [operationList, setOperationList] = useState([]);
const [workShopList, setWorkShopList] = useState([]);
// 查询包装机
const getEquip = useCallback((gid) => {
if (gid) {
request({
method: "POST",
url: `${window.config.mesUrl}/mdgeneric/md/bmWorkCenter/getEquipByOperationGid?operationGid=${gid}`,
}).then(({ data, success } = {}) => {
if (!success) data = [];
setEquipList(data);
});
}
}, []);
// 工序变化
const onOperationChange = useCallback(
(code, a, data) => {
if (!data) data = operationList;
// console.log("val", key, value, data);
let {gid: operationGid, name} = data.find((item) => item.code === code) || {};
getEquip(operationGid);
form.setFieldsValue({
operationName: name,
operationCode: code,
equipName: undefined,
equipCode: undefined,
});
setEquipList([]);
},
[operationList, form, getEquip]
);
// 查询工序
const getOperation = useCallback(
(gid, callback) => {
if (gid) {
request({
method: "POST",
url: `${window.config.mesUrl}/mdgeneric/md/bmWorkCenter/getOpByWorkCenter?workCenterGid=${gid}`,
}).then(({ data, success } = {}) => {
if (!success) data = [];
setOperationList(data);
if (callback) {
callback(data);
} else if (data.length) {
let { name, code } = data[0];
form.setFieldsValue({
operationName: name,
equipName: code,
});
onOperationChange(code,undefined, data)
}
});
} else setOperationList([]);
},
[onOperationChange]
);
// 车间变化
const onWorkShopChange = useCallback(
(key = {}) => {
let { gid: workShopGid, name: value } = workShopList.find(({ code }) => code === key) || {};
getOperation(workShopGid);
form.setFieldsValue({
workCenterCode: key,
workCenterName: value,
operationName: undefined,
operationCode: undefined,
equipName: undefined,
equipCode: undefined,
});
setOperationList([]);
setEquipList([]);
},
[workShopList, form, getOperation]
);
// 查询车间
const getWorkShop = useCallback((callback) => {
request({
method: "POST",
url: `${window.config.mesUrl}/mdgeneric/md/bmWorkCenter/findAllWithPage`,
bodys: {
// removePermission: true,
page: -1,
pageSize: -1,
sorted: "createTime desc",
filter: "type eq 'workshop'",
},
}).then(({ data, success } = {}) => {
if (!success) data = [];
setWorkShopList(data);
callback && callback(data);
});
}, []);
// 设备变化
const onEquipChange = useCallback(
(key) => {
let { name: value} = equipList.find(({ code }) => code === key) || {}
form.setFieldsValue({
equipName: value,
equipCode: key,
});
},
[form, equipList]
);
// 初始化
const init = useCallback(
(data, callback) => {
setVisible(true);
form.resetFields();
form.setFieldsValue(data);
callbackRef.current = callback;
let { workCenterCode, operationCode } = data;
getWorkShop((res) => {
if (workCenterCode) {
let gid = res.find(({ code }) => code === workCenterCode)?.gid;
getOperation(gid, (res) => {
if (operationCode) {
let gid = res.find(({ code }) => code === operationCode)?.gid;
getEquip(gid);
}
});
}
});
},
[form, getWorkShop, getOperation, getEquip]
);
const handleOk = useCallback(() => {
let formData = form.getFieldValue();
if (["", null, undefined].includes(formData.workCenterCode))
return message.error("请完善车间!");
if (["", null, undefined].includes(formData.equipCode))
return message.error("请完善包装机!");
if (["", null, undefined].includes(formData.bagWeight))
return message.error("请完善包装袋重量!");
if (["", null, undefined].includes(formData.packSpecs))
return message.error("请完包装规格!");
if (["", null, undefined].includes(formData.upperLimit))
return message.error("请完善上限值!");
if (["", null, undefined].includes(formData.lowerLimit))
return message.error("请完善下限值!");
// console.log("form", formData);
return new Promise((resolve, reject) => {
request({
url: `${window.config.mesUrl}/prodexec/prodexec/packageEquipment/setPackInfo`,
bodys: formData,
}).then((res) => {
handleCancel();
callbackRef.current();
})
.finally(() => {
resolve();
});
});
}, [handleCancel, form, workShopList, equipList]);
useImperativeHandle(
ref,
() => ({
show: init,
}),
[init]
);
return (
<ModalClient
visible={visible}
title="设置"
onCancel={handleCancel}
onOk={handleOk}
>
<Form
form={form}
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
// onFieldsChange={ onFieldsChange }
autoComplete="off"
size="large"
>
<Form.Item label="车间" name="workCenterName">
<Select
placeholder="选择车间"
onSelect={onWorkShopChange}
// labelInValue
>
{workShopList.map(({ code, name }) => (
<Select.Option value={code} key={code} className="modalClient">
{name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item label="工序" name="operationName">
<Select
placeholder="选择工序"
onSelect={onOperationChange}
// labelInValue
>
{operationList.map(({ code, name }) => (
<Select.Option value={code} key={code} className="modalClient">
{name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item label="包装机" name="equipName">
<Select
placeholder="选择包装机"
onSelect={onEquipChange}
// labelInValue
>
{equipList.map(({ code, name }) => (
<Select.Option value={code} key={code} className="modalClient">
{name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item label="包装袋重量" name="bagWeight">
<Input addonAfter="KG" placeholder="填写包装袋重量" />
</Form.Item>
<Form.Item label="包装规格" name="packSpecs">
<Input addonAfter="KG" placeholder="填写包装规格" />
</Form.Item>
<Form.Item label="上限-下限">
<Input.Group>
<Row gutter={8}>
<Col span={11}>
<Form.Item name={"upperLimit"} noStyle>
<Input addonAfter="KG" placeholder="上限" />
</Form.Item>
</Col>
<Col span={2}>
<div className="item-line">-</div>
</Col>
<Col span={11}>
<Form.Item name={"lowerLimit"} noStyle>
<Input addonAfter="KG" placeholder="下限" />
</Form.Item>
</Col>
</Row>
</Input.Group>
</Form.Item>
</Form>
</ModalClient>
);
});
// 中断
export const ModelBreak = React.forwardRef((props, ref) => {
const {} = props;
const callbackRef = useRef(() => {});
const [form] = Form.useForm();
const [visible, setVisible] = useState(false);
const handleCancel = useCallback(() => setVisible(false), []);
const handleOk = useCallback(() => {
let formData = form.getFieldValue();
if (["", null, undefined].includes(formData.reason))
return message.error("请完善中断原因!");
return new Promise((resolve, reject) => {
request({
url: `${window.config.mesUrl}/prodexec/prodexec/packageEquipment/interruptPack`,
bodys: {
gid: formData.gid,
},
})
.then((res) => {
if (res.success) {
handleCancel();
callbackRef.current();
} else message.error(res.message || "操作失败!");
})
.finally(() => {
resolve();
});
});
}, [handleCancel]);
useImperativeHandle(
ref,
() => ({
show() {
setVisible(true);
},
show: (data, callback) => {
setVisible(true);
form.resetFields();
form.setFieldsValue(data);
callbackRef.current = callback;
},
}),
[form]
);
return (
<ModalClient
visible={visible}
title="中断原因"
onCancel={handleCancel}
onOk={handleOk}
>
<Form autoComplete="off" size="large" form={form}>
<Form.Item name="reason">
<Input.TextArea placeholder="填写设备故障原因" rows={4} />
</Form.Item>
</Form>
</ModalClient>
);
});
// 结束
export const ModelFinish = React.forwardRef((props, ref) => {
const {} = props;
const [form, setForm] = useState([]);
const callbackRef = useRef(() => {});
const [visible, setVisible] = useState(false);
const handleCancel = useCallback(() => setVisible(false), []);
const handleOk = useCallback(() => {
return new Promise((resolve, reject) => {
request({
url: `${window.config.mesUrl}/prodexec/prodexec/packageEquipment/endPack`,
bodys: form,
})
.then((res) => {
if (res.success) {
handleCancel();
callbackRef.current();
message.success("包装完成!");
} else message.error(res.message || "操作失败!");
})
.finally(() => {
resolve();
});
});
}, [handleCancel, form]);
useImperativeHandle(
ref,
() => ({
show: (data, callback) => {
setVisible(true);
setForm(data);
callbackRef.current = callback;
},
}),
[]
);
return (
<ModalClient
visible={visible}
title="结束"
onCancel={handleCancel}
onOk={handleOk}
>
<p>是否确认结束</p>
</ModalClient>
);
});
// 超产
export const ModelOverproductionBatch = React.forwardRef((props, ref) => {
const { } = props;
const [form] = Form.useForm();
const codeRef = useRef();
const callbackRef = useRef(() => {});
const [visible, setVisible] = useState(false);
const handleCancel = useCallback(() => setVisible(false), []);
const handleOk = useCallback(() => {
let formData = form.getFieldValue();
if (codeRef.current && codeRef.current === formData.outBatch) {
message.error("超产批号不能与原单号一致!");
} else {
callbackRef.current && callbackRef.current(formData.outBatch);
handleCancel();
};
}, [handleCancel, form]);
useImperativeHandle(
ref,
() => ({
show(code, callback) {
codeRef.current = code;
callbackRef.current = callback;
setVisible(true);
},
}),
[]
);
return (
<ModalClient
visible={visible}
title="超产批号"
onCancel={handleCancel}
onOk={handleOk}
>
<Form autoComplete="off" size="large" form={form}>
<Form.Item name="outBatch">
<Input placeholder="填写超产批号" allowClear/>
</Form.Item>
</Form>
</ModalClient>
);
});
// 超量打印
export const ModelOverPrint = React.forwardRef((props, ref) => {
const {} = props;
const [form, setForm] = useState("");
const callbackRef = useRef(() => {});
const [visible, setVisible] = useState(false);
const handleCancel = useCallback(() => setVisible(false), []);
const handleOk = useCallback(() => {
return callbackRef.current(handleCancel);
}, [handleCancel, form]);
useImperativeHandle(
ref,
() => ({
show: (data, callback) => {
setVisible(true);
setForm(data);
callbackRef.current = callback;
},
}),
[]
);
return (
<ModalClient
visible={visible}
title="确认打印"
onCancel={handleCancel}
onOk={handleOk}
>
<p>{ form }</p>
</ModalClient>
);
});

View File

@ -0,0 +1,199 @@
import React, { useCallback, useEffect, useState, useRef } from "react";
import { message } from "antd";
import { request, getQueryVariable } from "../utils";
export const isDev = false;
let rules = (function () {
let data = [];
let callback = () => {};
let setData = (res) => {
setData = () => {};
data = res;
callback({ success: true, data: res });
};
return {
setData,
getData: () => data,
loaded: new Promise((resolve, reject) => {
callback = resolve;
}),
};
})();
let getRule = () => {
getRule = () => rules.loaded;
return request({
method: "get",
url: `${window.config.mesUrl}/console/getpermissions`,
});
};
export const useRule = (code) => {
const [ruleList, setRuleList] = useState(rules.getData());
useEffect(() => {
getRule().then(({ success, data } = {}) => {
if (success) {
setRuleList(data);
rules.setData(data);
}
});
}, []);
if (code) return ruleList.includes(code);
return true;
};
export const useUser = () => {
const [data, setData] = useState();
const getUser = useCallback(() => {
// let user = localStorage.getItem("userInfo");
// try {
// user = JSON.parse(user)
// } catch (e) {
// console.log(e);
// user = {};
// }
// console.log("user", user, localStorage.getItem("userInfo"))
// setData(user);
request({
method: "get",
url: `${window.config.mesUrl}/console/currentinfo`,
}).then(({ success, data } = {}) => {
if (success) {
setData(data);
} else setData();
});
}, [])
// useEffect(() => {
// getUser();
// }, [getUser]);
return [data, getUser];
};
export const useEquipKey = () => {
const [equipKey, setEquipKey] = useState();
const getEquipKey = useCallback(() => {
let ip = getQueryVariable("ip");
console.log("ip", ip);
if (!ip || ip === "null") {
message.error("获取 ip 失败!");
} else {
setEquipKey(ip);
}
}, []);
useEffect(() => {
getEquipKey();
}, [getEquipKey]);
return { equipKey, getEquipKey };
};
export const useSetting = (equipKey) => {
const [settingData, setSettingData] = useState({});
const getSettingData = useCallback(() => {
if (equipKey) {
request({
method: "post",
url: `${window.config.mesUrl}/prodexec/prodexec/packageEquipment/getPackInfoByKey?equipKey=${equipKey}`,
}).then(({ success, data } = {}) => {
if (success) setSettingData(data || {});
});
} else setSettingData({});
}, [equipKey]);
useEffect(() => {
getSettingData();
}, [getSettingData]);
return { settingData, getSettingData };
};
export const useMetaInfo = (equipCode) => {
const [data, setData] = useState({}); // bagNo outBagNo
const timmerRef = useRef();
const getData = useCallback(() => {
if (timmerRef.current) {
clearTimeout(timmerRef.current);
timmerRef.current = undefined;
}
if (equipCode) {
request({
method: "post",
url: `${window.config.mesUrl}/prodexec/prodexec/packageEquipment/getEquipRecord?equipCode=${equipCode}`,
}).then(({ success, data, message: msg } = {}) => {
if (success) {
let { status } = data || {};
// 没有数据,或者数据为中断状态,尝试定时重复请求
if (!data || status === "INTERRUPT") {
if (!data) {
data = { status: "no_job" };
message.error("请先分配任务!");
}
let { reloadTime } = window.config.packagingOperaConfig || {};
if (reloadTime !== undefined) {
timmerRef.current = setTimeout(getData, reloadTime);
}
}
setData(data);
} else {
message.error(msg);
}
});
} else setData({});
}, [equipCode]);
useEffect(() => {
getData();
}, [getData]);
return [data, getData];
};
export const useWsInfo = (equipCode, isEdit) => {
const [data, setData] = useState({});
useEffect(() => {
if (equipCode) {
let url = window.config.edgewsUrl;
if (isDev) {
url = "ws://120.202.38.15:8081/rongtong-edge-application-ws/";
}
if (!isEdit) {
console.log("link: ", url);
let ws = new WebSocket(url);
ws.onmessage = (evt) => {
let res = evt.data;
// if (isDev) {
// res = {
// czzl: 1.11,
// fczl: 2.22,
// }
// }
if (typeof res === "string") {
try {
res = JSON.parse(res);
} catch (e) {
console.log("ws", res);
res = {};
}
}
console.log("ws res:", res);
setData(res);
};
ws.onopen = () => {
if (isDev) {
// 01 02 03
equipCode = "L5-BZ-02";
}
console.log("Connection open ... send:", equipCode);
ws.send(equipCode);
};
}
return () => {
if (!isEdit) {
console.log("ws close:", equipCode);
ws.close();
}
};
}
}, [equipCode, isEdit]);
return [data];
};

View File

@ -0,0 +1,619 @@
import React, {
useCallback,
useRef,
useReducer,
useEffect,
useState,
useMemo,
} from "react";
import { message } from "antd";
import moment from "moment";
import Decimal from "decimal.js"
import Row from "../../../components/screen/Row";
import { FormItem } from "../FormItem";
import { Button } from "../Button";
import {
ModelSetting,
ModelBreak,
ModelOverproductionBatch,
ModelFinish,
ModelOverPrint,
} from "./ModalClient";
import { logo } from "./logo";
import { request } from "../utils";
import {
useEquipKey,
useSetting,
useMetaInfo,
useWsInfo,
useUser,
isDev,
} from "./hooks";
const headerHeight = 56;
/**
* url获取token
* 获取用户信息
* 获取权限信息
*
* url获取ip
* ip - 设置信息
* 设置信息 - 产品信息
* 设置信息 - socket 重量信息
*
* 任务状态:
* 无任务 待包装 包装中 中断 包装完成
*/
function formReducer(state, action) {
let { type, value } = action;
// console.log("action", action);
if (type === "FROM_INIT") return value;
if (type === "FROM_CHANGE") return Object.assign({}, state, value);
return state;
}
function PackagingOperation(props) {
const ModelSettingRef = useRef();
const ModelBreakRef = useRef();
const ModelOverproductionBatchRef = useRef();
const ModelFinishRef = useRef();
const [isEdit, setIsEdit] = useState(false);
const { equipKey, getEquipKey } = useEquipKey();
const { settingData, getSettingData } = useSetting(equipKey);
const [metaInfo, getMetaInfo] = useMetaInfo(settingData?.equipCode);
const [wsData] = useWsInfo(settingData?.equipCode, isEdit);
const [user, getUser] = useUser();
// 页面表单变化
const [form, dispatch] = useReducer(formReducer, {});
const onFormChange = useCallback((type, value) => {
dispatch({ type: "FROM_CHANGE", value: { [type]: value } });
}, []);
// 更新当前登录用户
useEffect(() => {
dispatch({ type: "FROM_CHANGE", value: { userName: user?.userName } });
}, [user]);
// 初始化表单
useEffect(() => {
dispatch({ type: "FROM_INIT", value: metaInfo });
getUser();
}, [metaInfo, getUser]);
// ws更新表单
useEffect(() => {
let { czzl, fczl } = wsData || {};
dispatch({ type: "FROM_CHANGE", value: { czzl, fczl } });
}, [wsData]);
// 更新净重
const netWeight = useMemo(() => {
let { bagWeight } = settingData;
let { fczl } = form;
if (isNaN(parseFloat(fczl))) return "";
if (isNaN(parseFloat(bagWeight))) return "";
let res = Decimal(fczl).sub(Decimal(bagWeight)).toNumber();
if (isNaN(res)) return "";
return res;
}, [form, settingData]);
// 更新持续时长
const [durationTime, setDurationTime] = useState(0);
useEffect(() => {
let getTime = () => {
let {
startTime,
status,
recoverDate,
interruptDate,
intrptDuration = 0,
} = metaInfo;
// console.log("metaInfo", metaInfo, {startTime, status, recoverDate, interruptDate, intrptDuration })
/**
* 无任务
* 待包装
* 包装中 currentDate - startTime - intrptDuration
* 中断 interruptDate - startTime - intrptDuration
* 包装完成 endTime - startTime - intrptDuration
*/
if (["no_job", "TO_BE_PACKAGED"].includes(status)) return "";
let dif = new Date(startTime).getTime() + intrptDuration * 1000;
if (status === "IN_PACKAGING") return new Date().getTime() - dif;
if (status === "INTERRUPT")
return new Date(interruptDate).getTime() - dif;
if (status === "PACKAGING_COMPLETED")
return new Date(endTime).getTime() - dif;
};
let timmer;
let run = () => {
setDurationTime(getTime());
timmer = setTimeout(() => {
run();
}, 1000);
};
run();
return () => {
clearTimeout(timmer);
};
}, [metaInfo]);
const durationTimeStr = useMemo(() => {
let time = moment.duration(durationTime);
let res = "";
let sec = time.seconds();
let min = time.minutes();
let hor = time.hours();
let day = time.days();
let m = time.months();
let y = time.years();
let flag = false;
let arr = [y, m, day, hor, min, sec];
let unt = ["年", "月", "日", "时", "分", "秒"];
arr.forEach((val, index) => {
if (val) {
flag = true;
res += val + unt[index];
} else if (flag) {
res += 0 + unt[index];
}
});
return res;
}, [durationTime]);
// 包装袋数
const [realBagNo, setRealBagNo] = useState(1);
useEffect(() => {
let { bagNo, outBagNo, outBatch } = form;
let res = 0;
if (outBatch) {
res = parseFloat(outBagNo) + 1;
} else {
res = parseFloat(bagNo) + 1;
}
setRealBagNo(isNaN(res) ? 0 : res);
}, [form.bagNo, form.outBagNo, form.outBatch]);
// 设备状态
const equipStatus = useMemo(() => {
if (form.status === "no_job") return "无任务";
if (form.status === "INTERRUPT") return "中断";
if (form.status === "TO_BE_PACKAGED") return "待包装";
if (form.status === "IN_PACKAGING") return "包装中";
if (form.status === "PACKAGING_COMPLETED") return "包装完成";
return "-";
}, [form.status]);
// 按钮启用禁用
const btnEnable = useMemo(() => {
/**
* 超产批号
* 设置 - 权限
* 历史记录 - 非无任务
* 开始 - 待包装
* 打印 - 包装中
* 结束 - 包装中 中断
* 中断 - 包装中
* 编辑 - 权限 包装中
* 编辑完成 - 权限 包装中
*/
return {
overPro: true,
setting: true,
history: ["中断", "待包装", "包装中", "包装完成"].includes(equipStatus),
start: ["待包装"].includes(equipStatus),
print: ["包装中"].includes(equipStatus),
finish: ["包装中", "中断"].includes(equipStatus),
break: ["包装中"].includes(equipStatus),
edit: ["包装中"].includes(equipStatus),
};
}, [equipStatus]);
// 退出
const handleCancel = useCallback(() => {
window.parent.postMessage({ type: "logOut" }, window.config.mesHost);
}, []);
// 设置
const handleSetting = useCallback(() => {
if (equipKey) {
let settingRes = Object.assign({}, settingData || {}, { equipKey });
ModelSettingRef.current.show(settingRes, getSettingData);
}
}, [equipKey, getSettingData, settingData]);
// 超产批号
const handleOverproduct = useCallback(() => {
ModelOverproductionBatchRef.current.show(
metaInfo.workOrderCode,
(outBatch) => {
onFormChange("outBatch", outBatch);
}
);
}, [metaInfo.workOrderCode, onFormChange]);
// 历史记录
const handleHistory = useCallback(() => {
window.parent.postMessage(
{ type: "history", data: metaInfo },
window.config.mesHost
);
}, [metaInfo]);
// 开始
const handleStart = useCallback(() => {
if (metaInfo.gid)
return new Promise((resolve, reject) => {
request({
url: `${window.config.mesUrl}/prodexec/prodexec/packageEquipment/startPack`,
bodys: { gid: metaInfo.gid },
})
.then((res) => {
if (res.success) {
message.success("开始成功!");
getMetaInfo();
}
})
.finally(() => {
resolve();
});
});
}, [metaInfo.gid, getMetaInfo]);
// 打印
const ModelOverPrintRef = useRef();
const [isGT, setIsGT] = useState(false)
const handlePrint = useCallback((GT, callback) => {
if (GT === undefined) GT = isGT;
let { gid: recordGid } = metaInfo;
let { gid: setInfoGid, bagWeight } = settingData;
let {
fczl,
czzl,
outBatch,
materialCode,
productLevel,
startTime,
materialName,
workOrderCode,
} = form;
// collectValue 页面可见数据 除自定义字段外的 对象json 字符串
let collectValue = {
materialCode,
productLevel,
startTime,
materialName,
workOrderCode,
czzl,
fczl,
bagWeight,
yetWeight: netWeight,
bagNo: realBagNo,
};
let data = {
recordGid,
mark: GT? "GT": undefined,
fczl,
bagNo: realBagNo,
setInfoGid,
yetWeight: netWeight,
outBatch,
collectValue: JSON.stringify(collectValue),
};
if (recordGid)
return new Promise((resolve, reject) => {
request({
url: `${window.config.mesUrl}/prodexec/prodexec/packageEquipment/print`,
bodys: data,
})
.then((res) => {
// if (isDev) {
// if (res.message.indexOf('打印命令发送失败') > -1) {
// res = {
// success: false,
// data: "GT",
// message: "message text"
// }
// }
// }
if (res && res.success) {
message.success("打印成功!");
setRealBagNo(parseFloat(realBagNo) + 1);
} else {
if (res?.data === "GT") {
ModelOverPrintRef.current.show(res.message, (close) => {
setIsGT(true);
return handlePrint(true, close);
})
}else message.error(res?.message);
}
})
.finally(() => {
resolve();
callback && callback();
});
});
}, [metaInfo, settingData, form, netWeight, realBagNo, isGT]);
// 结束
const handleFinish = useCallback(() => {
ModelFinishRef.current.show({ gid: metaInfo.gid }, getMetaInfo);
}, [metaInfo.gid, getMetaInfo]);
// 中断
const handleBreak = useCallback(() => {
ModelBreakRef.current.show({ gid: metaInfo.gid }, getMetaInfo);
}, [metaInfo.gid, getMetaInfo]);
// 编辑
const handleEdit = useCallback(() => {
// console.log("handleEdit");
if (isEdit) {
setIsEdit(false);
} else {
setIsEdit(true);
}
}, [isEdit]);
return (
<div className="client-content">
<div className="client-content-header">
<div className="logo-box">
<img src={logo} alt="logo" className="logo-img" />
</div>
<div className="btn-box">
<div className="btn link-btn" onClick={handleCancel}>
退出
</div>
</div>
</div>
<div className="client-content-body">
<div className="card">
<div className="card-header">
<span className="title">产品信息</span>
<div className="extra">
<Button
title="超产批号"
ghost
onClick={handleOverproduct}
enable={btnEnable.overPro}
/>
<ModelOverproductionBatch ref={ModelOverproductionBatchRef} />
<Button
title="设置"
ghost
ruleCode="set_pack_info"
onClick={handleSetting}
enable={btnEnable.setting}
/>
<ModelSetting ref={ModelSettingRef} />
</div>
</div>
<div className="card-body">
<div className="statu-box">{equipStatus}</div>
<div className="statu-content">
<Row gutter={32}>
<Row.Col span={8}>
<FormItem value={form.materialCode} title={"产品编码"} />
</Row.Col>
<Row.Col span={8}>
<FormItem value={form.productLevel} title={"产品型号"} />
</Row.Col>
<Row.Col span={8}>
<FormItem value={form.startTime} title={"开始时间"} />
</Row.Col>
<Row.Col span={8}>
<FormItem value={form.materialName} title={"产品名称"} />
</Row.Col>
<Row.Col span={8}>
<FormItem value={form.workOrderCode} title={"产品批次号"} />
</Row.Col>
<Row.Col span={8}>
<FormItem value={durationTimeStr} title={"持续时长"} />
</Row.Col>
</Row>
</div>
</div>
</div>
<div className="card">
<div className="card-header">
<span className="title">生产信息</span>
<div className="extra">
<Button
title="历史记录"
ghost={true}
onClick={handleHistory}
enable={btnEnable.history}
/>
</div>
</div>
<div className="card-body">
<Row gutter={32}>
<Row.Col span={8}>
<FormItem
value={form.czzl}
title={"毛重吨包/KG"}
horizontal={false}
type="number"
readonly={!isEdit}
onChange={(val) => onFormChange("czzl", val)}
/>
</Row.Col>
<Row.Col span={8}>
<FormItem
value={form.fczl}
title={"复称重量吨包/KG"}
horizontal={false}
type="number"
readonly={!isEdit}
onChange={(val) => onFormChange("fczl", val)}
/>
</Row.Col>
<Row.Col span={8}>
<FormItem
value={form.userName}
title={"复称人"}
horizontal={false}
/>
</Row.Col>
<Row.Col span={8}>
<FormItem
value={settingData.bagWeight}
title={"包装袋重量/KG"}
horizontal={false}
/>
</Row.Col>
<Row.Col span={8}>
<FormItem
value={netWeight}
title={"净重/KG"}
horizontal={false}
/>
</Row.Col>
<Row.Col span={8}>
<FormItem
value={realBagNo}
title={"包装袋数"}
horizontal={false}
type="number"
readonly={false}
min={1}
onChange={(val) => setRealBagNo(val)}
/>
</Row.Col>
</Row>
</div>
</div>
</div>
<div className="client-content-footer">
<Button
title="开始"
type="success"
size="large"
ruleCode="startPack"
onClick={handleStart}
enable={btnEnable.start}
/>
<Button
title="确认打印"
size="large"
ruleCode="print_pack"
onClick={handlePrint}
enable={btnEnable.print}
/>
<ModelOverPrint ref={ModelOverPrintRef}/>
<Button
title="结束"
type="danger"
size="large"
ruleCode="end_pack"
enable={btnEnable.finish}
onClick={handleFinish}
/>
<ModelFinish ref={ModelFinishRef} />
<Button
title="中断"
type="warning"
size="large"
ruleCode="interrupt_pack"
enable={btnEnable.break}
onClick={handleBreak}
/>
<ModelBreak ref={ModelBreakRef} />
<Button
title={!isEdit ? "编辑" : "编辑完成"}
type="info"
size="large"
ruleCode="eidt_fczl"
onClick={handleEdit}
enable={btnEnable.edit}
/>
</div>
<style jsx>{`
.client-content {
display: flex;
flex-direction: column;
height: 100vh;
}
.client-content-header {
height: ${headerHeight}px;
padding: 0 16px;
background: #272f3e;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.1);
}
.logo-box {
float: left;
line-height: ${headerHeight}px;
}
.logo-img {
width: 175px;
height: 28px;
display: inline-block;
}
.btn-box {
float: right;
}
.btn.link-btn {
display: inline-block;
line-height: ${headerHeight}px;
padding: 0 24px;
color: white;
font-size: 20px;
cursor: pointer;
}
.client-content-footer {
background: white;
height: 96px;
text-align: center;
padding: 20px 0;
}
.client-content-body {
background: rgb(243, 243, 243);
flex: 1;
overflow: auto;
padding: 18px 16px 0;
}
.card {
background: #ffffff;
border-radius: 4px;
margin-bottom: 16px;
padding: 24px 32px;
}
.card-header {
overflow: hidden;
}
.card-header .title {
font-size: 28px;
color: #000000 85%;
}
.card-header .extra {
float: right;
}
.card-body {
padding: 20px 0;
overflow: hidden;
}
.statu-box {
height: 112px;
width: 112px;
background: #fff7e8;
border: 1px solid #f16704;
border-radius: 6px;
line-height: 112px;
font-size: 24px;
text-align: center;
color: #faab0c;
float: left;
}
.statu-content {
margin-left: ${112 + 32}px;
}
`}</style>
</div>
);
}
export default PackagingOperation;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,17 @@
import {default as reportRequest} from "../../reportUtils/request"
export const getQueryVariable = (variable) => {
let query = window.location.search.substring(1);
let vars = query.split("&");
for (let i = 0; i < vars.length; i++) {
let pair = vars[i].split("=");
if (pair[0] == variable) { return pair[1]; }
}
return (false);
}
export const request = (params) => {
let token = getQueryVariable("token");
return reportRequest(params, token)
}

View File

@ -0,0 +1,97 @@
import React from 'react';
const titleSize = 28;
function Card(props) {
const {
full = true,
title,
titleSpace,
overVisible,
padding,
withBorder = true,
withBg = "rgba(6,36,109,0.80)"
} = props;
return (
<div className={'screen-card'}>
{title && <div className="screen-card-title">
{title}
</div>}
<div className="screen-card-content">
<div className="box">
{React.Children.map(props.children, item => item)}
</div>
</div>
<style jsx>{`
.screen-card {
background: ${withBg ? withBg : 'transparent'};
border: 2px solid ${withBorder ? '#0072EE' : 'transparent'};
border-radius: 8px;
position: relative;
color: white;
height: ${full ? "100%" : "unset"}
}
.screen-card-title {
background: #0072EE;
font-size: 16px;
font-weight: 600;
line-height: ${titleSize}px;
padding: 0 10px;
display: inline-block;
position: absolute;
z-index: 1;
}
.screen-card-title:after {
content: '';
display: block;
width: 10px;
height: ${titleSize}px;
position: absolute;
right: -10px;
bottom: 0;
border-color: transparent;
border-top-color: #0072EE;
border-left-color: #0072EE;
border-width: 14px 5px;
}
.screen-card-content {
overflow: ${overVisible ? "visible" : "auto"};
padding-top: ${titleSpace ? titleSize : 0}px;
height: 100%;
}
.screen-card-content > .box {
padding: ${padding ? padding : "unset"};
min-height: 100%;
height: 100%;
}
.screen-card-content::-webkit-scrollbar {
width: 16px;
height: 16px;
background-color: transparent;
}
.screen-card-content::-webkit-scrollbar-track {
border-radius: 10px;
background-color: transparent;
}
.screen-card-content::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 0 4px #092062;
background-color: #0072EE;
}
.screen-card-content::-webkit-scrollbar-corner {
background-color: transparent;
}
`}</style>
</div>
);
}
export default Card;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
import React, {useRef, useEffect, useImperativeHandle} from "react";
import * as echarts from "echarts";
import theme from "./theme.json";
import './echarts-liquidfill'
// import('echarts-liquidfill');
echarts.registerTheme("darkBlue", theme);
export default React.forwardRef(({option}, ref) => {
const chartRef = useRef(null);
const chart = useRef();
useEffect(() => {
chart.current = echarts.init(chartRef.current, "darkBlue");
let resize = () => {
chart.current.resize();
};
window.addEventListener("resize", () => resize());
return window.removeEventListener("resize", () => resize());
}, []);
useEffect(() => {
if (chart && option) {
// console.log("chart",chart)
chart.current.setOption(option);
}
});
useImperativeHandle(ref, () => ({
setOption: (opt) => {
// chart.current.setOption(Object.assign(opt, option));
chart.current.setOption(opt);
},
getChart: () => chart.current,
}));
return <div ref={chartRef} style={{height: "100%", width: "100%"}}/>;
});

View File

@ -0,0 +1,440 @@
{
"color": [
"#28a3ff",
"#ffc760",
"#6fe621",
"#f95757",
"#54c4f1",
"#fc8452",
"#9a60b4",
"#ea7ccc",
"#7289ab",
"#91ca8c",
"#f49f42"
],
"backgroundColor": "rgba(4,20,87,0)",
"textStyle": {},
"xAxis": {
"nameGap": 5
},
"yAxis": {
"nameGap": 5
},
"title": {
"textStyle": {
"color": "#eeeeee"
},
"subtextStyle": {
"color": "#aaaaaa"
}
},
"grid": {
"top": 30,
"left": 30,
"bottom": 30,
"right": 30
},
"line": {
"itemStyle": {
"borderWidth": 1
},
"lineStyle": {
"width": 2
},
"symbolSize": 10,
"symbol": "circle",
"smooth": false
},
"radar": {
"itemStyle": {
"borderWidth": 1
},
"lineStyle": {
"width": 2
},
"symbolSize": 4,
"symbol": "circle",
"smooth": false
},
"bar": {
"itemStyle": {
"barBorderWidth": 0,
"barBorderColor": "#ccc"
}
},
"pie": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"scatter": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"boxplot": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"parallel": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"sankey": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"funnel": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"gauge": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"candlestick": {
"itemStyle": {
"color": "#fd1050",
"color0": "#0cf49b",
"borderColor": "#fd1050",
"borderColor0": "#0cf49b",
"borderWidth": 1
}
},
"graph": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"lineStyle": {
"width": 1,
"color": "#aaaaaa"
},
"symbolSize": 4,
"symbol": "circle",
"smooth": false,
"color": [
"#dd6b66",
"#759aa0",
"#e69d87",
"#8dc1a9",
"#ea7e53",
"#eedd78",
"#73a373",
"#73b9bc",
"#7289ab",
"#91ca8c",
"#f49f42"
],
"label": {
"color": "#eeeeee"
}
},
"map": {
"itemStyle": {
"normal": {
"areaColor": "#eee",
"borderColor": "#444",
"borderWidth": 0.5
},
"emphasis": {
"areaColor": "rgba(255,215,0,0.8)",
"borderColor": "#444",
"borderWidth": 1
}
},
"label": {
"normal": {
"textStyle": {
"color": "#000"
}
},
"emphasis": {
"textStyle": {
"color": "rgb(100,0,0)"
}
}
}
},
"geo": {
"itemStyle": {
"normal": {
"areaColor": "#eee",
"borderColor": "#444",
"borderWidth": 0.5
},
"emphasis": {
"areaColor": "rgba(255,215,0,0.8)",
"borderColor": "#444",
"borderWidth": 1
}
},
"label": {
"normal": {
"textStyle": {
"color": "#000"
}
},
"emphasis": {
"textStyle": {
"color": "rgb(100,0,0)"
}
}
}
},
"categoryAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#eeeeee"
}
},
"axisTick": {
"show": true,
"lineStyle": {
"color": "#eeeeee"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#eeeeee"
}
},
"splitLine": {
"show": false,
"lineStyle": {
"color": [
"#aaaaaa"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"#eeeeee"
]
}
}
},
"valueAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#eeeeee"
}
},
"axisTick": {
"show": true,
"lineStyle": {
"color": "#eeeeee"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#eeeeee"
}
},
"splitLine": {
"show": false,
"lineStyle": {
"color": [
"#aaaaaa"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"#eeeeee"
]
}
}
},
"logAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#eeeeee"
}
},
"axisTick": {
"show": true,
"lineStyle": {
"color": "#eeeeee"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#eeeeee"
}
},
"splitLine": {
"show": false,
"lineStyle": {
"color": [
"#aaaaaa"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"#eeeeee"
]
}
}
},
"timeAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#eeeeee"
}
},
"axisTick": {
"show": true,
"lineStyle": {
"color": "#eeeeee"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#eeeeee"
}
},
"splitLine": {
"show": false,
"lineStyle": {
"color": [
"#aaaaaa"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"#eeeeee"
]
}
}
},
"toolbox": {
"iconStyle": {
"normal": {
"borderColor": "rgba(153,153,153,1)"
},
"emphasis": {
"borderColor": "rgba(102,102,102,1)"
}
}
},
"legend": {
"show": true,
"left": "center",
"textStyle": {
"color": "#eeeeee"
}
},
"tooltip": {
"axisPointer": {
"lineStyle": {
"color": "rgba(238,238,238,1)",
"width": "1"
},
"crossStyle": {
"color": "rgba(238,238,238,1)",
"width": "1"
}
}
},
"timeline": {
"lineStyle": {
"color": "#eeeeee",
"width": 1
},
"itemStyle": {
"normal": {
"color": "#dd6b66",
"borderWidth": 1
},
"emphasis": {
"color": "#a9334c"
}
},
"controlStyle": {
"normal": {
"color": "#eeeeee",
"borderColor": "#eeeeee",
"borderWidth": 0.5
},
"emphasis": {
"color": "#eeeeee",
"borderColor": "#eeeeee",
"borderWidth": 0.5
}
},
"checkpointStyle": {
"color": "#e43c59",
"borderColor": "#c23531"
},
"label": {
"normal": {
"textStyle": {
"color": "#eeeeee"
}
},
"emphasis": {
"textStyle": {
"color": "#eeeeee"
}
}
}
},
"visualMap": {
"color": [
"#bf444c",
"#d88273",
"#f6efa6"
]
},
"dataZoom": {
"backgroundColor": "rgba(47,69,84,0)",
"dataBackgroundColor": "rgba(255,255,255,0.3)",
"fillerColor": "rgba(167,183,204,0.4)",
"handleColor": "#a7b7cc",
"handleSize": "100%",
"textStyle": {
"color": "#eeeeee"
}
},
"markPoint": {
"label": {
"color": "#eeeeee"
},
"emphasis": {
"label": {
"color": "#eeeeee"
}
}
}
}

View File

@ -0,0 +1,162 @@
import React, { useRef, useEffect, useState, useMemo } from 'react'
const imgHeight = 951; //图片原始高度
const imgWidth = 184; //图片原始高度
const circleBorder = 2; //圆环的宽度
const baseSize = 170;//满状态宽度
const itemHeight = 370; //单个高度(包含空白)
const waveHeight = 29; //波浪高度
const colors = ["#8ee34d", "#f6c971", "#ea335d"]; //圆边框颜色
const colorIndexTrans = [2, 1, 0]; //调换第一个和最后一个位置
function CircleProcess(props) {
const sizeRef = useRef();
const outerRef = useRef();
const innerRef = useRef();
const [scale, setScale] = useState(1)
const [innerWidth, setInnerWidth] = useState(0)
//接受外部参数
let { percent:realPercent = 0.5, percentRule = [0, 0.8, 0.95], size = 0.9 } = props;
const percent = useMemo(() => {
return realPercent > 1 ? 1 : realPercent
}, [realPercent])
//颜色的下标
const colorIndex = useMemo(() => {
let res = 0;
percentRule.forEach((val, index) => {
if (percent > val) res = index;
})
return colorIndexTrans[res];
}, [percentRule, percent])
//颜色的位置
const positionY = useMemo(() => {
return -(-innerWidth + percent * innerWidth + waveHeight * scale + colorIndex * scale * itemHeight)
}, [innerWidth, percent, scale, colorIndex])
//计算组件宽度以及比例
const tableSetting = () => {
//组件实际宽度
let comWidth = sizeRef.current.clientWidth;
outerRef.current.style.height = comWidth * size + 'px';
outerRef.current.style.width = comWidth * size + 'px';
//波浪实际宽度
let innerWidth = innerRef.current.clientWidth;
setInnerWidth(innerWidth);
//计算比例
setScale(innerWidth / baseSize);
}
useEffect(() => {
//监听父级宽度变化
let innerWindow = sizeRef.current.contentDocument.defaultView;
innerWindow.addEventListener("resize", tableSetting);
tableSetting();
return () => {
innerWindow.removeEventListener("resize", tableSetting);
}
}, [])
return (
<div className='circle-process'>
<object
ref={sizeRef}
tabIndex="-1"
type="text/html"
aria-hidden="true"
data="about:blank"
style={{
display: "block",
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
border: "none",
padding: 0,
margin: 0,
opacity: 0,
zIndex: -1000,
pointerEvents: "none",
}} />
<div className='out-circle' ref={outerRef}>
<div className='in-circle' ref={innerRef}>
<div className='pic-one'></div>
<div className='pic-two'></div>
<div className='text'>{realPercent * 100 + "%"}</div>
</div>
</div>
<style jsx>{`
.circle-process{
position:relative;
height:100%;
}
.out-circle {
margin: auto;
overflow: hidden;
border-radius: 50%;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: ${circleBorder}px solid;
border-color: ${colors[colorIndex]};
padding: 2px;
}
.in-circle {
width: 100%;
height: 100%;
border-radius: 50%;
position: relative;
overflow: hidden;
}
.in-circle .pic-one, .in-circle .pic-two {
background-image: url('/img/hpPercent.png');
background-repeat: repeat-x;
background-size: ${scale * imgWidth}px ${scale * imgHeight}px;
width: 100%;
height: 100%;
position: absolute;
background-position-y: ${positionY}px;
}
.in-circle .pic-one {
animation: 5s one linear infinite normal;
}
.in-circle .pic-two {
opacity: 0.3;
animation: 10s one linear infinite normal;
}
.in-circle .text {
position: absolute;
color: #fff;
text-align: center;
width: 100%;
margin-top: 12px;
font-size: ${scale*30}px;
}
@keyframes one {
from {
background-position-x: 0;
}
to {
background-position-x: -${imgWidth * scale}px;
}
}
`}
</style>
</div>
)
}
export default CircleProcess

View File

@ -0,0 +1,60 @@
import React from 'react';
function DateItem(props) {
const { text } = props;
return (
<span className='screen-dateItem'>
<span className="screen-dateItem-line left"></span>
<span className={text !== '-' && 'screen-dateItem-text'}>{text}</span>
<span className="screen-dateItem-line right"></span>
<style jsx>{`
@font-face {
font-family: 'UnidreamLED';
src: url('/font/UnidreamLED.ttf');
}
.screen-dateItem{
font-family: 'UnidreamLED';
width: 40px;
display: inline-block;
position: relative;
margin-left: -1px;
}
.screen-dateItem-line{
position: absolute;
width: 1px;
height: 100%;
}
.screen-dateItem-line.left{
left: 0;
top:0;
}
.screen-dateItem-line.right{
right: 0;
bottom:0;
}
.screen-dateItem-line::after,
.screen-dateItem-line::before{
content: '';
display: block;
width: 1px;
height: 30%;
background: #28A3FF;
position: absolute;
}
.screen-dateItem-line::before{
top: 0;
}
.screen-dateItem-line::after{
bottom: 0;
}
.screen-dateItem-text{
display: inline-block;
background: linear-gradient(to bottom, #020E2E, #0072EE, #020E2E);
padding: 0 8px;
}
`}</style>
</span>
);
}
export default React.memo(DateItem);

View File

@ -0,0 +1,105 @@
import React, { useState, useCallback, useEffect, useRef } from 'react';
import DateItem from './DateItem'
function Footer(props) {
const { withTime = true, showDate } = props;
const [date, setDate] = useState([]);
const dateStrRef = useRef('');
const timeBoxRef = useRef();
const fixed0 = useCallback((num) => {
num = parseInt(num)
if (num < 10) num = "0" + num;
return num + '';
}, [])
const getTime = useCallback(() => {
let dateStr = dateStrRef.current;
let currentDate = new Date();
let year = currentDate.getUTCFullYear();
year = fixed0(year);
let month = currentDate.getMonth() + 1;
month = fixed0(month);
let day = currentDate.getDate();
day = fixed0(day);
let hour = currentDate.getHours();
hour = fixed0(hour);
let minute = currentDate.getMinutes();
minute = fixed0(minute);
let second = currentDate.getSeconds();
second = fixed0(second);
let newDate = `${year}-${month}-${day}`;
let time = `${hour}:${minute}:${second}`;
if (showDate) {
setDate(showDate.split(''));
}else if(dateStr !== newDate && !showDate) {
dateStrRef.current = newDate;
setDate(newDate.split(''));
}
withTime && timeBoxRef.current && (timeBoxRef.current.innerText = time)
}, [ withTime, showDate ]);
const updateTime = useCallback(() => {
getTime();
if (!showDate) {
setTimeout(updateTime, 1000)
}
}, [getTime, showDate])
useEffect(() => {
updateTime();
// eslint-disable-next-line
}, [updateTime]);
return (
<div className="screen-footer">
<div className="screen-footer-content">
<span className="screen-footer-date">
{date.map((item, index) => {
return (
<DateItem key={index} text={item} />
)
})}
</span>
<span className="screen-footer-time" ref={timeBoxRef}>
</span>
</div>
<style jsx>{`
.screen-footer{
height: 82px;
background: url('/img/footerBg.png') center bottom no-repeat;
background-size: cover;
position: absolute;
width: 100%;
bottom: 0;
padding-top: 18px;
}
.screen-footer-content{
color: white;
font-weight: bold;
width: 797px;
height: 44px;
line-height: 44px;
text-align: center;
margin: 0 auto;
}
.screen-footer-date{
font-size: 36px;
vertical-align: middle;
}
.screen-footer-time{
font-size: 30px;
font-family: 'Helvetica Neue';
letter-spacing: 4px;
vertical-align: middle;
padding: 0 25px;
}
`}</style>
</div>
)
}
export default React.memo(Footer)

View File

@ -0,0 +1,63 @@
import React from "react";
import { ClockIcon } from "@heroicons/react/solid";
function Header(props) {
const { title, time } = props;
return (
<div className="screen-header">
<div className="logo"></div>
<span className="screen-title">{title}</span>
<span className=" block text-white font-bold text-2xl leading-6 screen-time ">
<div className=" inline-block">
{time === undefined ? (
<></>
) : (
<ClockIcon className=" inline-block flex-0 w-8 h-8 -mt-1" />
)}
</div>
{time}
</span>
<style jsx>{`
.screen-header {
height: 96px;
background: url("/img/headerBg.png") center top no-repeat;
background-size: cover;
position: absolute;
width: 100%;
top: 0;
text-align: center;
}
.screen-header .screen-title {
text-align: center;
font-size: 32px;
line-height: 42px;
font-weight: bold;
background: linear-gradient(to right, #28a8ff, #3beaff, #28a8ff);
background-clip: text;
color: transparent;
margin-top: 10px;
display: inline-block;
}
@font-face {
font-family: "UnidreamLED";
src: url("/font/UnidreamLED.ttf");
}
.screen-header .screen-time {
font-family: "黑体";
}
.screen-header .logo {
position: absolute;
height: 32px;
width: 200px;
background: url("/img/logo.png") center center no-repeat;
background-size: 100%;
top: 4px;
left: 20px;
}
`}</style>
</div>
);
}
export default Header;

View File

@ -0,0 +1,138 @@
import React, {useRef, useEffect, useCallback, useLayoutEffect} from 'react';
import _ from 'lodash'
import {imgUrl as url} from '../../../utils/requests'
let defaultUrl = "/img/noImg.png";
const fetchImg = fileCode => {
// console.log("fetchImg")
return new Promise((resolve, reject) => {
let ajax = new XMLHttpRequest();
ajax.open("GET", `${url}${fileCode}`, true);
ajax.responseType = "blob";
ajax.setRequestHeader("Cache-Control", "max-age=3600")
ajax.setRequestHeader("Authorization", window.localStorage.getItem("token"))
ajax.onload = function () {
if (ajax.status == 200) {
ajax.response.text().then(res => {
// console.log("res", JSON.parse(res))
try{
res = JSON.parse(res);
}catch(e){
console.log(e)
}
if (res.error) {
resolve(defaultUrl)
} else {
let blob = ajax.response;
let oFileReader = new FileReader();
oFileReader.onloadend = function (e) {
console.log("e", e)
let base64 = e.target.result;
resolve(base64)
};
oFileReader.readAsDataURL(blob);
}
})
}
}
ajax.send();
})
}
function InfoCard(props) {
const imgBox = useRef();
const {imgKey, status = {}, columns = [],extraColumns = [], data = {}, cardWidth = 256, contentHeight} = props;
const {key = '', value, color} = status;
const setImg = useCallback(async () => {
let url = defaultUrl;
if (imgKey && data[imgKey]) {
url = await fetchImg(data[imgKey]);
}
imgBox.current && (imgBox.current.style.backgroundImage = `url('${url}')`)
}, [data[imgKey]])
useLayoutEffect(() => {
let observer = new IntersectionObserver((entries, observer) => {
if(entries[0].intersectionRatio > 0) setImg();
}, {});
observer && observer.observe(imgBox.current);
return () => {
observer && observer.unobserve(imgBox.current);
}
}, [setImg])
return (
<div className='infoCard'>
<div className="img" ref={imgBox}/>
{data[key] && (
<div className="status" style={{backgroundColor: color? color(data[key]) : "unset" }}>
{value? value(data[key]) : data[key]}
</div>
)}
<div className="contentBox">
{columns.concat(extraColumns).map(({title, code, text}) => {
return (
<div key={title} className="info-item">
<span className='label'>{title}</span>
<span className='value'>{text || data[code]}</span>
</div>
)
})}
</div>
<style jsx>{`
.infoCard{
height: 100%;
width: ${cardWidth? cardWidth + 'px' : '100%'};
min-width: ${cardWidth? cardWidth + 'px' : '100%'};
position: relative;
display: flex;
flex-direction: column;
}
.infoCard > .img{
background-size: cover;
background-position: center center;
background-repeat: no-repeat;
background-color: rgba(0,0,0,0.2);
flex: 1;
}
.infoCard > .status{
position: absolute;
top:0;
left:0;
font-size: 16px;
color: #FFFFFF;
font-weight: 600;
line-height: 28px;
padding: 0 8px;
}
.infoCard > .contentBox{
background: rgba(6,45,141,0.80);
border: 1px solid rgba(255,255,255,0.16);
padding: 5px 10px;
height: ${contentHeight? contentHeight + 'px' : 'unset'};
}
.infoCard .info-item{
display: flex;
justify-content: space-between;
line-height: 28px;
font-size: 12px;
white-space: nowrap;
}
.infoCard .info-item .label,
.infoCard .info-item .value{
text-overflow: ellipsis;
overflow: hidden;
}
.infoCard .info-item .value{
font-size: 14px;
color: #28EAFF;
font-weight: 500;
}
`}</style>
</div>
);
}
export default InfoCard;

View File

@ -0,0 +1,361 @@
import React, {
useState,
useImperativeHandle,
useRef,
useCallback,
useEffect,
useMemo,
} from "react";
import InfoCard from "./InfoCard";
import Row from "../Row";
const gutter = 20;
const cardBottom = 20;
const InfoCardList = React.forwardRef((props, ref) => {
const {
mainKey,
speed = 50,
span = 4,
withClassify = false,
autoNext = true,
classifyKey = { name: "name", children: "children" },
pageFinished,
type = "move",
extraColumnsOpt,
...otherProps
} = props;
const sizeRef = useRef();
const infoCardListRef = useRef();
const pageBoxRef = useRef();
const pageRef = useRef(1);
const [data, setData] = useState([]);
const [energy, setEnergy] = useState([]);
const [autoScroll, setAutoScroll] = useState([0, 0]);
const classifyRef = useRef([]);
const classifyBoxRef = useRef();
const pageSize = useMemo(() => Math.floor(24 / span), [span]);
const totalPage = useMemo(() => {
if (withClassify) {
let { children } = classifyKey;
let size = 0;
classifyRef.current = [];
data.forEach((item) => {
let itemPageSize = Math.ceil((item[children].length || 0) / pageSize);
size += itemPageSize;
classifyRef.current.push(size);
});
return size;
} else return Math.ceil(data.length / pageSize);
}, [data, pageSize, withClassify, classifyKey]);
const [pageHeight, setPageHeight] = useState(0);
const setActiveClassify = useCallback(
(page) => {
if (!classifyBoxRef.current) return;
let classifyIndex =
classifyRef.current.findIndex((maxPageSize) => page <= maxPageSize) ||
0;
let { name } = classifyKey;
let oprationname = data?.[classifyIndex]?.[name].split("】")[1];
let text = oprationname === undefined ? "" : "- " + oprationname;
if (text) {
classifyBoxRef.current.style.display = "block";
classifyBoxRef.current.innerText = text;
} else classifyBoxRef.current.style.display = "none";
},
[data]
);
const setPageBoxCss = useCallback(
(page) => {
pageRef.current = page;
setActiveClassify(page);
pageBoxRef.current.style.transition =
page === 1 ? "none" : "transform 0.3s";
pageBoxRef.current.style.transform = `translateY(${
-(page - 1) * (pageHeight + cardBottom)
}px)`;
},
[pageHeight, setActiveClassify]
);
const nextPage = useCallback(() => {
// console.log();
let page = pageRef.current;
if (page + 1 > totalPage) {
if (autoNext) {
setPageBoxCss(1);
} else {
pageFinished && pageFinished();
}
} else {
setPageBoxCss(page + 1);
}
}, [totalPage]);
const moveChecking = useCallback(() => {
let { offsetWidth, scrollWidth } = infoCardListRef.current;
setAutoScroll([offsetWidth, scrollWidth]);
}, [data]);
const pageChecking = useCallback(() => {
let { scrollHeight, offsetHeight } = sizeRef.current;
setPageHeight(scrollHeight);
}, []);
useEffect(() => {
if (type === "move") {
let innerWindow = sizeRef.current.contentDocument.defaultView;
innerWindow.addEventListener("resize", moveChecking);
moveChecking();
return () => {
innerWindow.removeEventListener("resize", moveChecking);
};
}
}, [moveChecking]);
useEffect(() => {
if (type === "page") {
let innerWindow = sizeRef.current.contentDocument.defaultView;
innerWindow.addEventListener("resize", pageChecking);
pageChecking();
return () => {
innerWindow.removeEventListener("resize", pageChecking);
};
}
}, [pageChecking]);
useEffect(() => {
setPageBoxCss(1);
}, [data, setPageBoxCss]);
useImperativeHandle(
ref,
() => ({
setData: (data) => {
setData(data);
setPageBoxCss(1);
},
setEnergy:(data)=>{
setEnergy(data);
},
nextPage,
}),
[nextPage]
);
return (
<div className="infoCardList" ref={infoCardListRef}>
{type === "move" && (
<div className="box move">
{data.map((item) => {
return (
<div key={item[mainKey]} className="item">
<InfoCard {...otherProps} data={item} />
</div>
);
})}
</div>
)}
{type === "page" && (
<div className="page-content">
{withClassify === true && (
<div className="classify" ref={classifyBoxRef}></div>
)}
<div className="classifycenter -mt-14 w-full text-center ">
<label className="bg-[#1890ff] p-1 pt-0.5 pb-0.5">能源信息</label>
</div>
<div className="classifycenterval grid grid-cols-2 gap-1 place-items-start -mt-7 w-full">
{console.log("ernergy",energy)}
{/* {props.energy ?? `日期:${props.energy[0]} 水: ${props.energy[0].water} 电: ${1} 气: ${1}`} */}
{
energy.map(x=>{
return (
<span className={ energy.indexOf(x)===0?`flex-1 mr-20 justify-self-end`:`flex-1 ml-20 justify-self-start`}> 日期{x.time} &nbsp; {x.water} &nbsp; {x.electricity} &nbsp; {x.gas}</span>
)
})}
</div>
<div className="page-box">
<object
ref={sizeRef}
tabIndex="-1"
type="text/html"
aria-hidden="true"
data="about:blank"
style={{
display: "block",
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
border: "none",
padding: 0,
margin: 0,
opacity: 0,
zIndex: -1000,
pointerEvents: "none",
}}
></object>
<div className="page" ref={pageBoxRef}>
{withClassify === false && (
<Row className="height-100" gutter={20}>
{data.map((item) => {
let extraColumns = [];
if (extraColumnsOpt) {
let { key, name, val, max } = extraColumnsOpt;
let extraData = item[key];
extraData.forEach((i, index) => {
if (index < max)
extraColumns.push({ title: i[name], text: i[val] });
});
}
return (
<Row.Col span={span} key={item[mainKey]}>
<div className="item height-100">
<InfoCard
{...otherProps}
extraColumns={extraColumns}
data={item}
cardWidth={false}
/>
</div>
</Row.Col>
);
})}
</Row>
)}
{withClassify === true &&
data.map((item) => {
let { name, children } = classifyKey;
return (
<Row gutter={20} key={item[name]}>
{item[children].map((item) => {
let extraColumns = [];
if (extraColumnsOpt) {
let { key, name, val, max } = extraColumnsOpt;
let extraData = item[key];
extraData.forEach((i, index) => {
if (index < max)
extraColumns.push({
title: i[name],
text: i[val],
});
});
}
return (
<Row.Col span={span} key={item[mainKey]}>
<div className="item">
<InfoCard
{...otherProps}
extraColumns={extraColumns}
data={item}
cardWidth={false}
/>
</div>
</Row.Col>
);
})}
</Row>
);
})}
</div>
</div>
</div>
)}
<style jsx>{`
.infoCardList {
height: 100%;
overflow: hidden;
position: relative;
}
.infoCardList > .box {
height: 100%;
display: flex;
justify-content: flex-start;
}
.infoCardList > .box > .item {
height: 100%;
padding-right: ${gutter}px;
}
.infoCardList > .box.move {
animation: move ${autoScroll[1] / speed}s linear infinite;
animation-fill-mode: forwards;
transform: translateX(0px);
}
@keyframes move {
from {
transform: translateX(${autoScroll[0]}px);
}
to {
transform: translateX(${-autoScroll[1]}px);
}
}
.infoCardList .page {
height: 100%;
}
.infoCardList .page :global(.col) {
margin-bottom: ${cardBottom}px;
}
.infoCardList .page .item {
height: ${pageHeight}px;
}
.infoCardList .page-content {
height: 100%;
padding-top: 55px;
padding-bottom: 15px;
position: relative;
}
.infoCardList .classify {
position: absolute;
z-index: 100;
font-weight: 600;
font-size: 1.125rem;
left: 100px;
top: 0px;
// background: #15579f;
// border-radius: 3px;
// transition: all .3s;
}
.infoCardList .classifycenter label {
font-weight: 600;
font-size: 1.125rem;
}
.infoCardList .classifycenter {
position: absolute;
z-index: 100;
// left: 48.5%;
// padding: 2px 5px;
// top: -3px;
// background: #1890ff;
// border-radius: 3px;
// transition: all .3s;
}
.infoCardList .classifycenterval {
position: absolute;
z-index: 100;
font-weight: 600;
font-size: 1.125rem;
// left: 19%;
// top: 20px;
// padding: 2px 5px;
// border-radius: 3px;
// transition: all .3s;
}
.infoCardList .page-box {
height: 100%;
overflow: hidden;
position: relative;
}
`}</style>
</div>
);
});
export default InfoCardList;

View File

@ -0,0 +1,207 @@
import React, {useRef} from 'react';
function Row(props) {
const {gutter = 0, bottom = 0, className} = props;
return (
<div {...props} className={`row ${className ? className : ''} `}>
{React.Children.map(props.children, item => item)}
<style jsx>{`
.row {
margin-left: ${gutter / -2}px;
margin-right: ${gutter / -2}px;
position: relative;
}
.row:before,
.row:after {
content: " ";
display: table;
}
.row:after {
clear: both;
visibility: hidden;
font-size: 0;
height: 0;
}
.row > :global(.col) {
padding-left: ${gutter / 2}px;
padding-right: ${gutter / 2}px;
display: block;
float: left;
height: 100%;
padding-bottom: ${bottom}px;
}
`}</style>
<style jsx global>{`
.height-100 {
height: 100%
}
.height-75 {
height: 75%
}
.height-70 {
height: 70%
}
.height-50 {
height: 50%
}
.height-55 {
height: 55%
}
.height-60 {
height: 60%
}
.height-45 {
height: 45%
}
.height-40 {
height: 40%
}
.height-35 {
height: 35%
}
.height-30 {
height: 30%
}
.col-0 {
display: none
}
.col-1 {
width: 4.166666666666666%
}
.col-2 {
width: 8.333333333333332%
}
.col-3 {
width: 12.5%
}
.col-4 {
width: 16.666666666666664%
}
.col-4-2 {
width: 20%
}
.col-5 {
width: 20.833333333333336%
}
.col-6 {
width: 25%
}
.col-7 {
width: 29.166666666666668%
}
.col-8 {
width: 33.33333333333333%
}
.col-9 {
width: 37.5%
}
.col-10 {
width: 41.66666666666667%
}
.col-11 {
width: 45.83333333333333%
}
.col-12 {
width: 50%
}
.col-13 {
width: 54.166666666666664%
}
.col-14 {
width: 58.333333333333336%
}
.col-15 {
width: 62.5%
}
.col-16 {
width: 66.66666666666666%
}
.col-17 {
width: 70.83333333333334%
}
.col-18 {
width: 75%
}
.col-19 {
width: 79.16666666666666%
}
.col-20 {
width: 83.33333333333334%
}
.col-21 {
width: 87.5%
}
.col-22 {
width: 91.66666666666666%
}
.col-23 {
width: 95.83333333333334%
}
.col-24 {
width: 100%
}
`}</style>
</div>
);
}
function Col(props) {
const {span = 24, bottom = 0, className, style = {}} = props;
return (
<div className={`col ${span && 'col-' + span} ${className ? className : ''} `} style={style}>
<div className='height-100'>
{React.Children.map(props.children, item => item)}
</div>
<style jsx>{`
.col {
padding-bottom: ${bottom}px !important;
position: relative;
}
`}</style>
</div>
);
}
Row.Col = Col;
export default Row;

View File

@ -0,0 +1,181 @@
import React, { useImperativeHandle, useRef, useEffect, useCallback, useState, useMemo } from 'react'
const itemSize = 40;
const Table = React.forwardRef((props, ref) => {
const {columns = [], mainKey = 'id', autoNext = true, pageFinished, dataSource} = props;
const sizeRef = useRef();
const tableRef = useRef();
const [pageSize, setPageSize] = useState(0);
const [page, setPage] = useState(1);
const [data, setData] = useState([])
const totalPage = useMemo(
() => Math.ceil(data.length / pageSize),
[data, pageSize]
);
const currentData = useMemo(
() => data.slice(pageSize * (page - 1), pageSize * page),
[data, pageSize, page]
)
const tableSetting = useCallback(() => {
let { offsetHeight, scrollHeight, offsetWidth, scrollWidth } = tableRef.current;
let pageSize = Math.floor(offsetHeight / itemSize) - 1;
setPageSize(pageSize);
}, [])
const nextPage = useCallback(() => {
if(page + 1 > totalPage){
if(autoNext){
setPage(1);
}else{
pageFinished && pageFinished();
}
}else{
setPage(page + 1);
}
}, [totalPage, page])
useEffect(() => {
let innerWindow = sizeRef.current.contentDocument.defaultView;
innerWindow.addEventListener("resize", tableSetting);
tableSetting();
return () => {
innerWindow.removeEventListener("resize", tableSetting);
}
}, [tableSetting])
useEffect(() => {
if (dataSource) {
setData(dataSource);
setPage(1);
}
}, [dataSource]);
useImperativeHandle(ref, () => ({
nextPage,
setData: data => {
setData(data);
setPage(1);
},
addData: addData => {
setData(data.concat(...addData))
},
updateData: updateData => {
setData(data.map(item => {
let mainValue = item[mainKey];
let update = updateData.find(i => i[mainKey] === mainValue);
if(update) item = update;
return item;
}))
},
deleteData: deleteData => {
setData(data.filter(item => {
let mainValue = item[mainKey];
return !deleteData.find(i => i[mainKey] === mainValue);
}))
},
}), [data, nextPage, mainKey])
return (
<div className="screen-table" ref={tableRef}>
<object
ref={sizeRef}
tabIndex="-1"
type="text/html"
aria-hidden="true"
data="about:blank"
style={{
display: "block",
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
border: "none",
padding: 0,
margin: 0,
opacity: 0,
zIndex: -1000,
pointerEvents: "none",
}}>
</object>
<table>
<thead>
<tr>
{columns.map(({title, width}) => {
let thProps = {key: title};
if(width !== undefined) thProps.style = {width: width + 'px'}
return <th {...thProps}>{title}</th>
})}
</tr>
</thead>
<tbody>
{currentData.map((item, index) => (
<tr key={item[mainKey]} className={index % 2 === 1 ? `delay-${index} stripe`: `delay-${index}`}>
{columns.map(config => {
let {title,code, render} = config;
if(render) return <td key={title}>{render(item[code], config, item)}</td>;
return <td key={title}>{item[code]}</td>
})}
</tr>
))}
</tbody>
</table>
<style jsx>{`
.screen-table{
height: 100%;
overflow: hidden;
text-align: center;
position: relative;
}
.screen-table > table{
width: 100%;
}
.screen-table th,
.screen-table td{
height: ${itemSize}px;
word-break: break-all;
color: rgba(255, 255, 255, 0.85);
font-weight: 400;
}
.screen-table th{
background: rgba(40, 163, 255, 0.25);
}
.screen-table .stripe td{
background: rgba(40, 163, 255, 0.1);
}
.screen-table tbody tr{
animation-name: show;
animation-duration: 0.3s;
animation-iteration-count: 1;
opacity: 0;
animation-fill-mode: forwards;
}
@keyframes show {
from {opacity: 0;}
to {opacity: 1;}
}
`}</style>
<style jsx global>{`
.delay-0{ animation-delay: 0s; }
.delay-1{ animation-delay: 0.2s; }
.delay-2{ animation-delay: 0.4s; }
.delay-3{ animation-delay: 0.6s; }
.delay-4{ animation-delay: 0.8s; }
.delay-5{ animation-delay: 1s; }
.delay-6{ animation-delay: 1.2s; }
.delay-7{ animation-delay: 1.4s; }
.delay-8{ animation-delay: 1.6s; }
.delay-9{ animation-delay: 1.8s; }
`}</style>
</div>
);
})
export default Table;

View File

@ -0,0 +1,82 @@
import React, {useMemo} from 'react';
const labelWidth = 60;
const flatRules = {
// 给1 展示100%
// 给1.0000 展示100.0%
// 给0.9 展示90%
// 给0.90000 展示90.0%
"strange": val => {
val = val + "";
let valArr = val.split("");
// console.log("val1", val);
for (let n = 2; n > 0; n--) {
let dotIndex = valArr.findIndex(i => i === ".");
if (dotIndex > 0) {
if (dotIndex === valArr.length - 1) {
valArr.push("0")
}
valArr[dotIndex] = valArr.splice(dotIndex + 1, 1, valArr[dotIndex])[0]
} else {
valArr.push("0")
}
}
let dotIndex = valArr.findIndex(i => i === ".");
if (dotIndex > 0) {
while (valArr[0] === "0" && valArr[1] !== ".") {
valArr = valArr.splice(1, valArr.length)
}
}
val = valArr.join("");
// console.log("val2", val);
return val + "%"
}
};
function TableProgress(props) {
const {percent, toFixed = 1, rule} = props;
const percentVal = useMemo(() => {
if (rule && flatRules[rule]) return flatRules[rule](percent);
if (toFixed === false) return percent * 100 + "%";
return (percent * 100).toFixed(toFixed) + "%"
}, [percent, toFixed])
const width = useMemo(() => {
if (percent > 1) percent = 1;
if (toFixed === false) return percent * 100;
return (percent * 100).toFixed(toFixed)
}, [percent, toFixed])
return (
<div className='table-progress'>
<div className='table-progress-label'>{percentVal}</div>
<div className='table-progress-bar'>
<div className='table-progress-bar-content'></div>
</div>
<style jsx>{`
.table-progress-bar{
margin-right: ${labelWidth}px;
padding: 5px 0;
}
.table-progress-bar-content{
height: 12px;
background: rgba(255,255,255,0.16);
}
.table-progress-bar-content:before{
content: '';
display: block;
height: 100%;
background: #28A3FF;
width: ${width}%;
}
.table-progress-label{
width: ${labelWidth}px;
float: right;
text-align: center;
}
`}</style>
</div>
);
}
export default TableProgress;

View File

@ -0,0 +1,141 @@
import React, { useRef, useEffect, useState } from 'react'
const TimeShaft = React.forwardRef((props, ref) => {
const sizeRef = useRef();
const timeShaftRef = useRef();
const timeShaftContentRef = useRef();
const [move, setTranslateY] = useState(false);
const { data = [], time = "time", type = "type", name = "name", detail = "detail" } = props
//获取组件高度
const tableSetting = () => {
//组件实际高度
let comHeight = timeShaftRef.current.clientHeight;
let contentHeight = timeShaftContentRef.current.clientHeight;
let slate = comHeight < contentHeight;
setTranslateY(slate)
}
useEffect(() => {
//监听父级宽度变化
let innerWindow = sizeRef.current.contentDocument.defaultView;
innerWindow.addEventListener("resize", tableSetting);
tableSetting();
return () => {
innerWindow.removeEventListener("resize", tableSetting);
}
}, [])
return (
<div ref={timeShaftRef} className="screen-time-shaft">
<object
ref={sizeRef}
tabIndex="-1"
type="text/html"
aria-hidden="true"
data="about:blank"
style={{
display: "block",
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
border: "none",
padding: 0,
margin: 0,
opacity: 0,
zIndex: -1000,
pointerEvents: "none",
}} />
<div ref={timeShaftContentRef} className={move ? "screen-time-shaft-content move" : "screen-time-shaft-content"}>
{
data.map((item, index) => {
return (<div className="screen-time-shaft-item" key={index}>
<div className={`${(item[type] === 'm' && item[time] <= 10) ? 'screen-time-shaft-time-before' : ''} screen-time-shaft-time`}>{`${item[time]}${item[type] === 'm' ? '分钟' : '小时'}`}</div>
<div className="screen-time-shaft-axle">
<div className="screen-time-shaft-dot"></div>
<div className="screen-time-shaft-line"></div>
</div>
<div className="screen-time-shaft-name">{item[name]}</div>
<div className="screen-time-shaft-detail">{item[detail]}</div>
</div>)
})
}
</div>
<style jsx>{`
.screen-time-shaft{
height: 100%;
width: 100%;
overflow: hidden;
}
@keyframes move {
0% ,10% {
transform: translateY(0px);
}
100% {
transform: translateY(-100%);
}
}
.screen-time-shaft-content.move{
animation: 20s move linear infinite;
}
.screen-time-shaft-item{
width: 100%;
line-height: 34px;
display: flex;
}
.screen-time-shaft-time{
width: 68px;
height: 24px;
line-height: 24px;
margin-top: 5px;
margin-right: 5px;
text-align: center;
background: #0072EE;
border-radius: 2px;
color: #FFFFFF;
padding: 0 5px;
white-space: nowrap;
}
.screen-time-shaft-time-before{
background: #F5A623;
}
.screen-time-shaft-axle{
width: 5px;
padding: 0 4px;
display: flex;
flex-direction: column;
align-items: center;
transform: translateY(16px);
}
.screen-time-shaft-dot{
width: 5px;
height: 5px;
border-radius: 50%;
background: #0072EE;
}
.screen-time-shaft-line{
height: 100%;
width: 1px;
background: #0072EE;
}
.screen-time-shaft-name{
width: 198px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
color: #28A3FF;
}
.screen-time-shaft-detail{
width: calc( 100% - 268px);
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
`}</style>
</div>
)
})
export default TimeShaft;

View File

@ -0,0 +1,12 @@
import React from 'react';
import Head from 'next/head'
function WithConfig(props) {
return (
<Head>
<script src="/config.js"></script>
</Head>
);
}
export default WithConfig;

11
next.config.js Normal file
View File

@ -0,0 +1,11 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
images: {
loader: 'imgix',
path: '',
},
}
module.exports = nextConfig

8361
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

39
package.json Normal file
View File

@ -0,0 +1,39 @@
{
"name": "equipment-dashboard",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "cross-env NODE_OPTIONS='--inspect' next dev",
"build": "next build && next export && rm -rf equipscreen && cp -r out equipscreen",
"start": "next start"
},
"dependencies": {
"@ant-design/charts": "^1.4.2",
"@headlessui/react": "^1.5.0",
"@heroicons/react": "^1.0.1",
"@tailwindcss/forms": "^0.5.0",
"antd": "^4.21.6",
"cross-env": "^7.0.3",
"d3-interpolate": "^3.0.1",
"decimal.js": "^10.4.1",
"dedupe": "^4.0.2",
"echarts": "^5.3.2",
"isomorphic-fetch": "^3.0.0",
"localStorage": "^1.0.4",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"next": "12.1.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-flip-move": "^3.0.4",
"reconnecting-websocket": "^4.4.0",
"tailwind-scrollbar-hide": "^1.0.3"
},
"devDependencies": {
"autoprefixer": "^10.2.5",
"eslint": "8.11.0",
"eslint-config-next": "12.1.0",
"postcss": "^8.3.0",
"tailwindcss": "3.0.24"
}
}

34
pages/_app.js Normal file
View File

@ -0,0 +1,34 @@
import React from "react";
import Script from "next/script";
import "../styles/globals.css";
import "antd/dist/antd.css";
import { ConfigProvider } from "antd";
import zhCN from "antd/lib/locale-provider/zh_CN";
import moment from "moment";
import "moment/locale/zh-cn";
moment.locale("zh-cn");
function MyApp({ Component, pageProps }) {
return (
<ConfigProvider locale={zhCN}>
<React.Fragment>
<Script src="/config.js" strategy="beforeInteractive"></Script>
<Component {...pageProps} />
<style jsx global>{`
@font-face {
font-family: "RubikRegular";
src: url("/font/rubik/Rubik-Regular.ttf");
}
@font-face {
font-family: "PingFang";
src: url("/font/PingFang SC Regular.ttf");
}
body {
font-family: "PingFang";
}
`}</style>
</React.Fragment>
</ConfigProvider>
);
}
export default MyApp;

5
pages/api/hello.js Normal file
View File

@ -0,0 +1,5 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}

View File

@ -0,0 +1,2 @@
import Page from "../../../components/client/PackagingOperation"
export default Page;

View File

@ -0,0 +1,107 @@
import { useState } from 'react'
import { RadioGroup } from '@headlessui/react'
const plans = [
{
name: 'Startup',
ram: '12GB',
cpus: '6 CPUs',
disk: '160 GB SSD disk',
},
{
name: 'Business',
ram: '16GB',
cpus: '8 CPUs',
disk: '512 GB SSD disk',
},
{
name: 'Enterprise',
ram: '32GB',
cpus: '12 CPUs',
disk: '1024 GB SSD disk',
},
]
export default function Alert() {
const [selected, setSelected] = useState(plans[0])
return (
<div className="w-full px-4 ">
<div className="mx-auto w-full max-w-md">
<RadioGroup value={selected} onChange={setSelected}>
<RadioGroup.Label className="sr-only">Server size</RadioGroup.Label>
<div className="space-y-2">
{plans.map((plan) => (
<RadioGroup.Option
key={plan.name}
value={plan}
className={({ active, checked }) =>
`${
active
? 'ring-2 ring-white ring-opacity-60 ring-offset-2 ring-offset-sky-300'
: ''
}
${
checked ? 'bg-sky-900 bg-opacity-75 text-white' : 'bg-white'
}
relative flex cursor-pointer rounded-lg px-5 py-4 shadow-md focus:outline-none`
}
>
{({ active, checked }) => (
<>
<div className="flex w-full items-center justify-between">
<div className="flex items-center">
<div className="text-sm">
<RadioGroup.Label
as="p"
className={`font-medium ${
checked ? 'text-white' : 'text-gray-900'
}`}
>
{plan.name}
</RadioGroup.Label>
<RadioGroup.Description
as="span"
className={`inline ${
checked ? 'text-sky-100' : 'text-gray-500'
}`}
>
<span>
{plan.ram}/{plan.cpus}
</span>{' '}
<span aria-hidden="true">&middot;</span>{' '}
<span>{plan.disk}</span>
</RadioGroup.Description>
</div>
</div>
{checked && (
<div className="shrink-0 text-white">
<CheckIcon className="h-6 w-6" />
</div>
)}
</div>
</>
)}
</RadioGroup.Option>
))}
</div>
</RadioGroup>
</div>
</div>
)
}
function CheckIcon(props) {
return (
<svg viewBox="0 0 24 24" fill="none" {...props}>
<circle cx={12} cy={12} r={12} fill="#fff" opacity="0.2" />
<path
d="M7 13l3 3 7-7"
stroke="#fff"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}

View File

@ -0,0 +1,367 @@
import react from "react";
import { useState } from "react";
import requests from "../../../utils/requests";
import { doPost } from "../../../utils/requests";
import {
PlayIcon,
StopIcon,
PauseIcon,
BellIcon,
ViewListIcon,
} from "@heroicons/react/outline";
import { BellIcon as BellIconS } from "@heroicons/react/solid";
import { PuzzleIcon } from "@heroicons/react/solid";
import VLabel from "../Header/VLabel";
import eqs from "../../../utils/eqs";
import { Tooltip } from 'antd';
const { eqstatus, orderstatus } = eqs;
function Card({
eq,
popHandle,
alartPopHandle,
classNamez,
typecode,
selectedTab,
alertData,
}) {
if (!eq) {
eq = {};
}
const [status, setStatus] = useState(eqstatus[0]);
const [alertcount, setAlertcount] = useState(0);
const [fieldValueMap, setFieldValueMap] = react.useState({});
const [imgUrl, setImgUrl] = react.useState(null);
// const [fields, setFields] = react.useState([]);
react.useEffect(() => {
if (eq.runningstatus === undefined) eq.runningstatus = 0;
setStatus(eqstatus.filter((item) => item.key === eq.runningstatus)[0]);
// console.log(eq.code,eq.runningstatus)
}, [eq.runningstatus]);
// eq.updatestatus=(statuscur)=>{
// setStatus(eqstatus.filter((item) => item.key === statuscur)[0]);
// }
react.useEffect(() => {
//updatefields();
// if (eq.operationCode === typecode) {
// setFields(eq.bmIotEquipmentRelationshipList);
// }
async function async1() {
if (typecode === "1002") {
let item = eq?.bmIotEquipmentRelationshipList.filter(
(x) => x.iotField.indexOf("IoStatus") > -1
);
if (item && item.length > 0) {
eq.runningstatus =
item[0].iotFieldValue || item[0].iotFieldValue === 1 ? 2 : 4;
}
}
}
async1();
}, [typecode]);
react.useEffect(() => {
if (alertData && alertData.length > 0) {
eq.alert = alertData.filter((al) => {
if (al.equipCode === eq.code) {
return al;
}
});
if(eq.alert.length>0)
{
let alertcout = eq?.alert?.filter((aa) => {
if (!aa?.eliminationTime) {
return aa;
}
});
eq.alertcount = alertcout.length;
setAlertcount(alertcout.length);
}
}
}, [alertData, selectedTab]);
// react.useEffect(() => {
// if(!eq.alertcount){
// eq.alertcount=0;
// eq.alert= [];
// }
// else{
// setAlertcount(eq.alertcount);
// }
// }, [eq.alertcount, selectedTab]);
react.useEffect(() => {
}, [eq.alert]);
react.useEffect(() => {
if (eq.Img) {
setImgUrl(eq.Img);
}
}, [eq.Img]);
react.useEffect(() => {}, [eq.runningstatus]);
react.useEffect(() => {
// setTimeout(() => {
// setFields(eq.bmIotEquipmentRelationshipList);
// }, 100);
}, [eq.bmIotEquipmentRelationshipList]);
eq.updateFields = () => {
// updatefields();
};
let updatefields = () => {
if (!eq?.bmIotEquipmentRelationshipList) {
if (!eq.code) {
return;
}
doPost(requests.fetchEquipFields.url, [eq.code])
.then((res) => res.json())
.then((data) => {
if (data.success) {
// var fileGroupIds = curdata.map((zz) => zz.fileGroupId);
//获取图片信息
if (data.data.length > 0) {
var eqcur = data.data[0][0];
eq.bmIotEquipmentRelationshipList =
eqcur.bmIotEquipmentRelationshipList;
updateonlyfields();
}
}
});
} else {
updateonlyfields();
}
};
let updateonlyfields = () => {
//接口请求物联网influxdb查询服务
doPost(requests.fetchLastFieldsValue.url, {
equipCode: eq.code,
fields: eq.bmIotEquipmentRelationshipList
.filter((re) => {
if (re.status && !re.iotIsalarm) {
return re;
}
})
.map((field) => field.iotField),
})
.then((res) => res.json())
.then((data) => {
if (data.success) {
if (data.data !== null && data.data !== []) {
var _fieldValueMap = data.data[0];
if (!_fieldValueMap) return;
eq.bmIotEquipmentRelationshipList.map((field) => {
field.iotFieldValue = !(field.iotField in _fieldValueMap)
? null
: _fieldValueMap[field.iotField];
if (field.iotIsalarm) {
if (!field.iotFieldValue) field.iotFieldValue = 0;
if (field.iotField.indexOf("IoStatus") > -1) {
field.iotFieldValue =
field.iotFieldValue === 1 ? true : false;
} else {
field.iotFieldValue =
field.iotFieldValue === 0 ? false : true;
}
}
});
if (eq.bmIotEquipmentRelationshipList) {
var _alertcount = eq.bmIotEquipmentRelationshipList.filter(
(re) => {
if (re.iotIsalarm && re.status && re.iotFieldValue) {
return re;
}
}
).length;
// setAlertcount(_alertcount);
setFieldValueMap(_fieldValueMap);
}
}
} else {
// console.log(data);
}
})
.catch((ex) => {
console.log(ex);
});
};
react.useEffect(() => {
if (eq.code === null) return;
//updatefields();
}, [eq]);
react.useEffect(() => {
//updatefields();
}, [eq.iotFieldValue]);
let togglePop = () => {
popHandle(eq);
// setState((prevState) => ({
// ...prevState,
// }));
};
let alartPop = () => {
alartPopHandle(eq);
// setState((prevState) => ({
// ...prevState,
// }));
};
return (
<div className={` bg-white h-fit rounded w-64 `}>
{/* 第一行 */}
<div className={`group flex flex-raw h-6 rounded-t ${status.bg}`}>
<div
className={`flex-0 inline-flex items-center text-white justify-center`}
>
{/* <div className="w-2"></div> {orderstatus[0].title} */}
<div className="w-2"></div>
<p className="truncate w-28 text-left cardP" alt={eq.code}>
{" "}
{eq.code}
</p>
{/* {eq?.typeName} */}
</div>
<div className="flex-auto"></div>
<div className=" flex-0 inline-flex items-center text-white justify-center ">
<p className="truncate w-28 text-right cardP" alt={eq.name}>
{" "}
{eq.name}
</p>
<div className="w-2"></div>
</div>
</div>
<div className="relative flex">
{!imgUrl ? (
<img
src={`../img/eqimg.jpeg`}
alt={eq.name}
className=" w-full h-20 bg-gray-500 "
/>
) : (
// eslint-disable-next-line
<img
src={imgUrl}
alt={eq.name}
className=" w-full h-20 bg-gray-500 "
/>
)}
<Tooltip
placement="top"
title={eq?.bmIotEquipmentRelationshipList?.length>0?eq?.bmIotEquipmentRelationshipList[0].updateTime:""}
mouseEnterDelay={1}
>
<button
className={` absolute top-1 left-2 inline-block px-1 text-xs ${status.bg}-txt text-white ${status.bg}-border border rounded-sm`}
>
{status.title}
{status.key > 0 ? "中" : ""}
</button>
</Tooltip>
</div>
{/* 第二行 */}
<div className="border-l border-r text-left pl-4 py-2 border-gray-200 flex flex-col h-48">
{!eq?.bmIotEquipmentRelationshipList ? (
<></>
) : (
eq?.bmIotEquipmentRelationshipList
.filter((re) => {
if (re.iotIsMainField && re.status) {
return re;
}
})
.sort((a, b) =>
parseInt(a.fieldSort) > parseInt(b.fieldSort) ? 1 : -1
)
.map((re, idx) => {
return (
<div className={` flex flex-row mb-1 mr-3 `} key={idx}>
<VLabel>{re.iotFieldDescribe}</VLabel>{" "}
<div className="flex-auto"></div>
<Tooltip
placement="top"
title={re.iotFieldValueTime}
mouseEnterDelay={1}
>
{!fieldValueMap ? (
""
) : (
<label
className={` text-right ${classNamez} text-[#297FC8]`}
>
{typeof re.iotFieldValue === "boolean"
? String(re.iotFieldValue)
: re.iotFieldValue}
</label>
)}
</Tooltip>
</div>
);
})
)}
</div>
{/* 第三行 */}
<div className=" border group flex items-center flex-raw h-12 rounded-b border-gray-200 px-1">
<div className=" flex flex-row flex-auto w-12 justify-center cursor-pointer">
{status.key === 2 ? (
<PlayIcon className={` flex-0 h-5 w-5 mr-1 ${status.text}`} />
) : status.key === 1 ? (
<PauseIcon className={` flex-0 h-5 w-5 mr-1 ${status.text}`} />
) : status.key === 3 ? (
<StopIcon className={` flex-0 h-5 w-5 mr-1 ${status.text}`} />
) : (
<BellIcon className={` flex-0 h-5 w-5 mr-1 ${status.text}`} />
)}
<div className=" flex-0 inline-flex items-center ml-1 text-black justify-center ">
{status.title}
</div>
</div>
<div className="border-l border-gray-200 h-full flex-0"></div>
<div
onClick={alartPop}
className=" flex items-center justify-center flex-auto w-12 border-gray-200 cursor-pointer"
>
{/* {alertcount > 0 ? (
<span className=" inline-flex rounded-full text-red-500 items-center justify-center ">
{alertcount}
</span>
) : */}
<div className="relative inline-flex">
{alertcount > 0 ? (
<BellIconS
className={`flex-0 h-5 w-5 mr-1 text-red-500 `}
></BellIconS>
) : (
<BellIcon className={`flex-0 h-5 w-5 mr-1 `}></BellIcon>
)}
{alertcount > 0 ? (
<span className="text-red-500">{alertcount}</span>
) : (
``
)}
</div>
<div
className={`flex-0 inline-flex items-center ml-1 ${
alertcount > 0 ? "text-red-500" : "text-black"
} justify-center`}
>
告警
</div>
</div>
<div className="border-l border-gray-200 h-full flex-0"></div>
<div
onClick={togglePop}
className=" flex flex-row items-center justify-center flex-auto w-12 border-gray-200 cursor-pointer"
>
{ViewListIcon && <ViewListIcon className=" flex-0 h-5 w-5 mr-1 " />}
<div className=" flex-0 inline-flex items-center ml-1 text-black justify-center ">
其他
</div>
</div>
</div>
</div>
);
}
export default Card;

View File

@ -0,0 +1,84 @@
import react from "react";
import { Fragment, useState } from "react";
import { Listbox, Transition } from "@headlessui/react";
import { CheckIcon, SelectorIcon } from "@heroicons/react/solid";
export default function AutoCompleteComboBox({
dataSource,
selectedone,
selectChanged,
}) {
if (!dataSource) {
dataSource = [{ name: "无" }];
}
react.useEffect(() => {
if (JSON.stringify(dataSource[0]) === JSON.stringify(selected)) {
return;
}
if (selectedone && dataSource.length > 0) {
setSelected(dataSource.filter((x) => x.code === selectedone)[0]);
} else {
setSelected(dataSource[0]);
}
}, [dataSource, selectedone]);
const [selected, setSelected] = useState(dataSource[0]);
react.useEffect(() => {
selectChanged(selected);
}, [selected]);
return (
<div className="w-60">
<Listbox value={selected} onChange={setSelected}>
<div className="relative mt-1">
<Listbox.Button className="relative w-full py-1 pl-3 pr-10 text-left bg-white rounded-lg border cursor-default focus:outline-none focus-visible:ring-2 focus-visible:ring-opacity-75 focus-visible:ring-white focus-visible:ring-offset-orange-300 focus-visible:ring-offset-2 sm:text-sm">
<span className="block truncate">{selected?.name}</span>
<span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<SelectorIcon
className="w-5 h-5 text-gray-400"
aria-hidden="true"
/>
</span>
</Listbox.Button>
<Transition
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="absolute w-full py-1 mt-1 overflow-auto text-base bg-white rounded-md shadow-lg max-h-60 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{dataSource.map((item, itemIdx) => (
<Listbox.Option
key={itemIdx}
className={({ active }) =>
`cursor-default select-none relative py-0.5 my-0.5 pl-10 pr-4 ${
active ? "text-amber-900 bg-amber-100" : "text-gray-900"
}`
}
value={item}
>
{({ selected }) => (
<>
<span
className={`block truncate ${
selected ? "font-medium" : "font-normal"
}`}
>
{item.name}
</span>
{selected ? (
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-amber-600">
<CheckIcon className="w-5 h-5" aria-hidden="true" />
</span>
) : null}
</>
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</Listbox>
</div>
);
}

View File

@ -0,0 +1,163 @@
/** @format */
import react from "react";
import LabelCheck from "./LabelCheck";
import VLabel from "./VLabel";
import { PuzzleIcon, PauseIcon, RefreshIcon } from "@heroicons/react/solid";
import eqs from "../../../utils/eqs";
import AutoCompleteCombobox from "../Combobox";
import requests from "../../../utils/requests";
import { doPost } from "../../../utils/requests";
const { eqstatus, orderstatus } = eqs;
function Header({
factorychanged,
setTypeList,
statuslist,
typeList,
typechanged,
zzref,
getEqData,
runningstatuschanged,
selectedone
}) {
const [factoryData, setfactoryData] = react.useState(null);
const [ischeckAll, setIscheckAll] = react.useState(true);
let getCount = (key) => {
if (statuslist) {
var aa = 0;
Object.keys(statuslist).forEach((x) => {
if (key === parseInt(x)) {
aa = statuslist[x];
}
});
return aa;
} else {
return 0;
}
};
var selectChanged = (item) => {
factorychanged(item);
};
react.useEffect(() => {}, [typeList]);
react.useEffect(() => {
doPost(requests.fetchFactory.url, {
removePermission: true,
page: 1,
pageSize: 100000,
sorted: "serialNumber"
})
.then((res) => res.json())
.then((data) => {
setfactoryData(data.data);
})
.catch((ex) => {
console.log(ex);
});
doPost(requests.fetchType.url, {
removePermission: true,
page: 1,
pageSize: 100000,
})
.then((res) => res.json())
.then((data) => {
if (data.success) {
if (data.data) {
var typecodedata = data.data
.sort((a, b) => (parseInt(a.code) > parseInt(b.code) ? 1 : -1))
.map((a) => {
return {
code: a.code,
name: a.name,
};
});
setTypeList(typecodedata);
}
}
});
}, []);
return (
<header
className="flex flex-col h-auto space-y-2 sticky top-0 z-20 "
ref={zzref}
>
{/* <div className="flex plblue h-12 px-3">
<PuzzleIcon className="flex-none w-11 h-11"></PuzzleIcon>
<div className=" border-l border-gray-50 my-3 mx-3 "></div>
<label className=" text-xl text-white flex items-center ">
融通高科设备状态大屏
</label>
</div> */}
<div className="px-3 bg-white h-14 flex items-center ">
<div className="flex items-center ">
<VLabel>车间</VLabel>
<AutoCompleteCombobox
dataSource={factoryData}
selectChanged={selectChanged}
selectedone={selectedone}
/>
<div className=" w-5"></div>
<VLabel>分类</VLabel>
<AutoCompleteCombobox
dataSource={typeList}
selectChanged={typechanged}
/>
</div>
<div className="flex-auto"></div>
<div className="flex flex-row ">
<div className="flex flex-row items-center mr-3">
<span> 刷新设备数据 </span>{" "}
<button type="button" onClick={getEqData}>
<RefreshIcon className="flex-none w-5 h-5 text-gray-300 "></RefreshIcon>{" "}
</button>
</div>
<div className="space-x-2 items-center flex flex-row ">
<VLabel>设备运行状态</VLabel>
{eqstatus
.sort((a, b) => (a.idx > b.idx ? 1 : -1))
.filter((z) => {
if (z.key >= -10) return z;
})
.map((item, idx) => (
<LabelCheck
oncheckchange={(val) => {
if(item.key===-10){
setIscheckAll(val.target.checked)
}
runningstatuschanged(item.key, val.target.checked);
}}
key={idx}
keyv={item.key}
PrefixTitle={item.title}
SuffixTitle={item.unit}
checked= {ischeckAll}
Count={item.key == -10 ? "" : getCount(item.key)}
Bg={item.bg}
></LabelCheck>
))}
</div>
{/* <div className=" border-l border-gray-300 mx-6"></div>
<div className="space-x-2 items-center flex flex-row ">
<VLabel>设备生产状态</VLabel>
{orderstatus.map((item, idx) => (
<LabelCheck
key={idx}
PrefixTitle={item.title}
SuffixTitle={item.unit}
Count={
item.key === "1" ? productCount : allEquipCount-productCount
}
Bg={item.bg}
></LabelCheck>
))}
</div> */}
</div>
</div>
<div className=" mt-4"></div>
</header>
);
}
export default Header;

View File

@ -0,0 +1,55 @@
import StateCheck from "./StateCheck";
import react from "react";
function LabelCheck({
PrefixTitle,
SuffixTitle,
Count,
Icon,
Bg,
oncheckchange,
checked,
keyv,
}) {
let refinput = react.createRef();
const [firstload, setFirstLoad] = react.useState(true);
react.useEffect(() => {
if ( keyv !== -10 && !firstload) {
if(refinput.current?.checked !==checked){
refinput.current?.click();
}
}
setFirstLoad(false)
}, [checked]);
return (
<div className=" grow items-center inline-flex">
<input
id={`shutdown-7`+ keyv}
type="checkbox"
ref={refinput}
onChange={oncheckchange}
// checked={checked}
defaultChecked={true}
className={`rounded ring-1 py-1 h-4 w-4 mr-2 border-0 focus:ring-1 text-${Bg} ${Bg} cursor-pointer`}
></input>
<label htmlFor="shutdown-7" className=" block text-base text-gray-900 cursor-pointer ">
<StateCheck
Title={
<label className="cursor-pointer">
{PrefixTitle}{" "}
<span className={`font-bold text-base text-${Bg}`}>{Count}</span>{" "}
{SuffixTitle}
</label>
}
Icon={Icon}
></StateCheck>
</label>
</div>
);
}
export default LabelCheck;

View File

@ -0,0 +1,10 @@
function StateCheck ({ Icon, Title }) {
return (
<div className="group flex flex-raw cursor-pointer ">
{Icon && <Icon className="h-4 mt-1 mr-1 group-hover:animate-bounce" />}
{Title}
</div>
);
};
export default StateCheck

View File

@ -0,0 +1,8 @@
function VLabel(props) {
return (
<div className="inline-block align-middle text-xs text-gray-900 mr-2 ">
<label> {props.children}</label>
</div>
);
}
export default VLabel;

View File

@ -0,0 +1,119 @@
/* This example requires Tailwind CSS v2.0+ */
import react from "react";
import { Fragment, useRef, useState } from "react";
import { Dialog, Transition } from "@headlessui/react";
import { PuzzleIcon } from "@heroicons/react/outline";
import { XIcon } from "@heroicons/react/solid";
export default function Modal({ title, img, closePopHandler, children }) {
const [open, setOpen] = useState(true);
const [imgUrl, setImgUrl] = react.useState(img);
const cancelButtonRef = useRef(null);
return (
<Transition.Root show={open} as={Fragment}>
<Dialog
as="div"
className="fixed z-30 inset-0 overflow-y-auto"
initialFocus={cancelButtonRef}
onClose={setOpen}
>
<div className="flex items-end justify-center min-h-screen px-4 pb-20 text-center sm:block sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
{/* This element is to trick the browser into centering the modal contents. */}
<span
className="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true"
>
&#8203;
</span>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<div className="relative inline-block align-bottom bg-white rounded text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
<div className="bg-white ">
<div className="sm:flex sm:items-start">
{/* <div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-gray-100 sm:mx-0 sm:h-10 sm:w-10">
{!imgUrl ? <PuzzleIcon
className="h-6 w-6 text-black-600"
aria-hidden="true"
/>
: <img
src={`${imgUrl}`}
alt={title}
className="mr-3 w-10 h-10 rounded-full bg-slate-50 dark:bg-slate-800 "
aria-hidden="true"
/>
}
</div> */}
<div className="w-full text-center sm:mt-0 sm:text-left ">
<Dialog.Title
as="h4"
className=" text-base leading-6 px-3 pt-2 font-medium text-gray-900 relative"
>
<XIcon
className="h-4 w-4 text-black-600 right-3 top-3 absolute cursor-pointer"
onClick={() => {
setOpen(false);
closePopHandler();
}}
ref={cancelButtonRef}
aria-hidden="true"
></XIcon>
{title}
</Dialog.Title>
<div className="hidden sm:block" aria-hidden="true">
<div className="pt-2">
<div className="border-t border-gray-200" />
</div>
</div>
<div className="pt-2 w-full px-3 h-96 overflow-auto ">
{children}
</div>
</div>
</div>
</div>
<div className="hidden sm:block" aria-hidden="true">
<div className="">
<div className="border-t border-gray-200" />
</div>
</div>
<div className="bg-gray-50 px-4 py-1.5 sm:px-6 sm:flex sm:flex-row-reverse ">
<button
type="button"
className=" w-full inline-flex justify-center border border-gray-300 shadow-sm px-4 py-1 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => {
setOpen(false);
closePopHandler();
}}
ref={cancelButtonRef}
>
关闭
</button>
</div>
</div>
</Transition.Child>
</div>
</Dialog>
</Transition.Root>
);
}

View File

@ -0,0 +1,107 @@
import React from 'react'
const colors = [
{code: "1", val: "待产中", bgColor: "#F1AB0A", borderColor: "#F1AB0A"}, //待产中
{code: "2", val: "生产中", bgColor: "#26A764", borderColor: "#26A764"}, //生产中
{code: "3", val: "停产中", bgColor: "#F05B4F", borderColor: "#F05B4F"}, //停产中
]
function ProcessCard(props) {
const {itemData = {}} = props;
let {
code = "L7-TL",
name = "-",
productBatchNo = "-",
productLevel = "",
productName = "",
runningstatus = "-"
} = itemData;
let {val, bgColor, borderColor} = colors.find(item => item.code == runningstatus) || {};
let prodInfo = productLevel ? (productName + "-" + productLevel) : (productName ? productName : "-")
return (
<div className='card'>
<div className='card-top'>
<div className='card-top-img'/>
<div className='card-top-text'>{name}</div>
</div>
<div className='card-bottom'>
<div className="card-bottom-info">当前生产信息</div>
<div className='card-bottom-item'>
<span>产品信息</span>
<span className='card-bottom-item-val'>{prodInfo}</span>
</div>
<div className='card-bottom-item'>
<span>批次信息</span>
<span className='card-bottom-item-val'>{productBatchNo}</span>
</div>
<div className='card-bottom-item'>
<span>生产状态</span>
<span className='card-bottom-item-val process-status'>{val}</span>
</div>
</div>
<style jsx>{`
.card {
width: 100%;
}
.card-top .card-top-img {
border: 1px solid #0497FD;
height: 190px;
background: url(/img/process/${code}.png) no-repeat center / cover;
}
.card-top {
border: 1px solid #0497FD;
}
.card-top .card-top-text {
height: 30px;
font-size: 18px;
font-weight: bold;
color: #28A3FF;
background-color: #0E2C55;
text-align: center;
}
.card-bottom {
margin-top: 4px;
background-color: #062D8D;
padding: 8px 4px 16px;
}
.card-bottom .card-bottom-info {
color: #F1AB0A;
margin-bottom: 6px;
}
.card-bottom .card-bottom-item {
display: flex;
justify-content: space-between;
line-height: 25px;
}
.card-bottom-item .card-bottom-item-val {
color: #28EAFF;
max-width: calc(100% - 60px);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-left: 6px;
}
.card-bottom-item .process-status {
color: #eee;
border-radius: 2px;
border: 1px solid ${borderColor};
background-color: ${bgColor};
padding: 0 2px;
}
`}</style>
</div>
)
}
export default ProcessCard;

View File

@ -0,0 +1,24 @@
import { useState } from 'react'
import { Switch } from '@headlessui/react'
export default function Example() {
const [enabled, setEnabled] = useState(false)
return (
<div className="py-16">
<Switch
checked={enabled}
onChange={setEnabled}
className={`${enabled ? 'bg-teal-900' : 'bg-teal-700'}
focus:outline-none relative inline-flex h-[38px] w-[74px] flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75`}
>
<span className="sr-only">Use setting</span>
<span
aria-hidden="true"
className={`${enabled ? 'translate-x-9' : 'translate-x-0'}
pointer-events-none inline-block h-[34px] w-[34px] transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out`}
/>
</Switch>
</div>
)
}

View File

@ -0,0 +1,75 @@
import { useState, useEffect } from "react";
import { Tab } from "@headlessui/react";
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
export default function VTab(props) {
let [categories, setCategories] = useState([]);
let [children, setChildren] = useState([]);
let [selectedOne, setSelectedOne] = useState(0);
useEffect(() => {
if (props.ops.length > 0) {
setCategories(props.ops);
setSelectedOne(0)
}
}, [props.ops]);
const getSelectedIndex = () => {
return selectedOne;
};
useEffect(() => {
setChildren(props.children);
}, [props.children]);
useEffect(() => {
// if(props.selectedtypecode!=="1001")
// {
// setCategories(props.ops.filter(x=>x.code==="-2"));
// }
// else{
// setCategories(props.ops);
// }
setSelectedOne(0)
}, [props.selectedtypecode]);
return (
<div className={classNames("w-full sm:px-0", props.className)}>
<Tab.Group
onChange={(idx) => {
props.tabchanged(categories[idx]);
}}
>
<Tab.List className="flex space-x-5 z-10 " >
{categories
.sort((a, b) => (a.sort > b.sort ? 1 : -1))
.map((category,idx) => (
<Tab
key={idx}
className={({ selected }) =>
classNames(
" flex-none mx-3 p-4 py-2.5 text-sm font-medium leading-5 text-[#0185FF]",
" focus:outline-none focus:ring-none",
selected
? "bg-white border-b-2 border-[#0185FF]"
: " hover:text-[#0185FF]/[0.6]"
)
}
>
{/* {category.sort} */}
{category.name}
</Tab>
))}
</Tab.List>
<Tab.Panels className="-mt-0.5 h-full overflow-y-auto border-t-2 ">
{categories
.sort((a, b) => (a.sort > b.sort ? 1 : -1))
.map((x) => (
<Tab.Panel className={classNames("bg-white p-3")}>
{children}
</Tab.Panel>
))}
</Tab.Panels>
</Tab.Group>
</div>
);
}

View File

@ -0,0 +1,167 @@
import React from 'react'
const colors = [
{code: "1", val: "待产中", bgColor: "#F1AB0A", borderColor: "#F1AB0A"}, //待产中
{code: "2", val: "生产中", bgColor: "#26A764", borderColor: "#26A764"}, //生产中
{code: "3", val: "停产中", bgColor: "#F05B4F", borderColor: "#F05B4F"}, //停产中
]
function ProcessTaskCard(props) {
const {
itemData = {},
operationCode = ""
} = props;
let {
code = "-",
productBatchNo = "-", //批次
productName = "-",
onlineTime = 0,
runningstatus = "", //运行状态 1待生产2生产中3停产中
equipParams = {},
routeParams = {},
} = itemData;
runningstatus = runningstatus ? runningstatus.split('.')[0] : ""
let {val, bgColor = "", borderColor} = colors.find(item => item.code == runningstatus) || {};
return (
<div className='card'>
<div className='card-top'>
<div className='card-top-title'>{code}</div>
<div className='card-top-img'>
<div className='card-top-status-mask'></div>
<div className='card-top-status'>{val}</div>
</div>
<div className='card-top-duration'>
<span>设备运行时长</span><span>{Number(onlineTime).toFixed(2)}h</span>
</div>
</div>
<div className='card-bottom'>
<div className="card-bottom-info">生产信息</div>
<div className='card-bottom-item'>
<span className='item-text'>产品名称</span>
<span className='item-val'>{productName}</span>
</div>
<div className='card-bottom-item'>
<span className='item-text'>产品批次</span>
<span className='item-val'>{productBatchNo}</span>
</div>
<div className="card-bottom-info">设备参数</div>
{equipParams && Object.keys(equipParams).map(item => {
return <div className='card-bottom-item'>
<span className='item-text'>{item}</span>
<span className='item-val'>{equipParams[item] || "-"}</span>
</div>
})}
<div className="card-bottom-info">工艺参数</div>
{routeParams && Object.keys(routeParams).map(item => {
return <div className='card-bottom-item'>
<span className='item-text'>{item}</span>
<span className='item-val'>{routeParams[item] || "-"}</span>
</div>
})}
</div>
<style jsx>{`
.card {
width: 100%;
}
.card-top {
border: 1px solid #0497FD;
}
.card-top-title {
color: #ffffff;
background-color: #3D7FFF;
text-align: center;
font-size: 17px;
font-weight: bold;
height: 26px;
}
.card-top-img {
position: relative;
height: 65px;
background: url(/img/process/${operationCode}.png) no-repeat center / cover;
}
.card-top-img .card-top-status-mask {
height: 100%;
width: 50%;
background-color: ${bgColor}80;
}
.card-top-img .card-top-status {
position: absolute;
display: inline-block;
top: 4px;
left: 4px;
color: #ffffff;
border-radius: 2px;
border: 1px solid ${borderColor};
background-color: ${bgColor}cc;
padding: 0 2px;
}
.card-top-duration {
color: #ffffff;
font-size: 15px;
font-weight: bold;
height: 20px;
text-align: center;
background-color: #0E2C55;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
padding: 0 6px;
}
.card-bottom {
height: 297px;
overflow: hidden;
background-color: #102242;
margin-top: 4px;
padding: 8px 14px 16px;
font-size: 12px;
}
.card-bottom .card-bottom-info {
color: #F1AB0A;
margin-top: 6px;
margin-bottom: 6px;
}
.card-bottom .card-bottom-item {
display: flex;
justify-content: space-between;
line-height: 20px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.card-bottom-item .item-text {
color: #ffffff;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.card-bottom-item .item-val {
color: #28A3FF;
margin-left: 6px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
`}</style>
</div>
)
}
export default ProcessTaskCard;

984
pages/index.js Normal file
View File

@ -0,0 +1,984 @@
/** @format */
import { Tooltip } from "antd";
import Head from "next/head";
import react from "react";
import Header from "./components/Header/Header";
import VTab from "./components/Tab";
// import Results from "./components/Results/Results";
import localStorage from "localStorage";
import Rcwebsocket from "reconnecting-websocket";
import eqs from "../utils/eqs";
import requests, { doPost, getWsUrl, imgUrl } from "../utils/requests";
import Card from "./components/Card";
import Modal from "./components/Modal";
const { eqinfos, eqdetail, eqstatus } = eqs;
var intX;
var isImgLoad = false;
var isAlertLoad = false;
var isEqDataUpdate = false;
var _selectedtypecode, _selectedTab;
var ws, connected, wseq, connectedeq;
// eslint-disable-next-line react-hooks/rules-of-hooks
export default function Home({ results }) {
const [isLoading, setLoading] = react.useState(false);
const [selectedTab, setSelectedTab] = react.useState("-2");
const [isPop, setPop] = react.useState(false);
const [isPopAlert, setIsPopAlert] = react.useState(false);
const [popData, setPopData] = react.useState({});
const [workCenterCode, setWorkCenterCode] = react.useState();
const [queryEqList, setQueryEqList] = react.useState(null);
const [eqlist, setEqlist] = react.useState([]);
const [operations, setOperations] = react.useState([]);
const [curstation, setCurstation] = react.useState(null);
const [runningStatus, setRunningStatus] = react.useState({
0: true,
1: true,
2: true,
3: true,
4: true,
});
const [statuslist, setStatuslist] = react.useState({});
// const [watingCount, setWaitingCount] = react.useState([]);
const [typeList, setTypeList] = react.useState(null);
// const [workingCount, setWorkingCount] = react.useState([]);
const [stopCount, setStopCount] = react.useState([]);
const [alertData, setAlertData] = react.useState([]);
// const [productCount, setProductCount] = react.useState(0);
const [selectedtypecode, setselectedtypecode] = react.useState("");
const [allEquipCount, setAllEquipCount] = react.useState([]);
// if (isLoading) return <p>Loading...</p>;
var handleClose = () => {
setPop(false);
};
var popHandle = (eq) => {
setIsPopAlert(false);
setPop(!isPop);
setPopData(eq);
};
var alartPopHandle = (eq) => {
setIsPopAlert(true);
setPop(!isPop);
setPopData(eq);
};
const listHeight = react.useRef(null);
const screenref = react.useRef(null);
const headerref = react.useRef(null);
let isresize = false;
let counter = 0;
react.useEffect(() => {
getWsUrl().then((url) => {
ws?.close();
wseq?.close();
//判断只有在是edge ws才连接否则会出现异常连接不停重连
if (url.indexOf("application-ws") > -1) {
setTimeout(() => {
try {
ws = new Rcwebsocket(url);
wseq = new Rcwebsocket(url);
const onOpen = function () {
console.log("OPENED: " + url);
connected = true;
ws.send("alert");
};
const onClose = function () {
console.log("CLOSED: " + url);
ws = null;
};
const onMessage = function (event) {
var data = event.data;
try {
var jsonobj = JSON.parse(data);
if (jsonobj.length > 0) {
setAlertData(jsonobj);
}
} catch (error) { }
};
const onError = function (event) {
//alert(event.data);
};
ws.onopen = onOpen;
ws.onclose = onClose;
ws.onmessage = onMessage;
ws.onerror = onError;
const onOpeneq = function () {
console.log("eq OPENED: " + url);
connectedeq = true;
wseq.send("equip");
};
const onCloseeq = function () {
console.log("eq CLOSED: " + url);
wseq = null;
};
const onMessageeq = function (event) {
var data = event.data;
try {
var jsonobj = JSON.parse(data);
//获取数据
// setLoading(true);
bindData(jsonobj, queryEqList);
// setLoading(false);
} catch (error) { }
};
const onErroreq = function (event) {
//alert(event.data);
console.log("event:连接websocket发生异常");
};
wseq.onopen = onOpeneq;
wseq.onclose = onCloseeq;
wseq.onmessage = onMessageeq;
wseq.onerror = onErroreq;
//设置启动时选择工作中心
let curstation = localStorage.getItem("workCenterCode");
setCurstation(curstation);
} catch (error) {
console.log("连接websocket发生异常");
}
}, 2000);
}
});
}, [queryEqList]);
react.useEffect(() => {
function handleResize() {
isresize = true;
setTimeout(() => {
isresize = false;
}, 100);
}
window.addEventListener("resize", handleResize);
});
// react.useEffect(() => {
// queryStauts(queryEqList);
// }, [alertData, isImgLoad]);
react.useEffect(() => {
queryStauts(queryEqList);
}, [alertData]);
// react.useEffect(() => {
// if (queryEqList === null || queryEqList === undefined) {
// if (intX) {
// clearInterval(intX);
// intX = null;
// }
// } else if (queryEqList.length > 0) {
// if (intX) {
// clearInterval(intX);
// intX = null;
// }
// setTimeout(() => {
// intX = setInterval(() => {
// // queryFields(queryEqList);
// // queryStauts(queryEqList);
// }, interval);
// }, 2000);
// }
// }, [queryEqList]);
const getAlertData = react.useMemo(() => {
return alertData;
}, [alertData]);
const getqueryEqList = react.useMemo(() => {
return queryEqList;
}, [queryEqList]);
var factorychanged = (item) => {
setQueryEqList([]);
setAlertData([]);
setStatuslist({});
if (!item) {
return;
}
isImgLoad = false;
setLoading(true);
if (item.code === undefined) return;
console.log("执行获取所有设备" + item.code);
setWorkCenterCode(item.code);
localStorage.setItem("workCenterCode", item.code);
let eqdata = undefined;//localStorage.getItem(item.code);
if (eqdata && JSON.parse(eqdata).length !== 0) {
let ed = JSON.parse(eqdata);
getTypeList(ed);
setEqlist(ed);
queryFields(ed);
setQueryEqList(ed);
setLoading(false);
return;
} else {
isEqDataUpdate = true;
}
//获取设备明细清单
doPost(requests.fetchEquipFieldsList.url, { workCenterCode: item.code })
.then((res) => res.json())
.then((data) => {
if (data.success) {
if (!data.data) {
setTypeList(null);
setEqlist(null);
setQueryEqList(null);
return;
}
//这里如果想要按照name进行分组即如下
const retdata = data.data;
const results = GroupBy(retdata, function (item) {
return [item.code];
});
const eqlisttmp = results.map((item) => {
return {
code: item[0].code,
fileGroupId: item[0].fileGroupId,
fieldSort: item[0].fieldSort,
name: item[0].name,
operationCode: item[0].operationCode,
operationName: item[0].operationName,
operationSort: item[0].operationSort,
screenShowSerialNumber: item[0].screenShowSerialNumber,
typeCode: item[0].typeCode,
runningstatus: undefined,
typeName: item[0].typeName,
bmIotEquipmentRelationshipList: item,
};
});
getTypeList(eqlisttmp);
setEqlist(eqlisttmp);
queryFields(eqlisttmp);
setQueryEqList(eqlisttmp);
//localStorage.setItem(item.code, JSON.stringify(eqlisttmp));
isEqDataUpdate = false;
} else {
setEqlist(null);
setTypeList(null);
queryFields(null);
setQueryEqList(null);
}
})
.catch((ex) => {
console.log(ex);
setTypeList(null);
setEqlist(null);
queryFields(null);
setQueryEqList(null);
})
.finally((data) => {
setTimeout(() => {
setLoading(false);
}, 300);
});
};
function refreshData(eqfields) {
if (!eqfields) {
eqfields = eqlist;
}
if (!eqfields) {
return;
}
//把所有字段赋值
const eqfieldstmp = eqfields.map((item) => {
var obj = {};
var fields = item.bmIotEquipmentRelationshipList.map((field) => {
return field.iotField;
});
obj["equipCode"] = item.code;
fields.map((aa) => {
obj[aa] = "";
});
return obj;
});
// doPost(requests.fetchLastEqFieldsValue.url, eqfieldstmp)
// .then((res) => res.json())
// .then((data) => {
// if (data.success) {
// bindData(data.data, eqfields);
// }
// })
// .catch((ex) => {
// console.log(ex);
// })
// .finally(() => {
// setLoading(false);
// });
}
function bindData(data, eqfields) {
const zret = data;
zret.map((ret) => {
eqfields.filter((a) => {
// var eqstatus1 = a;
// if (getAlertData && getAlertData.length > 0) {
// eqstatus1.alert = getAlertData.filter((al) => {
// if (al.equipCode === eqstatus1.code) {
// return al;
// }
// });
// let alertcout = eqstatus1?.alert?.filter((aa) => {
// if (!aa?.eliminationTime) {
// return aa;
// }
// });
// eqstatus1.alertcount = alertcout.length;
// }
if (ret && ret.equipCode && a.code === ret.equipCode) {
a.bmIotEquipmentRelationshipList.map((b) => {
b.iotFieldValue = ret[b.iotField];
b.iotFieldValueTime = ret[b.iotField + "__time"];
if (
(!a.runningstatus || a.runningstatus === 0) &&
a.runningstatus !== 3
) {
a.runningstatus = Number.parseInt(ret["runningstatus"]);
}
if (Number.isNaN(a.runningstatus)) {
a.runningstatus = 0;
}
if (!b.updateTime) {
b.updateTime = ret["time"];
}
});
}
});
});
queryStauts(eqfields);
}
function GroupBy(array, fn) {
const groups = {};
array.forEach(function (item) {
const group = JSON.stringify(fn(item));
//这里利用对象的key值唯一性的创建数组
groups[group] = groups[group] || [];
groups[group].push(item);
});
//最后再利用map循环处理分组出来
return Object.keys(groups).map(function (group) {
return groups[group];
});
}
var closePop = () => {
setPop(false);
};
function dateData(property, bol) {
//property是你需要排序传入的key,bol为true时是升序false为降序
return function (a, b) {
var value1 = a[property];
var value2 = b[property];
if (bol) {
// 升序
return Date.parse(value1) - Date.parse(value2);
} else {
// 降序
return Date.parse(value2) - Date.parse(value1);
}
};
}
return (
<div className="h-screen overflow-hidden modal-container " ref={screenref}>
{isPop ? (
<Modal
title={`${popData.name} · ${isPopAlert ? "告警" : "其他"} `}
img={popData.Img}
closePopHandler={closePop}
>
{console.log(popData.alert)}
{isPopAlert ? (
<div>
<div className="overflow-x-auto">
<table className="table table-compact w-full">
<thead>
<tr className=" ">
<th>告警发生时间</th>
<th>告警内容</th>
<th>告警消除时间</th>
</tr>
</thead>
<tbody>
{popData?.alert?.sort(dateData("time", false)).map((x) => {
return (
<tr>
<td>{x.time}</td>
<td>{x.phenomenonName}</td>
<td>{x.eliminationTime}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
) : (
<div className={`grid gap-2 grid-cols-2 w-full`}>
{!popData.bmIotEquipmentRelationshipList
? ``
: popData.bmIotEquipmentRelationshipList
.filter((re) => {
if (isPopAlert && re.iotIsalarm && re.status) {
return re;
} else if (
!isPopAlert &&
!re.iotIsalarm &&
!re.iotIsMainField &&
re.status
) {
return re;
}
})
.sort((a, b) =>
parseFloat(a.fieldSort) > parseFloat(b.fieldSort) ? 1 : -1
) // .sort((a, b) => (!a.iotFieldValue ? 1 : -1))
.map((re) => {
return (
<div className="">
<label
htmlFor="field-value"
className="block text-xs text-gray-700"
>
{re.iotFieldDescribe}
</label>
{!isPopAlert ? (
<Tooltip
placement="top"
title={re.iotFieldValueTime}
mouseEnterDelay={1}
>
<div>
<input
disabled
type="text"
name="field-value"
value={re.iotFieldValue}
id="field-value"
autoComplete="given-name"
className=" mt-1 text-xs disabled:bg-slate-50 disabled:text-slate-500 disabled:border-slate-200 disabled:shadow-none
focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm h-6 border-gray-300 "
/>
</div>
</Tooltip>
) : typeof re.iotFieldValue === "boolean" ? (
re.iotFieldValue ? (
<label
name="field-value"
id="field-value"
className=" mt-1 block w-10 bg-[#eb5f2c26] text-[#EB5E2C] border-[#EB5E2C] border py-0.5 px-1.5"
>
告警{" "}
</label>
) : (
<label
name="field-value"
id="field-value"
className=" mt-1 block w-10 bg-[#7caea02f] text-[#53B59A] border-[#53B59A] border py-0.5 px-1.5"
>
正常
</label>
)
) : (
re.iotFieldValue
)}
</div>
);
})}
</div>
)}
</Modal>
) : null}
<Head>
<title>融通高科设备大屏 1.0</title>
<link rel="icon" href="/favicon.ico" />
<script src="http://localhost:8097"></script>
</Head>
{/* {selectedtypecode==='1001'
} */}
<Header
getEqData={() => {
localStorage.removeItem(workCenterCode);
factorychanged({ code: workCenterCode });
}}
runningstatuschanged={(key, value) => {
setRunningStatus((prestate) => {
return {
...prestate,
[key]: value,
};
});
}}
zzref={headerref}
selectedone={curstation}
factorychanged={factorychanged}
setTypeList={(data) => {
setTypeList(data);
}}
typechanged={(ty) => {
if(ty.name==='无'){return}
setselectedtypecode(ty?.code);
_selectedtypecode = ty?.code;
if (eqlist&&eqlist.length > 0) {
getTypeList(eqlist);
queryFields(eqlist);
}
}}
typeList={typeList}
statuslist={statuslist}
/>
{/* overflow-y-scroll scrollbar-hide */}
{/* ${
screenref.current?.clientHeight
} ${headerref.current?.clientHeight} ${
listHeight.current?.clientHeight
} ${
screenref.current?.clientHeight - headerref.current?.clientHeight <
listHeight.current?.clientHeight && !isresize
? "h-[92%]"
: "h-fit"
} */}
<VTab
className="flex flex-col h-[94%] w-full bg-white px-3 pb-3"
ops={operations}
selectedtypecode={selectedtypecode}
tabchanged={(tab) => {
if (tab) {
setSelectedTab(tab.code);
_selectedTab = tab.code;
console.log(JSON.stringify(tab));
}
}}
>
<div className="h-full w-full">
{operations
.filter((x) => {
if (selectedTab === "-1" && x.code === "-1") {
return x;
} else if (selectedTab === "-2" && x.code !== "-2") {
return x;
} else if (
selectedTab !== "-2" &&
selectedTab !== "-1" &&
x.code === selectedTab
) {
return x;
}
})
.map((op) => {
return selectedtypecode === "-001" ? (
getTabContent(op.code)
) : !op.code ||
eqlist?.filter(
(z) =>
z.operationCode === op.code &&
z.typeCode === selectedtypecode
)?.length === 0 ? (
<></>
) : (
<div>
<span className="text-[#0185FF]">
<label className=" text-lg ">{"▌"}</label>
<label className=" text-lg ">{op.name}</label>
</span>
<div className="h-3"></div>
{getTabContent(op.code)}
<div className="h-3"></div>
</div>
);
})}
</div>
</VTab>
{/* <Results results={results} /> */}
</div>
);
function getTabContent(op) {
return (
<div
ref={listHeight}
className={`" relative grid grid-flow-row gap-4 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 text-sm text-center font-bold overflow-y-auto h-[92%] "`}
>
{isLoading ? (
<svg
className="animate-spin -ml-1 mr-3 h-5 w-5 text-red absolute top-4 right-4"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
// stroke-width="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
) : (
<></>
)}
{!eqlist || eqlist.length === 0
? `当前没有数据`
: eqlist
.filter((item) => {
{
if (op === item.operationCode) {
if (!item.runningstatus) {
if (runningStatus[0]) {
return item;
}
} else if (runningStatus[item.runningstatus]) {
return item;
} else if (item.runningstatus < 4 && item?.alertcount > 0) {
if (runningStatus[4] && !runningStatus[2]) {
return item;
} else if (!runningStatus[4] && runningStatus[2]) {
return item;
}
}
}
}
})
.filter((item) => {
if (item.typeCode === selectedtypecode) {
if (!item.screenShowSerialNumber)
item.screenShowSerialNumber = 1000000;
return item;
}
})
.sort((a, b) =>
a.screenShowSerialNumber > b.screenShowSerialNumber ? 1 : -1
)
.map((item, idx) => (
// eslint-disable-next-line react/jsx-key
<Card
// classNamez={isLoading ? `animate-pulse` : ``}
alertData={alertData}
key={idx}
selectedTab={selectedTab}
typecode={_selectedtypecode}
eq={item}
popHandle={popHandle}
alartPopHandle={alartPopHandle}
></Card>
))}
</div>
);
}
function getTypeList(curdata) {
var curdata = curdata.filter((zz) => zz.typeCode === _selectedtypecode);
var codes = curdata.map((zz) => zz.code);
// var _typelist = [];
var _opcodelist = [];
_opcodelist.push({
code: "-2",
name: "全部",
sort: 0,
});
var ctypes = curdata.map((zz) => {
// zz.typecode = zz.typecode;
// zz.typename = zz.bmEquipmentTypeIotVo.name;
// if (_typelist.filter((item) => item.code === zz.typeCode).length > 0) {
// } else {
// _typelist.push({
// name: zz.typeName,
// code: zz.typeCode,
// });
// }
if (
_opcodelist.filter((item) => item.code === zz.operationCode).length > 0
) {
} else {
if (zz.operationCode) {
_opcodelist.push({
code: zz.operationCode,
name: zz.operationName,
sort: zz.operationSort ?? 10000,
});
} else {
zz.operationCode = "-1";
zz.operationName = "其他";
}
}
});
if (_opcodelist.filter((x) => x.code === "-1")?.length === 0) {
_opcodelist.push({
code: "-1",
name: "其他",
sort: 100000,
});
}
var opslist = _opcodelist.sort((a, b) => (a.sort > b.sort ? 1 : -1));
setOperations(opslist);
// setTypeList(_typelist);
if (isImgLoad || !curdata) {
return;
}
curdata.map((eq) => {
if (!eq.fileGroupId) return;
doPost(requests.fetchEquipImg.url + eq.fileGroupId, {
clientId: "pc",
})
.then((res) => res.json())
.then((data) => {
if (data.success && data.rows.length > 0) {
var img = imgUrl + data.rows[0].fileCode;
doPost(img, {})
.then((res) => res.blob())
.then((file) => {
var blobUrl = URL.createObjectURL(file);
eq.Img = blobUrl;
})
.catch((ex) => {
console.log(ex);
});
} else {
// console.log(data);
}
})
.catch((ex) => {
console.log(ex);
});
});
isImgLoad = true;
}
function queryStauts(eqdata) {
if (!_selectedtypecode) {
_selectedtypecode = selectedtypecode;
}
if (!eqdata || !_selectedtypecode) {
// setLoading(false);
return;
}
var codes = eqdata
.filter((item) => {
if (item.typeCode === _selectedtypecode) {
return item;
}
})
.map((aa) => aa.code);
//获取设备状态
//接口请求物联网influxdb查询服务
//0 待产 1 生产 2 停产
if (_selectedtypecode && _selectedtypecode !== "1001") {
setStatuslist((prestate) => {
return {
...prestate,
0: codes.length,
1: 0,
2: 0,
3: 0,
4: 0,
};
});
return;
}
var alertcount = [];
if (_selectedtypecode) {
alertcount = eqdata.filter((eqstatus) => {
if (
eqstatus?.alertcount > 0 &&
eqstatus.typeCode === _selectedtypecode
) {
return eqstatus;
}
});
}
// setStatuslist((prestate) => {
// return {
// ...prestate,
// 4: alertcount?.length,
// };
// });
const alertcountl = alertcount?.length;
if (codes.length === 0) return;
var waitingcount = eqdata.filter((eqstatus) => {
if (eqstatus.runningstatus === 1) {
return eqstatus;
}
});
var workingcount = eqdata.filter((eqstatus) => {
if (eqstatus.runningstatus === 2) {
return eqstatus;
}
});
var stopcount = eqdata.filter((eqstatus) => {
if (eqstatus.runningstatus === 3) {
return eqstatus;
}
});
// setWaitingCount(waitingcount.length);
// setWorkingCount(workingcount.length);
// setStopCount(stopcount.length);
// setStatuslist((prestate) => {
// return {
// ...prestate,
// [eqstatus.runningstatus]: waitingcount.length,
// };
// });
setStatuslist((prestate) => {
return {
...prestate,
1: waitingcount.length,
2: workingcount.length,
3: stopcount.length,
4: alertcountl,
};
});
var wclist = waitingcount.map((count) => count.code);
var sclist = stopcount.map((count) => count.code);
var rclist = workingcount.map((count) => count.code);
var alertlist = alertcount?.map((count) => count.code);
var nonestate = 0;
eqdata
.filter((item) => {
if (codes.indexOf(item.code) > -1) {
return item;
}
})
.map((eq) => {
if (wclist.filter((a) => a === eq.code).length > 0) {
eq.runningstatus = 1;
// eq.updatestatus(1);
} else if (sclist.filter((a) => a === eq.code).length > 0) {
eq.runningstatus = 3;
// eq.updatestatus(3);
} else if (rclist.filter((a) => a === eq.code).length > 0) {
eq.runningstatus = 2;
// eq.updatestatus(2);
} else if (alertlist.filter((a) => a === eq.code).length > 0) {
eq.runningstatus = 4;
// eq.updatestatus(4);
} else {
eq.runningstatus = 0;
nonestate++;
}
// if (alertlist?.filter((a) => a === eq.code).length > 0) {
// eq.runningstatus = 1;
// // eq.updatestatus();
// }
});
setStatuslist((prestate) => {
return {
...prestate,
0: nonestate,
};
});
}
function queryFields(data) {
if (!data || data === undefined || data.length == 0) {
return;
}
// setLoading(true);
data.filter((item) => {
if (item.typeCode === _selectedtypecode && item.updateFields) {
item.updateFields();
}
});
// console.log(typelist);
refreshData(data);
//获取绑定的工单
// doPost(requests.fetchBindList.url, {})
// .then((res) => res.json())
// .then((data) => {
// if (data.success) {
// var bindlist = data.data;
// var arr = bindlist.map((bind) => bind.workOrderCode);
// const set = new Set(arr);
// setProductCount(set.length)
// }
// })
// .catch((ex) => {
// console.log(ex);
// });
//获取设备以及字段信息
// queryStauts(codes, eqdata);
}
// doPost(requests.fetchEquipFields.url, codes)
// .then((res) => res.json())
// .then((data) => {
// if (data.success) {
// // var fileGroupIds = curdata.map((zz) => zz.fileGroupId);
// //获取图片信息
// var eqdata = data.data;
// setEqlist(eqdata);
// setAllEquipCount(eqdata.length);
// queryStauts(codes, eqdata);
// if (isImgLoad) {
// return;
// }
// eqdata.map((eq) => {
// doPost(requests.fetchEquipImg.url + eq.fileGroupId, {
// clientId: "pc",
// })
// .then((res) => res.json())
// .then((data) => {
// if (data.success && data.rows.length > 0) {
// var img = imgUrl + data.rows[0].fileCode;
// doPost(img, {})
// .then((res) => res.blob())
// .then((file) => {
// var blobUrl = URL.createObjectURL(file);
// eq.Img = blobUrl;
// eq.updateImg();
// })
// .catch((ex) => {
// console.log(ex);
// });
// } else {
// // console.log(data);
// }
// })
// .catch((ex) => {
// console.log(ex);
// });
// });
// isImgLoad = true;
// } else {
// setEqlist([]);
// setAllEquipCount(0);
// }
// })
// .catch((ex) => {
// setEqlist([]);
// console.log(ex);
// });
// }
}
//
// export async function getStaticProps() {
// const request = await fetch(
// `&{
// requests.fetchFactory.url
// }`
// )
// .then((res) => res.json())
// .catch((ex) => console.log(ex)); // then get result and pass it with result.json
// return {
// props: {
// results: !request ? null : request.results,
// },
// };
// }

View File

@ -0,0 +1,216 @@
import React, { useRef, useEffect, useState, useMemo } from "react";
import { SearchOutlined } from "@ant-design/icons";
import _, { indexOf } from "lodash";
import moment from "moment";
import "antd/dist/antd.css";
import { Select, Button, DatePicker, Checkbox, message, Spin } from "antd";
const { Option } = Select;
import {
getFactory,
getEquipType,
getEquip,
getEquipmentLocation,
getEquipmentLocationEchart,
} from "../../../reportUtils/getQueryData";
function CurveParam() {
//HTML元素
let refinput = useRef();
//常量
const [factoryValue, setFactoryValue] = useState("");
const [factory, setFactory] = useState([]);
const [equipTypeValue, setEquipTypeValue] = useState("");
const [equipType, setEquipType] = useState([]);
const [equipValue, setEquipValue] = useState("");
const [equip, setEquip] = useState([]);
// 点位集合
const [locations, setLocations] = useState([]);
// 点位变化值
const [locationValue, setLocationValue] = useState([]);
//react render
useEffect(() => {
let _locationValue = locationValue;
// iframe 消息通讯
let locationsValues = _.cloneDeep(locations);
let retData = {};
let selectItems = [];
_locationValue.map((key) => {
let index = _.findIndex(locationsValues, function (o) {
return o.value === key;
});
selectItems.push(locationsValues[index]);
});
retData.selectItems = selectItems;
retData.equipCode = equipValue;
console.log(retData);
if (retData.selectItems?.length > 0) {
window.parent.postMessage(retData, "*");
}
}, [locationValue]);
// 查询设备类型,查询车间,
useEffect(() => {
getEquipType().then((data) => {
setEquipType(data);
});
getFactory().then((data) => {
setFactory(data);
});
}, []);
// 查询设备 监听设备类型
useEffect(() => {
getEquip({ equipTypeValue, factoryValue }).then((data) => {
setEquip(data);
setEquipValue("");
setLocations([]);
setLocationValue([]);
});
}, [equipTypeValue, factoryValue]);
//方法
const locationChange = (checkValue) => {
setLocationValue(checkValue);
};
const handleCancelClick = () => {
setLocationValue([]);
};
const equipTypeChange = (value) => {
setEquipTypeValue(value);
};
const factoryChange = (value) => {
setFactoryValue(value);
};
const equipChange = (value = "") => {
if (value && equipValue !== value) {
setLocationValue([]);
getEquipmentLocation(value).then((data) => {
let locationArr = data
.sort((a, b) => (a.iotfieldsort > b.iotfieldsort ? 1 : -1))
.map((item) => {
return {
label: item.iotfielddescribe,
value: item.iotField,
};
});
setLocations(locationArr);
});
} else {
setLocationValue([]);
setLocations([]);
}
setEquipValue(value);
};
return (
<div className="w-90 mt-4 mx-3">
<div className="flex flex-row space-x-4 ">
<div className="workShop">
<span>车间:</span>&nbsp;
<Select
allowClear
size="small"
style={{ width: 120 }}
onChange={factoryChange}
value={factoryValue}
>
{factory.map((item) => {
return (
<Option value={item.code} key={item.gid}>
{item.name}
</Option>
);
})}
</Select>
</div>
<div className="equipType">
<span>设备类型:</span>&nbsp;
<Select
allowClear
size="small"
style={{ width: 120 }}
onChange={equipTypeChange}
value={equipTypeValue}
>
{equipType.map((item) => {
return (
<Option value={item.equipTypeGid} key={item.gid}>
{item.name}
</Option>
);
})}
</Select>
</div>
<div className="equipNo">
<span>设备编号:</span>&nbsp;
<Select
showSearch
allowClear
size="small"
onChange={equipChange}
optionFilterProp="children"
style={{ width: 150 }}
value={equipValue}
filterOption={(input, option) =>
option.children.toLowerCase().includes(input.toLowerCase())
}
>
{equip.map((item) => {
return (
<Option
value={item.serialNumber}
key={item.serialNumber + item.name}
>
{item.name}
</Option>
);
})}
</Select>
</div>
</div>
<div className="toolBar flex flex-row mt-4">
<div className=" text-xs ">
{locations.length > 0 ? (
<Button
type="primary"
className="ml-2 mt-2"
size="small"
onClick={handleCancelClick}
>
取消全选
</Button>
) : (
<></>
)}
</div>
<div className="legend overflow-y-auto ml-10 mt-2 h-32 ">
<Checkbox.Group
ref={refinput}
value={locationValue}
options={locations}
onChange={locationChange}
/>
</div>
</div>
<style jsx>{`
.ant-checkbox-group-item {
width: 200px !important;
}
.toolBar {
position: relative;
border-width: 1px 0 1px 0;
border-color: #d3d3d3;
background: #f8f8f8;
min-height: 32px;
}
`}</style>
</div>
);
}
export default CurveParam;

View File

@ -0,0 +1,592 @@
import React, { useRef, useEffect, useState, useMemo } from "react";
import * as echarts from "echarts";
import { SearchOutlined } from "@ant-design/icons";
import _, { indexOf } from "lodash";
import moment from "moment";
import "antd/dist/antd.css";
import { Select, Button, DatePicker, Checkbox, message, Spin } from "antd";
import {
getFactory,
getEquipType,
getEquip,
getEquipmentLocation,
getEquipmentLocationEchart,
} from "../../../reportUtils/getQueryData";
const { Option } = Select;
import theme from "../../../reportUtils/theme.json";
function EquipmentCurve() {
const dataZoomRef = useRef();
let refinput = useRef();
echarts.registerTheme("black", theme);
// 设备类型集合
const [equipType, setEquipType] = useState([]);
// 车间集合
const [factory, setFactory] = useState([]);
// 设备集合
const [equip, setEquip] = useState([]);
//设备类型值
const [equipTypeValue, setEquipTypeValue] = useState("");
//车间值
const [factoryValue, setFactoryValue] = useState("");
//设备值
const [equipValue, setEquipValue] = useState("");
// 点位集合
const [locations, setLocations] = useState([]);
// 点位变化值
const [locationValue, setLocationValue] = useState([]);
// 时间变化值
const [dateValue, setDateValue] = useState("");
// 存放chartData
const [chartData, setChartData] = useState({});
// 存放chartref
const [chartRef, setChartRef] = useState([]);
const [spin, setSpin] = useState(false);
const equipTypeChange = (value) => {
setEquipTypeValue(value);
};
const factoryChange = (value) => {
setFactoryValue(value);
};
const equipChange = (value = "") => {
if (value && equipValue !== value) {
setLocationValue([]);
setChartData({});
setChartRef([]);
getEquipmentLocation(value).then((data) => {
let locationArr = data
.sort((a, b) => (a.iotfieldsort > b.iotfieldsort ? 1 : -1))
.map((item) => {
return {
label: item.iotfielddescribe,
value: item.iotField,
};
});
setLocations(locationArr);
});
} else {
setLocationValue([]);
setChartData({});
setChartRef([]);
setLocations([]);
}
setEquipValue(value);
};
const locationChange = (checkValue) => {
setLocationValue(checkValue);
if (checkValue.length > 0) {
doSearch(checkValue);
} else {
setChartRef([]);
}
};
const dateChange = (date, dateString) => {
setDateValue(dateString);
};
const handleCancelClick = () => {
setLocationValue([]);
setChartRef([]);
};
const handleClick = () => {
if (!equipValue) {
message.warning("请选择需要查询的设备!");
} else if (!dateValue) {
message.warning("请选择需要查询的时间!");
} else if (locationValue.length === 0) {
message.warning("请勾选需要查询的点位!");
} else {
setSpin(true);
doSearch();
}
};
// 查询设备类型,查询车间,
useEffect(() => {
getEquipType().then((data) => {
setEquipType(data);
});
getFactory().then((data) => {
setFactory(data);
});
}, []);
// 查询设备 监听设备类型
useEffect(() => {
getEquip({ equipTypeValue, factoryValue }).then((data) => {
setEquip(data);
setEquipValue("");
setLocations([]);
setLocationValue([]);
setChartData({});
setChartRef([]);
});
}, [equipTypeValue, factoryValue]);
useEffect(() => {
let dataZoomChart = echarts.init(dataZoomRef.current);
dataZoomChart.clear();
if (locationValue.length > 0) {
if (!dataZoomRef.current) {
return;
}
let dataZoomData = {};
let flag = true;
for (let i = 0; i < locationValue.length; i++) {
if (flag && chartData[locationValue[i]]) {
dataZoomData = chartData[locationValue[i]];
flag = false;
}
}
let dataZoomChart = echarts.init(dataZoomRef.current);
let option = {
xAxis: {
type: "category",
data: dataZoomData["times"] || [],
},
yAxis: {
type: "value",
scale: true,
},
legend: {
show: false,
height: "10",
width: "2000",
},
dataZoom: {
top: "10",
left: "0",
type: "slider",
width: "92%",
height: "30px",
},
series: [
{
data: dataZoomData["values"] || [],
type: "line",
},
],
};
dataZoomChart.setOption(option);
// 动态生成echart
let childsEchart = [];
for (let i = 0; i < chartRef.length; i++) {
if (!chartData[chartRef[i]]) {
break;
}
let dom = document.getElementById(chartRef[i]);
let echatItem = echarts.init(dom, "black");
let optionItem = {
grid: {
top: "30",
height: "125",
},
title: {
text: `${chartData[chartRef[i]]["title"]}`,
x: "center",
y: "top",
textAlign: "left",
},
tooltip: {
trigger: "axis",
axisPointer: {
// link: null,
animation: true,
type: "cross",
},
formatter: function (params) {
//在此处直接用 formatter 属性
let showdata = params[0];
// 根据自己的需求返回数据
return `
<div>时间${showdata.axisValueLabel}</div>
<div>数据<a style="color: #00E8D7">${showdata.data}</a></div>
`;
},
},
xAxis: {
type: "category",
data: chartData[chartRef[i]]["times"],
},
yAxis: {
type: "value",
},
legend: {
show: false,
height: "10",
width: "2000",
},
dataZoom: {
type: "inside",
zoomOnMouseWheel: false,
},
series: [
{
symbol: "circle",
symbolSize: 5,
data: chartData[chartRef[i]]["values"],
type: "line",
},
],
};
echatItem.setOption(optionItem);
childsEchart.push(echatItem);
}
echarts.connect([dataZoomChart].concatchildsEchart);
}
}, [chartRef]);
return (
<div>
<Spin spinning={spin}>
<div className="header">
<div className="search">
<div className="workShop">
<span>车间:</span>&nbsp;
<Select
allowClear
size="small"
style={{ width: 120 }}
onChange={factoryChange}
value={factoryValue}
>
{factory.map((item) => {
return (
<Option value={item.code} key={item.gid}>
{item.name}
</Option>
);
})}
</Select>
</div>
<div className="equipType">
<span>设备类型:</span>&nbsp;
<Select
allowClear
size="small"
style={{ width: 120 }}
onChange={equipTypeChange}
value={equipTypeValue}
>
{equipType.map((item) => {
return (
<Option value={item.equipTypeGid} key={item.gid}>
{item.name}
</Option>
);
})}
</Select>
</div>
<div className="equipNo">
<span>设备编号:</span>&nbsp;
<Select
showSearch
allowClear
size="small"
onChange={equipChange}
optionFilterProp="children"
style={{ width: 150 }}
value={equipValue}
filterOption={(input, option) =>
option.children.toLowerCase().includes(input.toLowerCase())
}
>
{equip.map((item) => {
return (
<Option
value={item.serialNumber}
key={item.serialNumber + item.name}
>
{item.name}
</Option>
);
})}
</Select>
</div>
<div className="equipDate">
<span>查询日期:</span>&nbsp;
<DatePicker
style={{ width: 150 }}
size="small"
onChange={dateChange}
/>
</div>
<div>
<Button
type="primary"
icon={<SearchOutlined />}
size="small"
shape="round"
onClick={handleClick}
>
查询
</Button>
</div>
</div>
</div>
<div className="dataZoom">
<div className="zoomTitle">时间窗口选择框:</div>
<div
className="zoomContent"
ref={dataZoomRef}
style={{
width: "90%",
height: "150px",
overflow: "hidden",
}}
></div>
</div>
<div className="toolBar flex flex-row">
<div className=" text-xs ">
{locations.length > 0 ? (
<Button
type="primary"
className="ml-2 mt-2"
size="small"
onClick={handleCancelClick}
>
取消全选
</Button>
) : (
<></>
)}
</div>
<div className="legend overflow-y-auto h-32 ">
<Checkbox.Group
ref={refinput}
value={locationValue}
options={locations}
onChange={locationChange}
/>
</div>
</div>
<div className="flex h-2"></div>
<div className="content ">
{chartRef.map((item) => {
return (
<div
id={item}
key={item}
style={{
width: "100%",
height: "200px",
marginBottom: "16px",
}}
></div>
);
})}
</div>
</Spin>
<div className={spin ? "mask" : "unmask"}></div>
<style jsx>{`
.ant-checkbox-group-item {
width: 200px !important;
}
* {
margin: 0;
padding: 0;
}
.header {
position: relative;
width: 100%;
height: 35px;
box-shadow: 5px 2px 20px -4px rgb(0, 0, 0, 0.3);
}
.search {
display: flex;
height: 35px;
margin: auto 15px;
}
.search div {
line-height: 35px;
margin-right: 20px;
}
.search .workShop {
margin-left: 15px;
}
.search .equipDate {
margin-right: 80px;
}
.dataZoom {
position: relative;
width: 100%;
height: 50px;
overflow: hidden;
}
.zoomTitle {
position: absolute;
top: 50%;
left: 10px;
margin-top: -8px;
}
.zoomContent {
position: absolute;
top: -2px;
left: 140px;
bottom: 0;
right: 0;
}
.content {
width: 100%;
height: 66vh;
overflow-y: auto;
overflow-x: hidden;
box-sizing: border-box;
}
.toolBar {
position: relative;
border-width: 1px 0 1px 0;
border-color: #d3d3d3;
background: #f8f8f8;
min-height: 32px;
}
.legend {
font-size: 12px;
color: white;
padding-left: 6%;
display: flex;
position: relative;
flex-wrap: wrap;
}
.mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 200;
}
.unmask {
display: none;
}
`}</style>
</div>
);
function doSearch(checkValue) {
let _locationValue = ``;
if (checkValue) {
_locationValue = checkValue;
} else {
_locationValue = locationValue;
}
let params = {
workCenterCode:`${factoryValue}`,
startTime: `${dateValue} 00:00:00`,
endTime: `${dateValue} 23:59:59`,
equipCode: equipValue,
fields: _locationValue,
};
getEquipmentLocationEchart(params).then((data) => {
if (data && data.length > 0) {
createCharts();
}
else{
createChartsEmp();
message.warning("未能查询到数据!");
}
setSpin(false);
function createChartsEmp() {
let locationsValues = _.cloneDeep(locations);
let processData = {};
let commonTimes = [];
_locationValue.map(key=> {
if (key !== "time") {
if (!processData.hasOwnProperty(key)) {
let index = _.findIndex(locationsValues, function (o) {
return o.value === key;
});
let title = index >= 0 ? locationsValues[index]["label"] : "";
processData[key] = {
title: title,
key: key,
times: [],
values: [],
};
}
// processData[key]["values"].push([]);
}
// let time = moment(data[i]["time"]).format("YYYY-MM-DD HH:mm:ss");
// commonTimes.push(time);
})
let domCharts = [];
commonTimes = _.reverse(commonTimes);
for (let key in processData) {
processData[key]["times"] = commonTimes;
}
for (let i = 0; i < _locationValue.length; i++) {
if (processData[_locationValue[i]]) {
domCharts.push(_locationValue[i]);
}
}
setChartData(processData);
setChartRef(domCharts);
}
function createCharts() {
let locationsValues = _.cloneDeep(locations);
let processData = {};
let commonTimes = [];
for (let i = 0; i < data.length; i++) {
for (let key in data[i]) {
if (key !== "time") {
if (!processData.hasOwnProperty(key)) {
let index = _.findIndex(locationsValues, function (o) {
return o.value === key;
});
let title = index >= 0 ? locationsValues[index]["label"] : "";
processData[key] = {
title: title,
key: key,
times: [],
values: [],
};
}
processData[key]["values"].push(data[i][key]);
}
}
let time = moment(data[i]["time"]).format("YYYY-MM-DD HH:mm:ss");
commonTimes.push(time);
}
let domCharts = [];
commonTimes = _.reverse(commonTimes);
for (let key in processData) {
processData[key]["times"] = commonTimes;
}
for (let i = 0; i < _locationValue.length; i++) {
if (processData[_locationValue[i]]) {
domCharts.push(_locationValue[i]);
}
}
_locationValue.map(x=>{
if(domCharts.indexOf(x)===-1){
domCharts.push(x);
let index = _.findIndex(locationsValues, function (o) {
return o.value === x;
});
let title = index >= 0 ? locationsValues[index]["label"] : "";
processData[x] = {
title: title,
key: x,
times: [],
values: [],
};
processData[x]["times"] = commonTimes;
}
})
setChartData(processData);
setChartRef(domCharts);
}
});
}
}
export default EquipmentCurve;

View File

@ -0,0 +1,981 @@
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;

View File

@ -0,0 +1,14 @@
{
"productBatchNo":"HRS10xxxxxxxxx",
"imgItemList":[
{
"operationName":"细磨",
"templateName":"L7细磨检验模板",
"imgShotTime": "2023-02-21 14:05:00",
"imgPath":[
"/quality/quality/simpling/downloadByFileCode?bmFileInfoGid=1231232432",
"/quality/quality/simpling/downloadByFileCode?bmFileInfoGid=1543543435"
]
}
]
}

View File

@ -0,0 +1,108 @@
import { useRouter, useCallback } from "next/router";
import "antd/dist/antd.css";
import { Image } from "antd";
import React, { useEffect, useMemo, useState } from "react";
import {
getShotImgByProductBatchNo,
getShotImg,
} from "../../../reportUtils/getQueryData";
const { config: { mesUrl } = {} } = global;
import _ from "lodash";
const ListItem = React.memo((props) => {
const { itemData } = props;
return (
<div className="border-t-[1px] border-[#D2D2D2] px-4 pb-2">
<div className=" flex flex-row space-x-7 py-2">
<div>
<label>工序</label>
<label>{itemData.operationName}</label>
</div>
<div>
<label>质检单模板名称</label>
<label>{itemData.templateName}</label>
</div>
<div>
<label>拍照时间</label>
<label>{itemData.imgShotTime}</label>
</div>
</div>
<div className="flex flex-row space-x-7 ">
<Imag imgurls={itemData.imgPath}></Imag>
</div>
</div>
);
});
function Imag(props) {
const { imgurls } = props;
const [imglist, setImgList] = useState([]);
useEffect(() => {
setImgList([]);
imgurls.map((imgsrc) => {
getShotImg(mesUrl + imgsrc).then((rep) => {
setImgList((prev) => {
return [...prev, URL.createObjectURL(rep)];
});
});
});
}, []);
return imglist.map((imgsrc) => {
return (
<Image
width={200}
height={240}
// className=" w-24 h-32 border"
src={imgsrc ?? "../img/noImg.png"}
/>
);
});
}
const List = React.memo((props) => {
const { dataSource } = props;
return (
<div className=" ">
{dataSource?.map((item) => {
return <ListItem itemData={item}></ListItem>;
})}
</div>
);
});
const Header = React.memo((props) => {
const { productBatchNo } = props;
return (
<div className=" py-6 px-4">
<label className=" ">批次生产单</label>
<label>{productBatchNo}</label>
</div>
);
});
const getQueryVariable = (name) => {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
var r = window.location.search.substr(1).match(reg);
if (r != null) return decodeURI(r[2]);
return null;
};
export default function PicView() {
const [data, setData] = useState([]);
const [productBatchNo, setProductBatchNo] = useState([]);
useEffect(() => {
setProductBatchNo(getQueryVariable("productBatchNo"));
}, []);
useEffect(() => {
getShotImgByProductBatchNo(productBatchNo).then((json) => {
setData(json);
});
}, [productBatchNo]);
return (
<div className="font-bold">
<Header productBatchNo={data.productBatchNo}></Header>
<List dataSource={data.imgItemList}></List>
</div>
);
}

View File

@ -0,0 +1,335 @@
import React, { useRef, useEffect, useState, useMemo } from "react";
import dynamic from 'next/dynamic';
const Mix = dynamic(() => import('@ant-design/plots').then(({ Mix }) => Mix),
{ ssr: false }
);
// import { Mix } from '@ant-design/charts'
import _, { max } from "lodash";
const symbol= function symbol(x, y, r) {
return [
["M", x - r, y],
["L", x + r, y],
];
};
const DemoDualAxes = ({ dataSource, UCL, LCL, CL }) => {
const [chartDatanew,setchartDatanew] = useState([]);
const [xAxisarr,setxAxisarr] = useState([]);
const data = dataSource;
const maxnnn = () => {
let max = Math.max.apply(
null,
data?.map((x) => x.value)
);
let tmparr = [];
tmparr.push(UCL, LCL, CL);
if (!isNaN(max)) {
tmparr.push(max);
}
console.log(tmparr);
let maxnew = Math.max.apply(null, tmparr);
return maxnew;
};
function closestTo(arr, target) {
return arr.reduce(function(prev, curr) {
return (Math.abs(curr - target) < Math.abs(prev - target) ? curr : prev);
});
}
let maxn = useMemo(()=>{return maxnnn},[maxnnn])
let category = ["y", "curve", "usl", "cl", "lsl"];
let categoryNames = ["频数", "正态曲线", "USL", "规格中线", "LSL"];
// let chartData = [];
// if (data && data.length > 0)
// data.map((item) => {
// Object.keys(item).map((key) => {
// if (category.includes(key)) {
// chartData.push({
// x: item.x,
// category: key,
// categoryName: categoryNames[category.indexOf(key)],
// value: item[key],
// });
// }
// });
// });
// let tmplist = [],
// chartDatanew = [];
// chartDatanew = chartData
// .map((x) => {
// if (x.category === "usl" || x.category === "lsl" || x.category === "cl") {
// let val = x.value;
// if (tmplist.indexOf(val) === -1) {
// x.x = val;
// x.value = maxn;
// tmplist.push(val);
// } else {
// return null;
// }
// }
// return x;
// })
// .filter((x) => x !== null);
useEffect(() => {
// data.push({ x: maxnnn(), y: 0 });
setchartDatanew(data)
setxAxisarr(data?.filter(x=>!isNaN(x.x)).map(x=>x.x))
}, [data]);
const config = {
tooltip: {
shared: true,
},
syncViewPadding: true,
plots: [
// {
// type: 'column',
// options:{
// data: chartData,
// xField: 'x',
// seriesField: 'category',
// yField: 'value',
// }
// },
{
type: "column",
options: {
data: chartDatanew?.map((c) => {
return {
x: c.x,
value: c.y,
categoryName: "频数",
};
}),
xField: "x",
minColumnWidth: 20,
maxColumnWidth: 20,
seriesField: "categoryName",
color: "#7B9DCE",
yField: "value",
yAxis: {
line: {
style: {
stroke: "#aaa",
},
},
},
xAxis: {
nice: true,
type: "linear", // 数据呈连续性
tickLine: {
length: 0,
alignTick:true,
style: {
lineWidth: 2,
stroke: "red",
},
},
label: {
style: {
opacity: 1,
},
formatter: (text,item,index)=>{
let newtext = text;
try {
newtext = closestTo(xAxisarr, text)
} catch (error) {
newtext = text;
}
return newtext
}
},
},
annotations: [
{
top: true,
type: "line",
start: [UCL, "min"],
end: [UCL, "max"],
style: {
stroke: "#FF0000",
fill: "#FF0000",
// lineDash: [6, 3],
lineWidth:2,
},
},
{
top: true,
type: "line",
start: [LCL, "min"],
end: [LCL, "max"],
style: {
stroke: "#FF0000",
fill: "#FF0000",
// lineDash: [6, 3],
lineWidth:2,
},
},
{
top: true,
type: "line",
start: [CL, "min"],
end: [CL, "max"],
style: {
stroke: "#7D679F",
fill: "#7D679F",
lineDash: [6, 3],
lineWidth:2,
},
},
],
},
},
{
type: "line",
options: {
data: chartDatanew?.map((c) => {
return {
x: c.x,
value: c.curve,
categoryName: "正态曲线",
};
}),
xField: "x",
smooth: true,
yField: "value",
seriesField: "categoryName",
color: "#B45A53",
xAxis: false,
yAxis: {
line: null,
grid: null,
position: "right",
},
},
},
// {
// type: "column",
// options: {
// data: chartDatanew.filter(
// (x) =>
// x.category === "usl" ||
// x.category === "cl" ||
// x.category === "lsl"
// ),
// minColumnWidth: 1,
// maxColumnWidth: 2,
// xField: "x",
// // color: "#EA3627",
// color: [ "#7D679F", "#F5C242","#EA3627"],
// yField: "value",
// seriesField: "categoryName",
// xAxis: false,
// yAxis: false,
// },
// },
// {
// type: "column",
// options: {
// data: chartDatanew.filter((x) => x.category === "cl"),
// minColumnWidth: 1,
// maxColumnWidth: 2,
// xField: "x",
// color: "#7D679F",
// yField: "value",
// seriesField: "categoryName",
// xAxis: false,
// },
// },
// {
// type: "column",
// options: {
// data: chartDatanew.filter((x) => x.category === "lsl"),
// minColumnWidth: 1,
// maxColumnWidth: 2,
// xField: "x",
// color: "#F5C242",
// yField: "value",
// seriesField: "categoryName",
// xAxis: false,
// },
// },
],
legend: {
position: "bottom-left",
layout: "horizontal",
items: [
{
value: "y",
name: "频数",
marker: {
symbol: "square",
style: {
stroke: "#5A80B8",
// lineDash: [4, 2],
fill: "#5A80B8",
r: 5,
},
},
},
{
value: "curve",
name: "正态曲线",
marker: {
symbol: symbol,
style: {
lineWidth: 2,
stroke: "#B45A53",
// lineDash: [4, 2],
fill: "#B45A53",
r: 8,
},
},
},
{
value: "usl",
name: "USL",
marker: {
symbol: symbol,
style: {
lineWidth: 2,
stroke: "#FF0000",
lineDash: [4, 2],
fill: "#FF0000",
r: 8,
},
},
},
{
value: "cl",
name: "规格中线",
marker: {
symbol: symbol,
style: {
lineWidth: 2,
stroke: "#7D679F",
lineDash: [4, 2],
fill: "#7D679F",
r: 8,
},
},
},
{
value: "lsl",
name: "LSL",
marker: {
symbol: symbol,
style: {
lineWidth: 2,
stroke: "#FF0000",
lineDash: [4, 2],
fill: "#FF0000",
r: 8,
},
},
},
],
},
};
return <Mix {...config} height={250}/>;
};
export default DemoDualAxes;

View File

@ -0,0 +1,216 @@
import React, {
useRef,
useEffect,
useState,
useMemo,
useCallback,
} from "react";
import dynamic from "next/dynamic";
const Line = dynamic(
() => import("@ant-design/charts").then(({ Line }) => Line),
{ ssr: false }
);
// import { Line } from '@ant-design/charts'
const DemoLine = ({
dataSource,
UCL,
LCL,
AVER,
legend,
USL,
LSL,
maxz,
minz,
}) => {
const data = dataSource;
const line = useRef();
const name = data?.map((x) => x.name);
let isX = false;
if (USL && LSL) {
isX = true;
}
let maxn = useMemo(() => {
let max = Math.max.apply(
null,
data?.map((x) => {
if (x?.value) {
return x.value;
}
return 0;
})
);
let tmparr = [];
tmparr.push(UCL, LCL, AVER);
if (isX) {
tmparr.push(USL, LSL);
}
if (!isNaN(max)) {
tmparr.push(max);
}
let maxnew = Math.max.apply(null, tmparr);
return maxnew;
}, [data]);
const config = {
data,
slider: {},
color: "#8AA1C9",
xField: "x",
yField: "value",
xAxis: {
animation: false,
},
yAxis: {
tickCount: 7,
nice: true,
animation: false,
max: maxz,
min: minz,
line: {
style: {
stroke: "#aaa",
},
},
tickLine: {},
},
point: {
style: {
r: 2.5,
},
},
smooth: true,
tooltip: {
showCrosshairs: true,
shared: true,
},
seriesField: "name",
annotations: [
// {
// type: "text",
// position: ["min", UCL],
// content: "UCL",
// offsetY: -4,
// style: {
// textBaseline: "bottom",
// },
// },
// {
// type: "text",
// position: ["min", LCL],
// content: "LCL",
// offsetY: -4,
// style: {
// textBaseline: "bottom",
// },
// },
// {
// type: "text",
// position: ["min", AVER],
// content: "AVER",
// offsetY: -4,
// style: {
// textBaseline: "bottom",
// },
// },
{
type: "line",
start: ["min", LCL],
end: ["max", LCL],
style: {
stroke: "#FF0000",
fill: "#FF0000",
// lineDash: [6, 3],
lineWidth: 2,
},
},
{
type: "line",
start: ["min", UCL],
end: ["max", UCL],
style: {
stroke: "#FF0000",
fill: "#FF0000",
// lineDash: [6, 3],
lineWidth: 2,
},
},
{
type: "line",
start: ["min", AVER],
end: ["max", AVER],
style: {
stroke: "#80FC5E",
fill: "#80FC5E",
lineDash: [6, 3],
lineWidth: 2,
},
},
],
legend: legend,
};
dataSource
?.filter((a) => a?.controlRefList?.length > 0)
.forEach((b) => {
const _controlPerf = "";
if (legend.items[0].name === "极差值") {
if (b.controlRefList?.indexOf("I") > -1) {
_controlPerf = "I";
}
} else {
_controlPerf = b.controlRefList?.filter((z) => z !== "I").join(",");
}
if (_controlPerf !== "") {
const tmplateann = {
type: "text",
content: _controlPerf,
position: (xScale, yScale) => {
return [
`${xScale.scale(b.x) * 100}%`,
b.value>0? `${(1 - yScale.value.scale(b.value)) * 50}%` : `${(1 - yScale.value.scale(b.value)) * 94}%`,
];
},
style: {
textAlign: "center",
fill: "rgba(0,0,0,0.85)",
},
};
config.annotations.push(tmplateann);
}
});
let usllsl = [
{
type: "line",
start: ["min", USL],
end: ["max", USL],
style: {
stroke: "#FF0000",
fill: "#FF0000",
lineDash: [6, 3],
lineWidth: 2,
},
},
{
type: "line",
start: ["min", LSL],
end: ["max", LSL],
style: {
stroke: "#FF0000",
fill: "#FF0000",
lineDash: [6, 3],
lineWidth: 2,
},
},
];
if (isX) {
usllsl.forEach((item) => {
config.annotations.push(item);
});
}
return <Line {...config} height={250} />;
};
export default DemoLine;

1359
pages/reports/xmr/index.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,438 @@
import React, { useRef, useEffect, useState, useCallback } from "react";
import Footer from "../../../components/screen/Footer";
import Header from "../../../components/screen/Header";
import Row from "../../../components/screen/Row";
import Card from "../../../components/screen/Card";
import Chart from "../../../components/screen/Chart";
import Table from "../../../components/screen/Table";
import TableProgress from "../../../components/screen/TableProgress";
import io from "../../../utils/socket.io.js";
const gutter = 20;
const bottom = 20;
const tableToNextPage = 6000;
const polarBarOpt = {
title: {
text: "计划总数",
left: "center",
bottom: 24,
textStyle: {
fontSize: 20,
},
},
legend: {
show: false,
},
radiusAxis: {
show: false,
type: "category",
data: ["%"],
},
tooltip: {
show: false,
},
series: {
type: "gauge",
center: ["50%", "40%"],
radius: "70%",
startAngle: 90,
endAngle: -270,
pointer: {
show: false,
},
progress: {
show: true,
overlap: false,
roundCap: false,
clip: false,
itemStyle: {
borderWidth: 0,
},
},
axisLine: {
lineStyle: {
width: 20,
color: [[1, "rgba(40, 163, 255, 0.2)"]],
},
},
splitLine: {
show: false,
},
axisTick: {
show: false,
},
axisLabel: {
show: false,
},
data: [
{
value: 0,
name: "生产总数",
},
],
title: {
offsetCenter: ["0%", "20"],
color: "#fff",
fontSize: 20,
},
detail: {
show: true,
offsetCenter: ["0%", "-20"],
width: "auto",
height: 32,
fontSize: 32,
color: "#00E1FA",
borderColor: "auto",
borderWidth: 0,
formatter: "{value}",
},
},
};
const setNumOpt = (total, done, title) => {
let res = Object.assign({}, polarBarOpt);
if (title) res.title.text = `${title}${total}`;
let percent = ((100 * done) / (total || 1)).toFixed(2);
res.series.data[0].value = percent;
res.series.detail.formatter = () => done;
return res;
};
const setOkNumOpt = (total, done, title) => {
let res = Object.assign({}, polarBarOpt);
let percent = ((100 * done) / (total || 1)).toFixed(2);
if (title) res.title.text = `${title}${percent}%`;
res.series.data[0].value = percent;
res.series.data[0].name = "实际总量";
res.series.detail.formatter = () => done;
res.series.detail.color = "#6FE621";
res.series.progress.itemStyle.color = "#6FE621";
return res;
};
const barOpt = {
legend: {
show: true,
data: ["生产数量", "合格数量"],
top: 10,
right: 10,
textStyle: {
fontSize: 14,
},
},
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
},
},
xAxis: {
type: "category",
axisTick: {
lineStyle: { opacity: 0.3 },
alignWithLabel: true,
},
axisLine: {
lineStyle: { opacity: 0.3 },
},
axisLabel: {
color: "rgba(255, 255, 255, 0.8)",
},
data: [],
},
yAxis: {
type: "value",
axisTick: {
lineStyle: { opacity: 0.3 },
},
axisLine: {
lineStyle: { opacity: 0.3 },
},
axisLabel: {
color: "rgba(255, 255, 255, 0.8)",
},
},
grid: {
bottom: 40,
top: 60,
left: 50,
},
barWidth: 20,
series: [
{
name: "生产数量",
type: "bar",
barGap: -1,
data: [],
},
{
name: "合格数量",
itemStyle: {
color: "#6FE621",
},
type: "bar",
data: [],
},
],
};
const setBarOpt = ({ product = {}, qualify = {} }) => {
let res = Object.assign({}, barOpt);
for (let n = 0; n < 24; n++) {
res.xAxis.data.push(`${n}:00`.padStart(5, "0"));
}
for (let index in product) {
res.series[0].data[parseInt(index)] = product[index];
}
for (let index in qualify) {
res.series[1].data[parseInt(index)] = qualify[index];
}
return res;
};
const productTableColumns = [
{ title: "客户编码", code: "vendorCode" },
{ title: "客户名称", code: "vendorName" },
{ title: "销售名称", code: "userName" },
{ title: "客户分类", code: "customType" },
{ title: "产品型号", code: "productMode" },
{ title: "计划发货日期", code: "planDeliveryTime" },
{ title: "计划发货数量", code: "planDeliveryQty" },
{ title: "实际发货数量", code: "deliveryQty" },
{
title: "发货率",
code: "deliveryRateNew",
render: (value, config, rowData) => {
console.log(config, rowData);
return <TableProgress percent={value} />;
},
},
// {title: "生产状态", code: "code",
// value: val => {
// if(val === '1') return "生产中";
// if(val === '2') return "中断";
// if(val === '3') return "转产";
// },
// color: val => {
// if(val === '1') return "#6FE621";
// if(val === '2') return "#28A3FF";
// if(val === '3') return "#F82D22";
// },
// render: (text, config, rowData) => {
// const {value, color} = config;
// let resColor = color(text);
// let resText = value(text);
// return (
// <div style={{color: resColor}}>
// <span style={{
// background: resColor,
// width: '8px',
// height: '8px',
// display: 'inline-block',
// borderRadius: '50%',
// verticalAlign: 'middle',
// }}></span>
// <span style={{
// verticalAlign: 'middle',
// marginLeft: '6px',
// }}>{resText}</span>
// </div>
// )
// },
// },
// {title: "产品编码", code: "code"},
// {title: "产品名称", code: "code"},
// {title: "工单编号", code: "code"},
// {title: "生产数量", code: "code"},
// {title: "计划数量", code: "code"},
// {title: "生产进度", code: "code",
// render: (value, config, rowData) => {
// console.log(config, rowData)
// return <TableProgress percent={0.31}/>
// },
// },
// {title: "不良品数量", code: "code"},
// {title: "不良品率", code: "code"},
];
const productTableColumnNew = [
{ title: "产品类型", code: "materialTypeName" },
{ title: "产品型号", code: "productLevel" },
{ title: "产线信息", code: "workCenterName" },
{ title: "计划产出日期", code: "planOutputDate" },
{ title: "计划产量", code: "planNum" },
{ title: "实际产量", code: "actualNum" },
{
title: "达成率",
code: "fulfillRateNew",
render: (value, config, rowData) => {
console.log(config, rowData);
return <TableProgress percent={value} />;
},
},
];
function Comprehensive(props) {
const { config: { ioSocketUrl, comprehensiveConfig = {} } = {} } = global;
const okNumRefNew = useRef();
const okNumRef = useRef();
const productTableRefNew = useRef();
const productTableRef = useRef();
const socket = useRef();
let date = "";
let query = {
screenType: "ComprehensiveType",
};
if (date) query.date = date;
else {
query.date = new Date().toJSON().split("T").join(" ").substr(0, 10);
}
const getData = () =>
socket.current.emit("timePerformanceGoodProduct", "channel", { query });
setTimeout(() => {
getData();
}, 300000);
const setTableNextPage = useCallback(() => {
setTimeout(() => {
productTableRef.current.nextPage();
setTableNextPage();
}, tableToNextPage);
}, []);
const setTableNextPageNew = useCallback(() => {
setTimeout(() => {
productTableRefNew.current.nextPage();
setTableNextPageNew();
}, tableToNextPage);
}, []);
useEffect(() => {
socket.current = io.connect(ioSocketUrl, {transports:['websocket']});
socket.current.on("connect", function () {
console.log("msg页面连接成功");
getData();
});
socket.current.on("timePerformanceGoodProduct", function (data) {
data = JSON.parse(data);
console.log(data, "data");
okNumRef.current.setOption(
setOkNumOpt(
data.data.deliveryRates.planQty,
data.data.deliveryRates.qty,
"发货总体达成率"
)
);
okNumRefNew.current.setOption(
setOkNumOpt(
data.data.planRates.planOutQty,
data.data.planRates.OutQty,
"计划总体达成率"
)
);
productTableRefNew.current.addData(data.data.planOutputList);
productTableRef.current.addData(data.data.deliveryPlanList);
});
//planNumRef.current.setOption(setNumOpt(500000, 212923, "计划总数"));
// productNumRef.current.setOption(setBarOpt({
// product: {
// "0": 10,
// "1": 40,
// "5": 30,
// "6": 40,
// },
// qualify: {
// "0": 5,
// "1": 4,
// "5": 3,
// },
// }));
setTableNextPageNew();
setTableNextPage();
}, []);
return (
<div className="screen-container">
<Header title={"综合看板"} />
<div className="screen-container-content">
<Row className="height-50" gutter={gutter} bottom={bottom}>
<Row.Col span={18}>
<Card
title="发货达成率"
titleSpace={true}
padding={`15px ${gutter}px`}
>
<Table
ref={productTableRef}
mainKey="code"
columns={productTableColumns}
/>
</Card>
</Row.Col>
{/*<Row.Col span={14}>*/}
{/* <Card title="今日生产统计" overVisible={true}>*/}
{/* <Chart ref={productNumRef} />*/}
{/* </Card>*/}
{/*</Row.Col>*/}
<Row.Col span={6}>
<Card title="发货总体达成率" titleSpace={true} overVisible={true}>
<Chart ref={okNumRef} />
</Card>
</Row.Col>
</Row>
<Row className="height-50" gutter={gutter} bottom={bottom}>
<Row.Col span={18}>
<Card
title="计划达成率"
titleSpace={true}
padding={`15px ${gutter}px`}
>
<Table
ref={productTableRefNew}
mainKey="code"
columns={productTableColumnNew}
/>
</Card>
</Row.Col>
<Row.Col span={6}>
<Card title="计划总体达成率" titleSpace={true} overVisible={true}>
<Chart ref={okNumRefNew} />
</Card>
</Row.Col>
</Row>
</div>
<Footer />
<style jsx>{`
.screen-container {
height: 100vh;
overflow: auto;
background: url("/img/bg.png") center center;
position: relative;
padding-top: 96px;
padding-bottom: 82px;
}
.screen-container-content {
height: 100%;
padding: 0 ${gutter}px;
}
`}</style>
</div>
);
}
const getQueryVariable = (variable) => {
let query = window.location.search.substring(1);
let vars = query.split("&");
for (let i = 0; i < vars.length; i++) {
let pair = vars[i].split("=");
if (pair[0] == variable) {
return pair[1];
}
}
return false;
};
export default Comprehensive;

View File

@ -0,0 +1,978 @@
import React, { useRef, useEffect, useState, useCallback } from 'react';
import { withRouter } from 'next/router';
import * as echarts from 'echarts'
import _ from 'lodash'
import Footer from '../../../components/screen/Footer';
import Header from '../../../components/screen/Header';
import Row from '../../../components/screen/Row';
import Card from '../../../components/screen/Card';
import Chart from '../../../components/screen/Chart';
import Table from '../../../components/screen/Table';
import InfoCardList from '../../../components/screen/InfoCardList';
import io from '../../../utils/socket.io.js';
const gutter = 20;
const bottom = 20;
const polarBarOpt = {
title: {
text: '',
left: 'center',
top: 24,
},
legend: {
data: ['时间稼动率', '性能稼动率', '良品率'],
bottom: 43,
orient: 'vertical',
itemHeight: 18,
textStyle: {
fontSize: 14,
lineHeight: 20,
},
},
polar: {
radius: ['20%', '90%'],
center: ['50%', '40%'],
},
angleAxis: {
show: false,
max: 100,
startAngle: 90,
},
radiusAxis: {
show: false,
type: 'category',
data: ['%']
},
tooltip: {
formatter: '{a}'
},
series: [{
name: '时间稼动率',
type: 'bar',
barGap: '0',
barWidth: 14,
data: [],
coordinateSystem: 'polar',
}, {
name: '性能稼动率',
type: 'bar',
barGap: '0',
barWidth: 14,
data: [],
coordinateSystem: 'polar',
}, {
name: '良品率',
type: 'bar',
barGap: '0',
barWidth: 14,
data: [],
coordinateSystem: 'polar',
}]
};
const setPolarBarOpt = (values, title) => {
values = values.map(i => (i * 100).toFixed(2));
values = values.map(i => {
if (isNaN(i)) i = '0.00';
return i;
});
const names = ['时间稼动率 ', '性能稼动率 ', '良品率 '];
let res = _.cloneDeep(polarBarOpt);
if (title) res.title.text = title;
values[0] !== undefined && (res.series[0].data[0] = values[0]);
values[1] !== undefined && (res.series[1].data[0] = values[1]);
values[2] !== undefined && (res.series[2].data[0] = values[2]);
res.legend.data = names.map((name, index) => name = values[index] !== undefined ? name + ` ${values[index]}%` : name);
res.series = res.series.map((item, index) => {
values[index] !== undefined && (item.name = names[index] + ` ${values[index]}%`);
return item;
})
return res;
}
const lineOpt = {
legend: {
data: ['OEE'],
top: 10,
right: 10,
textStyle: {
fontSize: 14,
},
},
xAxis: {
type: 'category',
axisTick: {
lineStyle: { opacity: 0.3 },
alignWithLabel: true,
},
axisLine: {
lineStyle: { opacity: 0.3 },
},
axisLabel: {
color: 'rgba(255, 255, 255, 0.8)'
},
},
yAxis: {
type: 'value',
max: 100,
axisTick: {
lineStyle: { opacity: 0.3 }
},
axisLine: {
lineStyle: { opacity: 0.3 }
},
axisLabel: {
formatter: '{value}%',
color: 'rgba(255, 255, 255, 0.8)'
},
},
grid: {
bottom: 40,
top: 110,
left: 50,
},
tooltip: {
formatter: data => `${data.value[0]}${data.value[1]}%`,
},
series: {
name: "OEE",
data: [],
type: 'line',
color: "#50D9B0",
label: {
show: true,
position: 'top',
formatter: data => `${data.value[1]}%`,
textBorderColor: 'rgba(0, 0, 0, 0)',
color: '#fff'
},
symbolSize: 8,
lineStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [{
offset: 0, color: 'rgba(255, 225, 50, 0.54)'
}, {
offset: 1, color: 'rgb(45, 240, 219)'
}])
},
areaStyle: {
opacity: 0.4,
color: new echarts.graphic.LinearGradient(0, 1, 1, 1, [{
offset: 0, color: 'rgba(255, 225, 50, 0)'
}, {
offset: 1, color: 'rgb(1, 191, 236)'
}])
},
}
};
const setLineOpt = data => {
let res = _.cloneDeep(lineOpt);
res.series.data = data;
return res;
}
const getOEEnearWeekTrendInfo = (data) => {
let total = 0;
let max = 0;
console.log("data", data)
data.forEach(([day, val]) => {
total = total + val;
if (val > max) max = val;
})
let average = (parseInt(total * 100 / data.length)) / 100;
if (isNaN(average)) average = 0;
return [max, average]
}
const getQueryVariable = (variable) => {
let query = window.location.search.substring(1);
let vars = query.split("&");
for (let i = 0; i < vars.length; i++) {
let pair = vars[i].split("=");
if (pair[0] == variable) { return pair[1]; }
}
return (false);
}
let data = {
"code": "0000",
"extra": {
"PieChart": {
"SY_TIME": 0.442,
"DT_GOODPRODUCT": 0.12,
"SY_GOODPRODUCT": 0.12,
"DY_GOODPRODUCT": 0.12,
"DT_PERFORMANCE": 0.52,
"DT_TIME": 0.27,
"DY_TIME": 0.33999999999999997,
"DY_PERFORMANCE": 0.32999999999999996,
"SY_PERFORMANCE": 0.431
},
"TrendChart": {
"03-31": 0.0514,
"04-10": 0.9423,
"04-11": 0.2197,
"04-12": 0.8591,
"04-13": 0.3331,
"04-08": 0.2534,
"04-09": 0.4141
},
"EquipStatus": [{
"phenomenonCode": "YC001",
"equipName": "窑炉1号",
"phenomenonName": "1002,1003,啊啊啊啊啊啊",
"equipCode": "YL001"
}, {
"phenomenonCode": "BBB",
"equipName": "111",
"phenomenonName": "1003,iiiiiiiii",
"equipCode": "YL006"
}, {
"phenomenonCode": "请求",
"equipName": "除磁001",
"phenomenonName": "请求",
"equipCode": "CC001"
}],
// "EquipData": {
// "C001": [{
// "code": "code",
// "name": "name",
// "temp": "32", // 温度
// "rps": "11", // 转速
// "ia": "12", // 电流
// "ua": "13", // 电压
// "runningstatus": "87" // 运行状态
// }]
// },
"EquipData": {
"CC": [
{
"equipCode": "123456789",
"equipName": "测试设备-dwj",
"fileCode": "9NO0EXP6MXQ3VHSN1OTS5OXZ2463PWZ8F247B02EF9CKHCP90F0TJHIV6HFMX148YIS3GONKYDAWLLWXS4GNPJUGG4O17PF6DEFIN94B549A0Y5XCWMB4IUDKSY62D0F7ZWQ32364DF7D9FB3E584D63QM5JHMLU33T5NXWQGPWDOXWL0K9I71RCUXY1OJ64FSXW9NB912L9J7NYUFK45YF8YLXYQA402E667DAA82A558675E26D345EFD35747",
"opCode": "CC",
"opName": "除磁",
"runningstatus": null,
"shipList": []
}
],
"XM": [
{
"equipCode": "eqtest419",
"equipName": "设备测试419",
"fileCode": "01TR2HYCOOH6APN22DUFA9P5EHCQ6WK9FA1B789C3CU087W5R57HC704LEOFQK69SAX02I2T329SKVELGB686CEC5KLJPA6ML7J8S3451196V9KYFSFSELQA0CINDS2B4HBCIAFF389A4BC89DCHQVSFX41AY8PAI7HTZC8UKY00ZZ1KDM3LHSLNMWDEE3X0YA81EZ0BGMLMHXSW4R1INRW7ZCYIA7XU7BB35C97C67D63F9390C342479E1A150",
"opCode": "XM",
"opName": "细磨",
"runningstatus": null,
"shipList": [
{
"data": null,
"deleted": false,
"displayWork": false,
"fieldSort": "1",
"flag": null,
"gid": "1681416533722673152",
"iotField": "ty1",
"iotFieldDescribe": "信号1",
"iotIsMainField": true,
"iotIsalarm": false,
"iottype": "string",
"relationGid": "1621704831515373568",
"serialNumber": null,
"status": true
},
{
"data": null,
"deleted": false,
"displayWork": false,
"fieldSort": "2",
"flag": null,
"gid": "1681424290735599616",
"iotField": "ty2",
"iotFieldDescribe": "信号2",
"iotIsMainField": true,
"iotIsalarm": false,
"iottype": "string",
"relationGid": "1621704831515373568",
"serialNumber": null,
"status": true
},
{
"data": null,
"deleted": false,
"displayWork": false,
"fieldSort": "4",
"flag": null,
"gid": "1681424290769154048",
"iotField": "ty4",
"iotFieldDescribe": "信号4",
"iotIsMainField": true,
"iotIsalarm": false,
"iottype": "string",
"relationGid": "1621704831515373568",
"serialNumber": null,
"status": true
},
{
"data": null,
"deleted": false,
"displayWork": false,
"fieldSort": "5",
"flag": null,
"gid": "1681424290802708480",
"iotField": "ty5",
"iotFieldDescribe": "信号5",
"iotIsMainField": true,
"iotIsalarm": false,
"iottype": "string",
"relationGid": "1621704831515373568",
"serialNumber": null,
"status": true
},
{
"data": null,
"deleted": false,
"displayWork": false,
"fieldSort": "3",
"flag": null,
"gid": "1681556221980717056",
"iotField": "ty3",
"iotFieldDescribe": "信号3",
"iotIsMainField": true,
"iotIsalarm": false,
"iottype": "string",
"relationGid": "1621704831515373568",
"serialNumber": null,
"status": true
}
]
}
],
"BZ": [
{
"equipCode": "PP001",
"equipName": "设备小明 001",
"fileCode": "1LM22ETECZCV25GCIUBNQRI5P96TA4I3AAE19CD0FCKWVLO17UI4KHG9HDX1R0MHMRKYWRTGCRF0IZ3HVL2EOTQUWZELDJS0OORIB44AB680W7K2DTXZVRF68FAI8SKPAOJ3UD0D203F20551EANV7C8580PMMRN4I67F070AN0IZJL76S2UKUQ7O6ATMKMGZ8NWPOAIEW75LRMZMNPD0GLZ06AKTD5W7FA073DC704B06D1FE9CDC08E156FB61",
"opCode": "BZ",
"opName": "包装",
"runningstatus": null,
"shipList": []
}
],
"TL": [
{
"equipCode": "L5_TLXT_B01",
"equipName": "L5_TLXT_B01设备",
"fileCode": "ZCFNP5MUJORD07O3N3SYM81VRH443R8F4A20A141A68H75DL162LU6XYA5QS4TWZN2CEKIKOJOVX6UE2CL6YRFMV6EFN0YAVH0SEZ14945A57UZ9JD466RKDULDMONJU3XPZMEA4E868EFF6BEDJ8YCQG2P61SKEJS4M82JBYXEV7L7S41E02S3NMC6S7JJ9EIO0EORRZP2STV73L0LGPKH6N6JXVCL163615505CB9899AD60CABB886B5E2B92",
"opCode": "TL",
"opName": "投料",
"runningstatus": "2",
"shipList": [
{
"data": "104.9",
"deleted": false,
"displayWork": false,
"fieldSort": "1",
"flag": null,
"gid": "1682683971810504704",
"iotField": "ChnSL_WG1_TL2_TL2_DB_AI_DB4_472",
"iotFieldDescribe": "信号472",
"iotIsMainField": true,
"iotIsalarm": false,
"iottype": "string",
"relationGid": "1682681371711123456",
"serialNumber": null,
"status": true
},
{
"data": "746.7",
"deleted": false,
"displayWork": false,
"fieldSort": "2",
"flag": null,
"gid": "1682683971894390784",
"iotField": "ChnSL_WG1_TL2_TL2_DB_AI_DB4_148",
"iotFieldDescribe": "信号148",
"iotIsMainField": true,
"iotIsalarm": false,
"iottype": "string",
"relationGid": "1682681371711123456",
"serialNumber": null,
"status": true
},
{
"data": "699.4",
"deleted": false,
"displayWork": false,
"fieldSort": "3",
"flag": null,
"gid": "1682683971860836352",
"iotField": "ChnSL_WG1_TL2_TL2_DB_AI_DB4_168",
"iotFieldDescribe": "信号168",
"iotIsMainField": true,
"iotIsalarm": false,
"iottype": "string",
"relationGid": "1682681371711123456",
"serialNumber": null,
"status": true
},
{
"data": "462",
"deleted": false,
"displayWork": false,
"fieldSort": "5",
"flag": null,
"gid": "1682853205282795520",
"iotField": "ChnSL_WG1_TL2_TL2_DB_AI_DB4_400",
"iotFieldDescribe": "信号400",
"iotIsMainField": true,
"iotIsalarm": false,
"iottype": "string",
"relationGid": "1682681371711123456",
"serialNumber": null,
"status": true
}
]
},
{
"equipCode": "L5_YMXT_A01",
"equipName": "L5_YMXT_A01设备",
"fileCode": "NGERX0JN0T2MNEZ8Z98XT7LKTKZ4D266EC596DDD8BVZM0KFO36T79MYAF4VY15VH76T12ID0XRJV6P2RG53MBE1U5HP0FDPAH8V554BC6A42BS7SB79ZP0X8S2U655EPDF3MC9309151D5558D4JQYD6CB3JQJ5J0H82EJOHJUTWOBUL5Y4L9I97OMP5BWP2I7K1E7ACEDWOC88TZOL48H4THCXA0AV15D0290575E73FB8B736A503522EE1D3",
"opCode": "TL",
"opName": "投料",
"runningstatus": "2",
"shipList": [
{
"data": "0",
"deleted": false,
"displayWork": false,
"fieldSort": "1",
"flag": null,
"gid": "1682898429577146368",
"iotField": "ChnSL_WG1_YM1_YM1_DB4_DB4_350",
"iotFieldDescribe": "350信号",
"iotIsMainField": true,
"iotIsalarm": true,
"iottype": "string",
"relationGid": "1682897844270411776",
"serialNumber": null,
"status": true
}
]
}
],
"FS": [
{
"equipCode": "CS001",
"equipName": "测试1号设备aaa",
"fileCode": null,
"opCode": "FS",
"opName": "粉碎",
"runningstatus": null,
"shipList": []
}
]
}
},
"message": "OK",
"success": true
}
data = JSON.stringify(data);
function Equipment(props) {
const { config: { ioSocketUrl, equipmentConfig = {} } = {} } = global;
const { equipListNext: listToNextPage, equipAbnormalNext: tableToNextPage } = equipmentConfig;
const { router } = props;
const socket = useRef();
const getData = () => {
// let {workCenterCode} = router.query;
let workCenterCode = getQueryVariable('workCenterCode');
let date = getQueryVariable('date');
console.log("fetch", workCenterCode)
if (!workCenterCode) return;
// console.log("00000", {
// workCenterCode,
// date,
// screenType: "EquipDashBoardType",
// });
let query = {
workCenterCode,
screenType: "EquipDashBoardType",
}
if (date) query.date = date;
else {
query.date = new Date().toJSON().split("T").join(" ").substr(0, 10);
}
// console.log("query", {query})
socket.current.emit('timePerformanceGoodProduct', 'channel', {query})
// update(data)
};
const OEEonDay = useRef();
const OEEonMonth = useRef();
const OEELastMonth = useRef();
const OEEnearWeekTrend = useRef();
const equipTableRef = useRef();
const infoCardListRef = useRef();
const [oEEnearWeekTrendInfo, setOEEnearWeekTrendInfo] = useState([0, 0]);
const tableTimeoutRef = useRef();
const listTimeoutRef = useRef();
const [{DT_OEE, DY_OEE, SY_OEE}, setOEE] = useState({})
const update = useCallback(data => {
try { data = JSON.parse(data) } catch (e) { console.log(e) }
const { PieChart = {}, TrendChart = {}, EquipStatus = [], EquipData = [],energySummary = [],recentAlert = []} = data.extra || {};
// energySummary = {
// "2022-08-19": {
// packingWeight: 0.0,
// packingQty: 0.0,
// gas: 10626.98,
// electricity: 101161.97,
// feedingWeight: 76002.0,
// water: 60551.0,
// },
// "2022-08-20": {
// packingWeight: 861.0,
// packingQty: 2.0,
// gas: 3555.55,
// electricity: 50378.71,
// feedingWeight: 27102.0,
// water: 24655.0,
// }
// };
// recentAlert = [
// {
// duration: 18677,
// eliminationTime: "2022-08-20 11:20:18",
// field: "ChnSL_WG1_TL2_TL2_BJ_DB3_0_4",
// fuzzyDate: "5小时12分钟",
// phenomenonName: " SL-101下料阀故障2",
// equipCode: "L5_TLXT_B01",
// alertTime: "2022-08-20 06:09:01",
// },
// {
// duration: 18574,
// eliminationTime: "2022-08-20 11:20:20",
// field: "ChnSL_WG1_TL2_TL2_BJ_DB3_1_6",
// fuzzyDate: "5小时10分钟",
// phenomenonName: " SL-103下料阀故障2",
// equipCode: "L5_TLXT_B01",
// alertTime: "2022-08-20 06:10:46",
// },
// {
// duration: 16931,
// eliminationTime: "2022-08-20 06:04:13",
// field: "ChnSL_WG1_TL2_TL2_BJ_DB3_0_4",
// fuzzyDate: "4小时43分钟",
// phenomenonName: " SL-101下料阀故障2",
// equipCode: "L5_TLXT_B01",
// alertTime: "2022-08-20 01:22:02",
// },
// {
// duration: 16832,
// eliminationTime: "2022-08-20 06:04:17",
// field: "ChnSL_WG1_TL2_TL2_BJ_DB3_1_6",
// fuzzyDate: "4小时41分钟",
// phenomenonName: " SL-103下料阀故障2",
// equipCode: "L5_TLXT_B01",
// alertTime: "2022-08-20 01:23:45",
// },
// {
// duration: 9365,
// eliminationTime: "2022-08-20 06:44:59",
// field: "ChnSL_WG5_HDSS_HDSS_BJ_DB25_9_2",
// fuzzyDate: "2小时37分钟",
// phenomenonName: "QF-616阀门故障",
// equipCode: "HDSSZDX001",
// alertTime: "2022-08-20 04:08:54",
// },
// {
// duration: 9203,
// eliminationTime: "2022-08-20 13:58:34",
// field: "ChnSL_WG1_TL2_TL2_BJ_DB3_0_4",
// fuzzyDate: "2小时34分钟",
// phenomenonName: " SL-101下料阀故障2",
// equipCode: "L5_TLXT_B01",
// alertTime: "2022-08-20 11:25:11",
// },
// {
// duration: 9054,
// eliminationTime: "2022-08-20 13:58:37",
// field: "ChnSL_WG1_TL2_TL2_BJ_DB3_1_6",
// fuzzyDate: "2小时31分钟",
// phenomenonName: " SL-103下料阀故障2",
// equipCode: "L5_TLXT_B01",
// alertTime: "2022-08-20 11:27:43",
// },
// {
// duration: 7636,
// eliminationTime: "2022-08-20 13:35:05",
// field: "ChnSL_WG5_HDSS_HDSS_BJ_DB25_9_2",
// fuzzyDate: "2小时8分钟",
// phenomenonName: "QF-616阀门故障",
// equipCode: "HDSSZDX001",
// alertTime: "2022-08-20 11:27:49",
// },
// {
// duration: 7221,
// eliminationTime: "2022-08-20 01:17:08",
// field: "ChnSL_WG1_TL2_TL2_BJ_DB3_0_4",
// fuzzyDate: "2小时1分钟",
// phenomenonName: " SL-101下料阀故障2",
// equipCode: "L5_TLXT_B01",
// alertTime: "2022-08-19 23:16:47",
// },
// {
// duration: 7084,
// eliminationTime: "2022-08-20 01:17:10",
// field: "ChnSL_WG1_TL2_TL2_BJ_DB3_1_6",
// fuzzyDate: "1小时59分钟",
// phenomenonName: " SL-103下料阀故障2",
// equipCode: "L5_TLXT_B01",
// alertTime: "2022-08-19 23:19:06",
// },
// {
// duration: 6484,
// eliminationTime: "2022-08-20 08:38:23",
// field: "ChnSL_WG5_HDSS_HDSS_BJ_DB25_9_2",
// fuzzyDate: "1小时49分钟",
// phenomenonName: "QF-616阀门故障",
// equipCode: "HDSSZDX001",
// alertTime: "2022-08-20 06:50:19",
// },
// {
// duration: 4652,
// eliminationTime: "2022-08-20 15:46:19",
// field: "ChnSL_WG5_HDSS_HDSS_BJ_DB25_9_2",
// fuzzyDate: "1小时18分钟",
// phenomenonName: "QF-616阀门故障",
// equipCode: "HDSSZDX001",
// alertTime: "2022-08-20 14:28:47",
// },
// {
// duration: 2331,
// eliminationTime: "2022-08-20 13:05:20",
// field: "ChnSL_WG1_YM1_YM1_BJ_M150_7",
// fuzzyDate: "39分钟",
// phenomenonName: " 粗磨机1总故障",
// equipCode: "L5_YMXT_A01",
// alertTime: "2022-08-20 12:26:29",
// },
// {
// duration: 2331,
// eliminationTime: "2022-08-20 13:05:20",
// field: "ChnSL_WG1_YM1_YM1_BJ_M150_4",
// fuzzyDate: "39分钟",
// phenomenonName: " 粗磨机1出料温度报警",
// equipCode: "L5_YMXT_A01",
// alertTime: "2022-08-20 12:26:29",
// },
// {
// duration: 1898,
// eliminationTime: "2022-08-20 15:33:21",
// field: "ChnSL_WG1_YM1_YM1_BJ_M150_7",
// fuzzyDate: "32分钟",
// phenomenonName: " 粗磨机1总故障",
// equipCode: "L5_YMXT_A01",
// alertTime: "2022-08-20 15:01:43",
// },
// {
// duration: 1898,
// eliminationTime: "2022-08-20 15:33:21",
// field: "ChnSL_WG1_YM1_YM1_BJ_M150_4",
// fuzzyDate: "32分钟",
// phenomenonName: " 粗磨机1出料温度报警",
// equipCode: "L5_YMXT_A01",
// alertTime: "2022-08-20 15:01:43",
// },
// {
// duration: 1667,
// eliminationTime: "2022-08-20 11:23:50",
// field: "ChnSL_WG1_YM1_YM1_BJ_M150_7",
// fuzzyDate: "28分钟",
// phenomenonName: " 粗磨机1总故障",
// equipCode: "L5_YMXT_A01",
// alertTime: "2022-08-20 10:56:03",
// },
// {
// duration: 1667,
// eliminationTime: "2022-08-20 11:23:50",
// field: "ChnSL_WG1_YM1_YM1_BJ_M150_4",
// fuzzyDate: "28分钟",
// phenomenonName: " 粗磨机1出料温度报警",
// equipCode: "L5_YMXT_A01",
// alertTime: "2022-08-20 10:56:03",
// },
// {
// duration: 1280,
// eliminationTime: "2022-08-20 04:54:41",
// field: "ChnSL_WG2_ylzdx1_ylzdx_1_bj_DBX85_3",
// fuzzyDate: "22分钟",
// phenomenonName: "下方输送管道故障_11",
// equipCode: "YLZDX001",
// alertTime: "2022-08-20 04:33:21",
// },
// {
// duration: 1160,
// eliminationTime: "2022-08-20 13:32:12",
// field: "ChnSL_WG1_YM1_YM1_BJ_M150_7",
// fuzzyDate: "20分钟",
// phenomenonName: " 粗磨机1总故障",
// equipCode: "L5_YMXT_A01",
// alertTime: "2022-08-20 13:12:52",
// },
// ];
console.log("data", data)
// 1 OEE趋势
let {
DT_TIME, DT_PERFORMANCE, DT_GOODPRODUCT,
DY_TIME, DY_PERFORMANCE, DY_GOODPRODUCT,
SY_TIME, SY_PERFORMANCE, SY_GOODPRODUCT,
} = PieChart;
let DT_OEE = DT_TIME * DT_PERFORMANCE * DT_GOODPRODUCT;
let DY_OEE = DY_TIME * DY_PERFORMANCE * DY_GOODPRODUCT;
let SY_OEE = SY_TIME * SY_PERFORMANCE * SY_GOODPRODUCT;
DT_OEE = (DT_OEE * 100).toFixed(2) + "%";
DY_OEE = (DY_OEE * 100).toFixed(2) + "%";
SY_OEE = (SY_OEE * 100).toFixed(2) + "%";
setOEE({DT_OEE, DY_OEE, SY_OEE})
try {
OEEonDay.current.setOption(setPolarBarOpt([
DT_TIME, DT_PERFORMANCE, DT_GOODPRODUCT
], "当日OEE"));
OEEonMonth.current.setOption(setPolarBarOpt([
DY_TIME, DY_PERFORMANCE, DY_GOODPRODUCT,
], "当月OEE"));
OEELastMonth.current.setOption(setPolarBarOpt([
SY_TIME, SY_PERFORMANCE, SY_GOODPRODUCT,
], "上月OEE"));
} catch (e) { }
// 2 近一周OEE趋势图
let lineData = [];
try {
for (let time in TrendChart || {}) {
let timeCode = new Date(time).getTime()
lineData.push([time, parseFloat((TrendChart[time] * 100).toFixed(2)), timeCode])
}
// console.log("lineData", lineData)
lineData.sort((a, b) => {
let [,,code1] = a;
let [,,code2] = b;
return code1 - code2;
})
OEEnearWeekTrend.current.setOption(setLineOpt(lineData));
setOEEnearWeekTrendInfo(getOEEnearWeekTrendInfo(lineData));
} catch (e) { }
// 3 设备异常监控
try {
recentAlert.map(item=>{
item.alertTime=item.alertTime.substring(5,item.alertTime.length);
item.eliminationTime=item.eliminationTime.substring(5,item.eliminationTime.length);
})
equipTableRef.current.setData(recentAlert);
let getNextPage = () => {
clearTimeout(tableTimeoutRef.current);
tableTimeoutRef.current = setTimeout(() => {
equipTableRef.current.nextPage();
getNextPage();
}, tableToNextPage)
};
getNextPage();
} catch (e) { }
// 4 设备运行状态
let withClassifyData = [];
// console.log("EquipData", EquipData)
console.log("res get")
try {
for (let name in EquipData) {
withClassifyData.push({ name, children: EquipData[name] })
}
infoCardListRef.current.setData(withClassifyData);
try {
infoCardListRef.current.setEnergy(energySummary);
} catch (error) {
console.log(error)
}
let getNextPage = () => {
clearTimeout(listTimeoutRef.current);
listTimeoutRef.current = setTimeout(() => {
infoCardListRef.current.nextPage();
getNextPage();
}, listToNextPage)
};
getNextPage();
} catch (e) { }
}, [])
const listFinished = useCallback(() => {
console.log("finished")
clearTimeout(listTimeoutRef.current);
clearTimeout(tableTimeoutRef.current);
getData();
}, [update])
useEffect(() => {
const { workCenterCode } = router.query;
if (!workCenterCode) return;
console.log("ioSocketUrl", ioSocketUrl)
socket.current = io.connect(ioSocketUrl, {transports:['websocket']});
socket.current.on('connect', function () {
console.log("msg页面连接成功");
getData();
});
socket.current.on('timePerformanceGoodProduct', function (data) {
// console.log("data", data)
update(data)
});
// update(data)
}, [router])
return (// 683 733 424
<div className='screen-container'>
<Header title={"设备看板"} />
<div className="screen-container-content">
<Row style={{ height: '50.55%' }} gutter={gutter} bottom={bottom}>
<Row.Col style={{ width: '33%' }}>
<Card title="OEE趋势" titleSpace={true} overVisible={true}>
<Row className='height-100'>
<Row.Col span={8} gutter={10}>
{DT_OEE && <div className="OEE"> {DT_OEE} </div>}
<Chart ref={OEEonDay} />
</Row.Col>
<Row.Col span={8}>
{DY_OEE && <div className="OEE"> {DY_OEE} </div>}
<Chart ref={OEEonMonth} />
</Row.Col>
<Row.Col span={8}>
{SY_OEE && <div className="OEE"> {SY_OEE} </div>}
<Chart ref={OEELastMonth} />
</Row.Col>
</Row>
</Card>
</Row.Col>
<Row.Col style={{ width: '33%' }}>
<Card title="近一周OEE趋势图" overVisible={true}>
<div className="OEEnearWeekTrend-info">
<span className="item">最高OEE {oEEnearWeekTrendInfo[0]}%</span>
<span className="item">平均OEE {oEEnearWeekTrendInfo[1]}%</span>
</div>
<Chart ref={OEEnearWeekTrend} />
</Card>
</Row.Col>
<Row.Col style={{ width: '34%' }}>
<Card title="设备异常监控" titleSpace={true} padding={`${gutter}px`}>
<Table
ref={equipTableRef}
mainKey="equipCode"
columns={[
{ title: "设备编码", code: "equipCode" },
{ title: "告警内容", code: "phenomenonName" },
{ title: "告警发生时间", code: "alertTime" },
{ title: "告警消除时间", code: "eliminationTime" },
{ title: "告警时长", code: "fuzzyDate" },
]}
/>
</Card>
</Row.Col>
</Row>
<Row style={{ height: '49.45%' }} gutter={gutter}>
<Row.Col span={24}>
<Card title="设备运行状态&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" titleSpace={false} padding={`0px ${gutter}px`}>
<InfoCardList
ref={infoCardListRef}
mainKey={"code"}
type={'page'}
withClassify={true}
classifyKey={{ name: 'name', children: 'children' }}
autoNext={false}
pageFinished={listFinished}
span={equipmentConfig.equipListSpan}
imgKey={"fileCode"}
status={{
key: "runningstatus",
value: val => {
if (val === '1') return "待机中";
if (val === '2') return "运行中";
if (val === '3') return "停机中";
},
color: val => {
if (val === '1') return "#F5A623";
if (val === '2') return "#58BC17";
if (val === '3') return "#FF005A";
},
}}
columns={[
{ title: "设备编号", code: "equipCode" },
{ title: "设备名称", code: "equipName" },
]}
extraColumnsOpt={{
key: "shipList",
name: "iotFieldDescribe",
val: "data",
max: equipmentConfig.equipItemLength - 2
}}
contentHeight={12 + 28 * equipmentConfig.equipItemLength}
/>
</Card>
</Row.Col>
</Row>
</div>
<Footer />
<style jsx>{`
.screen-container{
height: 100vh;
min-height: 1080px;
min-width: 1920px;
overflow: auto;
background: url('/img/bg.png') center center;
position: relative;
padding-top: 96px;
padding-bottom: 82px;
}
.screen-container-content{
height: 100%;
padding: 0 ${gutter}px;
}
.OEEnearWeekTrend-info{
position: absolute;
top: 46px;
left: 20px;
z-index: 1;
font-size: 16px;
background: rgba(216, 216, 216, 0.08);
padding: 8px 12px;
}
.OEEnearWeekTrend-info > .item{
margin-right: 40px;
}
.OEE{
font-size: 18px;
position: absolute;
top: 38%;
left: 50%;
transform: translateX(-50%);
font-family: 'RubikRegular';
}
`}</style>
</div>
);
}
export default withRouter(Equipment)

View File

@ -0,0 +1,668 @@
import React, { useRef, useEffect, useCallback } from 'react';
import _ from 'lodash';
import { withRouter } from 'next/router';
import Footer from '../../../components/screen/Footer';
import Header from '../../../components/screen/Header';
import Row from '../../../components/screen/Row';
import Card from '../../../components/screen/Card';
import Chart from '../../../components/screen/Chart';
import Table from '../../../components/screen/Table';
import TableProgress from '../../../components/screen/TableProgress';
import io from '../../../utils/socket.io.js';
const gutter = 20;
const bottom = 20;
// 返回结果是个字符串,需要转一下
let testData = {
"code": "0000",
"data": {
//今日生产总数
"productCount": {
"planQty": 3162.000000, //计划总数
"productQty": 199.000000, //生产总数
"unFinishedQty": 2963.000000 //未完成数量
},
//今日生产统计
"productDayCountList": [
{
"productQty": 0.000000, //生产数量
"qualityQty": 0.000000, //合格数量
"time": "00:00"
},
{
"productQty": 0.000000,
"qualityQty": 0.000000,
"time": "01:00"
},
{
"productQty": 0.000000,
"qualityQty": 0.000000,
"time": "02:00"
},
{
"productQty": 0.000000,
"qualityQty": 0.000000,
"time": "03:00"
},
{
"productQty": 0.000000,
"qualityQty": 0.000000,
"time": "04:00"
},
{
"productQty": 0.000000,
"qualityQty": 0.000000,
"time": "05:00"
},
{
"productQty": 0.000000,
"qualityQty": 0.000000,
"time": "06:00"
},
{
"productQty": 0.000000,
"qualityQty": 0.000000,
"time": "07:00"
},
{
"productQty": 0.000000,
"qualityQty": 0.000000,
"time": "08:00"
},
{
"productQty": 0.000000,
"qualityQty": 0.000000,
"time": "09:00"
},
{
"productQty": 69.000000,
"qualityQty": 17.000000,
"time": "10:00"
},
{
"productQty": 12.000000,
"qualityQty": 12.000000,
"time": "11:00"
},
{
"productQty": 0.000000,
"qualityQty": 0.000000,
"time": "12:00"
},
{
"productQty": 15.000000,
"qualityQty": 15.000000,
"time": "13:00"
},
{
"productQty": 25.000000,
"qualityQty": 25.000000,
"time": "14:00"
},
{
"productQty": 26.000000,
"qualityQty": 26.000000,
"time": "15:00"
},
{
"productQty": 52.000000,
"qualityQty": 51.000000,
"time": "16:00"
},
{
"productQty": 0.000000,
"qualityQty": 0.000000,
"time": "17:00"
},
{
"productQty": 0.000000,
"qualityQty": 0.000000,
"time": "18:00"
},
{
"productQty": 0.000000,
"qualityQty": 0.000000,
"time": "19:00"
},
{
"productQty": 0.000000,
"qualityQty": 0.000000,
"time": "20:00"
},
{
"productQty": 0.000000,
"qualityQty": 0.000000,
"time": "21:00"
},
{
"productQty": 0.000000,
"qualityQty": 0.000000,
"time": "22:00"
},
{
"productQty": 0.000000,
"qualityQty": 0.000000,
"time": "23:00"
}
],
//日产能概况
"productDayRecordList": [
{
"code": "WO202203300001", //工单号
"materialCode": "L5CP", //产品编码
"materialName": "L5车间成品", //产品名称
"planQty": 1000.000000, //计划数量
"productQty": 10.000000, //生产数量
"productRate": 0.010, //生产进度
"productStatus": "已完工", //生产状态
"unQualityQty": 0.000000, //不合格数量
"unQualityRate": 0.000, //不合格率
"workCenterCode": "L5", //产线编码
"workCenterName": "L5车间" //产线名称
},
{
"code": "WO202204010001",
"materialCode": "L5CP",
"materialName": "L5车间成品",
"planQty": 100.000000,
"productQty": 15.000000,
"productRate": 0.150,
"productStatus": "已创建",
"unQualityQty": 0.000000,
"unQualityRate": 0.000,
"workCenterCode": "L5",
"workCenterName": "L5车间"
},
{
"code": "WO202204010002",
"materialCode": "L5CP",
"materialName": "L5车间成品",
"planQty": 10.000000,
"productQty": 15.000000,
"productRate": 1.500,
"productStatus": "生产中",
"unQualityQty": 0.000000,
"unQualityRate": 0.000,
"workCenterCode": "L5",
"workCenterName": "L5车间"
},
{
"code": "WO202204010003",
"materialCode": "L5CP",
"materialName": "L5车间成品",
"planQty": 10.000000,
"productQty": 12.000000,
"productRate": 1.200,
"productStatus": "生产中",
"unQualityQty": 0.000000,
"unQualityRate": 0.000,
"workCenterCode": "L5",
"workCenterName": "L5车间"
},
{
"code": "WO202204060001",
"materialCode": "L5CP",
"materialName": "L5车间成品",
"planQty": 1000.000000,
"productQty": 14.000000,
"productRate": 0.014,
"productStatus": "生产中",
"unQualityQty": 0.000000,
"unQualityRate": 0.000,
"workCenterCode": "L5",
"workCenterName": "L5车间"
},
{
"code": "WO202204060002",
"materialCode": "L5CP",
"materialName": "L5车间成品",
"planQty": 10.000000,
"productQty": 12.000000,
"productRate": 1.200,
"productStatus": "生产中",
"unQualityQty": 0.000000,
"unQualityRate": 0.000,
"workCenterCode": "L5",
"workCenterName": "L5车间"
},
{
"code": "WO202204060003",
"materialCode": "L5CP",
"materialName": "L5车间成品",
"planQty": 10.000000,
"productQty": 14.000000,
"productRate": 1.400,
"productStatus": "生产中",
"unQualityQty": 0.000000,
"unQualityRate": 0.000,
"workCenterCode": "L5",
"workCenterName": "L5车间"
},
{
"code": "WO202204070001",
"materialCode": "L5CP",
"materialName": "L5车间成品",
"planQty": 10.000000,
"productQty": 52.000000,
"productRate": 5.200,
"productStatus": "生产中",
"unQualityQty": 1.000000,
"unQualityRate": 0.019,
"workCenterCode": "L5",
"workCenterName": "L5车间"
},
{
"code": "WO202204080001",
"materialCode": "L5CP",
"materialName": "L5车间成品",
"planQty": 12.000000,
"productQty": 55.000000,
"productRate": 4.583,
"productStatus": "生产中",
"unQualityQty": 52.000000,
"unQualityRate": 0.945,
"workCenterCode": "L5",
"workCenterName": "L5车间"
},
{
"code": "RcQuickWearStr",
"materialCode": "L5CP",
"materialName": "L5车间成品",
"planQty": 1000.000000,
"productQty": 0.000000,
"productRate": 0.000,
"productStatus": "待生产",
"unQualityQty": 0.000000,
"unQualityRate": 0.000,
"workCenterCode": "L5",
"workCenterName": "L5车间"
}
],
// 今日生产合格数
"qualityDayCount": {
"qualityQty": 146.000000, //合格数量
"qualityRate": 0.734, //合格率
"unQualityQty": 53.000000 //不合格数量
}
},
"success": true
};
const polarBarOpt = {
title: {
text: '计划总数',
left: 'center',
bottom: 24,
textStyle: {
fontSize: 20,
}
},
legend: {
show: false
},
radiusAxis: {
show: false,
type: 'category',
data: ['%']
},
tooltip: {
show: false,
},
series: {
type: 'gauge',
center: ['50%', "40%"],
radius: "70%",
startAngle: 90,
endAngle: -270,
pointer: {
show: false
},
progress: {
show: true,
overlap: false,
roundCap: false,
clip: false,
itemStyle: {
borderWidth: 0,
},
},
axisLine: {
lineStyle: {
width: 20,
color: [[1, 'rgba(40, 163, 255, 0.2)']],
}
},
splitLine: {
show: false,
},
axisTick: {
show: false
},
axisLabel: {
show: false,
},
data: [{
value: 0,
name: '生产总数'
}],
title: {
offsetCenter: ['0%', '20'],
color: "#fff",
fontSize: 20,
},
detail: {
show: true,
offsetCenter: ['0%', '-20'],
width: 'auto',
height: 32,
fontSize: 32,
color: '#00E1FA',
borderColor: 'auto',
borderWidth: 0,
formatter: '{value}'
}
}
};
const setNumOpt = (total, done, title) => {
let res = _.cloneDeep(polarBarOpt);
if(title) res.title.text = `${title}${total}`;
let percent = (100 * done / total).toFixed(2);
res.series.data[0].name = "生产总数";
res.series.data[0].value = percent;
res.series.detail.formatter = () => done;
return res;
}
const setOkNumOpt = (total, done, title) => {
let res = _.cloneDeep(polarBarOpt);
let percent = (100 * done / (total === 0 ? 1 : total)).toFixed(2);
if(isNaN(percent)) percent = 0;
if(title) res.title.text = `${title}${percent}%`;
res.series.data[0].value = percent;
res.series.data[0].name = "合格数量";
res.series.detail.formatter = () => done;
res.series.detail.color = "#6FE621";
res.series.progress.itemStyle.color = "#6FE621";
return res;
}
const barOpt = {
legend: {
show: true,
data: ['生产数量', '合格数量'],
top: 10,
right: 10,
textStyle: {
fontSize: 14,
},
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
xAxis: {
type: 'category',
axisTick: {
lineStyle: {opacity: 0.3},
alignWithLabel: true,
},
axisLine: {
lineStyle: {opacity: 0.3},
},
axisLabel: {
color: 'rgba(255, 255, 255, 0.8)'
},
data: []
},
yAxis: {
type: 'value',
axisTick: {
lineStyle: {opacity: 0.3}
},
axisLine: {
lineStyle: {opacity: 0.3}
},
axisLabel: {
color: 'rgba(255, 255, 255, 0.8)'
},
},
grid: {
bottom: 40,
top: 60,
left: 50,
},
barWidth: 20,
series: [{
name: '生产数量',
type: 'bar',
barGap: -1,
data: []
},
{
name: '合格数量',
itemStyle: {
color: "#6FE621",
},
type: 'bar',
data: []
}]
}
const setBarOpt = (data) => {
let res = _.cloneDeep(barOpt);
data.forEach(({productQty, qualityQty, time}, index) => {
res.xAxis.data.push(time);
res.series[0].data[index] = productQty;
res.series[1].data[index] = qualityQty;
})
return res;
};
const productTableColumns = [
{title: "机台", code: "workCenterName"},
{title: "生产状态", code: "productStatus",
color: val => {
if(val === '待生产') return "#d5542d";
if(val === '已创建') return "#1a7cc6";
if(val === '生产中') return "#0aac61";
if(val === '已完工') return "#0db5a4";
if(val === '已关闭') return "#c1c1c1";
if(val === '已取消') return "#c1c1c1";
},
render: (text, config, rowData) => {
const {color} = config;
let resColor = color(text);
return (
<div style={{color: resColor}}>
<span style={{
background: resColor,
width: '8px',
height: '8px',
display: 'inline-block',
borderRadius: '50%',
verticalAlign: 'middle',
}}></span>
<span style={{
verticalAlign: 'middle',
marginLeft: '6px',
}}>{text}</span>
</div>
)
},
},
{title: "产品编码", code: "materialCode"},
{title: "产品名称", code: "materialName"},
{title: "工单编号", code: "code"},
{title: "生产数量", code: "productQty"},
{title: "计划数量", code: "planQty"},
{title: "生产进度", code: "productRate",
width: 200,
render: (value, config, rowData) => {
return <TableProgress percent={value}/>
},
},
{title: "不良品数量", code: "unQualityQty"},
{title: "不良品率", code: "unQualityRate",
render: (value, config, rowData) => {
return <span>{value * 100}%</span>
},
},
]
function PlanBoard(props) {
const {config : {ioSocketUrl, planBoard = {}} = {}} = global;
const tableToNextPage = planBoard.productTableNext;
const {router} = props;
const planNumRef = useRef();
const okNumRef = useRef();
const productNumRef = useRef();
const productTableRef = useRef();
const socket = useRef();
const workCenterCodeRef = useRef();
const tableTimeoutRef = useRef();
const getQueryVariable = (variable) => {
let query = window.location.search.substring(1);
let vars = query.split("&");
for (let i = 0; i < vars.length; i++) {
let pair = vars[i].split("=");
if (pair[0] == variable) { return pair[1]; }
}
return (false);
}
const getData = () => {
let workCenterCode = workCenterCodeRef.current
let date = getQueryVariable('date');
console.log("fetch", workCenterCode)
if(!workCenterCode) return;
let query = {
workCenterCode,
screenType: "ProductPlanType",
}
if (date) query.date = date;
else{
query.date = new Date().toJSON().split('T').join(' ').substr(0,10)
}
socket.current.emit('timePerformanceGoodProduct', 'channel', {query})
setTimeGetData()
};
const setTimeGetData = () => {
setTimeout(() => {
getData()
}, 300000)
}
const pageFinished = useCallback(() => {
console.log("finished")
clearInterval(tableTimeoutRef.current);
getData();
})
const setTableNextPage = useCallback(() => {
tableTimeoutRef.current = setInterval(() => {
productTableRef.current.nextPage();
}, tableToNextPage);
}, [])
const update = (data) => {
try{ data = JSON.parse(data) }catch(e){console.log(e)}
// if(!data) data = testData;
const {productCount, productDayCountList, qualityDayCount, productDayRecordList} = data.data;
console.log("data", data.data)
planNumRef.current.setOption(setNumOpt(
productCount.planQty,
productCount.productQty,
"计划总数"
));
okNumRef.current.setOption(setOkNumOpt(
qualityDayCount.qualityQty,
qualityDayCount.qualityQty - qualityDayCount.unQualityQty,
"合格率"
));
productNumRef.current.setOption(setBarOpt(productDayCountList));
productTableRef.current.setData(productDayRecordList);
setTableNextPage();
};
useEffect(() => {
const {workCenterCode} = router.query;
if(!workCenterCode) return;
workCenterCodeRef.current = workCenterCode;
}, [router])
useEffect(() => {
socket.current = io.connect(ioSocketUrl, {transports:['websocket']});
socket.current.on('connect', function() {
console.log("msg页面连接成功");
getData();
});
socket.current.on('timePerformanceGoodProduct', function(data) {
update(data)
});
// update();
}, [])
return (
<div className='screen-container'>
<Header title={"计划看板"} />
<div className="screen-container-content">
<Row className='height-45' gutter={gutter} bottom={bottom}>
<Row.Col span={5}>
<Card title="今日生产总数" titleSpace={true} overVisible={true}>
<Chart ref={planNumRef} />
</Card>
</Row.Col>
<Row.Col span={14}>
<Card title="今日生产统计" overVisible={true}>
<Chart ref={productNumRef} />
</Card>
</Row.Col>
<Row.Col span={5}>
<Card title="今日生产合格总数" titleSpace={true} overVisible={true}>
<Chart ref={okNumRef} />
</Card>
</Row.Col>
</Row>
<Row className='height-55' gutter={gutter}>
<Row.Col span={24}>
<Card title="生产概况" titleSpace={true} padding={`15px ${gutter}px`}>
<Table
ref={productTableRef}
mainKey="code"
columns={productTableColumns}
autoNext={false}
pageFinished={pageFinished}
/>
</Card>
</Row.Col>
</Row>
</div>
<Footer />
<style jsx>{`
.screen-container{
height: 100vh;
overflow: auto;
background: url('/img/bg.png') center center;
position: relative;
padding-top: 96px;
padding-bottom: 82px;
}
.screen-container-content{
height: 100%;
padding: 0 ${gutter}px;
}
`}</style>
</div>
);
}
export default withRouter(PlanBoard)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,416 @@
import {useRouter} from "next/router";
import React, {useMemo, useState, useRef, useEffect} from "react";
import _ from "lodash";
import moment from 'moment';
import Footer from "../../../components/screen/Footer";
import Header from "../../../components/screen/Header";
import Chart from "../../../components/screen/Chart";
import Row from "../../../components/screen/Row";
import Card from '../../../components/screen/Card';
import Table from "../../../components/screen/Table";
import ProcessCard from "../../components/ProcessCard";
import {useSocket} from "../../../utils/hooks";
/**
* 生产工序总图
*/
const socketScreenType = 'AllOperationType';
const gutter = 20;
const bottom = 0;
//关键生产信息
const getProdOpt = (workCenter, data) => {
let {xData = [], yData = []} = data;
let opt = {
xAxis: {
type: 'category',
data: []
},
title: {
text: `${workCenter}日生产进度`,
textStyle: {
fontSize: "14px"
},
top: 15,
left: 60
},
yAxis: {
type: 'value'
},
grid: {
top: 50,
left: 55
},
series: [
{
data: yData,
type: 'bar'
}
]
};
let newXData = []
xData.forEach(item => {
let date = (new Date(item).getMonth() + 1) + "." + new Date(item).getDate();
newXData.push(date)
})
opt.xAxis.data = newXData;
return opt;
};
//关键能耗信息
const getUsedOpt = (data = {}, params = []) => {
let {
energyOfDay = 0, //电
energyOfMonth = 0,
gasOfDay = 0, //气
gasOfMonth = 0,
waterOfDay = 0,//水
waterOfMonth = 0,
} = data;
if (params.length > 0) {
params[0].values[0].value = energyOfMonth;
params[0].values[1].value = energyOfDay;
params[1].values[0].value = waterOfMonth;
params[1].values[1].value = waterOfDay;
params[2].values[0].value = gasOfMonth;
params[2].values[1].value = gasOfDay;
}
let opt = {
title: {
text: "",
left: "center",
top: 70,
textStyle: {
fontWeight: "normal",
fontSize: "16px"
},
},
tooltip: {
trigger: "item",
},
legend: {
bottom: 10,
left: "center",
orient: "vertical",
textStyle: {
fontSize: 13,
lineHeight: 16
},
},
series: [
{
type: "pie",
radius: ["45%", "70%"],
center: ["50%", "40%"],
avoidLabelOverlap: false,
label: {
show: false,
position: "center",
},
labelLine: {
show: false,
},
data: [],
},
],
};
let res = [];
let colors = ["#6FE621", "#FF005A", "#4D7BF3", "#4FCCFF"]
params.forEach(({name, unit, values}, index) => {
let newOpt = _.cloneDeep(opt);
newOpt.color = [colors[index], "#FFC760"];
newOpt.title.text = `${name}\n(${unit})`;
newOpt.series[0].data = values;
newOpt.legend.formatter = name => {
let {value = ""} = values.find(item => item.name === name) || {};
return `${name}: ${value}`
}
res.push(newOpt);
});
return res;
};
//维保列表字段
const prodTableColumns = [
{title: "任务编码", code: "code"},
{title: "维保类型", code: "type"},
{title: "设备名称", code: "equipSerialName"},
{title: "任务状态", code: "status"},
]
const ProcessBoard = () => {
const [isClear, setClear] = useState(true)
const router = useRouter();
const productTableRef = useRef();
const tableTimeoutRef = useRef();
const spacing = [30, 50]; //工序流程图间距
const {
processBoard: {
workCenter,
taskTableNext,
reloadTime,
pages = {},
energyParams = []
} = {}
} = global.config || {};
const {workCenterCode, date = moment().format('YYYY-MM-DD')} = router.query;
const title = pages['title'];
const [{productionRecords = {}, maintenance = [], coreProcess = [], energy = {}} = {},
reload,
] = useSocket({socketScreenType, workCenterCode, date});
//核心工序数据分组
const [ruleLength, setRuleLength] = useState(6); //一行摆放多少个
const memoData = useMemo(() => {
let res = [];
if (coreProcess.length > 0) {
let newCoreProcess = _.cloneDeep(coreProcess) || [];
let ccIndex = _.findIndex(coreProcess, item => item.code == "L7-CC"); //除磁->双层除磁
newCoreProcess[ccIndex].code = 'L7-SCCC'
newCoreProcess[ccIndex].name = '双层除磁'
let xmIndex = _.findIndex(coreProcess, item => item.code == "L7-XM"); //细磨
newCoreProcess.splice(xmIndex + 1, 0, coreProcess[ccIndex]) //细磨后面添加除磁
newCoreProcess.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;
}, [coreProcess, ruleLength]);
console.log("memoData", memoData)
//关键生产信息
const prodData = useMemo(() => {
return getProdOpt(workCenter, productionRecords);
}, [productionRecords]);
//关键能耗信息
const usedInfo = useMemo(() => {
return getUsedOpt(energy, energyParams);
}, [energy])
//表格定时刷新
useEffect(() => {
tableTimeoutRef.current = setInterval(() => {
productTableRef.current && productTableRef.current.nextPage();
}, taskTableNext);
return () => {
clearInterval(tableTimeoutRef.current)
}
}, [])
//整体刷新
useEffect(() => {
tableTimeoutRef.current = setInterval(() => {
reload()
}, reloadTime);
return () => {
clearInterval(tableTimeoutRef.current)
}
}, [reload])
return (
<div className="screen-container">
{isClear && (
<React.Fragment>
<Header title={title}/>
<div className="screen-container-content">
<Row className='height-100' gutter={gutter} bottom={bottom}>
<Row.Col span={17}>
<Card title="核心工序流程图" overVisible={true}>
<div className='process-flow-list'>
<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['code']}>
<ProcessCard itemData={item}/>
</div>
);
})}
</div>
);
})}
</div>
</div>
</Card>
</Row.Col>
<Row.Col span={7}>
<Card>
<div className="used-content">
<Row className="height-30">
<Card title="关键生产信息" titleSpace={true} overVisible={true}
withBorder={false}>
<Chart option={prodData}/>
</Card>
</Row>
<Row className="height-30">
<Card title="关键能耗信息" titleSpace={true} overVisible={true}
withBorder={false} padding={"0 16px"}>
<Row className='height-100'>
{usedInfo.map((opt, index) => {
return (
<Row.Col span={8} key={index}>
<Chart option={opt}/>
</Row.Col>
);
})}
</Row>
</Card>
</Row>
<Row className="height-40">
<Card title="车间维保计划" titleSpace={true} overVisible={true}
withBorder={false} padding={"16px"}>
<Table
ref={productTableRef}
mainKey="taskCode"
columns={prodTableColumns}
dataSource={maintenance}
/>
</Card>
</Row>
</div>
</Card>
</Row.Col>
</Row>
</div>
<Footer/>
</React.Fragment>
)}
<style jsx>{`
.screen-container {
height: 100vh;
min-height: 1080px;
min-width: 1920px;
background: url("/img/bg.png") center center;
position: relative;
padding-top: 96px;
padding-bottom: 82px;
overflow: hidden;
}
.screen-container-content {
height: 100%;
padding: ${gutter}px;
}
.process-flow-list {
width: 100%;
height: 100%;
padding: 50px 30px 30px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.list-flex {
width: 100%;
height: 100%;
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: 42px;
height: 42px;
right: 0;
left: unset;
bottom: unset;
top: 50%;
transform: translate(50%, -50%);
background: url('/img/arrow.png') center center no-repeat;
z-index: 9;
}
.reverse {
justify-content: space-between;
}
// 左箭头
.list-flex-row.reverse .list-flex-item::after {
width: 42px;
height: 42px;
left: -${spacing[1] / 2}px;
right: unset;
top: 50%;
bottom: unset;
transform: translate(-50%, -50%) rotate(180deg);
transform-origin: 50% 50%;
background: url('/img/arrow.png') center center no-repeat;
z-index: 9;
}
// 下箭头
.list-flex-row .list-flex-item:last-child::after {
width: 42px;
height: 42px;
left: unset;
right: calc(50% - 44px);
top: unset;
bottom: -${(spacing[1] - 44) / 2}px;
transform: translate(-50%, 100%) rotate(90deg);
transform-origin: 50% 50%;
background: url('/img/arrow.png') center center no-repeat;
z-index: 9;
}
// 无箭头
.list-flex-row:last-child .list-flex-item:last-child::after {
display: none;
}
.used-content {
overflow: hidden;
height: 100%;
}
.used-content :global(.screen-card) {
border: none !important;
}
`}</style>
</div>
);
};
export default ProcessBoard;

View File

@ -0,0 +1,809 @@
import {useRouter} from "next/router";
import React, {useEffect, useMemo, useRef, useState} from "react";
import {Carousel} from 'antd'
import _ from "lodash";
import Footer from "../../../components/screen/Footer";
import Header from "../../../components/screen/Header";
import Chart from "../../../components/screen/Chart";
import Row from "../../../components/screen/Row";
import Card from '../../../components/screen/Card';
import ProcessTaskCard from "../../components/processTaskCard";
import {useSocket} from "../../../utils/hooks";
import moment from "moment/moment";
/**
* 生产分工序
*/
const socketScreenType = 'BranchOperationType';
const gutter = 20;
const bottom = 0;
const chartsBgColor = '#102242';
//关键生产信息
const getProdOpt = ({xData, yData}) => {
let opt = {
xAxis: {
type: 'category',
data: []
},
title: {
text: "L7车间生产进度",
textStyle: {
fontSize: "14px"
},
top: 10,
left: 60
},
yAxis: {
type: 'value'
},
grid: {
top: 40,
left: 55
},
series: [
{
data: [],
type: 'bar'
}
]
};
opt.xAxis.data = xData
opt.series[0].data = yData
return opt;
};
//车间OEE
const getOEEOpt = (data = {}, params = []) => {
const {oeeOfMonth = {}, oeeOfDay = {}} = data;
// passRate 良品率, performanceRate 性能稼动率, timeRate 时间稼动率, oee OEE
if (params.length > 0) {
params[0].values = [oeeOfDay.timeRate || 0, oeeOfDay.performanceRate || 0, oeeOfDay.passRate || 0] //当日
params[0].result = [oeeOfDay.oee || 0]
params[1].values = [oeeOfMonth.timeRate || 0, oeeOfMonth.performanceRate || 0, oeeOfMonth.passRate || 0] //当日
params[1].result = [oeeOfMonth.oee || 0]
}
let opt = {
title: {
text: "",
left: "center",
top: 180,
},
graphic: {
elements: [
{
type: "text",
left: "center",
top: 85,
style: {
fill: "#fff",
text: "",
fontSize: 16,
fontWeight: "bold",
},
},
],
},
legend: {
// icon: "rect",
data: ["时间稼动率", "性能稼动率", "良品率"],
bottom: 10,
orient: "vertical",
itemHeight: 16,
textStyle: {
fontSize: 14,
lineHeight: 14,
},
},
polar: {
radius: ["25%", "65%"],
center: ["50%", "30%"],
},
angleAxis: {
show: false,
max: 100,
startAngle: 90,
},
radiusAxis: {
show: false,
type: "category",
data: ["%"],
},
tooltip: {
formatter: "{a}",
},
series: [
{
name: "时间稼动率",
type: "bar",
barGap: "0",
barWidth: 14,
data: [12],
coordinateSystem: "polar",
},
{
name: "性能稼动率",
type: "bar",
barGap: "0",
barWidth: 14,
data: [13],
coordinateSystem: "polar",
},
{
name: "良品率",
type: "bar",
barGap: "0",
barWidth: 14,
data: [14],
coordinateSystem: "polar",
},
],
};
let res = [];
params.forEach(({name, values, result}) => {
let newOpt = _.cloneDeep(opt);
newOpt.title.text = name;
newOpt.graphic.elements[0].style.text = result + "%";
const names = ["时间稼动率 ", "性能稼动率 ", "良品率 "];
newOpt.legend.data = names.map(
(name, index) => (name = name + ` ${values[index]}%`)
);
newOpt.series = newOpt.series.map((item, index) => {
item.name = names[index] + ` ${values[index]}%`;
item.data[0] = values[index];
return item;
});
res.push(newOpt);
});
return res;
};
//能耗信息
const getUsedOpt = (data = {}, params = []) => {
const {
energyOfMonth = 0, //电KW/H 今日用量
energyOfDay = 0, // 当月累计用量
gasOfMonth = 0, //气 (m3)
gasOfDay = 0,
waterOfDay = 0, //水T
waterOfMonth = 0,
} = data;
if (params.length > 0) {
params[0].values[0].value = gasOfMonth;
params[0].values[1].value = gasOfDay;
params[1].values[0].value = waterOfMonth;
params[1].values[1].value = waterOfDay;
params[2].values[0].value = energyOfMonth;
params[2].values[1].value = energyOfDay;
}
let opt = {
title: {
text: "",
left: "center",
top: 70,
textStyle: {
fontWeight: "normal",
fontSize: "16px"
},
},
tooltip: {
trigger: "item",
},
legend: {
bottom: 15,
left: "center",
orient: "vertical",
textStyle: {
fontSize: 14,
lineHeight: 14
},
},
series: [
{
type: "pie",
radius: ["45%", "70%"],
center: ["50%", "40%"],
avoidLabelOverlap: false,
label: {
show: false,
position: "center",
},
labelLine: {
show: false,
},
data: [],
},
],
};
let res = [];
let colors = ["#6FE621", "#FF005A", "#4D7BF3", "#4FCCFF"]
params.forEach(({name, unit, values}, index) => {
let newOpt = _.cloneDeep(opt);
newOpt.color = [colors[index], "#FFC760"];
newOpt.title.text = `${name}\n(${unit})`;
newOpt.series[0].data = values;
newOpt.legend.formatter = name => {
let {value = ""} = values.find(item => item.name === name) || {};
return `${name}: ${value}`
}
res.push(newOpt);
});
return res;
};
//设备监控参数
const getEquipMonitorOpt = (data = []) => {
let option = {
type: "equip",
backgroundColor: chartsBgColor,
grid: {
top: 25,
left: 60,
right: "5%"
},
title: {
text: "",
x: "center",
y: "top",
textStyle: {
fontSize: "14px"
}
},
xAxis: {
type: "category",
data: [],
},
yAxis: {
type: "value",
min: 0,
},
series: [
{
symbol: "circle",
symbolSize: 5,
data: [],
type: "line",
},
],
};
let newOpt = []
if (data.length > 0) {
data.forEach((item, index) => {
let opt = _.cloneDeep(option);
opt.title.text = item.name;
opt.xAxis.data = item.time.slice().reverse();
opt.series[0].data = item.data.slice().reverse();
newOpt.push(opt)
})
}
return newOpt;
}
//质量监控参数
const getQualityMonitorOpt = (data = {}) => {
let res = [];
let option = {
type: "quality",
backgroundColor: chartsBgColor,
title: {
show: false,
/* text: '',
x: 'right',
textStyle: {
color: '#ffffff',
fontSize: "14px"
}*/
},
tooltip: {
trigger: 'axis'
},
markLine: {},
legend: {
x: 'left',
y: 'bottom',
// data: ['极差值', 'UCLmr', 'LCLmr', 'CLmr'],
textStyle: { //文字 样式
color: '#ffffff',
fontSize: "12px"
},
itemStyle: { //圆点样式
opacity: 0
},
lineStyle: { //线样式
// type: [4, 4],
dashOffset: 4
}
},
grid: {
top: 15,
left: '3%',
right: '3%',
bottom: '15%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: [],
axisLine: {
show: true,
lineStyle: {
type: 'solid',
color: '#ffffff'
}
}
},
yAxis: {
type: 'value',
splitNumber: 8,
//轴线
axisLine: {
show: true,
lineStyle: {
type: 'solid',
color: '#ffffff'
}
},
splitLine: {
lineStyle: {
color: '#ffffff'
}
}
},
series: []
};
if (data.spcBasicDatas && data.topData) {
let {spcBasicDatas = [], topData = {}} = data;
let {
usl = 0, lsl = 0,
uclx = 0, averx = 0, lclx = 0,
uclr = 0, averr = 0, lclr = 0,
} = topData
let xData = [], yValData = [], yMrValData = [];
spcBasicDatas.forEach(item => {
if (!item.seq) item.seq = 0
if (!item.value) item.value = 0
if (!item.mrValue) item.mrValue = 0
xData.push(item.seq)
yValData.push(item.value)
yMrValData.push(item.mrValue)
})
//标准点数据处理
let basicOpt = _.cloneDeep(option);
const basicName = ['数据点', 'USLi', 'UCli', 'CLi', 'LCLi', 'LSLi'];
basicOpt.xAxis.data = xData
let min = _.min(yValData)
basicOpt.yAxis.min = min;
basicName.forEach((item, index) => {
basicOpt.series[index] = {
type: 'line',
smooth: true, //平滑
symbolSize: 4,
symbol: 'circle',
data: [],
lineStyle: {
type: 'dashed', //'dotted'点线 'dashed' 虚线 'solid' 实线
width: 1
},
markLine: {
symbol: 'none',
label: {
show: false
},
lineStyle: {},
data: [{
yAxis: 0,
x: '6.8%'
}]
}
}
basicOpt.series[index].name = basicName[index]
})
basicOpt.series[0].data = yValData;
basicOpt.series[0].lineStyle.type = "solid";
basicOpt.series[1].markLine.data[0].yAxis = usl
basicOpt.series[2].markLine.data[0].yAxis = lsl
basicOpt.series[3].markLine.data[0].yAxis = uclx
basicOpt.series[4].markLine.data[0].yAxis = averx
basicOpt.series[5].markLine.data[0].yAxis = lclx
//极差点数据处理
let mrOpt = _.cloneDeep(option);
const mrName = ['极差点', 'UCLmr', 'CLmr', 'LCLmr'];
mrOpt.xAxis.data = xData
mrName.forEach((item, index) => {
mrOpt.series[index] = {
type: 'line',
smooth: true, //平滑
symbolSize: 4,
data: [],
lineStyle: {
type: 'dashed', //'dashed'点线 'dashed' 虚线 'solid' 实线
width: 1
},
markLine: {
symbol: 'none',
label: {
show: false
},
data: [{
yAxis: 0,
x: '6.8%'
}]
}
}
mrOpt.series[index].name = mrName[index]
})
mrOpt.series[0].data = yMrValData;
mrOpt.series[0].lineStyle.type = "solid";
mrOpt.series[1].markLine.data[0].yAxis = uclr
mrOpt.series[2].markLine.data[0].yAxis = averr
mrOpt.series[3].markLine.data[0].yAxis = lclr
res = _.concat([], [basicOpt], [mrOpt])
}
return res;
}
const ProcessItem = () => {
const tableTimeoutRef = useRef();
const equipMonitorRef = useRef();
const router = useRouter();
const [isClear, setClear] = useState(false)
const [length, setLength] = useState(4) //卡片轮播个数
const [len, setLen] = useState(2) //监控轮播个数
const [height, setHeight] = useState(0);
const [width, setWidth] = useState(0);
const [monitorTitle, setMonitorTitle] = useState("设备")
const [isQuality, setIsQuality] = useState(false)
const {operationCode, workCenterCode, date = moment().format('YYYY-MM-DD')} = router.query;
const {
processItem: {
reloadTime, cardAutoNext, pages = {}, energyParams = [], OEEParams = []
} = {}
} = global.config || {};
//监控类型名,质量、设备
let title = "";
if (operationCode && pages[operationCode]) {
title = pages[operationCode].title;
}
const [
{
keyEnergy = {}, //关键能耗
keyProduction = {}, //关键生产
taskExecution = [], //任务执行状态
oee = {}, //车间OEE
paramMonitor = {} //监控
} = {},
reload,
] = useSocket({socketScreenType, operationCode, workCenterCode, date});
const {iotEquipResultData = [], spcResultData = {}} = paramMonitor; //监控
const {topData = {}} = spcResultData;
//质量监控检测项
let featureName = "";
if (topData.featureName) {
featureName = topData.featureName
}
//工序任务执行状态
const taskExecutData = useMemo(() => {
let res = [];
taskExecution.forEach((item, index) => {
let row = Math.ceil((index + 1) / length);
let rowIndex = index % length;
if (!res[row]) res[row] = [];
res[row][rowIndex] = item;
})
res = res.filter(item => item);
return res;
}, [taskExecution]);
//设备、质量参数监控
const monitorOpt = useMemo(() => {
let equipData = getEquipMonitorOpt(iotEquipResultData);
let qualityData = getQualityMonitorOpt(spcResultData);
let res = [], equipRes = [], qualityRes = [];
//设备参数分组
equipData.forEach((item, index) => {
let row = Math.ceil((index + 1) / len);
let rowIndex = index % len;
if (!equipRes[row]) equipRes[row] = [];
equipRes[row][rowIndex] = item;
})
//质量参数分组
qualityData.forEach((item, index) => {
let row = Math.ceil((index + 1) / len);
let rowIndex = index % len;
if (!qualityRes[row]) qualityRes[row] = [];
qualityRes[row][rowIndex] = item;
})
res = _.concat([], equipRes, qualityRes)
res = res.filter(item => item);
return res
}, [iotEquipResultData, spcResultData])
//关键生产信息
const prodOpt = useMemo(() => {
return getProdOpt(keyProduction)
}, [keyProduction])
//车间能耗信息
const usedOpt = useMemo(() => {
return getUsedOpt(keyEnergy, energyParams);
}, [keyEnergy])
//车间OEE
const OEEOpt = useMemo(() => {
return getOEEOpt(oee, OEEParams);
}, [oee]);
useEffect(() => {
if (operationCode) {
setClear(true);
}
}, [operationCode]);
useEffect(() => {
setTimeout(() => {
//轮播图标的宽高设置
if (equipMonitorRef.current) {
setHeight(equipMonitorRef.current.clientHeight)
setWidth(equipMonitorRef.current.clientWidth)
}
}, 100)
}, [])
//整体刷新
useEffect(() => {
tableTimeoutRef.current = setInterval(() => {
reload()
}, reloadTime);
return () => {
clearInterval(tableTimeoutRef.current)
}
}, [reload])
useEffect(() => {
if (iotEquipResultData.length <= 0 && _.get(spcResultData, "spcBasicDatas.length") > 0) {
setMonitorTitle('质量')
setIsQuality(true)
}
}, [iotEquipResultData, spcResultData])
//当前轮播位置,更新标题
const afterChange = (current) => {
let index = _.findIndex(monitorOpt, item => item[0].type == 'quality');
if (index == current) {
setMonitorTitle('质量')
setIsQuality(true)
} else {
setMonitorTitle('设备')
setIsQuality(false)
}
}
return (
<div className="screen-container">
{isClear && (
<React.Fragment>
<Header title={`${title}工序`}/>
<div className="screen-container-content">
<Row className='height-100' gutter={gutter} bottom={bottom}>
<Row.Col span={5}>
<Row className="height-55">
<Card title={`${title}工序设备示意图`} overVisible={true}
padding={"40px 20px 25px 20px"}>
<div className='screen-img img-gxsb'/>
</Card>
</Row>
<Row className="height-45" style={{paddingTop: `${gutter}px`}}>
<Card title={`${title}工序成品示意图`} overVisible={true}
padding={"40px 20px 25px 20px"}>
<div className='screen-img img-gxzzp'/>
</Card>
</Row>
</Row.Col>
<Row.Col span={12}>
<Row className="height-55">
<Card title="工序任务执行状态" overVisible={true} padding={"40px 20px 25px 20px"}>
<div className='process-task-list'>
<Carousel autoplay dots={false} autoplaySpeed={cardAutoNext}>
{taskExecutData.map((row, rowIndex) => {
return <div key={Math.random()}>
<div className='process-task'>
{row.map((item, index) => {
return <div className='process-task-item'
key={item.code}>
<ProcessTaskCard itemData={item}
operationCode={operationCode}/>
</div>
})}
</div>
</div>
})}
</Carousel>
</div>
</Card>
</Row>
<Row className="height-45" style={{paddingTop: `${gutter}px`}}>
<Card title={`${monitorTitle}参数监控`} overVisible={true}
padding={"35px 16px 16px 16px"}>
{
isQuality &&
<div className='monitor-quality-item'>
{`最新检测项:${featureName}`}
</div>
}
<div className='equip-monitor-list' ref={equipMonitorRef}>
<Row className='height-100'>
<Carousel autoplay dots={false} dotPosition={"right"}
autoplaySpeed={cardAutoNext} adaptiveHeight={true}
afterChange={afterChange}>
{monitorOpt.map((row, rowIndex) => {
return <div>
<div key={Math.random()}
style={{height: height, width: width}}>
{row.map((item, index) => {
return <div className='equip-monitor-item'
key={rowIndex + index}>
<Chart option={item}/>
</div>
})}
</div>
</div>
})}
</Carousel>
</Row>
</div>
</Card>
</Row>
</Row.Col>
<Row.Col span={7}>
<Card>
<div className="used-content">
<Row className="height-30">
<Card title="关键生产信息" titleSpace={true} overVisible={true}
withBorder={false}>
<Chart option={prodOpt}/>
</Card>
</Row>
<Row className="height-30">
<Card title="关键能耗信息" titleSpace={true} overVisible={true}
withBorder={false} padding={"0 16px"}>
<Row className='height-100'>
{usedOpt.map((opt, index) => {
return (
<Row.Col span={8} key={index}>
<Chart option={opt}/>
</Row.Col>
);
})}
</Row>
</Card>
</Row>
<Row className="height-40">
<Card title="车间OEE" titleSpace={true} overVisible={true}
withBorder={false} padding={"0 16px"}>
<Row className='height-100'>
{OEEOpt.map((opt, index) => {
return (
<Row.Col span={12} key={index}>
<Chart option={opt}/>
</Row.Col>
);
})}
</Row>
</Card>
</Row>
</div>
</Card>
</Row.Col>
</Row>
</div>
<Footer/>
</React.Fragment>
)
}
<style jsx>{`
.screen-container {
font-family: PingFangSC-Semibold;
height: 100vh;
min-height: 1080px;
min-width: 1920px;
background: url("/img/bg.png") center center;
position: relative;
padding-top: 96px;
padding-bottom: 82px;
overflow: hidden;
}
.screen-container-content {
height: 100%;
padding: ${gutter}px;
}
.OEE-content {
position: absolute;
width: 900px;
height: 320px;
top: 660px;
left: 460px;
}
.monitor-quality-item {
text-align: right;
margin-top: -22px;
font-weight: bold;
}
.equip-monitor-list {
width: 100%;
height: 100%;
overflow: hidden;
}
.equip-monitor-item {
width: 100%;
height: 50%;;
padding: 4px 0;
}
.process-task-list {
width: 100%;
height: 100%;
overflow: hidden;
}
.process-task {
display: flex;
justify-content: space-around;
}
.process-task-item {
width: calc(100% / 4);
padding: 0 16px;
}
.used-content {
overflow: hidden;
height: 100%;
}
.used-content :global(.screen-card) {
border: none !important;
}
.aaa {
width: 50%;
display: flex;
justify-content: center;
}
.screen-img {
height: 100%;
}
.img-gxsb {
background: url(/img/process/${operationCode}-SB.png) center center no-repeat;
}
.img-gxzzp {
background: url(/img/process/${operationCode}-ZZP.png) center center no-repeat;
}
`}</style>
</div>
);
};
export default ProcessItem;

File diff suppressed because it is too large Load Diff

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

179
public/config.js Normal file
View File

@ -0,0 +1,179 @@
(function () {
const gethost = () => {
let host = window.location.host;
// let host = '120.202.38.15:8081';
// host = '120.202.38.15:8081';
console.log(host)
if (host.indexOf("localhost") != -1 || host.indexOf("137.65") != -1) {
host = '120.202.38.15:8081';
// host = '10.10.21.8';
}
return host;
};
window.host = gethost();
window.config = {
// "ioSocketUrl": "http://192.168.137.212:4553",
// "ioSocketUrl": "http://192.168.136.234:4553",
// "ioSocketUrl": "http://192.168.132.204:4553",
// "ioSocketUrl": "http://192.168.132.204:4553",
// "mesUrl": "http://120.202.38.15:8081/gateway",
// "mesUrl": "http://192.168.132.204/gateway",
// "ioSocketUrl": "http://10.10.21.73:4553",
// "ioSocketUrl": "http://192.168.136.213:4553",
// "ioSocketUrl": "http://120.202.38.15:8081/",
mesHost: "http://" + gethost(),
ioSocketUrl: "http://" + gethost(),
mesUrl: "http://" + gethost() + "/gateway",
edgeQueryUrl: "http://" + gethost() + "/iot/edge/application/api",
edgeUrl: "http://" + gethost() + "/iot/edge/cxf/rongtong-edge-application",
edgewsUrl: "ws://" + gethost() + "/rongtong-edge-application-ws/",
equipservice: "/gateway/md-mdm-deploy-v1/md/bmEquipment",
ImgUrl: "/console/fs/file/viewimage?fileCode=",
refreshInterval: 15000,
packagingOperaConfig: {
reloadTime: 60 * 1000,
},
equipmentConfig: {
equipAbnormalNext: 4000, // 设备异常监控翻页时间
equipListNext: 4000, // 设备运行状态翻页时间
equipListSpan: 4, // 设备运行状态栅格值24分如设置 4 则每行展示 24/4 = 6 个
equipItemLength: 6, // 设备运行状态卡片内容条数
},
planBoard: {
productTableNext: 4000,
},
planDailyConfig: {
reloadTime: 5000, // 计划看板轮询时间
isSocket: true, // 是否使用socket服务
},
// 质量看板
qualityBoard: {
threeDayRejectTableNext: 4000, // 近三天不良率表格翻页时间
taskRemindTableNext: 4000, // 任务提醒表格翻页时间
},
// 工序总图看板
processBoard: {
workCenter: "L7车间",
reloadTime: 300000, //整体刷新时间
taskTableNext: 10000, //表格翻页时间
energyParams: [ //能耗参数
{
name: "电",
unit: "KW/h",
values: [
{name: "当月累计用量", value: 0},
{name: "今日用量", value: 0},
],
},
{
name: "水",
unit: "KG",
values: [
{name: "当月累计用量", value: 0},
{name: "今日用量", value: 0},
],
},
{
name: "气",
unit: "m³",
values: [
{name: "当月累计用量", value: 0},
{name: "今日用量", value: 0},
],
},
],
pages: {
// 键名对应路由 screen/processBoard?pid={key}
// 同时对应图片 /img/processBg/{key}.png
title: "工序总图",
withChart: {}
},
},
processItem: {
reloadTime: 300000, //整体刷新时间
cardAutoNext: 5000, //卡片轮播时间间隔
//能耗参数
energyParams: [
{
name: "电",
unit: "KW/h",
values: [
{name: "当月累计用量", value: 0},
{name: "今日用量", value: 0},
],
},
{
name: "水",
unit: "KG",
values: [
{name: "当月累计用量", value: 0},
{name: "今日用量", value: 0},
],
},
{
name: "气",
unit: "m³",
values: [
{name: "当月累计用量", value: 0},
{name: "今日用量", value: 0},
],
},
],
// 设备OEE数据配置
OEEParams: [
{
name: "当日OEE", // 标题
values: [], // 依次:时间, 性能, 良品 百分比值 必须填三个
result: "0", // 图形中间值
},
{
name: "当月OEE",
values: [],
result: "0",
},
],
pages: {
"L7-TL": {
title: "投料",
withChart: {},
},
"L7-YH": {
title: "预混",
withChart: {},
},
"L7-CM": {
title: "粗磨",
withChart: {},
},
"L7-XM": {
title: "细磨",
withChart: {},
},
"L7-PWGZ": {
title: "喷雾干燥",
withChart: {},
},
"L7-SJ": {
title: "烧结",
withChart: {},
},
"L7-FS": {
title: "粉碎",
withChart: {},
},
"L7-CC": {
title: "除磁",
withChart: {},
},
"L7-HL": {
title: "混料",
withChart: {},
},
"L7-BZ": {
title: "包装",
withChart: {},
},
}
}
};
})();

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

BIN
public/font/PingFang SC.ttf Normal file

Binary file not shown.

BIN
public/font/UnidreamLED.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,48 @@
Copyright (c) 2015 by Hubert & Fischer. All rights reserved.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the copyright statement(s).
"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.

BIN
public/img/arrow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
public/img/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
public/img/dc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
public/img/eqimg.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
public/img/footerBg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
public/img/headerBg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

BIN
public/img/hpPercent.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
public/img/img1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
public/img/jt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

BIN
public/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
public/img/noImg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

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