Commit fbfe7798 authored by 崔佳豪's avatar 崔佳豪

fix: 消息重构

parent cdba8029
...@@ -2,8 +2,10 @@ import React, { Component, useMemo, useState } from 'react'; ...@@ -2,8 +2,10 @@ import React, { Component, useMemo, useState } from 'react';
import { Button, Form, Input, Modal, notification, Pagination } from 'antd'; import { Button, Form, Input, Modal, notification, Pagination } from 'antd';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Notifier, NoticeIcon, FormattedMessage } from '@wisdom-utils/components'; import { FormattedMessage } from '@wisdom-utils/components';
import { ERR_OK, MESSAGE_TYPE, NEW_MESSAGE } from '@wisdom-utils/components/lib/AppLayout/notifier/constants'; import Notifier from '../../layouts/AppLayout/notifier/notice';
import NoticeIcon from '../../layouts/AppLayout/notifier';
import { ERR_OK, MESSAGE_TYPE, NEW_MESSAGE } from '../../layouts/AppLayout/notifier/constants';
import { findPathByWidget, isJSON } from '@wisdom-utils/components/lib/AppLayout/helpers'; import { findPathByWidget, isJSON } from '@wisdom-utils/components/lib/AppLayout/helpers';
import service from '../../api/service/notification'; import service from '../../api/service/notification';
import { actionCreators } from '../../containers/App/store'; import { actionCreators } from '../../containers/App/store';
...@@ -24,8 +26,11 @@ const PlatformModal = ({ platformVisible, handleClosePlatform, handlerMointer, m ...@@ -24,8 +26,11 @@ const PlatformModal = ({ platformVisible, handleClosePlatform, handlerMointer, m
<Modal <Modal
title={<FormattedMessage id='component.noticeIcon.modal.alarm.title'/>} title={<FormattedMessage id='component.noticeIcon.modal.alarm.title'/>}
maskClosable={false} maskClosable={false}
mask={false}
maskStyle={{ pointerEvents: 'none'}}
visible={platformVisible} visible={platformVisible}
zIndex={5000} zIndex={5000}
wrapClassName={styles.platformModalWrap}
className={styles.platformModal} className={styles.platformModal}
footer={<Pagination footer={<Pagination
simple simple
...@@ -69,16 +74,16 @@ const PlatformModal = ({ platformVisible, handleClosePlatform, handlerMointer, m ...@@ -69,16 +74,16 @@ const PlatformModal = ({ platformVisible, handleClosePlatform, handlerMointer, m
</div> </div>
<div className={styles['content-bottom']}> <div className={styles['content-bottom']}>
<p> <p>
报警值:
<b> <b>
{message && message.infoContent && message.infoContent.alarmValue} {message && message.infoContent && message.infoContent.alarmValue}
/
</b> </b>
{' / '}
预设值:
{message && message.infoContent && message.infoContent.alarmThreshold} {message && message.infoContent && message.infoContent.alarmThreshold}
</p> </p>
<span>
{message && message.time}
</span>
</div> </div>
<p className={styles['message-time']}>{message && message.time}</p>
</div> </div>
</div> </div>
</Modal> </Modal>
...@@ -122,7 +127,7 @@ class NoticeIconView extends Component { ...@@ -122,7 +127,7 @@ class NoticeIconView extends Component {
} }
get platformMessages() { get platformMessages() {
return this.state.noticeData.filter(item => item.infoLevel === '4' && item.infoType === 'scadaType'); return this.state.noticeData.filter(item => item.infoLevel === '4' && item.infoClasses === 'alarmType');
} }
async componentDidMount() { async componentDidMount() {
...@@ -188,38 +193,26 @@ class NoticeIconView extends Component { ...@@ -188,38 +193,26 @@ class NoticeIconView extends Component {
handleClosePlatform = event => { handleClosePlatform = event => {
this.setState({ platformVisible: false }); this.setState({ platformVisible: false });
this.notifier.destoryPlatform();
}; };
// 显示报警弹窗
renderPlatform = message => { renderPlatform = message => {
if (!this.state.platformVisible) { if (!this.state.platformVisible) {
this.setState({ this.setState({
platformVisible: true, platformVisible: true,
}) })
} }
// const messageContent =
// this.props.global.mqtt_mess.MessageLevel === '2.0' &&
// isJSON(message.infoContent)
// ? JSON.parse(message.infoContent)
// : message.infoContent;
// this.setState({
// platformVisible: true,
// alarmMessage: {
// message,
// messageContent,
// },
// });
}; };
renderVideo = message => { renderVideo = message => {
try {
this.setState({ this.setState({
videoVisible: true, videoVisible: true,
}); });
const { props } = this; const { props } = this;
const infoType = message.infoType const infoType = message.infoType
? message.infoType ? message.infoType
: MESSAGE_TYPE.SCADA_TYPE; : MESSAGE_TYPE.ALARM_TYPE;
let data = []; let data = [];
if (this.props.global.mqtt_mess.MessageLevel === '2.0') { if (this.props.global.mqtt_mess.MessageLevel === '2.0') {
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
...@@ -249,6 +242,10 @@ class NoticeIconView extends Component { ...@@ -249,6 +242,10 @@ class NoticeIconView extends Component {
url, url,
}, },
}); });
} catch (error) {
}
}; };
// 消息弹窗Footer // 消息弹窗Footer
...@@ -259,7 +256,7 @@ class NoticeIconView extends Component { ...@@ -259,7 +256,7 @@ class NoticeIconView extends Component {
} }
return ( return (
<div className={styles.notificationFoter}> <div className={styles.notificationFoter}>
<a onClick={toNotifications}>查看全部历史消息</a> <Button type='link' onClick={toNotifications}>查看通知消息</Button>
</div> </div>
) )
} }
...@@ -286,6 +283,7 @@ class NoticeIconView extends Component { ...@@ -286,6 +283,7 @@ class NoticeIconView extends Component {
}; };
renderSysPlatform = message => { renderSysPlatform = message => {
try {
const noticeContent = const noticeContent =
this.props.global.mqtt_mess.MessageLevel === '2.0' && this.props.global.mqtt_mess.MessageLevel === '2.0' &&
isJSON(message.infoContent) isJSON(message.infoContent)
...@@ -300,6 +298,7 @@ class NoticeIconView extends Component { ...@@ -300,6 +298,7 @@ class NoticeIconView extends Component {
noticeContent, noticeContent,
}, },
}); });
} catch (error) {}
}; };
handlerMointer = (event, item, detail) => { handlerMointer = (event, item, detail) => {
......
...@@ -231,8 +231,12 @@ ...@@ -231,8 +231,12 @@
} }
} }
} }
.platformModalWrap {
pointer-events: none;
}
.platformModal { .platformModal {
// pointer-events: all;
border-radius: 30px; border-radius: 30px;
:global { :global {
.@{ant-prefix}-modal-header { .@{ant-prefix}-modal-header {
...@@ -590,12 +594,12 @@ ...@@ -590,12 +594,12 @@
justify-content: space-between; justify-content: space-between;
line-height: 48px; line-height: 48px;
padding: 0 5px; padding: 0 5px;
}
.message-time { .message-time {
color: #fffaee; color: #fffaee;
} }
} }
} }
}
.feedbackBox { .feedbackBox {
flex: none; flex: none;
height: 304px; height: 304px;
...@@ -675,17 +679,10 @@ ...@@ -675,17 +679,10 @@
} }
.notificationFoter { .notificationFoter {
background-color: #f6f6f6; background-color: #FFF;
border-top: 1px solid #e3e3e3; border-top: 1px solid #E4E6EA;
& > a { line-height: 34px;
color: #44acb6; & > button {
display: block; width: 100%;
font-size: 14px;
line-height: 20px;
padding: 12px 16px;
text-align: center;
&:hover {
color: #51c6cf;
}
} }
} }
\ No newline at end of file
import { instanceRequest, service } from '@wisdom-utils/utils';
instanceRequest.reportCodeError = true;
import notificationService from './service';
import AppService from './service/user';
const api = service(notificationService);
const appService = service(AppService);
export { api, appService };
import { request } from '@wisdom-utils/utils';
import * as constants from '@wisdom-utils/components/lib/AppLayout/helpers/constants';
const API = {
GET_INFORMATION: '/PandaCore/GCK/Message/GetInformationInfo',
GET_MQTT_SITE_CODE: '/CityInterface/rest/services/CountyProduct.svc/SCADAOper/getMqttSitecode',
POST_INFORMATION_STATUS: '/PandaCore/GCK/Message/PostInformationStatus',
POST_ADD_OPTIONS: '/CityInterface/rest/services/WisdomUnion.svc/CustomerManage/AddOption',
};
const services = {
getInformationInfo: {
url: API.GET_INFORMATION,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_HTTP,
},
getMqttSiteCode: {
url: API.GET_MQTT_SITE_CODE,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_HTTP,
},
postInformationStatus: {
url: API.POST_INFORMATION_STATUS,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_HTTP,
},
postAddOptions: {
url: API.POST_ADD_OPTIONS,
method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_HTTP,
},
};
export const postInformationStatus = (param) =>
request({
url: API.POST_INFORMATION_STATUS,
method: constants.REQUEST_METHOD_GET,
data: param.data,
params: param.query,
});
export default services;
import * as constants from '@wisdom-utils/components/lib/AppLayout/helpers/constants';
export const API = {
CHANGE_PASSWORD: 'cityinterface/rest/services.svc/changepassword',
IOT_CHANGE_PASSWORD: 'CityInterface/rest/services/OMS.svc/U_UpdatePasswordQuickGCK',
FILE_DOWNLOAD: '/cityinterface/rest/services/filedownload.svc/download',
UPDATE_AVATAR: '/CityInterface/rest/services/OMs.svc/U_EditUser',
UPLOAD_FILE_URL:
'/cityinterface/rest/services/filedownload.svc/uploadfile/个人信息/{path}/{filename}',
AVATAR_FILE_URL: '/cityinterface/rest/services/filedownload.svc/download',
GET_VERSION: '/CityInterface/rest/services/OMs.svc/U_GetVersion',
};
const services = {
changePassword: {
url: () =>
window.globalConfig &&
(window.globalConfig.loginTemplate === 'Dark - IOTMultiLogin.html' ||
window.globalConfig.loginTemplate === '新春 - 智联.html')
? API.IOT_CHANGE_PASSWORD
: API.CHANGE_PASSWORD,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_HTTP,
},
getVersion: {
url: API.GET_VERSION,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_HTTP,
showErrorType: 0,
},
updateAvatar: {
url: API.UPDATE_AVATAR,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_HTTP,
},
};
export default services;
export const REQUEST_SERVICE = {
EIMTopic: '/EIM/info',
SaveWaTopic: '/saveWater/info',
SystemTopic: '/system/info',
WorkerOrderTopic: '/workerOrder/info',
ScadaTopic: '/scada/alarm',
UserTopic: '/Message/',
};
// 消息类型归类:报警类、工单类、系统消息类、其他
export const MESSAGE_TYPE = {
ALARM_TYPE: 'alarmType',
CASE_TYPE: 'caseType',
SYS_TYPE: 'sysType',
UNKNOWN: 'unknown',
};
export const NEW_MESSAGE = 'NEW_MESSAGE';
export const USERNAME = 'mao2080';
export const PASSWORD = '123';
export const PLATFORM_LEVEL = '4';
export const VIDEO_LEVEL = '5';
export const SYS_LEVEL = '6';
export const ERR_OK = '0000';
export const DEFAULT_TCP_PORT = 443;
export const DEFAULT_TCP_IP = 'emqttd.panda-water.cn';
export const DEFAULT_MQTT_PATH = '/mqtt';
export const DEFAULT_KEEPLIVE = 60;
export const DEFAULT_TIMEOUT = 50;
export const DEFAULT_PARSE_LEVEL = '1.0';
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
import React from 'react';
import { Badge, Button, ConfigProvider } from 'antd';
import classNames from 'classnames';
import useMergeValue from 'use-merge-value';
import Icon from '@ant-design/icons';
import HeaderDropdown from '@wisdom-utils/components/lib/AppLayout/components/HeaderDropdown';
import NoticeList from './list';
import './style/index.less';
const messageSvg = () => (
<svg
version="1.1"
x="0px"
y="0px"
width="24px"
height="24px"
viewBox="0 0 24 24"
enableBackground="new 0 0 24 24"
space="preserve"
>
<path
fill="hsla(221, 100%, 95%, 0.7)"
d="M20.486,16.373l-1.721-2.246v-0.984v-0.352V9.924c0-1.919-0.664-3.698-1.871-5.007
c-0.712-0.776-1.57-1.349-2.551-1.705c-0.091-0.514-0.35-0.983-0.737-1.335c-0.879-0.791-2.334-0.791-3.21,0
c-0.394,0.354-0.653,0.823-0.741,1.336C8.676,3.568,7.817,4.14,7.105,4.917C5.899,6.229,5.234,8.008,5.234,9.923l0.005,4.194
l-1.708,2.234c-0.241,0.256-0.372,0.584-0.372,0.932v1.092c0,0.75,0.615,1.357,1.372,1.357H19.47c0.757,0,1.371-0.607,1.371-1.357
v-1.092C20.841,16.936,20.71,16.607,20.486,16.373z M4.899,17.996v1.016l-0.001-2.061l1.628-2.154
c0.227-0.244,0.353-0.561,0.353-0.893v-4.05c0-1.516,0.509-2.914,1.436-3.935c0.459-0.506,1.001-0.895,1.608-1.166
c1.276-0.565,2.883-0.565,4.155,0c0.609,0.273,1.15,0.667,1.608,1.168c0.925,1.021,1.437,2.417,1.437,3.934v2.762v0.338v0.949
c0,0.334,0.123,0.654,0.336,0.875l1.642,2.164l0.013,1.037L4.899,17.996z"
/>
<path
fill="hsla(221, 100%, 95%, 0.7)"
d="M13.685,20.236c-0.101,0.238-0.248,0.453-0.444,0.631c-0.677,0.617-1.799,0.615-2.473,0.002
c-0.194-0.18-0.344-0.396-0.446-0.633H8.895c0.146,0.627,0.474,1.199,0.955,1.639c0.588,0.543,1.354,0.841,2.158,0.841
c0.801,0,1.566-0.298,2.154-0.837c0.481-0.439,0.808-1.012,0.954-1.641L13.685,20.236z"
/>
</svg>
);
const BellOutlined = (props) => <Icon component={messageSvg} {...props} />;
const NoticeIcon = (props) => {
const { getPrefixCls } = React.useContext(ConfigProvider.ConfigContext);
const prefixCls = props.prefixCls || getPrefixCls();
const btnPrefixCls = `${prefixCls}-head-notifier-button`;
const popPrefixCls = `${prefixCls}-head-notifier-popover`;
const getNotificationBox = () => {
const { children, confirmRead, renderFooter } = props;
if (!children) {
return null;
}
const panes = [];
React.Children.forEach(children, (child) => {
if (!child) {
return;
}
const { list, title } = child.props;
panes.push(
<NoticeList {...child.props} data={list} key={child} title={title} config={props.config} />,
);
});
const childProps = panes.length === 1 ? panes[0].props : { list: [] };
return (
<>
<div className={`${popPrefixCls}-header`}>
<span className={`${popPrefixCls}-title`}>通知</span>
<Button
type="link"
onClick={() => (childProps.list.length > 0 ? confirmRead(true) : null)}
disabled={childProps.list.length === 0}
>全部标记已读</Button>
</div>
{panes}
{renderFooter && renderFooter()}
</>
);
};
const { className, bell } = props;
const [visible, setVisible] = useMergeValue(false, {
value: props.popupVisible,
onChange: props.onPopupVisibleChange,
});
const noticeButtonClass = classNames(className, btnPrefixCls);
const notificationBox = getNotificationBox();
const NoticeBellIcon = bell || <BellOutlined className={`${btnPrefixCls}-icon`} title="报警" />;
const trigger = (
<span className={classNames(noticeButtonClass, { opened: visible })}>
<Badge
count={props.count}
overflowCount={99}
offset={[0, 8]}
style={{ boxShadow: 'none' }}
title="报警"
className={classNames(`${btnPrefixCls}-badge`, props.count > 0 ? `${btnPrefixCls}-fountMessage` : '')}
>
{NoticeBellIcon}
</Badge>
{!props.bell && <span className={`${btnPrefixCls}-title`}>消息</span>}
</span>
);
if (!notificationBox) {
return trigger;
}
return (
<>
<HeaderDropdown
placement="bottomRight"
overlay={notificationBox}
overlayClassName={popPrefixCls}
trigger={['click']}
visible={visible}
onVisibleChange={setVisible}
>
{trigger}
</HeaderDropdown>
</>
);
};
NoticeIcon.defaultProps = {
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg',
};
NoticeIcon.Tab = NoticeList;
export default NoticeIcon;
// const mapStateToProps = state => ({
// global: state.getIn(['global', 'globalConfig']),
// });
// export default connect(
// mapStateToProps,
// null,
// )(NoticeIcon);
import React from 'react';
import { List, Spin, ConfigProvider } from 'antd';
import classNames from 'classnames';
import './style/list.less';
import { Alarm, Case, Notice, Unknown } from './templates';
import { MESSAGE_TYPE } from './constants';
const Empty = ({ emptyText, ...props }) => {
const { getPrefixCls } = React.useContext(ConfigProvider.ConfigContext);
const prefixCls = props.prefixCls || getPrefixCls();
return (
<div className={`${prefixCls}-notFound`}>
<img
src="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg"
alt="not found"
/>
<div>{emptyText}</div>
</div>
);
};
class NoticeList extends React.Component {
constructor(props) {
super(props);
this.emptyText = props.emptyText;
this.confirmRead = props.confirmRead;
this.handlerSysDetail = props.handlerSysDetail;
this.handlerUnknowDetail = props.handlerUnknowDetail;
this.loadMore = props.loadMore;
this.hasMore = props.hasMore;
this.container = React.createRef();
this.state = {
isLoading: false,
};
this.mounted = false;
this.handleScrollCallback = this.throttle(this.handleScroll.bind(this), 30).bind(this);
}
componentDidMount() {
this.mounted = true;
if (this.container.current) {
this.container.current.addEventListener('scroll', this.handleScrollCallback);
}
}
componentWillUnmount() {
this.mounted = false;
}
throttle(fn, wait) {
/* eslint-disable */
let pre = Date.now();
return function () {
const context = this;
// eslint-disable-next-line prefer-rest-params
const args = arguments;
const now = Date.now();
if (now - pre >= wait) {
fn.apply(context, args);
pre = Date.now();
}
};
}
handleScroll(e) {
e.stopPropagation();
if (!this.mounted) return;
if (!this.container.current) return;
const { current } = this.container;
if (current.scrollHeight - current.scrollTop - current.offsetHeight <= 100) {
this.handleLoadMore();
}
}
handleLoadMore() {
if (this.state.isLoading) return;
if (!this.hasMore()) return;
this.setState(
{
isLoading: true,
},
() => {
if (!this.loadMore) return;
this.loadMore().then((data) => {
if (!this.mounted) return;
this.setState({
isLoading: false,
});
});
},
);
}
render() {
const { getPrefixCls } = this.context;
const prefixCls =
this.props.prefixCls || getPrefixCls ? getPrefixCls('head-notifier-list') : '';
if (!this.props.data || this.props.data.length === 0) {
return <Empty emptyText={this.emptyText} prefixCls={prefixCls} />;
}
return (
<div className={`${prefixCls}-container`} ref={this.container}>
<List
className={`${prefixCls}-list`}
dataSource={this.props.data}
renderItem={(item, i) => {
const itemCls = classNames(`${prefixCls}-item`, {
[`${prefixCls}-read`]: item.read,
});
let messageTemplate = <></>;
switch (item.infoClasses) {
case MESSAGE_TYPE.ALARM_TYPE:
messageTemplate = (
<Alarm message={item} confirmRead={this.confirmRead} config={this.props.config} />
);
break;
case MESSAGE_TYPE.CASE_TYPE:
messageTemplate = <Case message={item} confirmRead={this.confirmRead} />;
break;
case MESSAGE_TYPE.SYS_TYPE:
messageTemplate = (
<Notice
message={item}
confirmRead={this.confirmRead}
config={this.props.config}
handlerSysDetail={this.handlerSysDetail}
/>
);
break;
default:
messageTemplate = (
<Unknown
message={item}
confirmRead={this.confirmRead}
handlerUnknowDetail={this.handlerUnknowDetail}
/>
);
break;
}
return (
<List.Item className={itemCls} key={item.id || i}>
{messageTemplate}
</List.Item>
);
}}
/>
<div className={`${prefixCls}-bottomBar`}>
{this.state.isLoading ? (
<>
<Spin /> 加载中...
</>
) : this.hasMore() ? (
<span>下拉加载更多</span>
) : (
<span style={{ fontSize: '12px', color: 'rgba(0, 0, 0, 0.6)' }}>
已经没有更多消息了
</span>
)}
</div>
</div>
);
}
}
NoticeList.contextType = ConfigProvider.ConfigContext;
export default NoticeList;
/* eslint-disable */
import { formatTime } from './utils/format';
import { getMessageClasses, parseMessageToJSON } from './utils';
class Message {
constructor({
id,
infoContent,
infoLevel,
time,
infoType,
dateTime,
webConfig,
webPath,
messType,
infoClasses
} = message) {
this.id = id;
this.infoContent = infoContent;
this.infoLevel = infoLevel;
this.time = time;
this.infoType = infoType; // 方案类型 - 工单 报警 公告 定时推送
this.dateTime = dateTime;
this.webConfig = webConfig;
this.webPath = webPath;
this.messType = messType; // 方案名称 - 大类型下细类型
this.infoClasses = infoClasses;
}
}
function createMessage(message) {
return new Message(message);
}
/**
* 解析历史消息成统一消息对象
* "历史消息" 和 "实时推送" 的消息之间有些差距,所以统一处理成一样的 Message
* @param {*} hisMessage 历史消息: 通过接口查询到的 "历史消息"
* @param {object} options 扩展参数
* @returns {Message}
* 返回处理后的统一 Message 对象
*/
export const createMessageFromHis = (hisMessage, options = {}) => {
const { version } = options;
const time = formatTime(hisMessage.hisCreateTime);
const infoClasses = getMessageClasses(hisMessage.infoType);
let infoContent = {};
if (!version || version === '1.0') {
infoContent = parseMessageToJSON(infoClasses, hisMessage); // 1.0消息解析成统一格式
} else {
// 2.0 消息
infoContent = JSON.parse(hisMessage.infoContent || '{}');
}
const template = {
id: hisMessage.hisID,
infoContent,
time,
infoType: hisMessage.infoType,
dateTime: hisMessage.hisCreateTime,
infoLevel: hisMessage.infoLevel,
webConfig: hisMessage.web_config,
webPath: hisMessage.web_path,
messType: hisMessage.messType,
infoClasses,
};
return createMessage(template);
}
/**
* 解析实时消息成统一消息对象
* "历史消息" 和 "实时推送" 的消息之间有些差距,所以统一处理成一样的 Message
* @param {*} hisMessage 历史消息: 通过接口查询到的 "历史消息"
* @param {object} options 扩展参数
* @returns {Message}
* 返回处理后的统一 Message 对象
*/
export const createMessageFromReal = (realMesssage, options = {}) => {
const { version } = options;
const infoClasses = getMessageClasses(realMesssage.infoType);
const time = formatTime(realMesssage.createTime);
let infoContent = {};
if (!version || version === '1.0') {
infoContent = parseMessageToJSON(infoClasses, realMesssage); // 1.0消息解析成统一格式
} else {
// 2.0 消息
infoContent = JSON.parse(realMesssage.content || '{}');
}
const template = {
id: realMesssage.infoId,
infoContent,
time,
infoType: realMesssage.infoType,
infoLevel: realMesssage.level,
dateTime: realMesssage.createTime,
webConfig: realMesssage.web_config,
webPath: realMesssage.web_path,
messType: realMesssage.messType,
infoClasses,
}
return createMessage(template);
}
export default createMessage;
import logger from '@wisdom-utils/utils/lib/logger';
import _, { isEmpty, isString } from 'lodash';
import MqttClient from 'mqtt-client';
import { isJSON } from '@wisdom-utils/components/lib/AppLayout/helpers/index';
import { api } from './api';
import { replaceSpeak, generatedId, parseContentToJSON } from './utils';
import { postInformationStatus } from './api/service';
import {
DEFAULT_KEEPLIVE,
DEFAULT_MQTT_PATH,
DEFAULT_PARSE_LEVEL,
DEFAULT_TCP_IP,
DEFAULT_TCP_PORT,
DEFAULT_TIMEOUT,
ERR_OK,
MESSAGE_TYPE,
NEW_MESSAGE,
PASSWORD,
PLATFORM_LEVEL,
REQUEST_SERVICE,
SYS_LEVEL,
USERNAME,
VIDEO_LEVEL,
} from './constants';
import { createMessageFromHis, createMessageFromReal } from './message';
/* eslint-disable */
// eslint-disable-next-line no-undef
const Logger = logger('mqtt');
class Notifier {
constructor(userInfo, renderVideo, renderPlatform, renderSysPlatform, props) {
this.userInfo = userInfo;
this.messageCache = {
totalCount: 0,
messages: [],
};
// 当前消息缓存
this._subscribers = {}; // 订阅器缓存
this._siteConfig = {
site_code: this.userInfo.site,
TcpIP: '',
TcpPort: DEFAULT_TCP_PORT,
TimeOut: '',
KeepAlive: '',
IsSSL: true,
mqtt_path: DEFAULT_MQTT_PATH,
mqtt_mess: {},
nginxStart: false,
};
this.MQTTCount = 0;
this.MQTTClient = null;
this.MQTTOptions = {};
this.IsNeedReconnect = true;
this.currentPageIndex = 1;
this.currentPageSize = 10;
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
this.subscribe = this.subscribe.bind(this);
this.unsubscribe = this.unsubscribe.bind(this);
this.confirmRead = this.confirmRead.bind(this);
this.loadMore = this.loadMore.bind(this);
this.hasMore = this.hasMore.bind(this);
this.renderVideo = renderVideo;
this.renderPlatform = renderPlatform;
this.renderSysPlatform = renderSysPlatform;
this.props = props;
this.speakState = new window.SpeechSynthesisUtterance();
this.speakState.rate = 1;
this.speakState.lang = 'zh';
}
// 对外接口
async start() {
this.getMqttSiteCode().then(res => {
this.loadHisMessages(this.currentPageIndex, this.currentPageSize);
this.connectMQTTServer();
});
}
stop() {
this.disconnectMQTTServer();
}
subscribe(type, handler) {
if (!(type in this._subscribers)) {
this._subscribers[type] = [];
}
this._subscribers[type].push(handler);
}
unsubscribe(type, handler) {
if (!(type in this._subscribers)) {
logger.info('无效事件无法删除');
}
if (!handler) {
delete this._subscribers[type];
} else {
const idx = this._subscribers[type].findIndex(ele => ele === handler);
if (idx === -1) {
logger.info('无效事件无法删除');
return;
}
this._subscribers[type].splice(idx, 1);
if (this._subscribers[type].length === 0) {
delete this._subscribers[type];
}
}
}
publish(type, payload) {
if (!(type in this._subscribers) || this._subscribers[type].length === 0) {
return;
}
this._subscribers[type].forEach(handler => {
try {
handler(payload);
} catch (e) {
// eslint-disable-next-line no-undef
logger.warn(`订阅器委托错误${e.message}`);
}
});
}
confirmRead(isAll = false, hisIDs = []) {
if (this.messageCache && this.messageCache.totalCount === 0 && this.messageCache.messages.length === 0) {
return;
}
if (isAll) hisIDs = this.messageCache.messages.map(item => item.id);
const self = this;
// eslint-disable-next-line no-undef
postInformationStatus({
query: {
userID: this.userInfo.OID || window.globalConfig.userInfo.OID,
hisID: hisIDs.join(','),
isAll: isAll ? 1 : '',
},
})
.then(res => {
if (res.code !== 0) {
Logger.info(res.errMsg);
return;
}
if (isAll) {
self.messageCache.totalCount = 0;
self.messageCache.messages = [];
self.currentPageIndex = 1;
} else {
hisIDs.forEach(id => {
const index = self.messageCache.messages.findIndex(item => item.id === id);
if (index > -1) {
self.messageCache.messages.splice(index, 1);
// eslint-disable-next-line no-plusplus
self.messageCache.totalCount--;
}
});
}
self.publish(NEW_MESSAGE, self.messageCache);
})
.catch(err => {
// eslint-disable-next-line no-undef
logger.error(`postInformationStatus调用失败${err}`);
});
}
hasMore() {
if (!this.messageCache) return false;
if (!this.messageCache.totalCount) return false;
return this.messageCache.totalCount > this.messageCache.messages.length;
}
loadMore(callback) {
if (!this.hasMore()) return Promise.resolve([]);
this.currentPageIndex += 1;
return this.loadHisMessages(this.currentPageIndex, this.currentPageSize);
}
// mqtt
async connectMQTTServer() {
const hostname = this._siteConfig.TcpIP;
const port = this._siteConfig.TcpPort;
const clientId = `client-${generatedId()}`;
const timeout = DEFAULT_TIMEOUT;
const keepAlive = DEFAULT_KEEPLIVE;
const cleanSession = true;
const ssl = this._siteConfig.IsSSL;
const userName = USERNAME;
const password = PASSWORD;
const path = this._siteConfig.mqtt_path;
this.MQTTCount = 0;
if (hostname) {
this.MQTTClient = new MqttClient.Client(hostname, port, path, clientId);
this.MQTTOptions = {
invocationContext: {
host: hostname,
port,
path,
clientId,
},
timeout,
keepAliveInterval: keepAlive,
cleanSession,
useSSL: ssl,
userName,
password,
onSuccess: this.onMQTTConnect.bind(this),
onFailure(e) {
console.log(e);
},
};
this.MQTTClient.connect(this.MQTTOptions);
this.MQTTClient.onConnectionLost = this.onMQTTConnectionLost.bind(this);
this.MQTTClient.onMessageArrived = this.onMessageArrived.bind(this);
}
}
disconnectMQTTServer() {
if (this.MQTTClient) {
this.IsNeedReconnect = false;
this.MQTTClient.disconnect();
this.MQTTClient = null;
}
}
async getMqttSiteCode() {
const self = this;
return api.getMqttSiteCode({ 'request.preventCache': Date.now() }).then(res => {
if (res && res.say.statusCode === ERR_OK) {
let mqttConfig = {
mqtt_mess: {},
mqtt_path: self._siteConfig.mqtt_path,
nginxStart: self._siteConfig.NginxStart,
mqtt_IsSSL: true,
};
if (Array.isArray(res.getMe) && res.getMe.length > 0) {
if (res.getMe[0]) {
const data = res.getMe[0];
mqttConfig.mqtt_IsSSL = self._siteConfig.IsSSL = data.IsSSL ? data.IsSSL : false;
mqttConfig.mqtt_mess.site_code = self._siteConfig.site_code = data.SiteCode || self._siteConfig.site_code;
mqttConfig.mqtt_mess.TcpIP = self._siteConfig.TcpIP = data.TcpIP;
mqttConfig.mqtt_mess.TcpPort = self._siteConfig.TcpPort = data.TcpPort ? parseInt(data.TcpPort) : 8083;
mqttConfig.mqtt_mess.MessageLevel = self._siteConfig.MessageLevel = data.MessageLevel
? data.MessageLevel
: DEFAULT_PARSE_LEVEL;
if (data.NginxStart) {
mqttConfig.NginxStart = self._siteConfig.NginxStart = data.NginxStart;
mqttConfig.mqtt_mess.TcpIP = self._siteConfig.TcpIP = self._siteConfig.mqtt_mess.TcpIP =
window.location.hostname;
mqttConfig.mqtt_mess.TcpPort = self._siteConfig.TcpPort = self._siteConfig.mqtt_mess.TcpPort = parseInt(
window.location.port,
);
mqttConfig.mqtt_path = self._siteConfig.mqtt_path = '/ws/';
} else {
mqttConfig.nginxStart = data.NginxStart;
}
} else {
mqttConfig.mqtt_mess.TcpIP = self._siteConfig.TcpIP = self._siteConfig.mqtt_mess.TcpIP = DEFAULT_TCP_IP;
mqttConfig.mqtt_mess.TcpPort = self._siteConfig.TcpPort = self._siteConfig.mqtt_mess.TcpPort = DEFAULT_TCP_PORT;
mqttConfig.mqtt_IsSSL = self._siteConfig.IsSSL =
self._siteConfig.mqtt_mess.TcpIP + ':' + self._siteConfig.mqtt_mess.TcpPort;
}
mqttConfig.mqtt_iotIP = self._siteConfig.mqtt_iotIP =
mqttConfig.mqtt_mess.TcpIP + ':' + mqttConfig.mqtt_mess.TcpPort;
// self.props.updateConfig && self.props.updateConfig(Object.assign({}, self.props.global, {
// ...mqttConfig
// }))
// 应用共享状态只在initMicro中更新了一次,异步修改了的globalConfig,子应用读取到的是旧的,需要更新一下应用共享状态
// setGlobalState({
// globalConfig: window.globalConfig
// });
}
} else {
Logger.info('获取mqtt服务器参数失败');
}
});
}
getSiteCode() {
return this._siteConfig.site_code;
}
getUserInfo() {
return this.userInfo;
}
getSiteConfig() {
return this._siteConfig;
}
getMessageVersion() {
return this._siteConfig.MessageLevel;
}
onMQTTConnect() {
const site = this.getSiteCode();
// 信息化主题
this.MQTTClient.subscribe(site + REQUEST_SERVICE.EIMTopic);
// 节水主题
this.MQTTClient.subscribe(site + REQUEST_SERVICE.SaveWaTopic);
// 系统主题
this.MQTTClient.subscribe(site + REQUEST_SERVICE.SystemTopic);
// 工单主题
this.MQTTClient.subscribe(site + REQUEST_SERVICE.WorkerOrderTopic);
// 报警主题
this.MQTTClient.subscribe(site + REQUEST_SERVICE.ScadaTopic);
// 用户主题
this.MQTTClient.subscribe(`${site}${REQUEST_SERVICE.UserTopic}${this.userInfo.OID}`);
}
onMQTTConnectionLost(responseObject) {
const self = this;
if (this.IsNeedReconnect) {
this.MQTTClient.connect(self.MQTTOptions);
this.MQTTtester = setInterval(function() {
if (self.MQTTClient.isConnected) {
clearInterval(self.MQTTtester);
} else {
self.MQTTClient.connect(self.MQTTOptions);
}
}, 1000);
}
}
onMessageArrived(buffer) {
try {
const parseMessage = JSON.parse(buffer.payloadString);
const userInfo = this.getUserInfo();
if (
_.isEmpty(parseMessage.tousers) ||
userInfo.OID == parseMessage.tousers ||
parseMessage.tousers.includes(`${userInfo.OID},`) ||
parseMessage.tousers.includes(`,${userInfo.OID}`)
) {
const message = createMessageFromReal(parseMessage, {
version: this.getMessageVersion(),
}); // 创建消息对象
this.messageCache.totalCount += 1;
// if (message.infoLevel === SYS_LEVEL) {
this.messageCache.messages.unshift(message); // 新消息置顶
// } else {
// this.messageCache.messages.push(message);
// }
this.publish(NEW_MESSAGE, this.messageCache);
this.renderWindowsInfo(buffer); // 1.windows桌面消息提醒
this.speakMessage(message); // 2.语音播报
this.noticeHandler(message); // 3.其他业务
}
} catch (e) {
Logger.error(`收到消息处理异常:${e.message}`);
}
}
/**
* 实时消息浏览器桌面通知
* @param {*} message 推送的原始实时消息
*/
renderWindowsInfo(message) {
if (document.visibilityState !== 'visible' && !document.visibilityState !== 'hidden') return;
const self = this;
function notifyMessage(message) {
const parseMessage = JSON.parse(message.payloadString);
let content = '';
if (message.level !== SYS_LEVEL) {
if (self.getMessageVersion() === '2.0') {
if (message.level === '4') {
const messageContent = JSON.parse(parseMessage.content);
content += `${messageContent.alarmType} ${messageContent.alarmDevice} ${messageContent.alarmContent} ${
messageContent.alarmValue
} / ${messageContent.alarmThreshold}`;
}
}
}
const messageBody = {
title: '',
content,
};
if (parseMessage.tousers === '') {
messageBody.title = '新公告:';
} else {
messageBody.title = '新通知:';
}
if (content !== '') {
if (!('Notification' in window)) {
message.warn('This browser does not support desktop notification');
} else if (Notification.permission === 'granted') {
const notification = new Notification(messageBody.title, {
body: `${messageBody.content}`,
icon: 'https://panda-water.com/web4/assets/images/icon/熊猫新1.png',
});
notification.onclick = () => {
notification.close();
};
} else if (Notification.permission !== 'denied') {
Notification.requestPermission(permission => {
if (permission === 'granted') {
const notification = new Notification(messageBody.title, {
body: `${messageBody.content}`,
icon: 'https://panda-water.com/web4/assets/images/icon/熊猫新1.png',
});
notification.onclick = () => {
notification.close();
};
}
});
}
}
}
try {
notifyMessage(message);
} catch (error) {}
}
/**
* 实时消息语音播报
* @param {*} message
* @returns
*/
speakMessage = message => {
if (!message) return;
switch(message.infoType) {
case 'SCADA报警':
case '通用报警':
this.speakAlarm(message);
break;
case '工单流程':
case '工单提醒':
this.speakCase(message);
break;
case '系统通知':
case '系统消息':
this.speakSys(message);
break;
default:
this.speakOther(message);
}
};
speakAlarm = message => {
const device =
message?.infoContent?.title
?.split(' ')
?.splice(1)
?.join(' ') ?? ''; // 报警设备:“【阈值报警】 二供泵房 | 光谷物联港”
const alarmType = message?.infoContent?.alarmType; // 报警类型:“状态报警”
const content = message?.infoContent?.alarmContent; // 报警内容:“出水超压报警”
const alarmValue = message?.infoContent?.alarmValue ?? ''; // 报警值
let msg = `紧急报警:${device},${alarmType},${content},报警值:${isString(alarmValue) ? replaceSpeak(alarmValue) :'' },请注意!!!`;
this.speak(msg);
};
speakCase = message => {
const caseType = message?.infoContent?.caseType;
const flowName = message?.infoContent?.flowName;
const nodeName = message?.infoContent?.nodeName;
const flowNo = message?.infoContent?.flowNo;
const content = message?.infoContent?.content;
let msg = `您有新的${caseType}${flowName},${nodeName},${flowNo ? '工单编号:' + flowNo : ''},${content}`;
this.speak(msg);
};
speakSys = message => {
const noticeType = message?.infoContent?.noticeType ?? '系统通知';
const noticeTitle = message?.infoContent?.noticeTitle ?? '';
const noticeContent = message?.infoContent?.noticeContent ?? '';
let msg = `${noticeType}:${noticeTitle},${noticeContent}`;
this.speak(msg);
};
speakOther = message => {
// 类型区分‘全员公告’ 还是 ‘个人消息’
const type = isEmpty(message.tousers) || parseMessage.tousers === '' ? '公告' : '消息';
// 构建语音消息内容
const { infoContent, time } = message;
let content = replaceSpeak((infoContent?.content ?? '').replace(/\\n/g, ','));
let msg = `您有新的${type}${content.substring(0, content.lastIndexOf(',')).replace(':', ',')} 时间:${time}`;
this.speak(msg);
};
speak = msg => {
if (!this.speakState) return;
this.speakState.text = msg;
window.speechSynthesis.cancel();
window.speechSynthesis.speak(this.speakState);
};
/**
* 实时消息特殊处理
* @param {*} message
* @returns
*/
noticeHandler = message => {
if (!message) return;
if (message.infoLevel === SYS_LEVEL) {
this.renderSysNoticePlatform(message); // 显示系统弹窗
return;
}
if (message.infoLevel === VIDEO_LEVEL) {
this.renderPopVideo(message); // 显示视频弹窗
return;
}
if (message.infoLevel === PLATFORM_LEVEL && message.infoClasses === MESSAGE_TYPE.ALARM_TYPE) {
this.renderPopPlatform(message); // 显示报警弹窗
return;
}
};
renderPopPlatform(message) {
this.renderPlatformElement = this.renderPlatform && this.renderPlatform(message);
}
renderSysNoticePlatform(message) {
this.renderSysPlatform && this.renderSysPlatform(message);
}
renderPopVideo(message) {
this.renderVideoElement = this.renderVideo && this.renderVideo(message);
}
// 工具类
async loadHisMessages(pageIndex, pageSize) {
const self = this;
return api
.getInformationInfo({
userID: self.getUserInfo().OID,
pageIndex,
pageSize,
'request.preventCache': Date.now(),
})
.then(res => {
if (res && res.code === 0) {
const data = res.data || {};
const result = {
totalCount: data.totalCount,
messages: (Array.isArray(data.list) ? data.list : []).map(item =>
createMessageFromHis(item, { version: this.getMessageVersion() }),
),
};
self.messageCache.totalCount = result.totalCount;
(result.messages || []).forEach(message => {
const index = self.messageCache.messages.findIndex(item => item.id === message.id);
index === -1 && self.messageCache.messages.push(message);
});
if (self.messageCache.totalCount > self.messageCache.messages.length && data.list.length === 0) {
// 服务端返回总数还有,但是查不到数据了,前端修正服务端返回的总数
self.messageCache.totalCount = self.messageCache.messages.length;
}
self.publish(NEW_MESSAGE, self.messageCache);
console.log('render');
const selectData = result.messages.find(item => {
return item.infoLevel == 6;
});
if (selectData) {
self.renderSysNoticePlatform(selectData);
}
return Promise.resolve(result);
}
});
}
// 1.0报警消息格式解析播报
parseScadaMessage(message) {
const data = message.messContent.split('\\n');
const last = data[2];
const alarmType = data[0].split('】')[0].split('【')[1];
const sensor = data[1];
const station = data[0].split('】')[1];
let lastValue = last;
if (last.includes(' / ')) {
lastValue = '';
const alaVal = last.split(' / ');
let newVal = 0;
let setVal = 0;
let unit = '';
if (alaVal[0].includes(' ')) {
newVal = alaVal[0].split(' ')[0] * 1;
setVal = alaVal[1].split(' ')[0] * 1;
unit = alaVal[0].split(' ')[1];
} else {
newVal = alaVal[0] * 1;
setVal = alaVal[1] * 1;
}
lastValue = Math.abs(setVal - newVal).toFixed(2) + unit;
} else {
lastValue = `,报警实时值${lastValue}`;
}
let msg = `紧急报警:${station},${alarmType},${sensor}${lastValue},请注意!!!`;
for (let i = 0; i < 3; i++) {
msg += msg;
}
const state = new window.SpeechSynthesisUtterance(msg);
state.lang = 'zh';
state.rate = 1;
window.speechSynthesis.cancel();
window.speechSynthesis.speak(state);
}
}
export default Notifier;
@root-entry-name: 'default';
@import (reference) '~antd/es/style/themes/index.less';
@panda-notifier-prefix-cls: ~'@{ant-prefix}-head-notifier';
@notifier-button: ~'@{panda-notifier-prefix-cls}-button';
@notifier-popover: ~'@{panda-notifier-prefix-cls}-popover';
:global {
.@{notifier-button} {
display: inline-block;
cursor: pointer;
transition: border-color 0.3s, background 0.3s, padding 0.1s cubic-bezier(0.215, 0.61, 0.355, 1);
&:hover {
.@{notifier-button}-fountMessage {
svg {
path {
fill: #fff;
}
}
}
}
&-title {
color: hsla(221, 100%, 95%, 0.7);
}
&-icon {
padding: 4px;
vertical-align: middle;
}
&-badge {
font-size: 16px;
:global {
.@{ant-prefix}-badge-multiple-words {
padding: 0 4px;
}
}
&.@{notifier-button}-fountMessage {
.@{notifier-button}-icon {
animation: bellshake 2s ease-out infinite;
}
}
}
}
.@{notifier-popover} {
position: absolute;
z-index: 1100;
width: 360px;
padding: 0 10px !important;
box-shadow: 0 0 10px 2px rgba(0,0,0,0.1),0 0 10px 2px rgba(0,0,0,0.1);
border-radius: 4px;
* {
box-shadow: none;
}
&-title {
color: hsla(221, 100%, 95%, 0.7);
}
&-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px;
border-radius: 0 !important;
border-bottom: 1px solid #E4E6EA;
.@{notifier-popover}-title {
margin-left: 5px;
color: rgb(102, 102, 102);
font-weight: bold;
font-size: 16px;
}
}
}
@keyframes bellshake {
0% {
transform: rotate(0deg);
}
3.75% {
transform: rotate(25deg);
}
15% {
transform: rotate(-25deg);
}
22.5% {
transform: rotate(15deg);
}
29% {
transform: rotate(-10deg);
}
35% {
transform: rotate(5deg);
}
43% {
transform: rotate(-2deg);
}
50%,
to {
transform: rotate(0deg);
}
}
}
\ No newline at end of file
@root-entry-name: 'default';
@import (reference) '~antd/es/style/themes/index.less';
@panda-notifier-list-prefix-cls: ~'@{ant-prefix}-head-notifier-list';
:global {
.@{panda-notifier-list-prefix-cls}-container {
max-height: 400px;
overflow: auto;
&::-webkit-scrollbar {
width: 0;
height: 0;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.1);
/*滚动条里面小方块*/
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgba(177, 177, 177, 0.5);
box-shadow: inset 0 0 6px rgba(177, 177, 177, 0.5);
}
&::-webkit-scrollbar-track {
border-radius: 10px;
/*滚动条里面轨道*/
-webkit-box-shadow: inset 0 0 6px rgba(193, 193, 193, 0.3);
box-shadow: inset 0 0 6px rgba(193, 193, 193, 0.3);
}
.@{panda-notifier-list-prefix-cls}-list {
.@{panda-notifier-list-prefix-cls}-item {
padding: 0;
border-radius: 5px;
overflow: hidden;
cursor: pointer;
border-bottom: none;
.@{panda-notifier-list-prefix-cls}-meta {
width: 100%;
}
&:hover {
background-color: #F4F4F5;
}
&:first-of-type {
margin-top: 10px;
}
}
}
.@{panda-notifier-list-prefix-cls}-bottomBar {
height: 46px;
color: @text-color;
line-height: 46px;
text-align: center;
transition: all 0.3s;
}
}
.@{panda-notifier-list-prefix-cls}-notFound {
padding: 73px 0 88px;
color: @text-color-secondary;
text-align: center;
border-radius: 0 !important;
img {
display: inline-block;
height: 76px;
margin-bottom: 16px;
}
}
}
\ No newline at end of file
import React from 'react';
import _ from 'lodash';
import classNames from 'classnames';
import { findPathByWidget, isJSON } from '@wisdom-utils/components/lib/AppLayout/helpers';
import '../styles/common.less';
import './index.less';
import { ConfigProvider } from 'antd';
/* eslint-disable */
export class AlarmContent {
constructor({
alarmType,
deviceCode,
alarmDevice,
alarmContent,
alarmValue,
alarmThreshold,
time,
} = content) {
this.alarmType = alarmType;
this.deviceCode = deviceCode;
this.alarmDevice = alarmDevice;
this.alarmContent = alarmContent;
this.alarmValue = alarmValue;
this.alarmThreshold = alarmThreshold;
this.time = time;
}
}
const Alarm = ({ message, confirmRead, config }) => {
const { getPrefixCls } = React.useContext(ConfigProvider.ConfigContext);
const prefixCls = getPrefixCls();
const alarmType = message?.infoContent?.alarmType;
const alarmTitle = message?.infoContent?.title?.split(' ')?.splice(1)?.join(' ') ?? '';
// const alarmDevice = message?.infoContent?.alarmDevice;
const content = message?.infoContent?.alarmContent;
const alarmValue = message?.infoContent?.alarmValue;
const alarmThreshold = message?.infoContent?.alarmThreshold;
const goPath = (item) => {
confirmRead(false, [message.id]);
const widgetID = 'widget_city_综合运营_管网监控_实时监控_报警监控';
const webPath = 'product/scada/AlertMonitoring/AlertMonitoring';
const widget = findPathByWidget(
'productex/water/IOTMonitor/RealTimeAlarm/RealTimeAlarm',
config.widgets,
'',
'url',
);
window.share.event.emit('listenerMointer', {
widgetId: widgetID,
label: '实时报警',
url: widget.url || webPath,
});
};
return (
<div
className={classNames(`${prefixCls}-notifier-message_scada`, `${prefixCls}-notifier-message-container`)}
title="点击查看详情"
onClick={() => goPath(message)}
>
<div className={`${prefixCls}-notifier-message-title`}>
<span>{alarmType}</span>
<img
className={`${prefixCls}-notifier-message-confirm`}
title="点击标为已读"
onClick={(e) => {
e.stopPropagation();
confirmRead(false, [message.id]);
}}
alt=""
src={require('../../images/oper/ok_line.png')}
/>
</div>
<div className={`${prefixCls}-notifier-message-content`}>
<p>
{alarmTitle}
{/* <span className={`${prefixCls}-notifier-message_scada-deviceinfo`}>{alarmDevice ? `${alarmDevice}: ` : ''}</span> */}
<span className={`${prefixCls}-notifier-message_scada-deviceinfo`}> {message?.infoContent?.deviceCode}</span>
</p>
<p>
<span style={{ color: '#888' }}>{content}</span>
{ alarmThreshold && (<span className={`${prefixCls}-notifier-message_scada-threshold-value`}>预设值:{alarmThreshold}</span>)}
{ alarmValue && (<span className={`${prefixCls}-notifier-message_scada-alarm-value`}>报警值:{alarmValue}</span>)}
</p>
</div>
<p className={`${prefixCls}-notifier-message-time`}>{message.time}</p>
</div>
);
};
export default Alarm;
@root-entry-name: 'default';
@import (reference) '~antd/es/style/themes/index.less';
@message-cls: ~'@{ant-prefix}-notifier-message';
:global {
.@{message-cls}_scada {
background-image: url('../../images/types/alarm.png');
background-repeat: no-repeat;
background-size: 36px 36px;
background-position: 10px 10px;
&-deviceinfo {
color: @primary-color;
}
&-threshold-value {
color: @primary-color;
margin-left: 10px;
}
&-alarm-value {
color: @error-color;
margin-left: 10px;
}
}
}
import React from 'react';
import { ConfigProvider } from 'antd';
import classNames from 'classnames';
import '../styles/common.less';
import './index.less';
// "caseType":"待办工单","flowName":"维修处理流程","nodeName":"审核关单","content":"请迅速到现场处理","time":"2020-11-03 09:11:12"
export class CaseContent {
// eslint-disable-next-line no-use-before-define
constructor({ caseType, flowName, nodeName, content, time } = content) {
this.caseType = caseType;
this.flowName = flowName;
this.nodeName = nodeName;
this.content = content;
this.time = time;
}
}
const Case = ({ message, confirmRead }) => {
const { getPrefixCls } = React.useContext(ConfigProvider.ConfigContext);
const prefixCls = getPrefixCls();
const caseType = message?.infoContent?.caseType;
const flowName = message?.infoContent?.flowName;
const nodeName = message?.infoContent?.nodeName;
const flowNo = message?.infoContent?.flowNo;
const content = message?.infoContent?.content;
const goPath = item => {
confirmRead(false, [message.id]);
const messageType = item.messType;
let label = '';
let widgetID = '';
switch (messageType) {
case '任务派发':
label = '任务派发';
widgetID = 'widget_city_综合运营_二供管理_维修管理_工单办理';
break;
case '任务驳回':
label = '任务驳回';
widgetID = 'widget_city_综合运营_二供管理_维修管理_工单办理';
// eslint-disable-next-line no-underscore-dangle,no-case-declarations
let _tab;
// eslint-disable-next-line prefer-const
_tab = '任务驳回';
break;
default:
label = '工单办理';
widgetID = 'widget_city_综合运营_二供管理_维修管理_工单办理';
break;
}
const webPath = item.webPath || 'product/maintenance/CaseManage/CaseDoingBox/StardCaseDoingBoxView|isDelay=1';
window.share.event.emit('listenerMointer', {
widgetId: widgetID,
label,
url: webPath,
});
};
return (
<div
className={classNames(`${prefixCls}-notifier-message_case`, `${prefixCls}-notifier-message-container`)}
title="点击查看详情"
onClick={() => goPath(message)}
>
<div className={`${prefixCls}-notifier-message-title`}>
<span>{caseType}</span>
<img
className={`${prefixCls}-notifier-message-confirm`}
title="点击标为已读"
onClick={e => {
e.stopPropagation();
confirmRead(false, [message.id]);
}}
src={require('../../images/oper/ok_line.png')}
/>
</div>
<div className={`${prefixCls}-notifier-message-content`}>
<p className={`${prefixCls}-notifier-message_case-flowinfo`}>
<span>{flowName}</span>
<span></span>
<span>{nodeName}</span>
</p>
{flowNo && (
<p>
<span className={`${prefixCls}-notifier-message_case-flowcode`}>工单编码:{flowNo} </span>
</p>
)}
<p>{content}</p>
</div>
<p className={`${prefixCls}-notifier-message-time`}>{message.time}</p>
</div>
);
};
export default Case;
@root-entry-name: 'default';
@import (reference) '~antd/es/style/themes/index.less';
@message-cls: ~'@{ant-prefix}-notifier-message';
:global {
.@{message-cls}_case {
background-image: url('../../images/types/work.png');
background-repeat: no-repeat;
background-size: 36px 36px;
background-position: 10px 10px;
&-flowinfo {
font-weight: 500;
}
&-flowcode {
color: @primary-color;
}
}
}
\ No newline at end of file
import Alarm from './alarm';
import Case from './case';
import Notice from './notice';
import Unknown from './unknown';
export { Alarm, Case, Notice, Unknown };
import React from 'react';
import { ConfigProvider } from 'antd';
import classNames from 'classnames';
import '../styles/common.less';
import './index.less';
export class NoticeContent {
// eslint-disable-next-line no-undef
constructor({ noticeTitle, noticeType, noticeContent, time } = content) {
this.noticeTitle = noticeTitle;
this.noticeType = noticeType;
this.noticeContent = noticeContent;
this.time = time;
}
}
const Notice = ({ message, confirmRead, config, handlerSysDetail }) => {
const { getPrefixCls } = React.useContext(ConfigProvider.ConfigContext);
const prefixCls = getPrefixCls();
const noticeType = message?.infoContent?.noticeType ?? '系统通知';
const noticeTitle = message?.infoContent?.noticeTitle;
const noticeContent = message?.infoContent?.noticeContent ?? '';
// const remark = message?.infoContent?.remark;
const goPath = (item) => {
confirmRead(false, [message.id]);
handlerSysDetail && handlerSysDetail(message);
};
return (
<div
className={classNames(`${prefixCls}-notifier-message_notice`, `${prefixCls}-notifier-message-container`)}
title="点击查看详情"
onClick={() => goPath(message)}
>
<div className={`${prefixCls}-notifier-message-title`}>
<span>{noticeType}{noticeTitle ? `:${noticeTitle}` : ''}</span>
<img
className={`${prefixCls}-notifier-message-confirm`}
title="点击标为已读"
onClick={(e) => {
e.stopPropagation();
confirmRead(false, [message.id]);
}}
src={require('../../images/oper/ok_line.png')}
alt=""
/>
</div>
<div className={`${prefixCls}-notifier-message-content`}>
<p>{noticeContent}</p>
</div>
<p className={`${prefixCls}-notifier-message-time`}>{message.time}</p>
</div>
);
};
export default Notice;
@root-entry-name: 'default';
@import (reference) '~antd/es/style/themes/index.less';
@message-cls: ~'@{ant-prefix}-notifier-message';
:global {
.@{message-cls}_notice {
background-image: url('../../images/types/notice.png');
background-repeat: no-repeat;
background-size: 36px 36px;
background-position: 10px 10px;
&-type {
color: @primary-color;
}
}
}
@root-entry-name: 'default';
@import (reference) '~antd/es/style/themes/index.less';
@message-cls: ~'@{ant-prefix}-notifier-message';
:global {
.@{message-cls}-container {
width: 100%;
padding: 10px 10px 10px 56px;
padding-top: 8px;
padding-right: 10px;
padding-left: 57px;
&:hover {
.@{message-cls}-title {
.@{message-cls}-confirm {
visibility: visible;
}
}
}
.@{message-cls}-title {
display: flex;
justify-content: space-between;
align-items: center;
span {
color: #000;
font-weight: bold;
letter-spacing: 0;
text-shadow: none;
}
.@{message-cls}-confirm {
width: 17px;
height: 12px;
visibility: hidden;
}
}
.@{message-cls}-content {
margin-top: 2px;
p {
i {
margin-right: 10px;
color: #000;
color: #1ba6f9;
font-size: 14px;
font-style: normal;
text-shadow: none;
}
margin-bottom: 3px;
font-size: 12px;
}
}
.@{message-cls}-time {
font-size: 12px;
opacity: 0.65;
margin-bottom: 0;
}
}
}
\ No newline at end of file
import React from 'react';
import { ConfigProvider } from 'antd';
import classNames from 'classnames';
import '../styles/common.less';
import './index.less';
import { getMessageTypeIcon } from '../../utils';
const Unknown = ({ message, confirmRead, handlerUnknowDetail }) => {
const { getPrefixCls } = React.useContext(ConfigProvider.ConfigContext);
const prefixCls = getPrefixCls();
let content = '';
let noticeTitle = '';
try {
const type = typeof message.infoContent;
switch (type) {
case 'object':
let { infoContent } = message;
noticeTitle = infoContent.title || '';
content = infoContent.content || JSON.stringify(message.infoContent);
break;
case 'string':
content = message.infoContent;
break;
default:
break;
}
} catch (e) {
// i
}
const goPath = (item) => {
handlerUnknowDetail && handlerUnknowDetail(message);
confirmRead(false, [message.id]);
};
return (
<div
className={classNames(`${prefixCls}-notifier-message_unknown`, `${prefixCls}-notifier-message-container`)}
title="点击查看详情"
onClick={() => goPath(message)}
style={{backgroundImage: `url(${getMessageTypeIcon(message.messType)})`}}
>
<div className={`${prefixCls}-notifier-message-title`}>
<span>{noticeTitle}</span>
<img
className={`${prefixCls}-notifier-message-confirm`}
title="点击标为已读"
onClick={(e) => {
e.stopPropagation();
confirmRead(false, [message.id]);
}}
src={require('../../images/oper/ok_line.png')}
alt=""
/>
</div>
<div className={`${prefixCls}-notifier-message-content`}>
<p>{content}</p>
</div>
<div className={`${prefixCls}-notifier-message-time`}>{message.time}</div>
</div>
);
};
export default Unknown;
@root-entry-name: 'default';
@import (reference) '~antd/es/style/themes/index.less';
@message-cls: ~'@{ant-prefix}-notifier-message';
:global {
.@{message-cls}_unknown {
background-image: url('../../images/types/other.png');
background-repeat: no-repeat;
background-size: 36px 36px;
background-position: 10px 10px;
}
}
\ No newline at end of file
/**
* 格式化消息显示时间
* @param {*} timestring 'YYYY-MM-DD hh:mm:ss'格式的时间字符串
* @returns
*/
export const formatTime = timestring => {
const timeH = (new Date() - new Date(timestring)) / 1000 / 60;
const timeMss =
Math.abs(timeH) > 1440
? timestring.split('.')[0]
: Math.abs(timeH) > 60
? `${(timeH / 60).toFixed(0)}小时前`
: Math.abs(timeH) > 1
? `${timeH.toFixed(0)}分钟前`
: '刚刚';
return timeMss;
};
import { MESSAGE_TYPE } from '../constants';
import parseMessageToJSON from './parse';
import alarm_icon from '../images/types/alarm.png';
import approve_icon from '../images/types/approve.png';
import meeting_icon from '../images/types/meeting.png';
import notice_icon from '../images/types/notice.png';
import task_icon from '../images/types/task.png';
import work_icon from '../images/types/work.png';
import write_icon from '../images/types/write.png';
import other_icon from '../images/types/other.png';
export const getMessageTypeIcon = messageType => {
let icon = other_icon;
if (!messageType) return icon;
if (messageType === '工时填报') {
icon = write_icon;
} else if (messageType.indexOf('审批') > -1) {
icon = approve_icon;
} else if (messageType.indexOf('会议') > -1) {
icon = meeting_icon;
} else if (messageType.indexOf('任务') > -1) {
icon = task_icon;
}
return icon;
};
export const getMessageClasses = messageType => {
let infoType = '';
switch (messageType) {
// 报警类
case 'SCADA报警':
case '通用报警':
infoType = MESSAGE_TYPE.ALARM_TYPE;
break;
// 工单类
case '工单流程':
case '工单提醒':
infoType = MESSAGE_TYPE.CASE_TYPE;
break;
// 系统消息类
case '系统通知':
case '系统消息':
infoType = MESSAGE_TYPE.SYS_TYPE;
break;
// 其他类
default:
infoType = MESSAGE_TYPE.UNKNOWN;
break;
}
return infoType;
};
/**
* 消息语音单位转换
* 说明:消息通过语音播放时,需要将一些字母单位转换成文字,不然会当做字母播放。
* @param {*} msg
* @returns
*/
export const replaceSpeak = msg => {
msg = msg.replaceAll(' ', '');
msg = msg.replace(/MPa/gi, '兆帕');
msg = msg.replace(/m³\/h/gi, '立方米每小时');
msg = msg.replace(/m³/gi, '立方米');
msg = msg.replace(/kWh/gi, '千瓦时');
msg = msg.replace(/kW/gi, '千瓦');
msg = msg.replace(/min/gi, '分钟');
msg = msg.replace(/m/gi, '米');
msg = msg.replace(/A/gi, '安');
msg = msg.replace(/V/gi, '伏');
msg = msg.replace(/h/gi, '小时');
return msg;
};
export const generatedId = () => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
.replace(/[xy]/g, function(c) {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
})
.toUpperCase();
};
export {
parseMessageToJSON,
}
\ No newline at end of file
import logger from '@wisdom-utils/utils/lib/logger';
import { formatTime } from './format';
const parseMessageCase = messString => {
const attr = messString.infoContent.split('\\n');
const caseContent = {
caseType: attr[0].split('】')[0].split('【')[1],
flowName: attr[0].split('】')[1],
nodeName: attr[1],
content: attr[2],
};
return caseContent;
};
const parseMessageAlarm = messString => {
const attr = messString.infoContent.split('\\n');
const valueField = attr[2]?.split('【')?.[0] ?? '';
const alarmContent = {
title: attr[0],
alarmType: attr[0].split('】')[0].split('【')[1],
deviceCode: '',
alarmDevice: attr[0].split('】')[1],
alarmContent: attr[1],
alarmThreshold: valueField.includes(' / ') ? valueField.split('/')[1].trim(' ') : '',
alarmValue: valueField.includes(' / ')
? valueField.split('/')[0].trim(' ')
: valueField.includes(':')
? valueField.split(':')[1]
: '',
};
return alarmContent;
};
const parseMessageNotice = messString => {
const attr = messString.infoContent.split('\\n');
const noticeContent = {
noticeType: attr[0].split('】')[0].split('【')[1],
noticeTitle: attr[0].split('】')[1],
noticeContent: attr[1],
};
return noticeContent;
};
/**
* 解析1.0消息成消息对象格式
* @param {*} infoClasses
* @param {*} messageContent
* @returns
*/
const parseContentToJSON = (infoClasses, messageContent) => {
let messageBody = messageContent;
try {
switch (infoClasses) {
case 'alarmType':
messageBody = parseMessageAlarm(messageContent);
break;
case 'caseType':
messageBody = parseMessageCase(messageContent);
break;
case 'sysType':
messageBody = parseMessageNotice(messageContent);
break;
case 'unknown':
messageBody = parseMessageNotice(messageContent);
break;
default:
break;
}
} catch (e) {
logger.info(`1.0消息通知解析消息内容出错:${e.message}`, '消息对象:', messageBody);
} finally {
}
return messageBody;
};
export default parseContentToJSON
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment