index.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. import React, { useState, useEffect, useRef, useCallback } from 'react';
  2. import { FormattedMessage, useModel } from 'umi';
  3. import SearchInput from '@/components/SearchInput';
  4. import PageHeader from '@/components/PageHeader';
  5. import Map from '@/components/map';
  6. import TopNavigator from '@/components/topNavigator';
  7. import TopNavRight from './components/topNavRight';
  8. import DeviceModal from './components/deviceModal';
  9. import styles from './index.less';
  10. import mapstyles from '@/assets/css/map.less';
  11. import cx from 'classnames';
  12. import { Spin, Modal, message } from 'antd';
  13. import moment from 'moment';
  14. import { ExclamationCircleOutlined } from '@ant-design/icons';
  15. import { equipImageMap } from '@/config/image.js';
  16. import Icon from '@/tenants-ui/SgIcon';
  17. import type { navigatorItem } from '@/pages/Environment/index';
  18. import { projectId } from '@/config/api.js';
  19. import {
  20. getMapList,
  21. queryEquipStatistics,
  22. queryDeviceTimeManage,
  23. } from '@/services/ant-design-pro/environment';
  24. import {
  25. changeCurtain,
  26. changeAir,
  27. changeLight,
  28. getLamp,
  29. getAirInfo,
  30. } from '@/pages/Equipment/equipmentControl';
  31. //设备状态
  32. type equipStatus = {
  33. equipType: string;
  34. status: number;
  35. };
  36. //设备开启数量
  37. type statistics = {
  38. equipType: string;
  39. runCount: number;
  40. };
  41. const Environment: React.FC = () => {
  42. const { searchSpace } = useModel('searchInfo');
  43. const { getSpaceFunc } = useModel('spaceFunc');
  44. const { envir_all, equip_air, equip_lamp, envir_curtain } = equipImageMap;
  45. const [showModal, setShowModal] = useState<boolean>(false);
  46. const [showSpace, setShowSpace] = useState<API.MapInfo>({}); //当前弹框选中的空间
  47. const [navigatorList, setNavigatorList] = useState<navigatorItem[]>([
  48. {
  49. name: '全部设备',
  50. id: 'all',
  51. src: envir_all,
  52. //color: '#E5574F',
  53. color: '#E89E32',
  54. colorStr: '',
  55. },
  56. {
  57. name: '空调',
  58. id: 'airConditioner',
  59. src: equip_air,
  60. num: 0,
  61. color: '#5E8BCF',
  62. colorStr: '94,139,207,',
  63. },
  64. {
  65. name: '照明',
  66. id: 'light',
  67. src: equip_lamp,
  68. num: 0,
  69. color: '#FFE823',
  70. colorStr: '255,232,35,',
  71. },
  72. {
  73. name: '窗帘',
  74. id: 'curtain',
  75. src: envir_curtain,
  76. num: 0,
  77. color: '#E89E32',
  78. colorStr: '232, 158, 50,',
  79. },
  80. ]);
  81. const [mapList, setMapList] = useState<API.MapInfo[]>([]);
  82. const [mapListFloorId, setMapListFloorId] = useState<string>();
  83. const [mapSize, setMapSize] = useState<any>({});
  84. const [equipMapList, setEquipMapList] = useState<any[]>([]);
  85. const [timeMapList, setTimeMapList] = useState<any[]>([]);
  86. const [selNav, setSelNav] = useState<navigatorItem>(navigatorList[0]);
  87. const [selFloorId, setSelFloorId] = useState<string>();
  88. const [mapCombineList, setMapCombineList] = useState<any[]>([]);
  89. const [loading, setLoading] = useState<boolean>(false);
  90. const changeFloorId = (data: string) => {
  91. //debugger;
  92. setSelFloorId(data);
  93. };
  94. //导航条切换
  95. const navigatorChange = (item: navigatorItem) => {
  96. setSelNav(item);
  97. };
  98. const showModalClick = (item) => {
  99. // console.log('showChart', item);
  100. setShowModal(true);
  101. setShowSpace(item);
  102. };
  103. //当前设备的状态 all全部开启 part部分开启 close全都关闭 还有类型type 包括airConditioner light curtain
  104. //根据设备状态返回不同的 颜色
  105. const getColorOpacity = (value: number, type?: string): string => {
  106. let colorStr = '';
  107. if (selNav.id == 'all') {
  108. //全部设备时 colorStr赋值
  109. var filterRes = navigatorList.filter(function (item) {
  110. return item.id == type;
  111. });
  112. colorStr = (filterRes[0] || {}).colorStr;
  113. } else {
  114. colorStr = selNav.colorStr;
  115. }
  116. if (value === 2) {
  117. //2 是部分开启
  118. return 'rgba(' + colorStr + '0.4)';
  119. } else if (value === 1) {
  120. //1 是开启
  121. return 'rgba(' + colorStr + '1)';
  122. } else if (value === 0 || value == 'nostatus') {
  123. //0是关闭
  124. return 'rgba(196, 196, 196, 0.6)';
  125. }
  126. };
  127. const { confirm } = Modal;
  128. //单个空间的点击事情
  129. const spaceControl = (item, index: number) => {
  130. //全部设备 或者没有房间类型 则不可以点击
  131. if (selNav.id == 'all' || !item.canClick) return;
  132. //没有设备时
  133. if (item[selNav.id] == 'noHave') return;
  134. if (item[selNav.id] == 'nostatus') {
  135. message.success('该空间设备离线');
  136. return;
  137. }
  138. if (item[selNav.id] === 2) {
  139. //半开时
  140. confirm({
  141. title: '请确认',
  142. icon: <ExclamationCircleOutlined />,
  143. content: `是否进入单空间进行调节?`,
  144. okText: '确认',
  145. cancelText: '取消',
  146. onOk() {
  147. showModalClick(item);
  148. },
  149. onCancel() {
  150. //console.log('Cancel');
  151. },
  152. });
  153. return;
  154. }
  155. var status = item[selNav.id] === 1 ? '关闭' : '打开';
  156. confirm({
  157. title: '请确认',
  158. icon: <ExclamationCircleOutlined />,
  159. content: `确认${status}该${selNav.name}吗?`,
  160. okText: '确认',
  161. cancelText: '取消',
  162. onOk() {
  163. // function getDeviceStatus() {
  164. // var interval = setInterval(() => {
  165. // queryDeviceManage();
  166. // }, 1000);
  167. // setTimeout(() => {
  168. // console.log('setTimeout');
  169. // clearInterval(interval);
  170. // }, 60000);
  171. // }
  172. //现在加上循环调用 所以把手动关了
  173. function callback() {
  174. //这是在手动改变状态
  175. // var mapCopy = JSON.parse(JSON.stringify(mapCombineList));
  176. // mapCopy[index][selNav.id] = item[selNav.id] === 1 ? 0 : 1;
  177. // setMapCombineList(mapCopy);
  178. }
  179. //如果是空调
  180. if (selNav.id == 'airConditioner') {
  181. // function getDeviceStatus() {
  182. // //执行查询函数
  183. // getAirInfo(item, callback, 10, projectId);
  184. // }
  185. //changeAir(item, index, getDeviceStatus);
  186. changeAir(item, index, callback);
  187. }
  188. if (selNav.id == 'light') {
  189. // function getDeviceStatus() {
  190. // //请求状态 10是指循环调10次
  191. // getLamp(item, callback, 10);
  192. // }
  193. //debugger;
  194. // changeLight('one', [item], getDeviceStatus, status);
  195. changeLight('one', [item], callback, status);
  196. }
  197. if (selNav.id == 'curtain') {
  198. changeCurtain('one', [item], callback, status);
  199. }
  200. },
  201. onCancel() {
  202. //console.log('Cancel');
  203. },
  204. });
  205. };
  206. //获取地图 mapList
  207. useEffect(() => {
  208. if (selFloorId) {
  209. setLoading(true);
  210. getMapList({
  211. //floorId: 'Fl11010802593241ec348ecb4148806b9034c8957454',
  212. floorId: selFloorId,
  213. })
  214. .then((res) => {
  215. setLoading(false);
  216. var data: API.MapInfo[] = (res.data || {}).spaceList || [];
  217. var mapSize = {
  218. width: (res.data || {}).width,
  219. height: (res.data || {}).height,
  220. };
  221. setMapSize(mapSize);
  222. setMapList(data);
  223. //setSelNav(navigatorList[0]); //每当重新请求地图时 让选中导航的默认是第一个
  224. })
  225. .catch(() => {
  226. setLoading(false);
  227. });
  228. } else {
  229. setMapList([]);
  230. }
  231. }, [selFloorId]);
  232. const queryDeviceManage = () => {
  233. //setLoading(true);
  234. return queryEquipStatistics({
  235. floorId: selFloorId,
  236. projectId: projectId,
  237. })
  238. .then((res) => {
  239. // setLoading(false);
  240. var statistics = res.data.statistics || [];
  241. //赋值 运行中的设备数量
  242. navigatorList.forEach(function (nItem) {
  243. var fres = statistics.filter((sItem: statistics) => {
  244. return sItem.equipType == nItem.id;
  245. });
  246. nItem.num = (fres[0] || {}).runCount;
  247. });
  248. setNavigatorList(navigatorList);
  249. //设备空间信息
  250. var spaceList = res.data.spaceList || [];
  251. spaceList.forEach((item: any) => {
  252. var allType = ['airConditioner', 'light', 'curtain'];
  253. allType.forEach((etype) => {
  254. var filterEquip = (item.equipList || []).filter((eItem: any) => {
  255. return eItem.equipType == etype;
  256. });
  257. //有当前设备
  258. if (filterEquip.length > 0) {
  259. item.equipStatusList = item.equipStatusList ? item.equipStatusList : [];
  260. var filterRes = (item.equipStatusList || []).filter((eItem: equipStatus) => {
  261. return eItem.equipType == etype;
  262. });
  263. //不存时 赋值not 如果没有设备状态 则说明没有设备 1 是开启 0是关闭
  264. if (filterRes.length > 0) {
  265. item[etype] = filterRes[0].status;
  266. } else {
  267. item[etype] = 'nostatus';
  268. item.equipStatusList.push({
  269. equipType: etype,
  270. status: 'nostatus',
  271. });
  272. }
  273. } else {
  274. item[etype] = 'noHave';
  275. }
  276. });
  277. });
  278. //console.log('spaceList', spaceList);
  279. //debugger;
  280. setEquipMapList(spaceList);
  281. return 'next';
  282. })
  283. .catch(() => {
  284. // setLoading(false);
  285. });
  286. };
  287. let setTimer = useRef({});
  288. //获取设备状态 设备种类
  289. useEffect(() => {
  290. // function getDeviceStatus() {
  291. // queryDeviceManage().then((res) => {
  292. // setTimeout(() => {
  293. // getDeviceStatus();
  294. // }, 20000);
  295. // });
  296. // }
  297. if (selFloorId) {
  298. queryDeviceManage(); //第一次执行
  299. if (setTimer.current) {
  300. clearInterval(setTimer.current);
  301. setTimer.current = null;
  302. }
  303. //循环调接口
  304. setTimer.current = setInterval(() => {
  305. queryDeviceManage();
  306. }, 3000);
  307. return () => {
  308. console.log('selFloorId', selFloorId);
  309. clearInterval(setTimer.current);
  310. };
  311. }
  312. }, [selFloorId]);
  313. //获取时间列表 spaceTimeList
  314. useEffect(() => {
  315. if (selFloorId) {
  316. //setLoading(true);
  317. queryDeviceTimeManage({
  318. floorId: selFloorId,
  319. //floorId: 'Fl11010802593241ec348ecb4148806b9034c8957454',
  320. date: moment().format('YYYYMMDD'),
  321. })
  322. .then(function (res) {
  323. // setLoading(false);
  324. var resList = res.content || [];
  325. setTimeMapList(resList);
  326. })
  327. .catch(() => {
  328. // setLoading(false);
  329. });
  330. }
  331. }, [selFloorId]);
  332. //合并空间设备数据 和 空间数据
  333. useEffect(() => {
  334. var combineList: any = [];
  335. mapList.map(function (mitem) {
  336. var spaceId = mitem.spaceId;
  337. var filterSpace1 = equipMapList.filter((ele) => {
  338. return ele.id == spaceId;
  339. });
  340. var filterSpace2 = timeMapList.filter((ele) => {
  341. return ele.id == spaceId;
  342. });
  343. var combine = Object.assign({}, mitem, filterSpace1[0], filterSpace2[0]);
  344. combineList.push(combine);
  345. });
  346. setMapCombineList(combineList);
  347. console.log('combine');
  348. }, [equipMapList, mapList, timeMapList]);
  349. let firtTime: number = 0;
  350. let lastTime: number = 0;
  351. return (
  352. <>
  353. <PageHeader title={<FormattedMessage id="menu.equipment" />} action={<SearchInput />} />
  354. <TopNavigator
  355. navigatorList={navigatorList}
  356. type={'equipment'}
  357. navigatorChange={navigatorChange}
  358. action={
  359. <TopNavRight
  360. selNavObj={selNav}
  361. navigatorList={navigatorList}
  362. mapList={mapCombineList}
  363. queryDeviceManage={queryDeviceManage}
  364. ></TopNavRight>
  365. }
  366. changeFloorId={changeFloorId}
  367. selParamObj={selNav}
  368. ></TopNavigator>
  369. <div className={styles.maptop}>
  370. {selNav.id !== 'all' && (
  371. <div className={styles.right}>
  372. <div>
  373. <span
  374. className={styles.circle}
  375. style={{ backgroundColor: 'rgba(' + selNav.colorStr + '1)' }}
  376. ></span>
  377. <span>开启</span>
  378. </div>
  379. <div>
  380. <span
  381. className={styles.circle}
  382. style={{ backgroundColor: 'rgba(' + selNav.colorStr + '0.4)' }}
  383. ></span>
  384. <span>部分开启</span>
  385. </div>
  386. <div>
  387. <span
  388. className={styles.circle}
  389. style={{ backgroundColor: 'rgba(196, 196, 196, 0.6)' }}
  390. ></span>
  391. <span>关闭</span>
  392. </div>
  393. </div>
  394. )}
  395. </div>
  396. <Spin spinning={loading} wrapperClassName={mapstyles.spinnclass}>
  397. {mapCombineList.length > 0 && (
  398. <Map
  399. mapList={mapCombineList}
  400. mapSize={mapSize}
  401. type={'equipment'}
  402. selFloorId={selFloorId}
  403. render={(item, index) => {
  404. return (
  405. <div
  406. className={mapstyles.houseWrap}
  407. key={index + item.id}
  408. style={{
  409. left: item.left,
  410. top: item.top,
  411. width: item.width,
  412. height: item.height,
  413. }}
  414. >
  415. <div
  416. style={{
  417. backgroundColor: item.canClick
  418. ? selNav.id == 'all'
  419. ? 'rgba(196, 196, 196, 0.4)'
  420. : getColorOpacity(item[selNav.id])
  421. : '',
  422. }}
  423. className={cx(mapstyles.house, {
  424. [mapstyles.notclick]:
  425. !item.canClick || (selNav.id !== 'all' && item[selNav.id] == 'noHave'),
  426. [mapstyles.searchSel]: item.spaceId && item.spaceId === searchSpace.spaceId,
  427. })}
  428. onClick={(event) => {
  429. event.stopPropagation();
  430. if (lastTime - firtTime < 200) {
  431. spaceControl(item, index);
  432. }
  433. }}
  434. onMouseDown={(event) => {
  435. //event.stopPropagation();
  436. firtTime = new Date().getTime();
  437. }}
  438. onMouseUp={(event) => {
  439. //event.stopPropagation();
  440. lastTime = new Date().getTime();
  441. }}
  442. >
  443. {/* {selNav.id !== 'all' && item[selNav.id] == 'noHave' && (
  444. <div className={mapstyles.noDevice}>
  445. <span className={mapstyles.noText}>无设备</span>
  446. </div>
  447. )} */}
  448. {selNav.id == 'all' && (
  449. <div className={mapstyles.allDevice}>
  450. {/* 所有设备的图标列表 */}
  451. {(item.equipStatusList || []).map((ditem: equipStatus, dindex: number) => {
  452. return (
  453. <div
  454. key={dindex + 'device'}
  455. className={mapstyles.icon}
  456. style={{
  457. backgroundColor: getColorOpacity(ditem.status, ditem.equipType),
  458. }}
  459. >
  460. <Icon
  461. className=""
  462. type={ditem.equipType}
  463. style={{ color: '#ffffff', fontWeight: 'bold' }}
  464. ></Icon>
  465. </div>
  466. );
  467. })}
  468. </div>
  469. )}
  470. {item.canClick && (
  471. <div
  472. className={mapstyles.showModal}
  473. onClick={(event) => {
  474. event.stopPropagation();
  475. if (lastTime - firtTime < 200) {
  476. showModalClick(item);
  477. }
  478. }}
  479. onMouseDown={(event) => {
  480. //event.stopPropagation();
  481. firtTime = new Date().getTime();
  482. }}
  483. onMouseUp={(event) => {
  484. //event.stopPropagation();
  485. lastTime = new Date().getTime();
  486. }}
  487. >
  488. <Icon className="" type="hover"></Icon>
  489. </div>
  490. )}
  491. <div className={mapstyles.content}>
  492. <div className={mapstyles.contentDiv}>
  493. <Icon
  494. className=""
  495. type={getSpaceFunc(item.roomFuncType)}
  496. style={{ fontSize: 20 }}
  497. ></Icon>
  498. <div className={mapstyles.name}>{item.localName}</div>
  499. </div>
  500. </div>
  501. </div>
  502. </div>
  503. );
  504. }}
  505. ></Map>
  506. )}
  507. </Spin>
  508. {showModal && (
  509. <DeviceModal
  510. showSpace={showSpace}
  511. onClose={() => {
  512. setShowModal(false);
  513. }}
  514. ></DeviceModal>
  515. )}
  516. </>
  517. );
  518. };
  519. export default Environment;