#
vincentlu
2025-12-17 f02bb4f8cf6061851ec7c2c411629a8ae36e06b4
#
2个文件已添加
3个文件已修改
226 ■■■■■ 已修改文件
zy-acs-flow/src/i18n/zh.js 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-flow/src/map/MapPage.jsx 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-flow/src/map/header/AreaFab.jsx 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-flow/src/map/header/RouteFab.jsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-flow/src/map/insight/area/index.jsx 182 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-flow/src/i18n/zh.js
@@ -659,11 +659,11 @@
                monitor: '日志监控',
                save: '保存地图',
                clear: '清空地图',
                adapt: 'ADAPT',
                rotate: 'ROTATE',
                adapt: '适配',
                rotate: '旋转',
                flip: 'FLIP',
                fake: 'FAKE',
                route: 'ROUTE',
                fake: '模拟',
                route: '路线',
                disable: '禁用',
                enable: '启用',
                reset: '重置',
zy-acs-flow/src/map/MapPage.jsx
@@ -26,6 +26,7 @@
import PulseSignal from "../page/components/PulseSignal";
import FakeFab from "./header/FakeFab";
import RouteFab from "./header/RouteFab";
import AreaFab from "./header/AreaFab";
import MoreOperate from "./header/MoreOperate";
let player;
@@ -465,6 +466,16 @@
                        gap: 2
                    }}
                >
                    {mode !== MAP_MODE.AREA_MODE && (
                        <>
                            <AreaFab
                                curZone={curZone}
                                showRoutes={showRoutes}
                                setShowRoutes={setShowRoutes}
                                notify={notify}
                            />
                        </>
                    )}
                    {mode !== MAP_MODE.MOVABLE_MODE && (
                        <>
                            <RouteFab
zy-acs-flow/src/map/header/AreaFab.jsx
New file
@@ -0,0 +1,22 @@
import React from "react";
import { useTranslate } from "react-admin";
import { Fab } from '@mui/material';
import CropFreeIcon from '@mui/icons-material/CropFree';
const AreaFab = ({ active = false, onToggle }) => {
    const translate = useTranslate();
    return (
        <Fab
            variant="extended"
            color={active ? 'primary' : 'default'}
            size="small"
            onClick={onToggle}
        >
            <CropFreeIcon />
            &nbsp;{translate('page.map.devices.area')}&nbsp;
        </Fab>
    );
};
export default AreaFab;
zy-acs-flow/src/map/header/RouteFab.jsx
@@ -33,6 +33,9 @@
                size="small"
                disabled={loading}
                onClick={handleClick}
                sx={{
                    minWidth: 100
                }}
            >
                <AltRouteIcon />
                &nbsp;{translate('page.map.action.route')}&nbsp;
zy-acs-flow/src/map/insight/area/index.jsx
New file
@@ -0,0 +1,182 @@
import React, { useState, useEffect, useMemo } from 'react';
import { useTranslate } from "react-admin";
import {
    Box,
    Tabs,
    Tab,
    Divider,
    List,
    ListItemButton,
    ListItemText,
    Typography,
    Stack,
} from '@mui/material';
import JsonShow from '../../JsonShow';
import { fetchAreaList } from '../../http';
const AreaInsight = ({ curZone, setTitle }) => {
    const translate = useTranslate();
    const [areas, setAreas] = useState([]);
    const [selectedId, setSelectedId] = useState(null);
    const [activeTab, setActiveTab] = useState(0);
    const formatCoord = (value) => {
        if (value === null || value === undefined) {
            return '-';
        }
        if (typeof value === 'number') {
            return value.toFixed(2);
        }
        const num = Number(value);
        if (!Number.isNaN(num)) {
            return num.toFixed(2);
        }
        return String(value);
    };
    useEffect(() => {
        let mounted = true;
        if (!curZone) {
            setAreas([]);
            setSelectedId(null);
            return;
        }
        fetchAreaList(curZone).then((list) => {
            if (!mounted) {
                return;
            }
            const nextList = Array.isArray(list) ? list : [];
            setAreas(nextList);
            setSelectedId((prev) => {
                if (prev) {
                    const exists = nextList.some((item) => item?.id === prev);
                    if (exists) {
                        return prev;
                    }
                }
                return nextList[0]?.id ?? null;
            });
        });
        return () => {
            mounted = false;
        };
    }, [curZone]);
    const selectedArea = useMemo(
        () => areas.find((area) => area?.id === selectedId) || null,
        [areas, selectedId]
    );
    useEffect(() => {
        if (setTitle) {
            const label = selectedArea
                ? `${translate('page.map.devices.area')} - ${selectedArea.name || selectedArea.id}`
                : translate('page.map.devices.area');
            setTitle(label);
        }
    }, [selectedArea, setTitle, translate]);
    useEffect(() => {
        return () => {
            if (setTitle) {
                setTitle(null);
            }
        };
    }, [setTitle]);
    const handleTabChange = (_event, newValue) => {
        setActiveTab(newValue);
    };
    const detailPairs = [
        { label: translate('page.map.area.form.name'), value: selectedArea?.name },
        { label: translate('page.map.area.form.code'), value: selectedArea?.code },
        { label: translate('page.map.area.form.maxQty'), value: selectedArea?.maxCount },
        { label: translate('page.map.area.form.speedLimit'), value: selectedArea?.speedLimit },
        { label: translate('page.map.area.form.priority'), value: selectedArea?.priority },
    ];
    return (
        <Box sx={{ height: '100%', display: 'flex', gap: 2 }}>
            <Box width={220} sx={{ borderRight: 1, borderColor: 'divider', overflowY: 'auto' }}>
                <List dense disablePadding>
                    {areas.map((area) => (
                        <ListItemButton
                            key={area.id}
                            selected={area.id === selectedId}
                            onClick={() => setSelectedId(area.id)}
                        >
                            <ListItemText
                                primary={area.name || `#${area.id}`}
                                secondary={area.code}
                            />
                        </ListItemButton>
                    ))}
                    {areas.length === 0 && (
                        <Typography variant="body2" color="text.secondary" p={2}>
                            {translate('page.map.area.form.codesEmpty')}
                        </Typography>
                    )}
                </List>
            </Box>
            <Box sx={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
                <Tabs
                    value={activeTab}
                    onChange={handleTabChange}
                    sx={{ mb: 0 }}
                >
                    <Tab label={translate('page.map.insight.title')} />
                    <Tab label="JSON" />
                </Tabs>
                <Divider />
                <Box flex={1} pt={2}>
                    {activeTab === 0 && (
                        <Stack spacing={1}>
                            {detailPairs.map(({ label, value }) => (
                                <Stack key={label} direction="row" spacing={1}>
                                    <Typography variant="body2" color="text.secondary" minWidth={120}>
                                        {label}:
                                    </Typography>
                                    <Typography variant="body2">
                                        {value != null && value !== '' ? value : '-'}
                                    </Typography>
                                </Stack>
                            ))}
                            {selectedArea?.start && selectedArea?.end && (
                                <>
                                    <Typography variant="body2" color="text.secondary">
                                        {translate('page.map.area.form.startX')}/{translate('page.map.area.form.startY')}:
                                    </Typography>
                                    <Typography variant="body2">
                                        {`${formatCoord(selectedArea.start.x)} , ${formatCoord(selectedArea.start.y)}`}
                                    </Typography>
                                    <Typography variant="body2" color="text.secondary">
                                        {translate('page.map.area.form.endX')}/{translate('page.map.area.form.endY')}:
                                    </Typography>
                                    <Typography variant="body2">
                                        {`${formatCoord(selectedArea.end.x)} , ${formatCoord(selectedArea.end.y)}`}
                                    </Typography>
                                </>
                            )}
                            {(selectedArea?.codeList?.length ?? 0) > 0 && (
                                <Typography variant="body2">
                                    {translate('page.map.area.form.codes', { count: selectedArea.codeList.length })}
                                </Typography>
                            )}
                        </Stack>
                    )}
                    {activeTab === 1 && (
                        <JsonShow
                            data={selectedArea || {}}
                            height={550}
                        />
                    )}
                </Box>
            </Box>
        </Box>
    );
};
export default AreaInsight;