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

fix: 消息重构

parent cdba8029
......@@ -2,8 +2,10 @@ import React, { Component, useMemo, useState } from 'react';
import { Button, Form, Input, Modal, notification, Pagination } from 'antd';
import { connect } from 'react-redux';
import { Notifier, NoticeIcon, FormattedMessage } from '@wisdom-utils/components';
import { ERR_OK, MESSAGE_TYPE, NEW_MESSAGE } from '@wisdom-utils/components/lib/AppLayout/notifier/constants';
import { FormattedMessage } from '@wisdom-utils/components';
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 service from '../../api/service/notification';
import { actionCreators } from '../../containers/App/store';
......@@ -24,8 +26,11 @@ const PlatformModal = ({ platformVisible, handleClosePlatform, handlerMointer, m
<Modal
title={<FormattedMessage id='component.noticeIcon.modal.alarm.title'/>}
maskClosable={false}
mask={false}
maskStyle={{ pointerEvents: 'none'}}
visible={platformVisible}
zIndex={5000}
wrapClassName={styles.platformModalWrap}
className={styles.platformModal}
footer={<Pagination
simple
......@@ -69,16 +74,16 @@ const PlatformModal = ({ platformVisible, handleClosePlatform, handlerMointer, m
</div>
<div className={styles['content-bottom']}>
<p>
报警值:
<b>
{message && message.infoContent && message.infoContent.alarmValue}
/
</b>
{' / '}
预设值:
{message && message.infoContent && message.infoContent.alarmThreshold}
</p>
<span>
{message && message.time}
</span>
</div>
<p className={styles['message-time']}>{message && message.time}</p>
</div>
</div>
</Modal>
......@@ -122,7 +127,7 @@ class NoticeIconView extends Component {
}
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() {
......@@ -188,38 +193,26 @@ class NoticeIconView extends Component {
handleClosePlatform = event => {
this.setState({ platformVisible: false });
this.notifier.destoryPlatform();
};
// 显示报警弹窗
renderPlatform = message => {
if (!this.state.platformVisible) {
this.setState({
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 => {
try {
this.setState({
videoVisible: true,
});
const { props } = this;
const infoType = message.infoType
? message.infoType
: MESSAGE_TYPE.SCADA_TYPE;
: MESSAGE_TYPE.ALARM_TYPE;
let data = [];
if (this.props.global.mqtt_mess.MessageLevel === '2.0') {
// eslint-disable-next-line no-shadow
......@@ -249,6 +242,10 @@ class NoticeIconView extends Component {
url,
},
});
} catch (error) {
}
};
// 消息弹窗Footer
......@@ -259,7 +256,7 @@ class NoticeIconView extends Component {
}
return (
<div className={styles.notificationFoter}>
<a onClick={toNotifications}>查看全部历史消息</a>
<Button type='link' onClick={toNotifications}>查看通知消息</Button>
</div>
)
}
......@@ -286,6 +283,7 @@ class NoticeIconView extends Component {
};
renderSysPlatform = message => {
try {
const noticeContent =
this.props.global.mqtt_mess.MessageLevel === '2.0' &&
isJSON(message.infoContent)
......@@ -300,6 +298,7 @@ class NoticeIconView extends Component {
noticeContent,
},
});
} catch (error) {}
};
handlerMointer = (event, item, detail) => {
......
......@@ -231,8 +231,12 @@
}
}
}
.platformModalWrap {
pointer-events: none;
}
.platformModal {
//
pointer-events: all;
border-radius: 30px;
:global {
.@{ant-prefix}-modal-header {
......@@ -590,9 +594,9 @@
justify-content: space-between;
line-height: 48px;
padding: 0 5px;
.message-time {
color: #fffaee;
}
}
.message-time {
color: #fffaee;
}
}
}
......@@ -675,17 +679,10 @@
}
.notificationFoter {
background-color: #f6f6f6;
border-top: 1px solid #e3e3e3;
& > a {
color: #44acb6;
display: block;
font-size: 14px;
line-height: 20px;
padding: 12px 16px;
text-align: center;
&:hover {
color: #51c6cf;
}
background-color: #FFF;
border-top: 1px solid #E4E6EA;
line-height: 34px;
& > button {
width: 100%;
}
}
\ 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;
This diff is collapsed.
@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