Commit e7a194d1 authored by 陈龙's avatar 陈龙

feat: 新增alarmscrollassembly

parent 6e3364c1
# `@wisdom-components/AlarmScrollAssembly`
> TODO: description
## Usage
```
const alarmScrollAssembly = require('@wisdom-components/AlarmScrollAssembly');
// TODO: DEMONSTRATE API
```
{
"name": "alarmscrollassembly",
"version": "1.0.0",
"description": "滚动组件",
"author": "chenlong <857265978@qq.com>",
"homepage": "",
"license": "ISC",
"sideEffects": [
"*.less"
],
"module": "es/index.js",
"main": "lib/index.js",
"files": [
"lib",
"es",
"dist"
],
"directories": {
"lib": "lib",
"es": "es",
"dist": "dist",
"test": "__tests__"
},
"publishConfig": {
"registry": "https://g.civnet.cn:4873/"
},
"repository": {
"type": "git",
"url": "https://g.civnet.cn:8443/ReactWeb5/wisdom-components.git"
},
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1"
},
"dependencies": {
"@babel/runtime": "^7.17.9"
}
}
import React, { useEffect, useState } from 'react';
import styles from './NormChart.less';
import { monitorService } from '../api';
import { Checkbox, Row, Input, Tooltip, Tag, Spin } from 'antd';
import ECHistoryInfo from '@wisdom-components/ec_historyinfo';
import HistoryView from '@wisdom-components/ec_historyview';
const { Search } = Input;
const CheckboxGroup = Checkbox.Group;
const NormChart = (props) => {
const { deviceType, deviceCode, info } = props;
const [normList, setNormList] = useState([]);
const [checkedList, setCheckedList] = useState([info.sensorName]);
const [searchStr, setSearchStr] = useState('');
const [normListLoading, setNormListLoading] = useState(false);
const getPumpConfig = () => {
setNormListLoading(true);
monitorService
.GetDeviceQuotaList({
accountName: deviceType,
})
.then((res) => {
if (res.code === 0) {
setNormList(res.data);
} else {
setNormList([]);
}
setNormListLoading(false);
})
.catch((err) => {
console.log(err);
setNormListLoading(false);
});
};
const onChange = (e) => {
setCheckedList(e);
};
const search = (e) => {
setSearchStr(e.target.value);
};
useEffect(() => {
getPumpConfig();
}, []);
return (
<div className={styles.normChart}>
<div className={styles.listWrapper}>
<div className={styles.normList}>
<h3 className={styles.listTitle}>泵房指标</h3>
<Search onChange={search} style={{ marginBottom: 10 }} />
<CheckboxGroup
style={{ height: 'calc(100% - 77px)', overflowY: 'scroll' }}
onChange={onChange}
value={checkedList}
>
<div style={{ display: 'flex', flexDirection: 'column', overflowY: 'scroll' }}>
{normList
.filter((item) => item.name.includes(searchStr))
.map((item) => {
return (
<Row style={{ width: 'calc(100% - 20px)' }}>
<Checkbox value={item.name}>
<Tooltip title={item.name}>
<div
style={{
width: '120px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{item.name}
</div>
</Tooltip>
</Checkbox>
</Row>
);
})}
</div>
</CheckboxGroup>
</div>
</div>
<div className={styles.chartWrapper}>
<Spin className={styles.normSpin} spinning={normListLoading} />
{checkedList && checkedList.length ? (
<div className={styles.selectedNorm}>
<h3 className={styles.listTitle}>已选指标</h3>
<div className={styles.selectedNormWrapper}>
{checkedList.map((item) => {
return (
<Tag
style={{ marginBottom: 8 }}
closable
color="#108ee9"
onClose={() => {
let _checkedList = [...checkedList];
_checkedList = _checkedList.filter((checked) => checked !== item);
setCheckedList(_checkedList);
}}
>
{item}
</Tag>
);
})}
</div>
</div>
) : (
''
)}
<div className={styles.chart}>
{checkedList && checkedList.length && (
<HistoryView
title={'历史数据'}
deviceParams={[
{
deviceCode,
deviceType,
sensors: checkedList.join(','),
},
]}
/>
)}
</div>
</div>
</div>
);
};
export default NormChart;
@import '~antd/es/style/themes/default.less';
.normChart {
display: flex;
width: 100%;
height: 100%;
.listWrapper {
width: 160px;
margin-right: 40px;
.normList {
height: 600px;
padding: 12px 14px 0 6px;
overflow: scroll;
//padding-top: 7px;
box-shadow: 1px 0 0 0 #eeeeee;
.listTitle {
color: rgba(0, 0, 0, 0.85);
font-weight: 500;
}
}
}
.chartWrapper {
position: relative;
flex: 1;
.normSpin {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.selectedNorm {
padding: 10px 0;
.selectedNormWrapper {
height: 50px;
overflow-y: scroll;
}
}
.filters {
}
.chart {
width: 100%;
height: 500px;
}
}
}
import { Modal, notification } from 'antd';
import { instanceRequest, service } from '@wisdom-utils/utils';
import monitorServices from './service/monitor';
const { warning } = Modal;
// eslint-disable-next-line no-return-await
instanceRequest.reportCodeError = true;
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌、用户名、密码错误), 或当前的会话已超时,请重新登录',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
};
export const SUCCESS_CODE = 0;
export const ERROR_CODE = -1;
let instance = null;
instanceRequest.setErrorHandler((error) => {
const { response } = error;
if (response && response.status) {
const errorText = codeMessage[response.status] || response.statusText;
const { status, config } = response;
if (status === 401) {
if (!instance) {
instance = warning({
title: '错误信息',
content: `${codeMessage[status]}`,
centered: true,
onOk(close) {
window.share && window.share.event && window.share.event.emit('triggerLoginout');
close();
},
});
}
} else {
notification.error({
message: `请求错误 ${status}: ${config.url}`,
description: errorText,
});
}
} else if (!response) {
notification.error({
description: '您的网络发生异常,无法连接服务器',
message: '网络异常',
});
}
return response;
});
const monitorService = service(monitorServices);
export { monitorService };
import { request } from '@wisdom-utils/utils';
import * as constants from '../../constants';
const MonitorDevice = '/PandaMonitor/Monitor/Device';
const API = {
GET_ALARM_LIST_REAL_TIME: `${MonitorDevice}/GetAlarmListRealTime`,
GET_DEVICE_QUOTA_LIST: `${MonitorDevice}/GetQuotaList`, //
};
const monitorServices = {
GetAlarmListRealTime: {
url: API.GET_ALARM_LIST_REAL_TIME, // 获取 数据维护日志
method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_HTTP,
},
GetDeviceQuotaList: {
url: API.GET_DEVICE_QUOTA_LIST,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_HTTP,
},
};
export default monitorServices;
export const SERVICE_APP_GET_UI_META = 'app.getUIMeta';
export const SERVICE_APP_LOGIN_MODE = {
password: 'password',
dingding: 'dingding',
weixin: 'weixin',
phone: 'phone',
};
export const LOGIN_DISPLAY = {
Account: 'Account',
WeChart: 'WeChart',
Mobile: 'Mobile',
};
export const LOGIN_WAY = {
Account: 'pdw',
WeChart: 'iotWechat',
Mobile: 'iotPhone',
};
export const WX_REDIRECT_URI = 'https://panda-water.com/civbase/user/login';
export const SERVICE_INTERFACE_SUCCESS_CODE = 0;
export const SERVICE_INTERFACE_PARAMS_EXCEPTION_CODE = -1; // 服务参数异常
export const SERVICE_INTERFACE_HANDLE_EXCEPTION_CODE = -2; // 服务处理异常
export const SERVICE_APP_CLOSE_ALL_TABS = 'app.close.tabs';
export const REQUEST_HTTP = 'http';
export const REQUEST_POP = 'pop';
export const REQUEST_METHOD_GET = 'get';
export const REQUEST_METHOD_POST = 'post';
export const REQUEST_METHOD_PUT = 'put';
export const REQUEST_METHOD_DELETE = 'delete';
import React, { useState, useEffect, useRef } from 'react';
import styles from './index.less';
import PropTypes from 'prop-types';
import { Tag, Tooltip, Modal } from 'antd';
import { RightOutlined, EnvironmentOutlined } from '@ant-design/icons';
import { monitorService } from './api';
import moment from 'moment';
import classnames from 'classnames';
import { switchTimeToPeriod } from './utils';
import NormChart from './NormChart/NormChart';
import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/swiper.min.css';
import 'swiper/components/pagination/pagination.min.css';
import 'swiper/components/navigation/navigation.min.css';
import SwiperCore, { Autoplay, Pagination, Navigation } from 'swiper/core';
SwiperCore.use([Autoplay, Pagination, Navigation]);
/*
* 1. 获取当前所有数据;
* 2. 倒序滚动播放;
* 3. 初始化进入时,取截止到当前时间为止的所有的实时报警数据。
* 4. 播放结束后,重新获取实时报警数据进行第二轮的播放。
* */
const InfoItem = ({ info }) => {
const returnClassName = (type, key) => {
let _className = '';
if (type === '普通') {
_className = 'normal';
} else if (type === '紧急') {
_className = 'warning';
}
return `${_className}${key}`;
};
const { alertLevel, startTime, stationName, alertMsg, deviceAddress } = info;
return (
<div className={styles[returnClassName(alertLevel, 'Wrapper')]}>
<Tag className={styles[returnClassName(alertLevel, 'Tag')]}>{alertLevel}</Tag>
<span className={classnames(styles.time, styles.fontSize13)}>{startTime}</span>
<Tooltip title={deviceAddress} placement={'topLeft'}>
<span className={styles.location}>
<EnvironmentOutlined
style={{ fontSize: 12, color: '#666666', marginRight: 5, verticalAlign: 'middle' }}
/>
{deviceAddress}
</span>
</Tooltip>
<Tooltip title={alertMsg} placement={'topLeft'}>
<span className={styles[returnClassName(alertLevel, 'Content')]}>{alertMsg}</span>
</Tooltip>
<div className={styles.periodWrapper}>
<span className={styles.period}>{switchTimeToPeriod(startTime)}</span>
</div>
</div>
);
};
const AlarmScrollAssembly = (props) => {
const [modalVisible, setModalVisible] = useState(false);
const [currentInfo, setCurrentInfo] = useState({});
const constanceRef = useRef({
level: '1,2',
type: '直接取值,取变化量,取变化率,取是否',
userID: window.globalConfig.userInfo.OID,
userAccess: true,
});
const [realTimeDataList, setRealTimeDataList] = useState(null);
const getRealTimeData = (pagination) => {
return monitorService.GetAlarmListRealTime({
deviceType: props.deviceType,
...constanceRef.current,
pageIndex: pagination.pageIndex,
pageSize: pagination.pageSize,
dateFrom: moment().subtract(1, 'days').format('YYYY-MM-DD 00:00:00'),
dateTo: moment().format('YYYY-MM-DD 23:59:59'),
});
};
const getData = async () => {
let firstRequest = await getRealTimeData({ pageSize: 1, pageIndex: 1 });
let secondRequest = await getRealTimeData({
pageIndex: 1,
pageSize: firstRequest.data.totalCount,
});
setRealTimeDataList(secondRequest.data.list);
};
useEffect(() => {
getData();
}, []);
return (
<div className={styles.alarScrollAssembly} id={'alarmListDiv'}>
{/*{realTimeDataList && realTimeDataList.length ? <Swiper*/}
{realTimeDataList && realTimeDataList.length ? (
<Swiper
// spaceBetween={50}
slidesPerView={1}
modules={[Pagination]}
pagination={{
type: 'fraction',
formatFractionCurrent: (num) => `第${num}条`,
formatFractionTotal: (num) => `共${num}条`,
}}
navigation
autoplay={{
delay: 3000,
disableOnInteraction: false,
}}
loop
direction="vertical"
onSlideChange={(e) => {
if (e.activeIndex === realTimeDataList.length - 1) getData();
}}
>
{realTimeDataList.map((item, index) => {
return (
<SwiperSlide
key={index}
virtualIndex={index}
onClick={() => {
setCurrentInfo(item);
setModalVisible(true);
}}
>
<InfoItem key={index} info={item} />
</SwiperSlide>
);
})}
</Swiper>
) : (
''
)}
{modalVisible && (
<Modal
visible={modalVisible}
onCancel={() => setModalVisible(false)}
width={'80%'}
destroyOnClose
>
<NormChart
info={currentInfo}
deviceType={currentInfo.deviceType}
deviceCode={currentInfo.stationCode}
/>
</Modal>
)}
</div>
);
};
AlarmScrollAssembly.defaultProps = {
deviceType: '二供泵房,二供机组',
};
AlarmScrollAssembly.propTypes = {
deviceType: PropTypes.string,
};
export default AlarmScrollAssembly;
.alarScrollAssembly {
:global {
.swiper-container {
width: 950px;
height: 42px;
.swiper-button-prev {
right: 150px;
left: auto !important;
}
.swiper-button-next:after,
.swiper-button-prev:after {
font-size: 12px;
}
.swiper-pagination-fraction {
top: 15px;
right: 24px;
left: auto;
width: 130px;
line-height: 12px;
}
.swiper-wrapper {
width: calc(100% - 140px);
}
.swiper-slide {
height: 32px;
line-height: 32px;
text-align: center;
}
}
}
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
height: 42px;
overflow-y: scroll;
.warningWrapper {
display: flex;
align-items: center;
max-width: 760px;
padding: 4px 12px;
background: #ffeeee;
border: 1px solid #ffceca;
border-radius: 3px;
}
.normalWrapper {
display: flex;
align-items: center;
max-width: 760px;
padding: 4px 12px;
background: #fffbeb;
border: 1px solid #ffd9a7;
border-radius: 3px;
}
.warningTag {
color: #ffffff;
background: linear-gradient(180deg, #f98b5e 0%, #f44545 100%);
border: none !important;
}
.normalTag {
color: #ffffff;
background: linear-gradient(180deg, #ffbf11 0%, #ff9c00 100%);
border: none !important;
}
.fontSize13 {
font-size: 13px;
}
.time {
display: inline-block;
width: 120px;
color: #333333;
}
.location {
display: inline-block;
//flex: 230;
//width: 230px;
//min-width: 100px;
flex: 1;
align-items: center;
margin-left: 14px;
overflow: hidden;
color: #333333;
white-space: nowrap;
text-overflow: ellipsis;
}
.periodWrapper {
display: flex;
align-items: center;
justify-content: right;
max-width: 190px;
.period {
display: inline-block;
width: 120px;
margin-left: 10px;
color: #333333;
}
}
.normalContent {
display: inline-block;
flex: 2;
margin-left: 14px;
overflow: hidden;
//width: 290px;
color: #fe7200;
white-space: nowrap;
text-overflow: ellipsis;
}
.warningContent {
display: inline-block;
flex: 2;
margin-left: 14px;
overflow: hidden;
//width: 290px;
color: #ff2929;
white-space: nowrap;
text-overflow: ellipsis;
}
}
import Cookies from 'js-cookie';
import pathRegexp from 'path-to-regexp';
import { parse } from 'querystring';
import moment from 'moment';
import pkg from '../../package.json';
const { toString } = Object.prototype;
/* eslint no-useless-escape:0 import/prefer-default-export:0 */
const reg =
/(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
export const isUrl = (path) => reg.test(path);
export const getPageQuery = () => parse(window.location.href.split('?')[1]);
export const getAuthorityFromRouter = (router, pathname) => {
const authority = router.find(
({ routes, path = '/', target = '_self' }) =>
(path && target !== '_blank' && pathRegexp(path).exec(pathname)) ||
(routes && getAuthorityFromRouter(routes, pathname)),
);
if (authority) return authority;
return undefined;
};
export const getRouteAuthority = (path, routeData) => {
let authorities;
routeData.forEach((route) => {
if (pathRegexp(`${route.path}/(.*)`).test(`${path}/`)) {
if (route.authority) {
authorities = route.authority;
}
if (route.path === path) {
authorities = route.authority || authorities;
}
if (route.routes) {
authorities = getRouteAuthority(path, route.routes) || authorities;
}
}
});
return authorities;
};
export function isPromise(obj) {
return (
!!obj && // 有实际含义的变量才执行方法,变量null,undefined和''空串都为false
(typeof obj === 'object' || typeof obj === 'function') && // 初始promise 或 promise.then返回的
typeof obj.then === 'function'
);
}
export function getBaseName() {
return pkg.name.toLocaleLowerCase();
}
const cache = {};
export function findPathByLeafId(leafId, nodes, path, key) {
if (path === undefined) {
path = {};
}
let tmpPath = path;
if (cache[leafId]) {
return cache[leafId];
}
// eslint-disable-next-line no-plusplus
for (let i = 0; i < nodes.length; i++) {
if (nodes[i] && nodes[i][key] && leafId === nodes[i][key]) {
tmpPath = nodes[i];
cache[leafId] = tmpPath;
return tmpPath;
}
if (nodes[i] && nodes[i].routes) {
const findResult = findPathByLeafId(
leafId,
nodes[i].routes,
tmpPath,
key,
// eslint-disable-next-line no-restricted-globals
location,
);
if (findResult) {
cache[leafId] = findResult;
return findResult;
}
}
}
}
export function transformURL(path) {
// path = path.replace(/[(\\|)|(&)]widget=[0-9]*/, '');
const el = document.createElement('input');
el.value = path;
String.fromCharCode(92);
return el.value.replace(/\\/g, '/');
}
export function findPathByWidget(leafId, nodes, path, key) {
if (path === undefined) {
path = {};
}
let tmpPath = path;
// eslint-disable-next-line no-plusplus
for (let i = 0; i < nodes.length; i++) {
if (nodes[i] && nodes[i][key] && nodes[i][key].indexOf(leafId) > -1) {
tmpPath = nodes[i];
return tmpPath;
}
if (nodes[i] && nodes[i].widgets) {
const findResult = findPathByWidget(
leafId,
nodes[i].widgets,
tmpPath,
key,
// eslint-disable-next-line no-restricted-globals
location,
);
if (findResult) {
return findResult;
}
}
}
return tmpPath;
}
export function isJSON(str) {
if (typeof str === 'string') {
try {
const obj = JSON.parse(str);
if (typeof obj === 'object' && obj) {
return true;
}
return false;
} catch (e) {
return false;
}
}
}
export function isString(str) {
return toString.call(str) === '[object String]';
}
export const getKeyName = (path) => {
const truePath = path.split('?')[0];
const curRoute = [].filter((item) => item.path.includes(truePath));
if (!curRoute[0]) {
return {
title: '暂无权限',
tabKey: '403',
};
}
const { name, key, component } = curRoute[0];
return { title: name, tabKey: key, component };
};
export const asyncAction = (action) => {
const wait = new Promise((resolve) => resolve(action));
return (callback) => {
wait.then(() => setTimeout(() => callback()));
};
};
export function getToken() {
return Cookies.get('token');
}
export function setToken(token) {
Cookies.set(token);
}
export const closeTabAction = (history, returnUrl, callback) => {};
const requireContext = require.context('../assets/images', true, /^\.\/.*\.(svg|png)$/);
const localMap = {};
requireContext.keys().map((item) => {
localMap[item] = requireContext(item);
});
/**
* @param context
* @param name
* @returns {any} Example: getLocalImages('device', '二供.svg')
*/
export const getLocalImages = (context, name) => localMap[`./${context}/${name}`];
/**
* @param {any} path 文件路径
* @param {any} filename 下载提升文件名
*/
export const downLoadFile = (path, filename = '') => {
const a = document.createElement('a');
a.href = `/CityInterface/rest/services/FileDownload.svc/download/${path}?_site=${window.globalConfig.userInfo.site}`;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
export const switchTimeToPeriod = (time) => {
if (!time) return '';
let _day = 0;
let _hour = 0;
let _minutes = 0;
_day = moment().diff(moment(time), 'days');
_hour = moment().diff(moment(time), 'hours');
_minutes = moment().diff(moment(time), 'minutes');
return (
`${_day ? _day + '天' : ''}${_hour ? _hour - _day * 24 + '小时' : ''}${
_minutes ? _minutes - _hour * 60 + '分钟' : ''
}` || '刚刚'
);
};
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