From f02bb4f8cf6061851ec7c2c411629a8ae36e06b4 Mon Sep 17 00:00:00 2001
From: vincentlu <t1341870251@gmail.com>
Date: 星期三, 17 十二月 2025 10:57:09 +0800
Subject: [PATCH] #

---
 zy-acs-flow/src/map/header/AreaFab.jsx     |   22 +++++
 zy-acs-flow/src/map/header/RouteFab.jsx    |    3 
 zy-acs-flow/src/map/insight/area/index.jsx |  182 +++++++++++++++++++++++++++++++++++++++++++++
 zy-acs-flow/src/i18n/zh.js                 |    8 +-
 zy-acs-flow/src/map/MapPage.jsx            |   11 ++
 5 files changed, 222 insertions(+), 4 deletions(-)

diff --git a/zy-acs-flow/src/i18n/zh.js b/zy-acs-flow/src/i18n/zh.js
index 28cb7bc..7706c13 100644
--- a/zy-acs-flow/src/i18n/zh.js
+++ b/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: '閲嶇疆',
diff --git a/zy-acs-flow/src/map/MapPage.jsx b/zy-acs-flow/src/map/MapPage.jsx
index 9251563..1c4b71c 100644
--- a/zy-acs-flow/src/map/MapPage.jsx
+++ b/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
diff --git a/zy-acs-flow/src/map/header/AreaFab.jsx b/zy-acs-flow/src/map/header/AreaFab.jsx
new file mode 100644
index 0000000..76d9708
--- /dev/null
+++ b/zy-acs-flow/src/map/header/AreaFab.jsx
@@ -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;
diff --git a/zy-acs-flow/src/map/header/RouteFab.jsx b/zy-acs-flow/src/map/header/RouteFab.jsx
index e961aaf..c390a07 100644
--- a/zy-acs-flow/src/map/header/RouteFab.jsx
+++ b/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;
diff --git a/zy-acs-flow/src/map/insight/area/index.jsx b/zy-acs-flow/src/map/insight/area/index.jsx
new file mode 100644
index 0000000..cc961f5
--- /dev/null
+++ b/zy-acs-flow/src/map/insight/area/index.jsx
@@ -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;

--
Gitblit v1.9.1