From 8429a44614733562117601fd4d9084d426003557 Mon Sep 17 00:00:00 2001
From: vincentlu <t1341870251@gmail.com>
Date: 星期一, 15 十二月 2025 09:52:14 +0800
Subject: [PATCH] #

---
 zy-acs-flow/src/i18n/en.js                          |    1 
 zy-acs-flow/src/map/areaSettings/MapSettings.jsx    |  376 +++++++++++++++++++
 zy-acs-flow/src/map/areaSettings/ConfigSettings.jsx |  178 +++++++++
 zy-acs-flow/src/i18n/zh.js                          |    1 
 zy-acs-flow/src/map/areaSettings/CopyDrawer.jsx     |  412 +++++++++++++++++++++
 zy-acs-flow/src/map/areaSettings/index.jsx          |  108 +++++
 zy-acs-flow/src/map/MapPage.jsx                     |   16 
 7 files changed, 1,091 insertions(+), 1 deletions(-)

diff --git a/zy-acs-flow/src/i18n/en.js b/zy-acs-flow/src/i18n/en.js
index bff65a7..3aa3872 100644
--- a/zy-acs-flow/src/i18n/en.js
+++ b/zy-acs-flow/src/i18n/en.js
@@ -637,6 +637,7 @@
                 direction: 'DIRECTION',
                 agv: 'AGV',
                 point: 'POINT',
+                area: 'AREA',
             },
             action: {
                 startup: 'Startup',
diff --git a/zy-acs-flow/src/i18n/zh.js b/zy-acs-flow/src/i18n/zh.js
index 575200b..87f6658 100644
--- a/zy-acs-flow/src/i18n/zh.js
+++ b/zy-acs-flow/src/i18n/zh.js
@@ -637,6 +637,7 @@
                 direction: '鏂瑰悜',
                 agv: '杞﹁締',
                 point: '瀹氫綅鐐�',
+                area: '鍖哄煙',
             },
             action: {
                 startup: '鍚姩RCS',
diff --git a/zy-acs-flow/src/map/MapPage.jsx b/zy-acs-flow/src/map/MapPage.jsx
index 89aa0c6..1b6ef7e 100644
--- a/zy-acs-flow/src/map/MapPage.jsx
+++ b/zy-acs-flow/src/map/MapPage.jsx
@@ -16,6 +16,7 @@
 import Device from "./Device";
 import Settings from "./settings";
 import Batch from "./batch";
+import AreaSettings from "./areaSettings";
 import * as Http from './http';
 import WebSocketClient from './websocket'
 import ConfirmButton from "../page/components/ConfirmButton";
@@ -49,6 +50,7 @@
     const [deviceVisible, setDeviceVisible] = useState(false);
     const [settingsVisible, setSettingsVisible] = useState(false);
     const [batchSelectionVisible, setBatchSelectionVisible] = useState(false);
+    const [areaSettingsVisible, setAreaSettingsVisible] = useState(false);
     const [areaDrawing, setAreaDrawing] = useState(false);
 
     const [curSprite, setCurSprite] = useState(null);
@@ -133,6 +135,7 @@
         setDeviceVisible(false);
         setSettingsVisible(false);
         setBatchSelectionVisible(false);
+        setAreaSettingsVisible(false);
         setAreaDrawing(false);
         Tool.cancelAreaDrawing();
 
@@ -221,12 +224,13 @@
             }
             if (mode === MAP_MODE.AREA_MODE) {
                 Tool.showSelectedEffect(curSprite);
-                setSettingsVisible(true);
+                setAreaSettingsVisible(true);
             }
         } else {
             Tool.removeSelectedEffect();
             setInsightVisible(false);
             setSettingsVisible(false);
+            setAreaSettingsVisible(false);
         }
     }, [curSprite]);
 
@@ -532,6 +536,16 @@
                 width={570}
             />
 
+            <AreaSettings
+                open={areaSettingsVisible}
+                onCancel={() => {
+                    setCurSprite(null);
+                }}
+                sprite={curSprite}
+                setSpriteSettings={setCurSprite}
+                width={570}
+            />
+
         </Box>
     );
 }
diff --git a/zy-acs-flow/src/map/areaSettings/ConfigSettings.jsx b/zy-acs-flow/src/map/areaSettings/ConfigSettings.jsx
new file mode 100644
index 0000000..2ac2917
--- /dev/null
+++ b/zy-acs-flow/src/map/areaSettings/ConfigSettings.jsx
@@ -0,0 +1,178 @@
+import React, { useEffect } from 'react';
+import { useForm, Controller } from 'react-hook-form';
+import {
+    Box,
+    Grid,
+    Typography,
+    TextField,
+    Slider,
+    Button,
+    Select,
+    MenuItem,
+    InputLabel,
+    FormControl,
+    Stack,
+    Divider,
+} from '@mui/material';
+import { useTranslate } from 'react-admin';
+import * as Tool from '../tool';
+import ConfirmButton from '../../page/components/ConfirmButton';
+import {
+    DEVICE_TYPE,
+} from '../constants';
+import { useNotification } from '../Notification';
+
+const ConfigSettings = (props) => {
+    const { sprite, onSubmit } = props;
+    const notify = useNotification();
+    const translate = useTranslate();
+
+    const { control, handleSubmit, reset, watch, setValue, formState: { errors } } = useForm({
+        defaultValues: { ...sprite?.data },
+    });
+
+    useEffect(() => {
+        if (sprite?.data) {
+            reset({
+                ...sprite.data
+            });
+        }
+    }, [sprite, reset]);
+
+    const deviceType = sprite?.data?.type;
+
+    const rowValue = watch('row');
+    const bayValue = watch('bay');
+
+    useEffect(() => {
+        if (deviceType === DEVICE_TYPE.SHELF) {
+            if (rowValue != null && bayValue != null && rowValue !== '' && bayValue !== '') {
+                setValue('no', `${rowValue}-${bayValue}`);
+            } else {
+                setValue('no', '');
+            }
+        }
+    }, [
+        setValue,
+        deviceType,
+        rowValue,
+        bayValue,
+    ]);
+
+    const onFormSubmit = (data) => {
+        if (sprite?.data) {
+            Object.keys(data).forEach((key) => {
+                sprite.data[key] = data[key];
+            });
+        }
+        if (onSubmit) {
+            onSubmit(data);
+        }
+        notify.info(translate('common.response.success'));
+    };
+
+    return (
+        <>
+            <Box component="form" onSubmit={handleSubmit(onFormSubmit)} noValidate sx={{ mt: 0 }}>
+                <Grid container spacing={1.4}>
+
+                    {deviceType === DEVICE_TYPE.SHELF && (
+                        <>
+                            <Grid item xs={6}>
+                                <Controller
+                                    name="row"
+                                    control={control}
+                                    render={({ field }) => (
+                                        <TextField
+                                            {...field}
+                                            label={translate('page.map.settings.config.shelf.row')}
+                                            type="number"
+                                            value={field.value ?? ''}
+                                            fullWidth
+                                            onChange={(e) => {
+                                                field.onChange(e.target.value === '' ? '' : Number(e.target.value));
+                                            }}
+                                        />
+                                    )}
+                                />
+                            </Grid>
+                            <Grid item xs={6}>
+                                <Controller
+                                    name="bay"
+                                    control={control}
+                                    render={({ field }) => (
+                                        <TextField
+                                            {...field}
+                                            label={translate('page.map.settings.config.shelf.bay')}
+                                            type="number"
+                                            value={field.value ?? ''}
+                                            fullWidth
+                                            onChange={(e) => {
+                                                field.onChange(e.target.value === '' ? '' : Number(e.target.value));
+                                            }}
+                                        />
+                                    )}
+                                />
+                            </Grid>
+                        </>
+                    )}
+
+                    {deviceType === DEVICE_TYPE.CHARGE && (
+                        <>
+                        </>
+                    )}
+
+                    {deviceType === DEVICE_TYPE.STATION && (
+                        <>
+                        </>
+                    )}
+
+                    {deviceType === DEVICE_TYPE.POINT && (
+                        <>
+                        </>
+                    )}
+
+                    <Grid item xs={12}>
+                        <Divider />
+                    </Grid>
+
+                    <Grid item xs={6}>
+                        <Controller
+                            name="no"
+                            control={control}
+                            rules={{
+                                required: translate('ra.validation.required')    // warn msg
+                            }}
+                            render={({ field }) => {
+                                return (
+                                    <TextField
+                                        {...field}
+                                        label={translate('page.map.settings.config.base.no')}
+                                        type="text"
+                                        value={field.value ?? ''}
+                                        error={!!errors.no} // show red warn
+                                        helperText={errors.no ? errors.no.message : null}   // show warn msg
+                                        fullWidth
+                                        onChange={(e) => {
+                                            field.onChange(e);
+                                        }}
+                                    />
+                                )
+                            }}
+                        />
+                    </Grid>
+
+                    <Grid item xs={12} mt={2}>
+                        <Stack direction="row" spacing={2}>
+                            <Button variant="contained" color="primary" type="submit">
+                                {translate('ra.action.confirm')}
+                            </Button>
+                        </Stack>
+                    </Grid>
+                </Grid>
+            </Box>
+        </>
+    );
+};
+
+export default ConfigSettings;
diff --git a/zy-acs-flow/src/map/areaSettings/CopyDrawer.jsx b/zy-acs-flow/src/map/areaSettings/CopyDrawer.jsx
new file mode 100644
index 0000000..19fc976
--- /dev/null
+++ b/zy-acs-flow/src/map/areaSettings/CopyDrawer.jsx
@@ -0,0 +1,412 @@
+import React, { useEffect } from 'react';
+import { useForm, useWatch, Controller } from 'react-hook-form';
+import {
+    Box,
+    Grid,
+    Typography,
+    TextField,
+    Card,
+    CardContent,
+    Button,
+    Select,
+    MenuItem,
+    useTheme,
+    FormControl,
+    Stack,
+    Divider,
+    Drawer,
+    IconButton,
+    Switch,
+    FormControlLabel,
+    FormLabel,
+    ToggleButtonGroup,
+    ToggleButton,
+} from '@mui/material';
+import CloseIcon from '@mui/icons-material/Close';
+import { useTranslate } from 'react-admin';
+import * as Tool from '../tool';
+import { PAGE_DRAWER_WIDTH } from '@/config/setting';
+import {
+    DEVICE_TYPE,
+} from '../constants';
+import { useNotification } from '../Notification';
+
+const incrementOptionsMap = {
+    [DEVICE_TYPE.SHELF]: [
+        { value: 'row', label: 'page.map.settings.config.shelf.row' },
+        { value: 'bay', label: 'page.map.settings.config.shelf.bay' },
+    ],
+    [DEVICE_TYPE.CHARGE]: [
+        { value: 'no', label: 'page.map.settings.config.base.no' },
+    ],
+    [DEVICE_TYPE.STATION]: [
+        { value: 'no', label: 'page.map.settings.config.base.no' },
+    ],
+    [DEVICE_TYPE.POINT]: [
+        { value: 'no', label: 'page.map.settings.config.base.no' },
+    ],
+};
+
+
+const validateIncrement = (value, deviceType, sprite, translate) => {
+    if (!value) {
+        return true;
+    }
+    switch (deviceType) {
+        case DEVICE_TYPE.SHELF:
+            if (!sprite?.data?.row || !sprite?.data?.bay) {
+                return translate('page.map.settings.map.copy.valid.shelf');
+            }
+            break;
+        default:
+            if (!sprite?.data?.no) {
+                return translate('page.map.settings.map.copy.valid.common');
+            }
+            break;
+    }
+    return true;
+};
+
+const getDefaultFormValues = (value = {}) => ({
+    copyDirect: value.copyDirect || '',
+    copyCount: value.copyCount || '',
+    gap: 0.0,
+    autoIncrement: false,
+    incrementMode: 'ascending',
+    incrementValue: undefined,
+});
+
+const CopyDrawer = (props) => {
+    const {
+        open,
+        onCancel,
+        sprite,
+        value,
+        width = PAGE_DRAWER_WIDTH,
+        handleOnCopy,
+        setLastCopiedSprites,
+        setSpriteSettings,
+    } = props;
+    const notify = useNotification();
+    const translate = useTranslate();
+    const theme = useTheme();
+    const themeMode = theme.palette.mode;
+
+    const deviceType = sprite?.data?.type;
+    const incrementOptions = incrementOptionsMap[deviceType]
+
+    const { control, handleSubmit, reset, watch, setValue, formState: { errors } } = useForm({
+        defaultValues: getDefaultFormValues(value),
+    });
+
+    const autoIncrement = useWatch({ control, name: 'autoIncrement' });
+    const incrementValue = useWatch({ control, name: 'incrementValue' });
+
+    useEffect(() => {
+        if (sprite && value && Object.keys(value).length > 0) {
+            reset(getDefaultFormValues(value));
+        }
+    }, [sprite, value, reset, incrementOptions, setValue]);
+
+    useEffect(() => {
+        if (autoIncrement && incrementOptions && incrementOptions.length > 0) {
+            if (!incrementValue) {
+                setValue('incrementValue', incrementOptions[0].value);
+            }
+        } else {
+            setValue('incrementValue', undefined);
+        }
+    }, [autoIncrement, incrementOptions, setValue]);
+
+    const handleClose = () => {
+        onCancel();
+    }
+
+    const onFormSubmit = (data) => {
+        if (!sprite || !data || Object.keys(data).length === 0) {
+            return;
+        }
+
+        const { copyCount, copyDirect, gap, autoIncrement, incrementMode, incrementValue } = data;
+        const copiedSprites = [];
+
+        const adjustPosition = (sprite, direction, gap, index) => {
+            const factor = index + 1;
+            switch (direction) {
+                case 'left':
+                    sprite.position.x -= factor * (gap + sprite.width);
+                    break;
+                case 'right':
+                    sprite.position.x += factor * (gap + sprite.width);
+                    break;
+                case 'up':
+                    sprite.position.y -= factor * (gap + sprite.height);
+                    break;
+                case 'down':
+                    sprite.position.y += factor * (gap + sprite.height);
+                    break;
+                default:
+                    break;
+            }
+        };
+
+        const incrementSpriteData = (copiedSprite, index) => {
+            const incrementAmount = incrementMode === 'descending' ? -(index + 1) : index + 1;
+            switch (deviceType) {
+                case DEVICE_TYPE.SHELF:
+                    if (incrementValue === 'row') {
+                        copiedSprite.data.row = sprite.data.row + incrementAmount;
+                    }
+                    if (incrementValue === 'bay') {
+                        copiedSprite.data.bay = sprite.data.bay + incrementAmount;
+                    }
+                    if (copiedSprite.data.row && copiedSprite.data.bay) {
+                        copiedSprite.data.no = `${copiedSprite.data.row}-${copiedSprite.data.bay}`;
+                    }
+                    break;
+                default:
+                    if (incrementValue === 'no') {
+                        copiedSprite.data.no = Tool.incrementSpriteNo(sprite.data.no, incrementAmount);
+                    }
+                    break;
+            }
+        };
+
+        for (let i = 0; i < copyCount; i++) {
+            const copiedSprite = Tool.copySprite(sprite);
+
+            adjustPosition(copiedSprite, copyDirect, gap, i);
+
+            // auto-increment-value
+            if (autoIncrement && deviceType) {
+                incrementSpriteData(copiedSprite, i);
+            }
+
+            Tool.getMapContainer().addChild(copiedSprite);
+            Tool.beSettings(copiedSprite, setSpriteSettings);
+            copiedSprites.push(copiedSprite);
+        }
+
+        setLastCopiedSprites(copiedSprites);
+        onCancel();
+        handleOnCopy?.(data);
+        notify.info(translate('common.response.success'));
+    };
+
+    return (
+        <>
+            <Drawer
+                variant="persistent"
+                open={open}
+                anchor="right"
+                onClose={handleClose}
+                sx={{ zIndex: 100, opacity: 1 }}
+            >
+                {(open) && (
+                    <Box pt={12} width={{ xs: '100vW', sm: width }} height={'calc(100vh - 200px);'} mt={{ xs: 2, sm: 1 }} sx={{
+                    }}>
+                        <Stack direction="row" p={2}>
+                            <Typography variant="h6" flex="1">
+                                {translate('page.map.settings.map.copy.title')}
+                            </Typography>
+                            <IconButton onClick={handleClose} size="small">
+                                <CloseIcon />
+                            </IconButton>
+                        </Stack>
+
+                        <Box p={3}>
+                            <Card sx={{
+                                p: 2,
+                                transition: '0.3s',
+                                boxShadow: themeMode === 'light'
+                                    ? '0px 2px 8px rgba(0, 0, 0, 0.1)'
+                                    : '0px 2px 2px rgba(255, 255, 255, 0.1)',
+                                '&:hover': {
+                                    boxShadow: themeMode === 'light'
+                                        ? '0px 4px 16px rgba(0, 0, 0, 0.2)'
+                                        : '0px 4px 8px rgba(255, 255, 255, 0.2)',
+                                },
+                                borderRadius: '8px',
+                            }}>
+                                <CardContent>
+                                    <Box component="form" onSubmit={handleSubmit(onFormSubmit)} noValidate sx={{ mt: 0 }}>
+                                        <Grid container spacing={1.4}>
+                                            <Grid item xs={6}>
+                                                <Controller
+                                                    name="copyDirect"
+                                                    control={control}
+                                                    render={({ field }) => (
+                                                        <TextField
+                                                            {...field}
+                                                            label={translate('page.map.settings.map.copy.direction')}
+                                                            InputProps={{
+                                                                readOnly: true,
+                                                            }}
+                                                            fullWidth
+                                                        />
+                                                    )}
+                                                />
+                                            </Grid>
+                                            <Grid item xs={6}>
+                                                <Controller
+                                                    name="copyCount"
+                                                    control={control}
+                                                    render={({ field }) => (
+                                                        <TextField
+                                                            {...field}
+                                                            label={translate('page.map.settings.map.copy.count')}
+                                                            InputProps={{
+                                                                readOnly: true,
+                                                            }}
+                                                            fullWidth
+                                                        />
+                                                    )}
+                                                />
+                                            </Grid>
+
+                                            <Grid item xs={12}>
+                                                <Controller
+                                                    name="gap"
+                                                    control={control}
+                                                    render={({ field }) => (
+                                                        <TextField
+                                                            {...field}
+                                                            label={translate('page.map.settings.map.copy.field.gap')}
+                                                            type="number"
+                                                            sx={{ width: '50%' }}
+                                                            fullWidth
+                                                            inputProps={{
+                                                                // min: 0,
+                                                                step: 1,
+                                                            }}
+                                                            onChange={(e) => {
+                                                                field.onChange(e.target.value === '' ? '' : Number(e.target.value));
+                                                            }}
+                                                        />
+                                                    )}
+                                                />
+                                            </Grid>
+
+                                            <Grid item xs={12} mt={2} mb={1}>
+                                                <Divider />
+                                            </Grid>
+
+                                            <Grid item xs={12}>
+                                                <Controller
+                                                    name="autoIncrement"
+                                                    control={control}
+                                                    rules={{ validate: (value) => validateIncrement(value, deviceType, sprite, translate) }}
+                                                    render={({ field }) => (
+                                                        <FormControlLabel
+                                                            control={
+                                                                <Switch
+                                                                    {...field}
+                                                                    checked={field.value || false}
+                                                                    onChange={(e) => field.onChange(e.target.checked)}
+                                                                />
+                                                            }
+                                                            label={translate('page.map.settings.map.copy.field.autoIncrement')}
+                                                        />
+                                                    )}
+                                                />
+                                                {errors.autoIncrement && (
+                                                    <Typography color="error">
+                                                        {errors.autoIncrement.message}
+                                                    </Typography>
+                                                )}
+                                            </Grid>
+
+                                            {(!!incrementOptions?.length && autoIncrement) && (
+                                                <Grid item xs={12}>
+                                                    <FormControl fullWidth>
+                                                        <FormLabel sx={{ mb: 1 }}>
+                                                            {translate('page.map.settings.map.copy.field.incrementValue')}
+                                                        </FormLabel>
+                                                        <Controller
+                                                            name='incrementValue'
+                                                            control={control}
+                                                            render={({ field }) => (
+                                                                <ToggleButtonGroup
+                                                                    {...field}
+                                                                    value={field.value}
+                                                                    exclusive
+                                                                    onChange={(event, value) => {
+                                                                        if (value !== null) {
+                                                                            field.onChange(value);
+                                                                        }
+                                                                    }}
+                                                                    fullWidth
+                                                                >
+                                                                    {incrementOptions.map((option, idx) => (
+                                                                        <ToggleButton key={idx} value={option.value}>
+                                                                            {translate(option.label)}
+                                                                        </ToggleButton>
+                                                                    ))}
+                                                                </ToggleButtonGroup>
+                                                            )}
+                                                        />
+                                                    </FormControl>
+                                                </Grid>
+                                            )}
+
+                                            {autoIncrement && (
+                                                <Grid item xs={12}>
+                                                    <FormControl fullWidth>
+                                                        <FormLabel sx={{
+                                                            mb: 1
+                                                        }}>
+                                                            {translate('page.map.settings.map.copy.field.incrementMode')}
+                                                        </FormLabel>
+                                                        <Controller
+                                                            name="incrementMode"
+                                                            control={control}
+                                                            render={({ field }) => (
+                                                                <ToggleButtonGroup
+                                                                    {...field}
+                                                                    value={field.value}
+                                                                    exclusive
+                                                                    onChange={(event, value) => {
+                                                                        if (value !== null) {
+                                                                            field.onChange(value);
+                                                                        }
+                                                                    }}
+                                                                    fullWidth
+                                                                >
+                                                                    <ToggleButton value="ascending">
+                                                                        {translate('page.map.settings.map.copy.field.ascend')}
+                                                                    </ToggleButton>
+                                                                    <ToggleButton value="descending">
+                                                                        {translate('page.map.settings.map.copy.field.descend')}
+                                                                    </ToggleButton>
+                                                                </ToggleButtonGroup>
+                                                            )}
+                                                        />
+                                                    </FormControl>
+                                                </Grid>
+                                            )}
+
+                                            <Grid item xs={12} mt={2}>
+                                                <Divider />
+                                            </Grid>
+
+                                            <Grid item xs={12} mt={2}>
+                                                <Stack direction="row" spacing={2}>
+                                                    <Button variant="contained" color="primary" type="submit">
+                                                        {translate('ra.action.confirm')}
+                                                    </Button>
+                                                </Stack>
+                                            </Grid>
+
+                                        </Grid>
+                                    </Box>
+                                </CardContent>
+                            </Card>
+                        </Box>
+                    </Box>
+                )}
+            </Drawer>
+        </>
+    )
+}
+
+export default CopyDrawer;
\ No newline at end of file
diff --git a/zy-acs-flow/src/map/areaSettings/MapSettings.jsx b/zy-acs-flow/src/map/areaSettings/MapSettings.jsx
new file mode 100644
index 0000000..6bc16c2
--- /dev/null
+++ b/zy-acs-flow/src/map/areaSettings/MapSettings.jsx
@@ -0,0 +1,376 @@
+import React, { useEffect, useState } from 'react';
+import { useForm, Controller } from 'react-hook-form';
+import {
+    Box,
+    Grid,
+    Typography,
+    TextField,
+    Slider,
+    Button,
+    Select,
+    MenuItem,
+    InputLabel,
+    FormControl,
+    Stack,
+    Divider,
+} from '@mui/material';
+import MuiInput from '@mui/material/Input';
+import { useTranslate } from 'react-admin';
+import * as Tool from '../tool';
+import ConfirmButton from '../../page/components/ConfirmButton';
+import CopyDrawer from './CopyDrawer';
+
+const MapSettings = (props) => {
+    const { sprite, setSpriteSettings, onSubmit, width, lastCopiedSprites, setLastCopiedSprites } = props;
+    const translate = useTranslate();
+    const [copyVisible, setCopyVisible] = useState(false);
+
+    const { control, handleSubmit, reset, watch } = useForm({
+        defaultValues: {
+            x: sprite?.position?.x || 0,
+            y: sprite?.position?.y || 0,
+            scaleX: sprite?.scale?.x || 1,
+            scaleY: sprite?.scale?.y || 1,
+            rotation: (sprite?.rotation * 180) / Math.PI || 0,
+            copyDirection: 'right',
+            copyCount: 1,
+        },
+    });
+
+    const watchAllFields = watch();
+
+    useEffect(() => {
+        if (sprite) {
+            setCopyVisible(false);
+            reset({
+                x: sprite?.position.x,
+                y: sprite?.position.y,
+                scaleX: sprite?.scale.x,
+                scaleY: sprite?.scale.y,
+                rotation: (sprite?.rotation * 180) / Math.PI,
+            });
+        }
+    }, [sprite, reset]);
+
+    const updateSprite = (data) => {
+        if (sprite) {
+            sprite.position.x = data.x;
+            sprite.position.y = data.y;
+            sprite.scale.x = data.scaleX;
+            sprite.scale.y = data.scaleY;
+            sprite.rotation = (data.rotation * Math.PI) / 180;
+        }
+    };
+
+    const onFormSubmit = (data) => {
+        updateSprite(data);
+        if (onSubmit) {
+            onSubmit(data);
+        }
+    };
+
+    return (
+        <>
+            <Box component="form" onSubmit={handleSubmit(onFormSubmit)} noValidate sx={{ mt: 0 }}>
+                <Grid container spacing={1.4}>
+                    {/* position */}
+                    <Grid item xs={12}>
+                        <Typography variant="inherit">
+                            {translate('page.map.settings.map.base.position')}
+                        </Typography>
+                    </Grid>
+                    <Grid item xs={6} pt={0} sx={{
+                        paddingTop: '8px !important',
+                    }}>
+                        <Controller
+                            name="x"
+                            control={control}
+                            render={({ field }) => (
+                                <TextField
+                                    {...field}
+                                    label="X"
+                                    type="number"
+                                    fullWidth
+                                    onChange={(e) => {
+                                        const value = parseFloat(e.target.value);
+                                        field.onChange(e);
+                                        if (!isNaN(value)) {
+                                            updateSprite({ ...watchAllFields, x: value });
+                                        }
+                                    }}
+                                />
+                            )}
+                        />
+                    </Grid>
+                    <Grid item xs={6} sx={{
+                        paddingTop: '8px !important',
+                    }}>
+                        <Controller
+                            name="y"
+                            control={control}
+                            render={({ field }) => (
+                                <TextField
+                                    {...field}
+                                    label="Y"
+                                    type="number"
+                                    fullWidth
+                                    onChange={(e) => {
+                                        const value = parseFloat(e.target.value);
+                                        field.onChange(e);
+                                        if (!isNaN(value)) {
+                                            updateSprite({ ...watchAllFields, y: value });
+                                        }
+                                    }}
+                                />
+                            )}
+                        />
+                    </Grid>
+
+                    {/* scale */}
+                    <Grid item xs={12}>
+                        <Typography variant="inherit">
+                            {translate('page.map.settings.map.base.scale')}
+                        </Typography>
+                    </Grid>
+                    <Grid item xs={6} sx={{
+                        paddingTop: '8px !important',
+                    }}>
+                        <Controller
+                            name="scaleX"
+                            control={control}
+                            render={({ field }) => (
+                                <TextField
+                                    {...field}
+                                    label="X"
+                                    type="number"
+                                    fullWidth
+                                    inputProps={{ step: 0.1, min: 0.1, max: 10 }}
+                                    onChange={(e) => {
+                                        const value = parseFloat(e.target.value);
+                                        field.onChange(e);
+                                        if (!isNaN(value)) {
+                                            updateSprite({ ...watchAllFields, scaleX: value });
+                                        }
+                                    }}
+                                />
+                            )}
+                        />
+                    </Grid>
+                    <Grid item xs={6} sx={{
+                        paddingTop: '8px !important',
+                    }}>
+                        <Controller
+                            name="scaleY"
+                            control={control}
+                            render={({ field }) => (
+                                <TextField
+                                    {...field}
+                                    label="Y"
+                                    type="number"
+                                    fullWidth
+                                    inputProps={{ step: 0.1, min: 0.1, max: 10 }}
+                                    onChange={(e) => {
+                                        const value = parseFloat(e.target.value);
+                                        field.onChange(e);
+                                        if (!isNaN(value)) {
+                                            updateSprite({ ...watchAllFields, scaleY: value });
+                                        }
+                                    }}
+                                />
+                            )}
+                        />
+                    </Grid>
+
+                    {/* rotation */}
+                    <Grid item xs={12}>
+                        <Typography variant="inherit">
+                            {translate('page.map.settings.map.base.rotation')}
+                        </Typography>
+                    </Grid>
+                    <Grid item xs={12}>
+                        <Box display="flex" alignItems="center">
+                            <Box flex={1} mr={3}>
+                                <Controller
+                                    name="rotation"
+                                    control={control}
+                                    render={({ field }) => (
+                                        <Slider
+                                            {...field}
+                                            // size="small"
+                                            min={0}
+                                            max={360}
+                                            step={1}
+                                            valueLabelDisplay="auto"
+                                            valueLabelFormat={(value) => `${value}掳`}
+                                            onChange={(e, value) => {
+                                                field.onChange(value);
+                                                updateSprite({ ...watchAllFields, rotation: value });
+                                            }}
+                                        />
+                                    )}
+                                />
+                            </Box>
+                            <Box >
+                                <Controller
+                                    name="rotation"
+                                    control={control}
+                                    render={({ field }) => (
+                                        <MuiInput
+                                            {...field}
+                                            size="small"
+                                            value={field.value}
+                                            onChange={(e) => {
+                                                const value = e.target.value === '' ? 0 : Number(e.target.value);
+                                                if (!isNaN(value)) {
+                                                    field.onChange(value);
+                                                    updateSprite({ ...watchAllFields, rotation: value });
+                                                }
+                                            }}
+                                            onBlur={() => {
+                                                if (field.value < 0) {
+                                                    field.onChange(0);
+                                                } else if (field.value > 360) {
+                                                    field.onChange(360);
+                                                }
+                                            }}
+                                            inputProps={{
+                                                step: 1,
+                                                min: 0,
+                                                max: 360,
+                                                type: 'number',
+                                                'aria-labelledby': 'input-slider',
+                                            }}
+                                        />
+                                    )}
+                                />
+                            </Box>
+                        </Box>
+                    </Grid>
+
+                    <Grid item xs={12}>
+                        <Divider />
+                    </Grid>
+
+                    {/* copy */}
+                    <Grid item xs={12}>
+                        <Typography variant="inherit">
+                            {translate('page.map.settings.map.copy.title')}
+                        </Typography>
+                    </Grid>
+                    <Grid item xs={6} sx={{
+                        paddingTop: '8px !important',
+                    }}>
+                        <Controller
+                            name="copyDirection"
+                            control={control}
+                            render={({ field }) => (
+                                <FormControl fullWidth>
+                                    <InputLabel>
+                                        {translate('page.map.settings.map.copy.direction')}
+                                    </InputLabel>
+                                    <Select
+                                        {...field}
+                                        label={translate('page.map.settings.map.copy.direction')}
+                                    >
+                                        <MenuItem value="left">
+                                            {translate('page.map.settings.map.copy.left')}
+                                        </MenuItem>
+                                        <MenuItem value="right">
+                                            {translate('page.map.settings.map.copy.right')}
+                                        </MenuItem>
+                                        <MenuItem value="up">
+                                            {translate('page.map.settings.map.copy.up')}
+                                        </MenuItem>
+                                        <MenuItem value="down">
+                                            {translate('page.map.settings.map.copy.down')}
+                                        </MenuItem>
+                                    </Select>
+                                </FormControl>
+                            )}
+                        />
+                    </Grid>
+                    <Grid item xs={6} sx={{
+                        paddingTop: '8px !important',
+                    }}>
+                        <Controller
+                            name="copyCount"
+                            control={control}
+                            render={({ field }) => (
+                                <TextField
+                                    {...field}
+                                    label={translate('page.map.settings.map.copy.count')}
+                                    type="number"
+                                    fullWidth
+                                    inputProps={{ min: 1 }}
+                                />
+                            )}
+                        />
+                    </Grid>
+                    <Grid item xs={12}>
+                        <Stack direction="row" spacing={2}>
+                            <Button variant="contained" color="primary" onClick={() => {
+                                setCopyVisible(true);
+                            }}>
+                                {translate('page.map.settings.map.copy.execute')}
+                            </Button>
+                            <Button variant="text" color="primary" onClick={() => {
+                                if (lastCopiedSprites && lastCopiedSprites.length > 0) {
+                                    lastCopiedSprites.forEach(copiedSprite => {
+                                        Tool.getMapContainer().removeChild(copiedSprite);
+                                    })
+                                    setLastCopiedSprites([]);
+                                }
+                            }}>
+                                {translate('page.map.settings.map.copy.reverse')}
+                            </Button>
+                        </Stack>
+                    </Grid>
+
+                    <Grid item xs={12}>
+                        <Divider />
+                    </Grid>
+
+                    {/* more */}
+                    <Grid item xs={12}>
+                        <Typography variant="inherit">
+                            {translate('page.map.settings.map.more.title')}
+                        </Typography>
+                    </Grid>
+                    <Grid item xs={12}>
+                        <Stack direction="row" spacing={2}>
+                            <Button variant="outlined" color="error" onClick={() => {
+                                Tool.getMapContainer().removeChild(sprite);
+                                setSpriteSettings(null);
+                                Tool.removeSelectedEffect();
+                                reset();
+                            }}>
+                                {translate('ra.action.delete')}
+                            </Button>
+                            {/* <ConfirmButton /> */}
+                        </Stack>
+                    </Grid>
+                </Grid>
+            </Box >
+
+            <CopyDrawer
+                open={copyVisible}
+                onCancel={() => {
+                    setCopyVisible(false);
+                }}
+                width={width}
+                sprite={sprite}
+                value={{
+                    copyDirect: watch('copyDirection'),
+                    copyCount: watch('copyCount'),
+                }}
+                handleOnCopy={() => {
+
+                }}
+                setLastCopiedSprites={setLastCopiedSprites}
+                setSpriteSettings={setSpriteSettings}
+            />
+        </>
+    );
+};
+
+export default MapSettings;
diff --git a/zy-acs-flow/src/map/areaSettings/index.jsx b/zy-acs-flow/src/map/areaSettings/index.jsx
new file mode 100644
index 0000000..39b0fd1
--- /dev/null
+++ b/zy-acs-flow/src/map/areaSettings/index.jsx
@@ -0,0 +1,108 @@
+import React, { useState, useRef, useEffect } from 'react';
+import { useTranslate } from "react-admin";
+import { Drawer, Box, Typography, Tabs, Tab, IconButton, Stack, useTheme, Card, CardContent, Divider } from '@mui/material';
+import CloseIcon from '@mui/icons-material/Close';
+import { PAGE_DRAWER_WIDTH } from '@/config/setting';
+import MapSettings from './MapSettings';
+import ConfigSettings from './ConfigSettings';
+
+const AreaSettings = (props) => {
+    const { open, onCancel, sprite, width = PAGE_DRAWER_WIDTH, title, setSpriteSettings } = props;
+    const theme = useTheme();
+    const themeMode = theme.palette.mode;
+    const translate = useTranslate();
+
+    const [lastCopiedSprites, setLastCopiedSprites] = useState([]);
+
+    const handleClose = () => {
+        onCancel();
+    }
+
+    const [activeTab, setActiveTab] = useState(0);
+
+    const handleTabChange = (event, newValue) => {
+        setActiveTab(newValue);
+    };
+
+    return (
+        <>
+            <Drawer
+                variant="persistent"
+                open={open}
+                anchor="right"
+                onClose={handleClose}
+                sx={{ zIndex: 100, opacity: .9 }}
+            >
+                {open && (
+                    <Box pt={12} width={{ xs: '100vW', sm: width }} height={'calc(100vh - 200px);'} mt={{ xs: 2, sm: 1 }} sx={{
+                    }}>
+                        <Stack direction="row" p={2}>
+                            <Typography variant="h6" flex="1">
+                                {sprite
+                                    ? translate(`page.map.devices.${sprite?.data?.type?.toLowerCase()}`) + ' - ' + sprite?.data?.name
+                                    : translate('page.map.settings.title')}
+                            </Typography>
+                            <IconButton onClick={handleClose} size="small">
+                                <CloseIcon />
+                            </IconButton>
+                        </Stack>
+
+                        <Box p={3}>
+                            <Card sx={{
+                                transition: '0.3s',
+                                boxShadow: themeMode === 'light'
+                                    ? '0px 2px 8px rgba(0, 0, 0, 0.1)'
+                                    : '0px 2px 2px rgba(255, 255, 255, 0.1)',
+                                '&:hover': {
+                                    boxShadow: themeMode === 'light'
+                                        ? '0px 4px 16px rgba(0, 0, 0, 0.2)'
+                                        : '0px 4px 8px rgba(255, 255, 255, 0.2)',
+                                },
+                                borderRadius: '8px',
+                            }}>
+                                <CardContent>
+                                    <Tabs
+                                        value={activeTab}
+                                        onChange={handleTabChange}
+                                        indicatorColor="primary"
+                                        textColor="primary"
+                                        variant="fullWidth"
+                                        sx={{ mb: 0 }}
+                                    >
+                                        <Tab label={translate('page.map.settings.map.title')} />
+                                        <Tab label={translate('page.map.settings.config.title')} />
+                                    </Tabs>
+
+                                    <Divider />
+
+                                    <Box p={3}>
+                                        {activeTab === 0 && (
+                                            <MapSettings
+                                                sprite={sprite}
+                                                setSpriteSettings={setSpriteSettings}
+                                                onSubmit={() => {
+                                                }}
+                                                width={width}
+                                                lastCopiedSprites={lastCopiedSprites}
+                                                setLastCopiedSprites={setLastCopiedSprites}
+                                            />
+                                        )}
+                                        {activeTab === 1 && (
+                                            <ConfigSettings
+                                                sprite={sprite}
+                                                onSubmit={() => {
+                                                }}
+                                            />
+                                        )}
+                                    </Box>
+                                </CardContent>
+                            </Card>
+                        </Box>
+                    </Box>
+                )}
+            </Drawer>
+        </>
+    )
+}
+
+export default AreaSettings;
\ No newline at end of file

--
Gitblit v1.9.1