#
Junjie
2024-10-17 d835d1b51f832889929cdf69010034a30ef44d02
zy-asrs-flow/src/pages/map/index.jsx
@@ -1,24 +1,26 @@
import * as React from 'react'
import * as PIXI from 'pixi.js';
import * as TWEEDLE from 'tweedle.js';
import { FormattedMessage, useIntl, useModel } from '@umijs/max';
import { Layout, Button, Flex, Row, Col, FloatButton, Select, notification, Segmented } from 'antd';
import { Layout, Button, Flex, Row, Col, FloatButton, Select, notification, Segmented, message, Popconfirm } from 'antd';
const { Header, Content } = Layout;
import {
    AppstoreAddOutlined,
    FileAddOutlined,
    CompressOutlined,
    SettingOutlined,
    CloseOutlined
} from '@ant-design/icons';
import './index.css'
import { createStyles } from 'antd-style';
import Edit from './components/device';
import Http from '@/utils/http';
import Settings from './components/settings'
import * as Utils from './utils'
import WebSocketClient from './websocket'
import Player from './player';
import MapSearch from './header/search';
import MapFloor from './header/floor';
import MapDrawer from './drawer';
import BatchDrawer from './batch';
const useStyles = createStyles(({ token }) => {
    let dark = token.colorBgBase === '#000';
@@ -40,7 +42,8 @@
        },
        content: {
            backgroundColor: '#F8FAFB',
            height: 'calc(100vh - 120px)'
            height: 'calc(100vh - 120px)',
            position: 'relative'
        },
        select: {
            color: 'red',
@@ -58,7 +61,7 @@
    SETTINGS_MODEL: "3",
})
let player;
let player, websocket;
const Map = () => {
    const intl = useIntl();
@@ -77,24 +80,36 @@
    });
    const [app, setApp] = React.useState(null);
    const [mapContainer, setMapContainer] = React.useState(null);
    const [didClickSprite, setDidClickSprite] = React.useState(false);
    const [spriteBySettings, setSpriteBySettings] = React.useState(null);
    const prevSpriteBySettingsRef = React.useRef();
    const [drawerVisible, setDrawerVisible] = React.useState(false);
    const [dataFetched, setDataFetched] = React.useState(false);
    const [curSprite, setCurSPrite] = React.useState(null);
    const prevCurSpriteRef = React.useRef();
    const [curFloor, setCurFloor] = React.useState(1);
    const [floorList, setFloorList] = React.useState([]);
    const [curFloor, setCurFloor] = React.useState(() => {
        const storedValue = localStorage.getItem('curFloor');
        return storedValue !== null ? JSON.parse(storedValue) : null;
    });
    const curFloorRef = React.useRef(curFloor);
    const [batchSprites, setBatchSprites] = React.useState([]);
    const [batchDrawerVisible, setBatchDrawerVisible] = React.useState(false);
    // init func
    React.useEffect(() => {
        const initialize = async () => {
            player = new Player(mapRef.current, styles.dark, didClickSprite);
            player = new Player(mapRef.current, styles.dark);
            setApp(player.app);
            setMapContainer(player.mapContainer);
            Utils.syncApp(player.app);
            Utils.syncMapContainer(player.mapContainer);
            Utils.syncNotify(notify);
            websocket = new WebSocketClient('/ws/map/websocket');
            websocket.connect();
            websocket.onMessage = (data) => {
                Utils.updateMapStatusInRealTime(data, () => curFloorRef.current, setCurSPrite);
            }
            const handleResize = () => {
                setWindowSize({
@@ -103,7 +118,12 @@
                });
            };
            window.addEventListener('resize', handleResize);
            await Utils.fetchMapData(intl);
            const mapFloorData = await Utils.fetchMapFloor();
            setFloorList(mapFloorData);
            let defaultFloor = curFloor || mapFloorData?.[0]?.value;
            setCurFloor(defaultFloor);
            await Utils.fetchMapData(defaultFloor);
            setDataFetched(true);
            setModel(MapModel.OBSERVER_MODEL)
            setTimeout(() => {
@@ -112,6 +132,13 @@
            }, 200)
        }
        initialize();
        return () => {
            websocket.onMessage = (data) => { }
            if (websocket) {
                websocket.close();
            }
        }
    }, []);
    // resize
@@ -127,61 +154,69 @@
        }
    }, [app, mapContainer, windowSize])
    // model
    React.useEffect(() => {
        if (!mapContainer && !dataFetched) {
            return;
        }
    // fn switch model
    const switchModel = (model) => {
        Utils.removeSelectedEffect();
        setCurSPrite(null);
        setDrawerVisible(false);
        setSpriteBySettings(null);
        setSettingsVisible(false);
        setBatchSprites([]);
        setBatchDrawerVisible(false);
        switch (model) {
            case MapModel.OBSERVER_MODEL:
                player.hideGridlines();
                player.hideStarryBackground();
                player.activateMapEvent(null);
                Utils.removeSelectedEffect();
                setCurSPrite(null);
                setDeviceVisible(false);
                setSettingsVisible(false);
                player.activateMapEvent(Utils.MapEvent.SELECTION_BOX, (selectedSprites, resetFn) => {
                    setBatchSprites(selectedSprites);
                });
                mapContainer.children.forEach(child => {
                    Utils.viewFeature(child, setCurSPrite);
                })
                break
            case MapModel.MOVABLE_MODEL:
                player.showGridlines();
                player.hideStarryBackground();
                player.activateMapEvent(Utils.MapEvent.SELECTION_BOX);
                Utils.removeSelectedEffect();
                setSpriteBySettings(null);
                setSettingsVisible(false);
                setDrawerVisible(false);
                player.activateMapEvent(Utils.MapEvent.SELECTION_BOX, (selectedSprites, resetFn) => {
                    Utils.spriteListBeMovable(selectedSprites, () => {
                        resetFn();
                    });
                });
                mapContainer.children.forEach(child => {
                    Utils.beMovable(child, setDidClickSprite);
                    Utils.beMovable(child);
                })
                break
            case MapModel.SETTINGS_MODEL:
                player.showGridlines();
                player.showStarryBackground();
                player.activateMapEvent(null);
                setDeviceVisible(false);
                setDrawerVisible(false);
                player.activateMapEvent(Utils.MapEvent.SELECTION_BOX, (selectedSprites, resetFn) => {
                    setBatchSprites(selectedSprites);
                });
                mapContainer.children.forEach(child => {
                    Utils.beSettings(child, setSpriteBySettings, setDidClickSprite);
                    Utils.beSettings(child, setSpriteBySettings);
                })
                break
            default:
                break
        }
    }
    // model
    React.useEffect(() => {
        if (!mapContainer && !dataFetched) {
            return;
        }
        switchModel(model);
    }, [model]);
    // Add New Device
@@ -192,7 +227,7 @@
        Utils.initSprite(sprite, type);
        mapContainer.addChild(sprite);
        Utils.beMovable(sprite, setDidClickSprite);
        Utils.beMovable(sprite);
    };
    // watch curSprite
@@ -215,14 +250,49 @@
    }, [curSprite]);
    const prevCurSprite = prevCurSpriteRef.current;
    const clearLockPathConfirm = (e) => {
        clearLockPath(curFloor);
    };
    const clearLockPath = async (floor) => {
        const hide = message.loading(intl.formatMessage({ id: 'page.clearing', defaultMessage: '正在清空' }));
        try {
            const resp = await Http.doGet('api/map/clearLockPath', { lev: floor });
            if (resp.code === 200) {
                message.success(intl.formatMessage({ id: 'page.clearing.success', defaultMessage: '清空成功' }));
                return true;
            } else {
                message.error(resp.msg);
                return false;
            }
        } catch (error) {
            message.error(intl.formatMessage({ id: 'page.clearing.fail', defaultMessage: '清空失败请重试!' }));
            return false;
        } finally {
            hide();
        }
    }
    // fn switch floor
    const switchFloor = async (floor) => {
        await Utils.fetchMapData(floor);
        switchModel(model);
        setTimeout(() => {
            player.adaptScreen();
        }, 200)
    }
    // watch curFloor
    React.useEffect(() => {
        curFloorRef.current = curFloor;
        if (!mapContainer && !dataFetched) {
            return;
        }
        switchFloor(curFloor);
        localStorage.setItem('curFloor', JSON.stringify(curFloor));
    }, [curFloor]);
    // didClickSprite, stop triggers both sprite click and play's selection boxs
    React.useEffect(() => {
        player.updateDidClickSprite(didClickSprite);
    }, [didClickSprite])
    // watch spriteBySettings
    React.useEffect(() => {
@@ -242,11 +312,18 @@
    }, [spriteBySettings])
    const prevSpriteBySettings = prevSpriteBySettingsRef.current;
    const settingsFinish = (values, fn) => {
        fn();
        // setSettingsVisible(false);
        // setSpriteBySettings(null);
    }
    // watch batchSprites
    React.useEffect(() => {
        if (!mapContainer) {
            return;
        }
        if (batchSprites?.length > 0) {
            setBatchDrawerVisible(true)
        } else {
            player.clearSelectedSprites();
            setBatchDrawerVisible(false)
        }
    }, [batchSprites])
    return (
        <>
@@ -260,6 +337,7 @@
                                    model={model}
                                    setModel={setModel}
                                    ModelEnum={MapModel}
                                    curFloor={curFloor}
                                    curSprite={curSprite}
                                    setCurSPrite={setCurSPrite}
                                    setSpriteBySettings={setSpriteBySettings}
@@ -271,27 +349,27 @@
                                {model === MapModel.OBSERVER_MODEL && (
                                    <>
                                        <Popconfirm
                                            title="清空路径"
                                            description="此操作可能导致小车碰撞,确认清空路径吗?"
                                            onConfirm={clearLockPathConfirm}
                                            okText="确认"
                                            cancelText="取消"
                                        >
                                            <Button
                                                className='map-header-button'
                                                size={'large'}
                                            >
                                                <FormattedMessage id='map.clearLockPath' defaultMessage='清空路径' />
                                            </Button>
                                        </Popconfirm>
                                        <Button
                                            className='map-header-button'
                                            size={'large'}
                                            onClick={async () => {
                                                await Utils.fetchMapData(intl);
                                                player.hideGridlines();
                                                player.hideStarryBackground();
                                                player.activateMapEvent(null);
                                                Utils.removeSelectedEffect();
                                                setCurSPrite(null);
                                                setDeviceVisible(false);
                                                setSettingsVisible(false);
                                                setDrawerVisible(false);
                                                mapContainer.children.forEach(child => {
                                                    Utils.viewFeature(child, setCurSPrite);
                                                })
                                            onClick={() => {
                                                switchFloor(curFloor)
                                            }}
                                        >
                                            <FormattedMessage id='map.load' defaultMessage='加载地图' />
@@ -314,7 +392,7 @@
                                            className='map-header-button'
                                            size={'large'}
                                            onClick={() => {
                                                Utils.saveMapData(intl);
                                                Utils.saveMapData(intl, curFloor);
                                            }}
                                        >
                                            <FormattedMessage id='map.save' defaultMessage='保存地图' />
@@ -346,69 +424,86 @@
                    </Row>
                </Header>
                <Content ref={contentRef} className={styles.content}>
                    <div ref={mapRef} style={{ position: "relative" }} >
                    <div ref={mapRef} />
                    {floorList.length > 0 && (
                        <MapFloor
                            floorList={floorList}
                            curFloor={curFloor}
                            setCurFloor={setCurFloor}
                        />
                        <FloatButton.Group
                            shape="square"
                            style={{
                                left: 35,
                                bottom: 35
                            }}
                        >
                            <FloatButton
                                icon={<CompressOutlined />}
                                onClick={() => {
                                    player.adaptScreen();
                                }}
                            />
                        </FloatButton.Group>
                    )}
                        <FloatButton.Group
                            hidden={model === MapModel.OBSERVER_MODEL}
                            style={{
                                left: 35,
                                bottom: window.innerHeight / 2
                    <FloatButton.Group
                        shape="square"
                        style={{
                            left: 35,
                            bottom: 35
                        }}
                    >
                        <FloatButton
                            icon={<CompressOutlined />}
                            onClick={() => {
                                player.adaptScreen();
                            }}
                            icon={<AppstoreAddOutlined />}
                        >
                            <FloatButton
                                hidden={model === MapModel.OBSERVER_MODEL}
                                type={deviceVisible ? 'primary' : 'default'}
                                tooltip={<div><FormattedMessage id='map.device.add' defaultMessage='添加设备' /></div>}
                                icon={<FileAddOutlined />}
                                onClick={() => {
                                    if (deviceVisible) {
                                        setDeviceVisible(false);
                                    } else {
                                        setDeviceVisible(true);
                                        setModel(MapModel.MOVABLE_MODEL);
                                    }
                                }}
                            />
                            <FloatButton
                                hidden={model === MapModel.OBSERVER_MODEL}
                                type={model === MapModel.SETTINGS_MODEL ? 'primary' : 'default'}
                                tooltip={<div><FormattedMessage id='map.device.oper' defaultMessage='参数设置' /></div>}
                                icon={<SettingOutlined />}
                                onClick={() => {
                                    setModel(model === MapModel.SETTINGS_MODEL ? MapModel.MOVABLE_MODEL : MapModel.SETTINGS_MODEL)
                                }}
                            />
                        </FloatButton.Group>
                    </div>
                        />
                    </FloatButton.Group>
                    <FloatButton.Group
                        hidden={model === MapModel.OBSERVER_MODEL}
                        style={{
                            left: 35,
                            bottom: window.innerHeight / 2
                        }}
                        icon={<AppstoreAddOutlined />}
                    >
                        <FloatButton
                            hidden={model === MapModel.OBSERVER_MODEL}
                            type={deviceVisible ? 'primary' : 'default'}
                            tooltip={<div><FormattedMessage id='map.device.add' defaultMessage='添加设备' /></div>}
                            icon={<FileAddOutlined />}
                            onClick={() => {
                                if (deviceVisible) {
                                    setDeviceVisible(false);
                                } else {
                                    setModel(MapModel.MOVABLE_MODEL);
                                    setDeviceVisible(true);
                                }
                            }}
                        />
                        <FloatButton
                            hidden={model === MapModel.OBSERVER_MODEL}
                            type={model === MapModel.SETTINGS_MODEL ? 'primary' : 'default'}
                            tooltip={<div><FormattedMessage id='map.device.oper' defaultMessage='参数设置' /></div>}
                            icon={<SettingOutlined />}
                            onClick={() => {
                                setModel(model === MapModel.SETTINGS_MODEL ? MapModel.MOVABLE_MODEL : MapModel.SETTINGS_MODEL)
                            }}
                        />
                    </FloatButton.Group>
                </Content>
            </Layout >
            <MapDrawer
                open={drawerVisible}
                curSprite={curSprite}
                curFloor={curFloor}
                refCurr={mapRef.current}
                onCancel={() => {
                    setCurSPrite(null);
                    setDrawerVisible(false);
                }}
            />
            <BatchDrawer
                open={batchDrawerVisible}
                batchSprites={batchSprites}
                refCurr={mapRef.current}
                model={model}
                ModelEnum={MapModel}
                onCancel={() => {
                    setBatchSprites([]);
                    setBatchDrawerVisible(false);
                }}
            />
@@ -429,9 +524,7 @@
                    setSpriteBySettings(null);
                }}
                setSpriteBySettings={setSpriteBySettings}
                setDidClickSprite={setDidClickSprite}
                refCurr={mapRef.current}
                onSubmit={settingsFinish}
            />
        </>
    )