From ec2e79e0d510568d51714a7121e4a32c026e6d44 Mon Sep 17 00:00:00 2001
From: Junjie <xjj@123>
Date: 星期二, 19 三月 2024 14:02:52 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/entity/MapItem.java                |   28 +
 zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/service/MapService.java            |   37 +
 zy-asrs-flow/package.json                                                             |    2 
 zy-asrs-flow/src/pages/map/index.css                                                  |   23 
 zy-asrs-flow/src/locales/en-US/map.ts                                                 |   30 +
 zy-asrs-flow/src/components/RightContent/AvatarDropdown.jsx                           |    5 
 zy-asrs-flow/src/pages/map/header/search.jsx                                          |  164 ++++++
 zy-asrs-flow/public/img/map/point.svg                                                 |    3 
 zy-asrs-flow/public/favicon.jpg                                                       |    0 
 zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/controller/param/MapDataParam.java |   16 
 zy-asrs-flow/src/pages/map/drawer/shelf/index.jsx                                     |   60 ++
 zy-asrs-flow/src/pages/map/index.jsx                                                  |  200 +++++--
 zy-asrs-flow/src/config/setting.ts                                                    |    2 
 zy-asrs-flow/src/pages/map/drawer/agv/index.jsx                                       |   60 ++
 zy-asrs-flow/config/config.ts                                                         |    6 
 zy-asrs-flow/src/pages/map/components/settings.jsx                                    |    2 
 zy-asrs-flow/src/pages/map/components/configSettings.jsx                              |  128 ++++
 zy-asrs-flow/src/pages/map/components/mapSettings.jsx                                 |   40 +
 zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/controller/MapController.java      |   38 +
 zy-asrs-flow/src/App.jsx                                                              |    4 
 zy-asrs-flow/src/pages/map/drawer/index.jsx                                           |   52 +
 zy-asrs-flow/src/pages/map/drawer/point/index.jsx                                     |   60 ++
 zy-asrs-flow/src/locales/en-US.ts                                                     |    5 
 zy-asrs-flow/src/pages/map/player.js                                                  |   91 ++
 zy-asrs-flow/src/pages/map/components/mapCopySettings.jsx                             |  166 +++++
 zy-asrs-flow/src/pages/map/components/device.jsx                                      |   94 ++-
 zy-asrs-flow/src/pages/map/drawer/showJson.jsx                                        |   56 ++
 zy-asrs-flow/src/pages/map/utils.js                                                   |  204 +++++++
 28 files changed, 1,407 insertions(+), 169 deletions(-)

diff --git a/zy-asrs-flow/config/config.ts b/zy-asrs-flow/config/config.ts
index 7decf08..fd287f1 100644
--- a/zy-asrs-flow/config/config.ts
+++ b/zy-asrs-flow/config/config.ts
@@ -76,7 +76,7 @@
    * @name layout 鎻掍欢
    * @doc https://umijs.org/docs/max/layout-menu
    */
-  title: 'Ant Design Pro',
+  title: 'zy-asrs-wcs',
   layout: {
     locale: true,
     ...defaultSettings,
@@ -153,4 +153,8 @@
   },
   esbuildMinifyIIFE: true,
   requestRecord: {},
+  // title logo
+  favicons: [
+    '/favicon.jpg'
+  ]
 });
diff --git a/zy-asrs-flow/package.json b/zy-asrs-flow/package.json
index f8dba02..708a4ae 100644
--- a/zy-asrs-flow/package.json
+++ b/zy-asrs-flow/package.json
@@ -1,5 +1,5 @@
 {
-  "name": "ant-design-pro",
+  "name": "zy-asrs-wcs",
   "version": "6.0.0",
   "private": true,
   "description": "An out-of-box UI solution for enterprise applications",
diff --git a/zy-asrs-flow/public/favicon.jpg b/zy-asrs-flow/public/favicon.jpg
new file mode 100644
index 0000000..403da8f
--- /dev/null
+++ b/zy-asrs-flow/public/favicon.jpg
Binary files differ
diff --git a/zy-asrs-flow/public/img/map/point.svg b/zy-asrs-flow/public/img/map/point.svg
new file mode 100644
index 0000000..e042908
--- /dev/null
+++ b/zy-asrs-flow/public/img/map/point.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="15" height="15" viewBox="0 0 15 15">
+  <image id="鍥惧眰_1" data-name="鍥惧眰 1" width="15" height="15" xlink:href="data:img/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAA4ElEQVQokZ3TvUoDQRgF0OMiiqA2NmJlG0xjIWKR1spSES20SpNXMo02Kr6B4BOIAf9rO8FSJdrJJzOwSiDJ3G7hHtjduTPRvXpRyxR2sYMm5vGOB1zgHN+5PlmDKzjChr9ZwDK20EEb99GoUq2BywHwf9ZTr5nxNI6xNATmLOIEM4G3sTYizFmNfxN4f0yYs1fl9y9II/BcIZ6t0jmW5DPwYyF+CnxaiM8Cx+x6Y8LbjL9wiNcR4RsO0M/zjOFv4noIvEm9u3ioX4wYeyuNJm5V7D2O8QPP6fPi//R/2/gBn1UmkhfMfDoAAAAASUVORK5CYII="/>
+</svg>
diff --git a/zy-asrs-flow/src/App.jsx b/zy-asrs-flow/src/App.jsx
index 9283200..84866e2 100644
--- a/zy-asrs-flow/src/App.jsx
+++ b/zy-asrs-flow/src/App.jsx
@@ -8,7 +8,7 @@
 import { getRemoteMenu, getRoutersInfo, getUserInfo, setRemoteMenu, patchRouteWithRemoteMenus } from './services/route';
 import { getToken, setToken } from '@/utils/token-util'
 import { TOKEN_HEADER_NAME, TOKEN_STORE_NAME } from '@/config/setting';
-import { API_BASE_URL } from '@/config/setting'
+import { API_BASE_URL, API_TIMEOUT } from '@/config/setting'
 import { message } from 'antd';
 
 import logo from '../public/img/logo.png'
@@ -231,7 +231,7 @@
 export const request = {
   baseURL: API_BASE_URL,
   ...errorConfig,
-  timeout: 60000,
+  timeout: API_TIMEOUT * 1000,
   // 鍓嶇疆瀹堝崼
   requestInterceptors: [
     (url, options) => {
diff --git a/zy-asrs-flow/src/components/RightContent/AvatarDropdown.jsx b/zy-asrs-flow/src/components/RightContent/AvatarDropdown.jsx
index 9f1889b..fef5588 100644
--- a/zy-asrs-flow/src/components/RightContent/AvatarDropdown.jsx
+++ b/zy-asrs-flow/src/components/RightContent/AvatarDropdown.jsx
@@ -1,6 +1,6 @@
 import { outLogin } from '@/services/ant-design-pro/api';
 import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
-import { history, useModel } from '@umijs/max';
+import { history, useModel, FormattedMessage, useIntl } from '@umijs/max';
 import { Spin } from 'antd';
 import { createStyles } from 'antd-style';
 import { stringify } from 'querystring';
@@ -35,6 +35,7 @@
 };
 
 export const AvatarDropdown = ({ menu, children }) => {
+  const intl = useIntl();
   const { styles } = useStyles();
   const { initialState, setInitialState } = useModel('@@initialState');
 
@@ -116,7 +117,7 @@
     {
       key: 'logout',
       icon: <LogoutOutlined />,
-      label: '閫�鍑虹櫥褰�',
+      label: intl.formatMessage({ id: 'common.account.logout', defaultMessage: '閫�鍑虹櫥褰�' }),
     },
   ];
 
diff --git a/zy-asrs-flow/src/config/setting.ts b/zy-asrs-flow/src/config/setting.ts
index 32a86f4..0d5a234 100644
--- a/zy-asrs-flow/src/config/setting.ts
+++ b/zy-asrs-flow/src/config/setting.ts
@@ -1,6 +1,8 @@
 // 鎺ュ彛鍦板潃
 export const API_BASE_URL: string = 'http://127.0.0.1:9090/wcs';
 
+export const API_TIMEOUT: number = 60;
+
 // 椤圭洰鍚嶇О
 export const PROJECT_NAME: string = 'admin';
 
diff --git a/zy-asrs-flow/src/locales/en-US.ts b/zy-asrs-flow/src/locales/en-US.ts
index 7e1627b..61f4e4e 100644
--- a/zy-asrs-flow/src/locales/en-US.ts
+++ b/zy-asrs-flow/src/locales/en-US.ts
@@ -19,6 +19,11 @@
   'common.idcard':'ID Number',
   'common.introduction':'Introduction',
   'common.execute':'Execute',
+  'common.success':'Success',
+  'common.fail':'Fail',
+  'common.account.logout': 'Logout',
+  'common.search.placeholder': 'Please enter search content',
+  'common.loading.api.message': 'Calling Server...',
   '':'',
   '':'',
   '':'',
diff --git a/zy-asrs-flow/src/locales/en-US/map.ts b/zy-asrs-flow/src/locales/en-US/map.ts
index b14533a..4048b88 100644
--- a/zy-asrs-flow/src/locales/en-US/map.ts
+++ b/zy-asrs-flow/src/locales/en-US/map.ts
@@ -5,6 +5,20 @@
     'map.device.oper': 'Device Settings',
     'map.model.observer': 'Observer Pattern',
     'map.model.editor': 'Editor Pattern',
+    'map.save': 'Save Map',
+    'map.load': 'Load Map',
+    'map.clear': 'Clear Map',
+    '': '',
+    '': '',
+    '': '',
+    '': '',
+    'map.sensor.type.shelf': 'Shelf',
+    'map.sensor.type.agv': 'Agv',
+    'map.sensor.type.point': 'Point',
+    '': '',
+    '': '',
+    '': '',
+    'map.drawer.json': 'JSON',
     '': '',
     '': '',
     '': '',
@@ -30,9 +44,16 @@
     'map.settings.config.param': 'Config Parameters',
     '': '',
     '': '',
+    'map.settings.no': 'No.',
     'map.settings.shelf.no': 'Shelf No',
     'map.settings.shelf.row': 'Row',
     'map.settings.shelf.bay': 'Bay',
+    '': '',
+    '': '',
+    '': '',
+    '': '',
+    'map.settings.point.horizontal': 'Horizontal',
+    'map.settings.point.vertical': 'Vertical',
     '': '',
     '': '',
     '': '',
@@ -45,6 +66,15 @@
     'map.settings.sub.copy.count': 'Count',
     'map.settings.sub.copy.gap': 'Gap',
     'map.settings.sub.copy.id': 'ID',
+    'map.settings.sub.copy.shelf.auto-increment': 'Auto Increment',
+    'map.settings.sub.copy.shelf.increment-value': 'Increment Value',
+    'map.settings.sub.copy.increment.mode': 'Increment Mode',
+    'map.settings.sub.copy.ascend': 'Ascending',
+    'map.settings.sub.copy.descend': 'Descending',
+    '': '',
+    '': '',
+    '': '',
+    'map.settings.sub.copy.warn.config.shelf': 'Please set the shelf parameters first!',
     '': '',
     '': '',
     '': '',
diff --git a/zy-asrs-flow/src/pages/map/components/configSettings.jsx b/zy-asrs-flow/src/pages/map/components/configSettings.jsx
index 7a6a67a..01697ee 100644
--- a/zy-asrs-flow/src/pages/map/components/configSettings.jsx
+++ b/zy-asrs-flow/src/pages/map/components/configSettings.jsx
@@ -1,5 +1,5 @@
 import React, { useState, useRef, useEffect } from 'react';
-import { Col, Form, Input, Row, Checkbox, Slider, Select, Drawer, Space, Button, InputNumber, Card } from 'antd';
+import { message, Form, Input, Row, Checkbox, Slider, Select, Drawer, Space, Button, InputNumber, Card } from 'antd';
 import { FormattedMessage, useIntl, useModel } from '@umijs/max';
 import { createStyles } from 'antd-style';
 import * as Utils from '../utils'
@@ -15,7 +15,17 @@
     const { curSprite, configForm: form } = props;
 
     useEffect(() => {
-    }, []);
+        form.resetFields();
+        if (curSprite) {
+            form.setFieldsValue({
+
+                // shelf
+                row: curSprite?.data?.row,
+                bay: curSprite?.data?.bay,
+                no: curSprite?.data?.no,
+            })
+        }
+    }, [props, form]);
 
     const formValuesChange = (changeList) => {
         if (curSprite && changeList && changeList.length > 0) {
@@ -27,11 +37,11 @@
                             const bay = form.getFieldValue('bay')
                             if (value && bay) {
                                 form.setFieldsValue({
-                                    shelfNo: Utils.pureNumStr(value) + '-' + Utils.pureNumStr(bay)
+                                    no: Utils.pureNumStr(value) + '-' + Utils.pureNumStr(bay)
                                 });
                             } else {
                                 form.setFieldsValue({
-                                    shelfNo: ''
+                                    no: ''
                                 });
                             }
                             break;
@@ -39,11 +49,35 @@
                             const row = form.getFieldValue('row')
                             if (value && row) {
                                 form.setFieldsValue({
-                                    shelfNo: Utils.pureNumStr(row) + '-' + Utils.pureNumStr(value)
+                                    no: Utils.pureNumStr(row) + '-' + Utils.pureNumStr(value)
                                 });
                             } else {
                                 form.setFieldsValue({
-                                    shelfNo: ''
+                                    no: ''
+                                });
+                            }
+                            break;
+                        case 'vertical':
+                            const horizontal = form.getFieldValue('horizontal')
+                            if (value && horizontal) {
+                                form.setFieldsValue({
+                                    no: Utils.pureNumStr(value) + '-' + Utils.pureNumStr(horizontal)
+                                });
+                            } else {
+                                form.setFieldsValue({
+                                    no: ''
+                                });
+                            }
+                            break;
+                        case 'horizontal':
+                            const vertical = form.getFieldValue('vertical')
+                            if (value && vertical) {
+                                form.setFieldsValue({
+                                    no: Utils.pureNumStr(vertical) + '-' + Utils.pureNumStr(value)
+                                });
+                            } else {
+                                form.setFieldsValue({
+                                    no: ''
                                 });
                             }
                             break;
@@ -60,8 +94,32 @@
     const onFinishFailed = (errorInfo) => {
     };
 
-    const handleFinish = async (values) => {
-        props.onSubmit({ ...values });
+    const handleFinish = (values) => {
+        // execute where the form was finished
+        const confirmSettings = () => {
+            if (curSprite && curSprite?.data?.type) {
+                switch (curSprite.data.type) {
+                    case Utils.SENSOR_TYPE.SHELF:
+                        curSprite.data.no = values.no; // *
+                        curSprite.data.row = values.row;
+                        curSprite.data.bay = values.bay;
+                        break;
+                    case Utils.SENSOR_TYPE.POINT:
+                        curSprite.data.no = values.no; // *
+                        curSprite.data.horizontal = values.horizontal;
+                        curSprite.data.vertical = values.vertical;
+                        break;
+                    case Utils.SENSOR_TYPE.AGV:
+                        curSprite.data.no = values.no; // *
+                        break;
+                    default:
+                        break;
+                }
+            }
+            message.success(intl.formatMessage({ id: 'common.success', defaultMessage: '鎿嶄綔鎴愬姛' }));
+        }
+
+        props.onSubmit({ ...values }, confirmSettings);
     }
 
     return (
@@ -90,14 +148,14 @@
                 }}
             >
                 <br />
-                
+
                 <Form.Item
                     label={intl.formatMessage({ id: 'map.settings.type', defaultMessage: '绫诲瀷' })}
                 >
                     <span>{curSprite?.data?.type}</span>
                 </Form.Item>
                 <Form.Item
-                    label={intl.formatMessage({ id: 'map.settings.uuid', defaultMessage: '缂栧彿' })}
+                    label={intl.formatMessage({ id: 'map.settings.uuid', defaultMessage: '鍦板浘鍙�' })}
                 >
                     <span>{curSprite?.data?.uuid}</span>
                 </Form.Item>
@@ -115,7 +173,7 @@
                             label={intl.formatMessage({ id: 'map.settings.shelf.row', defaultMessage: '鎺�' })}
                             rules={[
                                 {
-                                    required: true,
+                                    required: false,
                                 },
                             ]}
                         >
@@ -130,7 +188,27 @@
                             label={intl.formatMessage({ id: 'map.settings.shelf.bay', defaultMessage: '鍒�' })}
                             rules={[
                                 {
-                                    required: true,
+                                    required: false,
+                                },
+                            ]}
+                        >
+                            <InputNumber
+                                style={{
+                                    width: '50%',
+                                }}
+                            />
+                        </Form.Item>
+                    </>
+                )}
+
+                {curSprite?.data?.type === Utils.SENSOR_TYPE.POINT && (
+                    <>
+                        <Form.Item
+                            name='vertical'
+                            label={intl.formatMessage({ id: 'map.settings.point.vertical', defaultMessage: '绾靛悜' })}
+                            rules={[
+                                {
+                                    required: false,
                                 },
                             ]}
                         >
@@ -141,15 +219,15 @@
                             />
                         </Form.Item>
                         <Form.Item
-                            name='shelfNo'
-                            label={intl.formatMessage({ id: 'map.settings.shelf.no', defaultMessage: '璐ф灦鍙�' })}
+                            name='horizontal'
+                            label={intl.formatMessage({ id: 'map.settings.point.horizontal', defaultMessage: '妯悜' })}
                             rules={[
                                 {
-                                    required: true,
+                                    required: false,
                                 },
                             ]}
                         >
-                            <Input
+                            <InputNumber
                                 style={{
                                     width: '50%',
                                 }}
@@ -159,11 +237,27 @@
                 )}
 
                 <Form.Item
+                    name='no'
+                    label={intl.formatMessage({ id: 'map.settings.no', defaultMessage: '缂栧彿' })}
+                    rules={[
+                        {
+                            required: false,
+                        },
+                    ]}
+                >
+                    <Input
+                        style={{
+                            width: '50%',
+                        }}
+                    />
+                </Form.Item>
+
+                <Form.Item
                     wrapperCol={{
                         offset: 4,
                         span: 16,
                     }}>
-                    <Button type="primary" onClick={handleFinish}>
+                    <Button type="primary" htmlType="submit">
                         <FormattedMessage id='common.submit' defaultMessage='淇濆瓨' />
                     </Button>
                 </Form.Item>
diff --git a/zy-asrs-flow/src/pages/map/components/device.jsx b/zy-asrs-flow/src/pages/map/components/device.jsx
index 72a6818..18477fd 100644
--- a/zy-asrs-flow/src/pages/map/components/device.jsx
+++ b/zy-asrs-flow/src/pages/map/components/device.jsx
@@ -37,9 +37,10 @@
 
 import agv from '/public/img/map/agv.svg'
 import shelf from '/public/img/map/shelf.png'
-import { Util } from '@antv/g6';
+import point from '/public/img/map/point.svg'
 
 const Device = (props) => {
+    const intl = useIntl();
     const { styles } = useStyles();
     const [dragging, setDragging] = useState(false);
     const [dragSprite, setDragSprite] = useState(null);
@@ -75,6 +76,9 @@
                 rootStyle={{ position: "absolute" }}
                 mask={false}
                 width={378}
+                style={{
+                    opacity: .8
+                }}
                 extra={
                     <Space>
                         <Button onClick={() => props.onCancel()}><FormattedMessage id='common.cancel' defaultMessage='鍙栨秷' /></Button>
@@ -90,7 +94,9 @@
                             draggable="true"
                             onDragStart={(e) => onDragStart(e, Utils.SENSOR_TYPE.AGV)}
                         />
-                        <div className={styles.title}>AGV</div>
+                        <div className={styles.title}>
+                            <FormattedMessage id='map.sensor.type.agv' defaultMessage='鏃犱汉灏忚溅' />
+                        </div>
                     </Col>
                     <Col className={styles.mapCol} span={8} >
                         <Image
@@ -100,17 +106,23 @@
                             draggable="true"
                             onDragStart={(e) => onDragStart(e, Utils.SENSOR_TYPE.SHELF)}
                         />
-                        <div className={styles.title}>SHELF</div>
+                        <div className={styles.title}>
+                            <FormattedMessage id='map.sensor.type.shelf' defaultMessage='璐ф灦' />
+                        </div>
                     </Col>
-                    <Col className={styles.mapCol} span={8} >
+                    <Col className={styles.mapCol} span={8}>
                         <Image
-                            src={agv}
+                            src={point}
+                            style={{
+                            }}
                             width='50px'
                             preview={false}
                             draggable="true"
-                            onDragStart={(e) => onDragStart(e, 'AGV')}
+                            onDragStart={(e) => onDragStart(e, Utils.SENSOR_TYPE.POINT)}
                         />
-                        <div>AGV</div>
+                        <div className={styles.title}>
+                            <FormattedMessage id='map.sensor.type.point' defaultMessage='瀹氫綅鐐�' />
+                        </div>
                     </Col>
                 </Row>
                 <Row className={styles.mapRow}>
@@ -120,9 +132,23 @@
                             width='50px'
                             preview={false}
                             draggable="true"
-                            onDragStart={(e) => onDragStart(e, 'AGV')}
+                            onDragStart={(e) => onDragStart(e, Utils.SENSOR_TYPE.AGV)}
                         />
-                        <div>AGV</div>
+                        <div className={styles.title}>
+                            <FormattedMessage id='map.sensor.type.agv' defaultMessage='鏃犱汉灏忚溅' />
+                        </div>
+                    </Col>
+                    <Col className={styles.mapCol} span={8} >
+                        <Image
+                            src={shelf}
+                            width='35px'
+                            preview={false}
+                            draggable="true"
+                            onDragStart={(e) => onDragStart(e, Utils.SENSOR_TYPE.SHELF)}
+                        />
+                        <div className={styles.title}>
+                            <FormattedMessage id='map.sensor.type.shelf' defaultMessage='璐ф灦' />
+                        </div>
                     </Col>
                     <Col className={styles.mapCol} span={8} >
                         <Image
@@ -130,19 +156,11 @@
                             width='50px'
                             preview={false}
                             draggable="true"
-                            onDragStart={(e) => onDragStart(e, 'AGV')}
+                            onDragStart={(e) => onDragStart(e, Utils.SENSOR_TYPE.AGV)}
                         />
-                        <div>AGV</div>
-                    </Col>
-                    <Col className={styles.mapCol} span={8} >
-                        <Image
-                            src={agv}
-                            width='50px'
-                            preview={false}
-                            draggable="true"
-                            onDragStart={(e) => onDragStart(e, 'AGV')}
-                        />
-                        <div>AGV</div>
+                        <div className={styles.title}>
+                            <FormattedMessage id='map.sensor.type.agv' defaultMessage='鏃犱汉灏忚溅' />
+                        </div>
                     </Col>
                 </Row>
                 <Row className={styles.mapRow}>
@@ -152,9 +170,23 @@
                             width='50px'
                             preview={false}
                             draggable="true"
-                            onDragStart={(e) => onDragStart(e, 'AGV')}
+                            onDragStart={(e) => onDragStart(e, Utils.SENSOR_TYPE.AGV)}
                         />
-                        <div>AGV</div>
+                        <div className={styles.title}>
+                            <FormattedMessage id='map.sensor.type.agv' defaultMessage='鏃犱汉灏忚溅' />
+                        </div>
+                    </Col>
+                    <Col className={styles.mapCol} span={8} >
+                        <Image
+                            src={shelf}
+                            width='35px'
+                            preview={false}
+                            draggable="true"
+                            onDragStart={(e) => onDragStart(e, Utils.SENSOR_TYPE.SHELF)}
+                        />
+                        <div className={styles.title}>
+                            <FormattedMessage id='map.sensor.type.shelf' defaultMessage='璐ф灦' />
+                        </div>
                     </Col>
                     <Col className={styles.mapCol} span={8} >
                         <Image
@@ -162,19 +194,11 @@
                             width='50px'
                             preview={false}
                             draggable="true"
-                            onDragStart={(e) => onDragStart(e, 'AGV')}
+                            onDragStart={(e) => onDragStart(e, Utils.SENSOR_TYPE.AGV)}
                         />
-                        <div>AGV</div>
-                    </Col>
-                    <Col className={styles.mapCol} span={8} >
-                        <Image
-                            src={agv}
-                            width='50px'
-                            preview={false}
-                            draggable="true"
-                            onDragStart={(e) => onDragStart(e, 'AGV')}
-                        />
-                        <div>AGV</div>
+                        <div className={styles.title}>
+                            <FormattedMessage id='map.sensor.type.agv' defaultMessage='鏃犱汉灏忚溅' />
+                        </div>
                     </Col>
                 </Row>
             </Drawer>
diff --git a/zy-asrs-flow/src/pages/map/components/mapCopySettings.jsx b/zy-asrs-flow/src/pages/map/components/mapCopySettings.jsx
index e2af697..1ebb8ac 100644
--- a/zy-asrs-flow/src/pages/map/components/mapCopySettings.jsx
+++ b/zy-asrs-flow/src/pages/map/components/mapCopySettings.jsx
@@ -1,5 +1,5 @@
 import React, { useState, useRef, useEffect } from 'react';
-import { Col, Form, Input, Row, Checkbox, Slider, Select, Drawer, Space, Button, InputNumber, Switch } from 'antd';
+import { Col, Form, Input, Row, Switch, Slider, message, Drawer, Space, Button, InputNumber, Segmented } from 'antd';
 import { FormattedMessage, useIntl, useModel } from '@umijs/max';
 import { createStyles } from 'antd-style';
 import * as Utils from '../utils'
@@ -14,16 +14,36 @@
     const { styles } = useStyles();
     const { curSprite } = props;
     const [form] = Form.useForm();
+    const [autoIncrement, setAutoIncrement] = useState(false);
+    const [autoIncrementError, setAutoIncrementError] = useState(null);
 
     useEffect(() => {
+        setAutoIncrement(false);
         form.resetFields();
         if (curSprite && props) {
             form.setFieldsValue({
                 ...props.values,
-                copyGap: 0
             });
         }
     }, [form, props]);
+
+    useEffect(() => {
+        if (autoIncrement === true && curSprite) {
+            switch (curSprite.data?.type) {
+                case Utils.SENSOR_TYPE.SHELF:
+                    if (!curSprite.data?.row || !curSprite.data?.bay) {
+                        setAutoIncrementError(intl.formatMessage({ id: 'map.settings.sub.copy.warn.config.shelf', defaultMessage: '璇峰厛璁剧疆璐ф灦鍙傛暟锛�' }));
+                    } else {
+                        setAutoIncrementError(null);
+                    }
+                    break;
+                default:
+                    break;
+            }
+        } else {
+            setAutoIncrementError(null);
+        }
+    }, [autoIncrement])
 
     const handleCancel = () => {
         props.onClose();
@@ -34,11 +54,12 @@
     }
 
     const handleFinish = (values) => {
-        props.submit({ ...values, ...props.values })
-    }
-
-    const formValuesChange = () => {
-
+        props.submit({
+            ...values
+            , ...props.values
+            , autoIncrement: autoIncrement
+            , type: curSprite?.data?.type
+        })
     }
 
     return (
@@ -64,8 +85,10 @@
             >
                 <Form
                     form={form}
-                    onFieldsChange={formValuesChange}
                     initialValues={{
+                        copyGap: 0,
+                        autoIncrement: false,
+                        incrementMode: 'ascending',
                     }}
                     onFinish={handleFinish}
                     autoComplete="off"
@@ -108,26 +131,121 @@
                                     style={{
                                         width: '60%',
                                     }}
-                                    // min={0}
+                                // min={0}
                                 />
                             </Form.Item>
                         </Col>
 
-                        {curSprite?.data?.type === 'AGV' && (
-                            <Col span={24}>
-                                <Form.Item
-                                    name='id'
-                                    label={intl.formatMessage({ id: 'map.settings.sub.copy.id', defaultMessage: '搴忓彿' })}
-                                    labelCol={{ span: 8 }}
-                                >
-                                    <InputNumber
-                                        style={{
-                                            width: '60%',
-                                        }}
-                                        min={0}
-                                    />
-                                </Form.Item>
-                            </Col>
+                        {/* switch auto increment  */}
+                        <Col span={24}>
+                            <Form.Item
+                                label={intl.formatMessage({ id: 'map.settings.sub.copy.shelf.auto-increment', defaultMessage: '鑷闀�' })}
+                                labelCol={{ span: 8 }}
+                                help={autoIncrementError}
+                                validateStatus={autoIncrementError ? "error" : null}
+                            >
+                                <Switch value={autoIncrement} onChange={setAutoIncrement} />
+                            </Form.Item>
+                        </Col>
+
+                        {autoIncrement && curSprite?.data?.type === Utils.SENSOR_TYPE.AGV && (
+                            <>
+                                <Col span={24}>
+                                    <Form.Item
+                                        name='incrementValue'
+                                        label={intl.formatMessage({ id: 'map.settings.sub.copy.shelf.increment-value', defaultMessage: '鑷闀垮��' })}
+                                        labelCol={{ span: 8 }}
+                                        initialValue='no'
+                                    >
+                                        <Segmented
+                                            block
+                                            options={[
+                                                {
+                                                    label: intl.formatMessage({ id: 'map.settings.no', defaultMessage: '缂栧彿' }),
+                                                    value: 'no'
+                                                },
+                                            ]}
+                                            onChange={(value) => {
+                                            }}
+                                        />
+                                    </Form.Item>
+                                </Col>
+                                <Col span={24}>
+                                    <Form.Item
+                                        name='incrementMode'
+                                        label={intl.formatMessage({ id: 'map.settings.sub.copy.increment.mode', defaultMessage: '澧為暱鏂瑰紡' })}
+                                        labelCol={{ span: 8 }}
+                                    >
+                                        <Segmented
+                                            block
+                                            options={[
+                                                {
+                                                    label: intl.formatMessage({ id: 'map.settings.sub.copy.ascend', defaultMessage: '鍗囧簭' }),
+                                                    value: 'ascending'
+                                                },
+                                                {
+                                                    label: intl.formatMessage({ id: 'map.settings.sub.copy.descend', defaultMessage: '闄嶅簭' }),
+                                                    value: 'descending'
+                                                },
+                                            ]}
+                                            onChange={(value) => {
+                                            }}
+                                        />
+                                    </Form.Item>
+                                </Col>
+                            </>
+                        )}
+
+                        {autoIncrement && curSprite?.data?.type === Utils.SENSOR_TYPE.SHELF && (
+                            <>
+                                <Col span={24}>
+                                    <Form.Item
+                                        name='incrementValue'
+                                        label={intl.formatMessage({ id: 'map.settings.sub.copy.shelf.increment-value', defaultMessage: '鑷闀垮��' })}
+                                        labelCol={{ span: 8 }}
+                                        initialValue='row'
+                                    >
+                                        <Segmented
+                                            block
+                                            options={[
+                                                {
+                                                    label: intl.formatMessage({ id: 'map.settings.shelf.row', defaultMessage: '鎺�' }),
+                                                    value: 'row'
+                                                },
+                                                {
+                                                    label: intl.formatMessage({ id: 'map.settings.shelf.bay', defaultMessage: '鍒�' }),
+                                                    value: 'bay'
+                                                },
+                                            ]}
+                                            onChange={(value) => {
+                                            }}
+                                        />
+                                    </Form.Item>
+                                </Col>
+                                <Col span={24}>
+                                    <Form.Item
+                                        name='incrementMode'
+                                        label={intl.formatMessage({ id: 'map.settings.sub.copy.increment.mode', defaultMessage: '澧為暱鏂瑰紡' })}
+                                        labelCol={{ span: 8 }}
+                                    >
+                                        <Segmented
+                                            block
+                                            options={[
+                                                {
+                                                    label: intl.formatMessage({ id: 'map.settings.sub.copy.ascend', defaultMessage: '鍗囧簭' }),
+                                                    value: 'ascending'
+                                                },
+                                                {
+                                                    label: intl.formatMessage({ id: 'map.settings.sub.copy.descend', defaultMessage: '闄嶅簭' }),
+                                                    value: 'descending'
+                                                },
+                                            ]}
+                                            onChange={(value) => {
+                                            }}
+                                        />
+                                    </Form.Item>
+                                </Col>
+                            </>
                         )}
 
                     </Row>
diff --git a/zy-asrs-flow/src/pages/map/components/mapSettings.jsx b/zy-asrs-flow/src/pages/map/components/mapSettings.jsx
index ba5b05f..e01c7f2 100644
--- a/zy-asrs-flow/src/pages/map/components/mapSettings.jsx
+++ b/zy-asrs-flow/src/pages/map/components/mapSettings.jsx
@@ -1,5 +1,5 @@
 import React, { useState, useRef, useEffect } from 'react';
-import { Col, Form, Input, Row, Checkbox, Slider, Select, Drawer, Space, Button, InputNumber, Card } from 'antd';
+import { Col, Form, Input, Row, message, Slider, Select, Drawer, Space, Button, InputNumber, Card } from 'antd';
 import { FormattedMessage, useIntl, useModel } from '@umijs/max';
 import { createStyles } from 'antd-style';
 import * as Utils from '../utils'
@@ -99,6 +99,42 @@
         setLastCopiedSprites([]);
         for (let i = 0; i < values.copyCount; i++) {
             const copiedSprite = Utils.copySprite(curSprite);
+            // auto-increment-value
+            if (values.autoIncrement && values.type) {
+                switch (values.type) {
+                    case Utils.SENSOR_TYPE.SHELF:
+                        if (values.incrementValue === 'row') {
+                            if (values.incrementMode === 'descending') {
+                                copiedSprite.data.row = curSprite.data.row - i - 1;
+                            } else {
+                                copiedSprite.data.row = curSprite.data.row + i + 1;
+                            }
+                        }
+                        if (values.incrementValue === 'bay') {
+                            if (values.incrementMode === 'descending') {
+                                copiedSprite.data.bay = curSprite.data.bay - i - 1;
+                            } else {
+                                copiedSprite.data.bay = curSprite.data.bay + i + 1;
+                            }
+                        }
+                        if (copiedSprite.data.row && copiedSprite.data.bay) {
+                            copiedSprite.data.no = Utils.pureNumStr(copiedSprite.data.row) + '-' + Utils.pureNumStr(copiedSprite.data.bay);
+                        }
+                        break;
+                    case Utils.SENSOR_TYPE.AGV:
+                        if (values.incrementValue === 'no') {
+                            if (values.incrementMode === 'descending') {
+                                copiedSprite.data.no = Number(curSprite.data.no) - i - 1;
+                            } else {
+                                copiedSprite.data.no = Number(curSprite.data.no) + i + 1;
+                            }
+                        }
+                        break;
+                    default:
+                        break;
+                }
+            }
+            // graph copy
             switch (values.copyDire) {
                 case 'left':
                     copiedSprite.position.x -= (i + 1) * (values.copyGap + copiedSprite.width);
@@ -339,7 +375,7 @@
                                     </Form.Item>
                                     <Form.Item>
                                         <Button
-                                            type="dashed"
+                                            type="link"
                                             onClick={() => {
                                                 if (lastCopiedSprites) {
                                                     lastCopiedSprites.forEach(copiedSprite => {
diff --git a/zy-asrs-flow/src/pages/map/components/settings.jsx b/zy-asrs-flow/src/pages/map/components/settings.jsx
index 2bb2233..0a82de3 100644
--- a/zy-asrs-flow/src/pages/map/components/settings.jsx
+++ b/zy-asrs-flow/src/pages/map/components/settings.jsx
@@ -73,7 +73,7 @@
                         <Button onClick={handleCancel}>
                             <FormattedMessage id='common.cancel' defaultMessage='鍙栨秷' />
                         </Button>
-                        <Button hidden={activeTabKey === 'map'} onClick={handleOk} type="primary">
+                        <Button hidden={activeTabKey === 'map' || activeTabKey === 'config'} onClick={handleOk} type="primary">
                             <FormattedMessage id='common.submit' defaultMessage='淇濆瓨' />
                         </Button>
                     </Space>
diff --git a/zy-asrs-flow/src/pages/map/drawer/agv/index.jsx b/zy-asrs-flow/src/pages/map/drawer/agv/index.jsx
new file mode 100644
index 0000000..6899ae9
--- /dev/null
+++ b/zy-asrs-flow/src/pages/map/drawer/agv/index.jsx
@@ -0,0 +1,60 @@
+import React, { useState, useRef, useEffect } from 'react';
+import { Card, Form, Button } from 'antd';
+import { FormattedMessage, useIntl, useModel } from '@umijs/max';
+import { createStyles } from 'antd-style';
+import * as Utils from '../../utils'
+import Http from '@/utils/http';
+import ShowJson from '../showJson';
+
+const useStyles = createStyles(({ token, css }) => {
+
+})
+
+const AgvDrawer = (props) => {
+    const intl = useIntl();
+    const { styles } = useStyles();
+    const [activeTabKey, setActiveTabKey] = useState('json');
+
+    const contentList = {
+        json: (
+            <ShowJson
+                curSprite={props.curSprite}
+            />
+        ),
+    };
+
+    return (
+        <>
+            <Card
+                className='drawer-card'
+                hoverable
+                bordered={false}
+                type='inner'
+                tabList={[
+                    {
+                        key: 'json',
+                        tab: intl.formatMessage({ id: 'map.drawer.json', defaultMessage: 'JSON' }),
+                    },
+                ]}
+                activeTabKey={activeTabKey}
+                onTabChange={(key) => {
+                    setActiveTabKey(key)
+                }}
+                tabProps={{
+                    centered: true,
+                    size: 'large',
+                    type: "card",
+                    style: {
+                    }
+                }}
+                style={{
+                    height: '100%'
+                }}
+            >
+                {contentList[activeTabKey]}
+            </Card>
+        </>
+    )
+}
+
+export default AgvDrawer;
\ No newline at end of file
diff --git a/zy-asrs-flow/src/pages/map/drawer/index.jsx b/zy-asrs-flow/src/pages/map/drawer/index.jsx
index 1394379..0662017 100644
--- a/zy-asrs-flow/src/pages/map/drawer/index.jsx
+++ b/zy-asrs-flow/src/pages/map/drawer/index.jsx
@@ -1,9 +1,11 @@
 import React, { useState, useRef, useEffect } from 'react';
-import { Drawer } from 'antd';
+import { Drawer, Space, Button } from 'antd';
 import { FormattedMessage, useIntl, useModel } from '@umijs/max';
 import { createStyles } from 'antd-style';
 import * as Utils from '../utils'
-import Http from '@/utils/http';
+import ShelfDrawer from './shelf';
+import AgvDrawer from './agv';
+import PointDrawer from './point'
 
 const useStyles = createStyles(({ token, css }) => {
 
@@ -12,12 +14,54 @@
 const MapDrawer = (props) => {
     const intl = useIntl();
     const { styles } = useStyles();
+    const { curSprite } = props;
+
+    const handleCancel = () => {
+        props.onCancel();
+    };
 
     return (
         <>
             <Drawer
-
-            />
+                open={props.open}
+                onClose={handleCancel}
+                getContainer={props.refCurr}
+                rootStyle={{ position: "absolute" }}
+                mask={false}
+                width={600}
+                style={{
+                    opacity: .8
+                }}
+                extra={
+                    <Space>
+                        <Button onClick={handleCancel}>
+                            <FormattedMessage id='common.cancel' defaultMessage='鍙栨秷' />
+                        </Button>
+                    </Space>
+                }
+            >
+                {props.curSprite?.data?.type === Utils.SENSOR_TYPE.SHELF && (
+                    <>
+                        <ShelfDrawer
+                            curSprite={curSprite}
+                        />
+                    </>
+                )}
+                {props.curSprite?.data?.type === Utils.SENSOR_TYPE.POINT && (
+                    <>
+                        <PointDrawer
+                            curSprite={curSprite}
+                        />
+                    </>
+                )}
+                {props.curSprite?.data?.type === Utils.SENSOR_TYPE.AGV && (
+                    <>
+                        <AgvDrawer
+                            curSprite={curSprite}
+                        />
+                    </>
+                )}
+            </Drawer>
         </>
     )
 }
diff --git a/zy-asrs-flow/src/pages/map/drawer/point/index.jsx b/zy-asrs-flow/src/pages/map/drawer/point/index.jsx
new file mode 100644
index 0000000..5fd6e94
--- /dev/null
+++ b/zy-asrs-flow/src/pages/map/drawer/point/index.jsx
@@ -0,0 +1,60 @@
+import React, { useState, useRef, useEffect } from 'react';
+import { Card, Form, Button } from 'antd';
+import { FormattedMessage, useIntl, useModel } from '@umijs/max';
+import { createStyles } from 'antd-style';
+import * as Utils from '../../utils'
+import Http from '@/utils/http';
+import ShowJson from '../showJson';
+
+const useStyles = createStyles(({ token, css }) => {
+
+})
+
+const PointDrawer = (props) => {
+    const intl = useIntl();
+    const { styles } = useStyles();
+    const [activeTabKey, setActiveTabKey] = useState('json');
+
+    const contentList = {
+        json: (
+            <ShowJson
+                curSprite={props.curSprite}
+            />
+        ),
+    };
+
+    return (
+        <>
+            <Card
+                className='drawer-card'
+                hoverable
+                bordered={false}
+                type='inner'
+                tabList={[
+                    {
+                        key: 'json',
+                        tab: intl.formatMessage({ id: 'map.drawer.json', defaultMessage: 'JSON' }),
+                    },
+                ]}
+                activeTabKey={activeTabKey}
+                onTabChange={(key) => {
+                    setActiveTabKey(key)
+                }}
+                tabProps={{
+                    centered: true,
+                    size: 'large',
+                    type: "card",
+                    style: {
+                    }
+                }}
+                style={{
+                    height: '100%'
+                }}
+            >
+                {contentList[activeTabKey]}
+            </Card>
+        </>
+    )
+}
+
+export default PointDrawer;
\ No newline at end of file
diff --git a/zy-asrs-flow/src/pages/map/drawer/shelf/index.jsx b/zy-asrs-flow/src/pages/map/drawer/shelf/index.jsx
new file mode 100644
index 0000000..faef89f
--- /dev/null
+++ b/zy-asrs-flow/src/pages/map/drawer/shelf/index.jsx
@@ -0,0 +1,60 @@
+import React, { useState, useRef, useEffect } from 'react';
+import { Card, Form, Button } from 'antd';
+import { FormattedMessage, useIntl, useModel } from '@umijs/max';
+import { createStyles } from 'antd-style';
+import * as Utils from '../../utils'
+import Http from '@/utils/http';
+import ShowJson from '../showJson';
+
+const useStyles = createStyles(({ token, css }) => {
+
+})
+
+const ShelfDrawer = (props) => {
+    const intl = useIntl();
+    const { styles } = useStyles();
+    const [activeTabKey, setActiveTabKey] = useState('json');
+
+    const contentList = {
+        json: (
+            <ShowJson
+                curSprite={props.curSprite}
+            />
+        ),
+    };
+
+    return (
+        <>
+            <Card
+                className='drawer-card'
+                hoverable
+                bordered={false}
+                type='inner'
+                tabList={[
+                    {
+                        key: 'json',
+                        tab: intl.formatMessage({ id: 'map.drawer.json', defaultMessage: 'JSON' }),
+                    },
+                ]}
+                activeTabKey={activeTabKey}
+                onTabChange={(key) => {
+                    setActiveTabKey(key)
+                }}
+                tabProps={{
+                    centered: true,
+                    size: 'large',
+                    type: "card",
+                    style: {
+                    }
+                }}
+                style={{
+                    height: '100%'
+                }}
+            >
+                {contentList[activeTabKey]}
+            </Card>
+        </>
+    )
+}
+
+export default ShelfDrawer;
\ No newline at end of file
diff --git a/zy-asrs-flow/src/pages/map/drawer/showJson.jsx b/zy-asrs-flow/src/pages/map/drawer/showJson.jsx
new file mode 100644
index 0000000..c545c33
--- /dev/null
+++ b/zy-asrs-flow/src/pages/map/drawer/showJson.jsx
@@ -0,0 +1,56 @@
+import React, { useState, useRef, useEffect } from 'react';
+import { Card, Space, Button } from 'antd';
+import { FormattedMessage, useIntl, useModel } from '@umijs/max';
+import { createStyles } from 'antd-style';
+import * as Utils from '../utils'
+import Http from '@/utils/http';
+
+const useStyles = createStyles(({ token, css }) => {
+    let dark = token.colorBgBase === '#000';
+    return {
+        jsonBox: {
+            height: '100%',
+            border: dark ? '2px solid #747d8c' : '2px solid #535c68',
+            borderRadius: '5px',
+            padding: '5px',
+            cursor: 'text'
+        },
+        jsonContent: {
+            height: '100%',
+            overflowY: 'auto',
+            width: '100%',
+            border: 'none',
+            backgroundColor: 'transparent',
+            resize: 'none',
+            fontFamily: '"Courier New", monospace',
+            fontWeight: 'bold',
+            fontSize: '1em',
+            lineHeight: '1.5',
+            color: dark ? '#eee' : '#333',
+            '&:focus': {
+                outline: 'none'
+            }
+        }
+    }
+})
+
+const ShowJSON = (props) => {
+    const { styles } = useStyles();
+    const { curSprite } = props;
+
+    const formattedJSON = JSON.stringify(curSprite.data, null, 2);
+
+    return (
+        <>
+            <div className={styles.jsonBox}>
+                <textarea
+                    readOnly
+                    className={styles.jsonContent}
+                    value={formattedJSON}
+                />
+            </div>
+        </>
+    )
+}
+
+export default ShowJSON;
\ No newline at end of file
diff --git a/zy-asrs-flow/src/pages/map/header/search.jsx b/zy-asrs-flow/src/pages/map/header/search.jsx
new file mode 100644
index 0000000..5c70fe6
--- /dev/null
+++ b/zy-asrs-flow/src/pages/map/header/search.jsx
@@ -0,0 +1,164 @@
+import React, { useState, useRef, useEffect } from 'react';
+import { Select, AutoComplete } from 'antd';
+import { FormattedMessage, useIntl } from '@umijs/max';
+import { CloseOutlined } from '@ant-design/icons';
+import * as Utils from '../utils'
+
+const renderTitle = (title, uuid) => (
+    <>
+        <span style={{ fontWeight: 'bold' }} >{title}</span>
+        <span style={{ float: 'right', opacity: .3 }} >{uuid}</span>
+    </>
+);
+
+const sensorTypeSelectOptionsFn = (intl) => {
+    let options = [];
+    Object.entries(Utils.SENSOR_TYPE).forEach(([key, value]) => {
+        switch (key) {
+            case Utils.SENSOR_TYPE.SHELF:
+                options.push({
+                    value: value,
+                    label:
+                        (
+                            <>
+                                <span style={{ fontWeight: 'bold' }} >{intl.formatMessage({ id: 'map.sensor.type.shelf', defaultMessage: '璐ф灦' })}</span>
+                            </>
+                        )
+                })
+                break;
+            case Utils.SENSOR_TYPE.AGV:
+                options.push({
+                    value: value,
+                    label:
+                        (
+                            <>
+                                <span style={{ fontWeight: 'bold' }} >{intl.formatMessage({ id: 'map.sensor.type.agv', defaultMessage: '鏃犱汉灏忚溅' })}</span>
+                            </>
+                        )
+                })
+                break;
+            case Utils.SENSOR_TYPE.POINT:
+                options.push({
+                    value: value,
+                    label:
+                        (
+                            <>
+                                <span style={{ fontWeight: 'bold' }} >{intl.formatMessage({ id: 'map.sensor.type.point', defaultMessage: '瀹氫綅鐐�' })}</span>
+                            </>
+                        )
+                })
+                break;
+            default:
+                break;
+        }
+
+    })
+    return options;
+}
+
+function getAllSensorList(curSensorType) {
+    let sensorListAll = [];
+    Utils.getMapContainer().children.forEach(child => {
+        if (child?.data?.type === curSensorType && child?.data?.no) {
+            sensorListAll.push({
+                value: child.data.no,
+                label: renderTitle(child.data.no, child.data.uuid)
+            })
+        }
+    });
+    return sensorListAll;
+}
+
+const MapSearch = (props) => {
+    const intl = useIntl();
+    const {
+        curSprite: curSensor,
+        setCurSPrite: setCurSensor,
+        setSpriteBySettings,
+        model,
+        setModel,
+        ModelEnum,
+    } = props;
+
+    const sensorTypeSelectOptions = sensorTypeSelectOptionsFn(intl);
+    const [curSensorType, setCurSensorType] = React.useState(sensorTypeSelectOptions?.[0]?.value);
+
+    const [sensorList, setSensorList] = React.useState([]);
+    const [filterSensorList, setFilterSensorList] = React.useState([]);
+    const [curSensorLabel, setCurSensorLabel] = React.useState(null);
+
+    // first select
+    React.useEffect(() => {
+        if (!Utils.getMapContainer()) { return; }
+        let sensorListAll = getAllSensorList(curSensorType);
+        setSensorList(sensorListAll);
+        setFilterSensorList(sensorListAll);
+        setCurSensorLabel(null);
+    }, [curSensorType])
+
+    // second select
+    React.useEffect(() => {
+        if (!Utils.getMapContainer()) { return; }
+        if ((curSensorLabel !== null || curSensorLabel != undefined)
+            && sensorList && sensorList.length > 0) {
+            setFilterSensorList(sensorList.filter(item => item.value.includes(curSensorLabel)));
+        }
+    }, [curSensorLabel])
+
+    const onSecondSelect = (value, option) => {
+        const uuid = option.label?.props?.children?.[1].props.children;
+        const selectSensor = Utils.findSpriteByUuid(uuid);
+        if (selectSensor) {
+            Utils.beCenter(selectSensor);
+        }
+        switch (model) {
+            case ModelEnum.OBSERVER_MODEL:
+                setCurSensor(selectSensor);
+                break;
+            case ModelEnum.MOVABLE_MODEL:
+                setModel(ModelEnum.SETTINGS_MODEL);
+                setSpriteBySettings(selectSensor);
+                break;
+            case ModelEnum.SETTINGS_MODEL:
+                setSpriteBySettings(selectSensor);
+                break;
+            default:
+                break;
+        }
+    }
+
+    return (
+        <>
+            <Select
+                className='map-header-select'
+                variant='filled'
+                style={{
+                    width: 160,
+                }}
+                size={'large'}
+                options={sensorTypeSelectOptions}
+                value={curSensorType}
+                onChange={setCurSensorType}
+            />
+            <AutoComplete
+                className='map-header-select'
+                variant='filled'
+                style={{
+                    width: 360,
+                }}
+                size={'large'}
+                placeholder={intl.formatMessage({ id: 'common.search.placeholder', defaultMessage: '璇疯緭鍏ユ悳绱㈠唴瀹�' })}
+                allowClear={{
+                    clearIcon: <CloseOutlined />
+                }}
+                popupMatchSelectWidth={500}
+                options={filterSensorList}
+                value={curSensorLabel}
+                onSelect={onSecondSelect}
+                onChange={setCurSensorLabel}
+            />
+        </>
+    )
+}
+
+export default MapSearch;
\ No newline at end of file
diff --git a/zy-asrs-flow/src/pages/map/index.css b/zy-asrs-flow/src/pages/map/index.css
index 6c27554..a60f254 100644
--- a/zy-asrs-flow/src/pages/map/index.css
+++ b/zy-asrs-flow/src/pages/map/index.css
@@ -18,3 +18,26 @@
 * {
     box-sizing: border-box;
 }
+
+
+.map-header-select .ant-select-selector {
+    border-radius: 0px !important;
+}
+
+.map-header-button {
+    border-radius: 0px !important;
+    font-weight: bolder !important;
+}
+
+.map-header-select.ant-select .ant-select-selector .ant-select-selection-item {
+    font-weight: bolder !important;
+}
+
+.map-header-select .ant-select-selector .ant-select-selection-search .ant-select-selection-search-input {
+    font-weight: bolder !important;
+}
+
+.drawer-card .ant-card-body {
+    height: 95%;
+    padding: 12px;
+}
\ No newline at end of file
diff --git a/zy-asrs-flow/src/pages/map/index.jsx b/zy-asrs-flow/src/pages/map/index.jsx
index 9e56171..0d84c56 100644
--- a/zy-asrs-flow/src/pages/map/index.jsx
+++ b/zy-asrs-flow/src/pages/map/index.jsx
@@ -1,13 +1,14 @@
 import * as React from 'react'
 import * as PIXI from 'pixi.js';
 import { FormattedMessage, useIntl, useModel } from '@umijs/max';
-import { Layout, Button, Flex, Row, Col, FloatButton, Select, Spin } from 'antd';
+import { Layout, Button, Flex, Row, Col, FloatButton, Select, Spin, AutoComplete } from 'antd';
 const { Header, Content } = Layout;
 import {
     AppstoreAddOutlined,
     FileAddOutlined,
     CompressOutlined,
     SettingOutlined,
+    CloseOutlined
 } from '@ant-design/icons';
 import './index.css'
 import { createStyles } from 'antd-style';
@@ -15,6 +16,7 @@
 import Settings from './components/settings'
 import * as Utils from './utils'
 import Player from './player';
+import MapSearch from './header/search';
 import MapDrawer from './drawer';
 
 const useStyles = createStyles(({ token }) => {
@@ -42,6 +44,9 @@
         select: {
             color: 'red',
             fontWeight: 'bold',
+        },
+        headerCol: {
+            paddingLeft: '50px'
         }
     };
 });
@@ -61,7 +66,7 @@
     const mapRef = React.useRef();
     const contentRef = React.useRef();
 
-    const [model, setModel] = React.useState(() => MapModel.OBSERVER_MODEL);
+    const [model, setModel] = React.useState(null);
     const [deviceVisible, setDeviceVisible] = React.useState(false);
     const [settingsVisible, setSettingsVisible] = React.useState(false);
     const [windowSize, setWindowSize] = React.useState({
@@ -73,22 +78,35 @@
     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();
 
     // init func
     React.useEffect(() => {
-        player = new Player(mapRef.current, styles.dark, didClickSprite);
-        setApp(player.app);
-        setMapContainer(player.mapContainer);
-        Utils.syncApp(player.app);
-        Utils.syncMapContainer(player.mapContainer);
+        const initialize = async () => {
+            player = new Player(mapRef.current, styles.dark, didClickSprite);
+            setApp(player.app);
+            setMapContainer(player.mapContainer);
+            Utils.syncApp(player.app);
+            Utils.syncMapContainer(player.mapContainer);
 
-        const handleResize = () => {
-            setWindowSize({
-                width: window.innerWidth,
-                height: window.innerHeight,
-            });
-        };
-        window.addEventListener('resize', handleResize);
+            const handleResize = () => {
+                setWindowSize({
+                    width: window.innerWidth,
+                    height: window.innerHeight,
+                });
+            };
+            window.addEventListener('resize', handleResize);
+            await Utils.fetchMapData(intl);
+            setDataFetched(true);
+            setModel(MapModel.OBSERVER_MODEL)
+            setTimeout(() => {
+                player.adaptScreen();
+            }, 200)
+        }
+        initialize();
     }, []);
 
     // resize
@@ -100,14 +118,13 @@
         const height = contentRef.current.offsetHeight;
         app.renderer.resize(width, height);
         if (model !== MapModel.OBSERVER_MODEL) {
-            player.hideGridlines();
             player.showGridlines();
         }
     }, [app, mapContainer, windowSize])
 
     // model
     React.useEffect(() => {
-        if (!mapContainer) {
+        if (!mapContainer && !dataFetched) {
             return;
         }
         switch (model) {
@@ -119,14 +136,12 @@
                 player.activateMapEvent(null);
 
                 Utils.removeSelectedEffect();
+                setCurSPrite(null);
                 setDeviceVisible(false);
                 setSettingsVisible(false);
 
                 mapContainer.children.forEach(child => {
-                    child.off('pointerup');
-                    child.off('pointermove');
-                    child.off('pointerdown');
-                    child.off('click');
+                    Utils.viewFeature(child, setCurSPrite);
                 })
                 break
             case MapModel.MOVABLE_MODEL:
@@ -139,6 +154,7 @@
                 Utils.removeSelectedEffect();
                 setSpriteBySettings(null);
                 setSettingsVisible(false);
+                setDrawerVisible(false);
 
                 mapContainer.children.forEach(child => {
                     Utils.beMovable(child, setDidClickSprite);
@@ -152,6 +168,7 @@
                 player.activateMapEvent(null);
 
                 setDeviceVisible(false);
+                setDrawerVisible(false);
 
                 mapContainer.children.forEach(child => {
                     Utils.beSettings(child, setSpriteBySettings, setDidClickSprite);
@@ -172,6 +189,26 @@
         mapContainer.addChild(sprite);
         Utils.beMovable(sprite, setDidClickSprite);
     };
+
+    // watch curSprite
+    React.useEffect(() => {
+        if (!mapContainer) {
+            return;
+        }
+        prevCurSpriteRef.current = curSprite;
+        if (curSprite && prevCurSprite !== curSprite) {
+            Utils.removeSelectedEffect();
+        }
+        if (curSprite) {
+            if (model === MapModel.OBSERVER_MODEL) {
+                Utils.showSelectedEffect(curSprite)
+                setDrawerVisible(true)
+            }
+        } else {
+            Utils.removeSelectedEffect();
+        }
+    }, [curSprite]);
+    const prevCurSprite = prevCurSpriteRef.current;
 
     // didClickSprite, stop triggers both sprite click and play's selection boxs
     React.useEffect(() => {
@@ -196,9 +233,10 @@
     }, [spriteBySettings])
     const prevSpriteBySettings = prevSpriteBySettingsRef.current;
 
-    const settingsFinish = () => {
-        setSettingsVisible(false);
-        setSpriteBySettings(null);
+    const settingsFinish = (values, fn) => {
+        fn();
+        // setSettingsVisible(false);
+        // setSpriteBySettings(null);
     }
 
     return (
@@ -206,42 +244,76 @@
             <Layout className={styles.layout}>
                 <Header className={styles.header}>
                     <Row style={{ height: '100%' }}>
-                        <Col span={8} style={{ backgroundColor: '#dcdde1' }}>
-
-
-                            <Select
-                                defaultValue="agv"
-                                style={{
-                                    width: 120,
-                                }}
-                                size={'large'}
-                                onChange={(value, option) => {
-                                    console.log(value, option);
-                                }}
-                                options={[
-                                    {
-                                        value: 'agv',
-                                        label: 'agv',
-                                    },
-                                    {
-                                        value: 'crn',
-                                        label: 'crn',
-                                    },
-                                ]}
-                            />
-
-                            <Select
-
-                            // notFoundContent={loading ? <Spin size="small" /> : null}
-
-                            />
-
-
+                        <Col className={styles.headerCol} span={12} style={{}}>
+                            {dataFetched && (
+                                <MapSearch
+                                    model={model}
+                                    setModel={setModel}
+                                    ModelEnum={MapModel}
+                                    curSprite={curSprite}
+                                    setCurSPrite={setCurSPrite}
+                                    setSpriteBySettings={setSpriteBySettings}
+                                />
+                            )}
                         </Col>
-                        <Col span={16} style={{ backgroundColor: '#3C40C6' }}>
+                        <Col span={12} style={{ backgroundColor: styles.dark ? '#2C3A47' : '#4a69bd' }}>
                             <Flex className={styles.flex} gap={'large'} justify={'flex-end'} align={'center'}>
+
+                                {model === MapModel.OBSERVER_MODEL && (
+                                    <>
+                                        <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);
+                                                })
+
+                                            }}
+                                        >
+                                            <FormattedMessage id='map.load' defaultMessage='鍔犺浇鍦板浘' />
+                                        </Button>
+                                    </>
+                                )}
+
+                                {model !== MapModel.OBSERVER_MODEL && (
+                                    <>
+                                        <Button
+                                            className='map-header-button'
+                                            size={'large'}
+                                            onClick={() => {
+                                                Utils.clearMapData(intl);
+                                            }}
+                                        >
+                                            <FormattedMessage id='map.clear' defaultMessage='娓呴櫎鍦板浘' />
+                                        </Button>
+                                        <Button
+                                            className='map-header-button'
+                                            size={'large'}
+                                            onClick={() => {
+                                                Utils.saveMapData(intl);
+                                            }}
+                                        >
+                                            <FormattedMessage id='map.save' defaultMessage='淇濆瓨鍦板浘' />
+                                        </Button>
+                                    </>
+                                )}
+
                                 <Select
-                                    className={styles.select}
+                                    className='map-header-select'
                                     size={'large'}
                                     defaultValue={MapModel.OBSERVER_MODEL}
                                     style={{
@@ -274,8 +346,10 @@
                         >
                             <FloatButton
                                 icon={<CompressOutlined />}
+                                onClick={() => {
+                                    player.adaptScreen();
+                                }}
                             />
-                            <FloatButton.BackTop visibilityHeight={0} />
                         </FloatButton.Group>
 
                         <FloatButton.Group
@@ -312,7 +386,17 @@
                         </FloatButton.Group>
                     </div>
                 </Content>
-            </Layout>
+            </Layout >
+
+            <MapDrawer
+                open={drawerVisible}
+                curSprite={curSprite}
+                refCurr={mapRef.current}
+                onCancel={() => {
+                    setCurSPrite(null);
+                    setDrawerVisible(false);
+                }}
+            />
 
             <Edit
                 open={deviceVisible}
diff --git a/zy-asrs-flow/src/pages/map/player.js b/zy-asrs-flow/src/pages/map/player.js
index 35edc66..772f874 100644
--- a/zy-asrs-flow/src/pages/map/player.js
+++ b/zy-asrs-flow/src/pages/map/player.js
@@ -22,7 +22,7 @@
         this.activateMapScale();
         this.activateMapPan();
         this.showCoordinates();
-        this.appTicker();
+        this.getStartedTicker();
     }
 
     activateMapEvent = (leftEvent, rightEvent) => {
@@ -146,20 +146,34 @@
     }
 
     activateMapScale = () => {
-        this.scale = 1; // 缂╂斁
+        this.scale = 1;
         this.app.view.addEventListener('wheel', (event) => {
             event.preventDefault();
+            if (this.scale !== this.mapContainer.scale.x) {
+                this.scale = this.mapContainer.scale.x;
+            }
+
             const delta = Math.sign(event.deltaY);
 
-            if (delta === 1) {
-                this.scale *= 0.9;
-            } else if (delta === -1) {
-                this.scale *= 1.1;
-            }
+            const mousePosition = new PIXI.Point();
+            this.app.renderer.plugins.interaction.mapPositionToPoint(mousePosition, event.clientX, event.clientY);
+
+            const diffPositionX = mousePosition.x - this.mapContainer.x;
+            const diffPositionY = mousePosition.y - this.mapContainer.y;
+
+            const newScale = this.scale * (delta === 1 ? 0.9 : 1.1);
+            const scaleFactor = newScale / this.scale;
+
+            this.mapContainer.x = mousePosition.x - diffPositionX * scaleFactor;
+            this.mapContainer.y = mousePosition.y - diffPositionY * scaleFactor;
+
+            this.scale = newScale;
+
             this.mapContainer.scale.set(this.scale);
+
             this.mapContainer.children.forEach(child => {
                 // child.scale.set(1 / this.scale); // 闃叉鍥炬爣鍙樺皬
-            })
+            });
         });
     }
 
@@ -183,17 +197,18 @@
     }
 
     showGridlines = () => {
+        this.hideGridlines();
         if (!this.gridLineContainer) {
             this.gridLineContainer = generatePixiContainer('gridLineContainer');
             this.app.stage.addChild(this.gridLineContainer);
         }
 
         const inte = 30;
-        const lineDefaultAlpha = .5;;
+        const lineDefaultAlpha = .1;;
         const lineDefaultColor = 0x000000;
         for (let i = 0; i < this.app.view.width / inte; i++) {
             const graphics = new PIXI.Graphics();
-            graphics.lineStyle(.3, lineDefaultColor, lineDefaultAlpha);
+            graphics.lineStyle(1, lineDefaultColor, lineDefaultAlpha);
             graphics.beginFill(lineDefaultColor);
             graphics.moveTo(i * inte, 0);
             graphics.lineTo(i * inte, this.app.view.height);
@@ -203,7 +218,7 @@
 
         for (let i = 0; i < this.app.view.height / inte; i++) {
             const graphics = new PIXI.Graphics();
-            graphics.lineStyle(.3, lineDefaultColor, lineDefaultAlpha);
+            graphics.lineStyle(1, lineDefaultColor, lineDefaultAlpha);
             graphics.beginFill(lineDefaultColor);
             graphics.moveTo(0, i * inte);
             graphics.lineTo(this.app.view.width, i * inte);
@@ -258,7 +273,6 @@
             warpSpeed = warpSpeed > 0 ? 0 : 1;
         }, 5000);
 
-
         this.starryTicker = (delta) => {
             speed += (warpSpeed - speed) / 20;
             cameraZ += delta * 10 * (speed + baseSpeed);
@@ -294,7 +308,7 @@
     }
 
     hideStarryBackground = () => {
-        if(this.starryTicker) {
+        if (this.starryTicker) {
             this.app.ticker.remove(this.starryTicker);
             this.starryTicker = null;
         }
@@ -311,12 +325,59 @@
         }
     }
 
+    adaptScreen = () => {
+        if (!this.mapContainer || !this.app) {
+            return;
+        }
+        
+        this.mapContainer.scale.set(1);
+        this.mapContainer.position.set(0, 0);
+        if (this.mapContainer.children.length === 0) {
+            return;
+        }
+
+        let minX, maxX, minY, maxY;
+        for (let sprite of this.mapContainer.children) {
+            if (sprite?.data?.uuid) {
+                let bounds = sprite.getBounds();
+                minX = minX !== undefined ? Math.min(minX, bounds.x) : bounds.x;
+                minY = minY !== undefined ? Math.min(minY, bounds.y) : bounds.y;
+                maxX = maxX !== undefined ? Math.max(maxX, bounds.x + bounds.width) : bounds.x + bounds.width;
+                maxY = maxY !== undefined ? Math.max(maxY, bounds.y + bounds.height) : bounds.y + bounds.height;
+            }
+        }
+
+        this.scale = Math.min(
+            this.app.renderer.width / (maxX - minX) * 0.8,
+            this.app.renderer.height / (maxY - minY) * 0.8
+        );
+
+        let centerPoint = {
+            x: (minX + maxX) / 2 * this.mapContainer.scale.x,
+            y: (minY + maxY) / 2 * this.mapContainer.scale.y
+        };
+
+        new TWEEDLE.Tween(this.mapContainer.scale).easing(TWEEDLE.Easing.Quadratic.Out)
+            .to({
+                x: this.scale,
+                y: this.scale
+            }, 200).start();
+
+        new TWEEDLE.Tween(this.mapContainer.position).easing(TWEEDLE.Easing.Quadratic.Out)
+            .to({
+                x: this.app.renderer.width / 2 - centerPoint.x * this.scale,
+                y: this.app.renderer.height / 2 - centerPoint.y * this.scale
+            }, 200).start();
+    }
+
     updateDidClickSprite = (value) => {
         this.didClickSprite = value;
     }
 
-    appTicker = () => {
-        TWEEDLE.Group.shared.update();
+    getStartedTicker = () => {
+        this.app.ticker.add((delta) => {
+            TWEEDLE.Group.shared.update();
+        });
     }
 
 }
diff --git a/zy-asrs-flow/src/pages/map/utils.js b/zy-asrs-flow/src/pages/map/utils.js
index a1ca052..d00398a 100644
--- a/zy-asrs-flow/src/pages/map/utils.js
+++ b/zy-asrs-flow/src/pages/map/utils.js
@@ -1,4 +1,11 @@
 import * as PIXI from 'pixi.js';
+import * as TWEEDLE from 'tweedle.js';
+import Http from '@/utils/http';
+import { message } from 'antd';
+import { API_TIMEOUT } from '@/config/setting'
+import agv from '/public/img/map/agv.svg'
+import shelf from '/public/img/map/shelf.png'
+import point from '/public/img/map/point.svg'
 
 let app = null;
 let mapContainer = null;
@@ -21,8 +28,9 @@
 })
 
 export const SENSOR_TYPE = Object.freeze({
-    AGV: "AGV",
     SHELF: "SHELF",
+    POINT: "POINT",
+    AGV: "AGV",
 })
 
 export const getRealPosition = (x, y, mapContainer) => {
@@ -43,6 +51,21 @@
     };
 }
 
+// show sprite feature from sprite click event
+export const viewFeature = (sprite, setCurSPrite) => {
+    sprite.off('pointerup');
+    sprite.off('pointermove');
+    sprite.off('pointerdown');
+    sprite.off('click');
+
+    sprite.on("click", onClick);
+
+    function onClick(event) {
+        setCurSPrite(sprite);
+    }
+
+}
+
 // sprite be movable from sprite click event
 export const beMovable = (sprite, setDidClickSprite) => {
     sprite.off('pointerup');
@@ -54,13 +77,15 @@
 
     let dragTarget;
     function onDragStart(event) {
-        setDidClickSprite(true);
-        dragTarget = event.currentTarget;
-        mapContainer.parent.off('pointermove');
-        mapContainer.parent.on('pointermove', onDragMove, dragTarget);
+        if (event.button === 0) {
+            setDidClickSprite(true);
+            dragTarget = event.currentTarget;
+            mapContainer.parent.off('pointermove');
+            mapContainer.parent.on('pointermove', onDragMove, dragTarget);
 
-        mapContainer.parent.off('pointerup');
-        mapContainer.parent.on('pointerup', onDragEnd.bind(mapContainer));
+            mapContainer.parent.off('pointerup');
+            mapContainer.parent.on('pointerup', onDragEnd.bind(mapContainer));
+        }
     }
 
     function onDragMove(event) {
@@ -248,4 +273,169 @@
     } else {
         return '';
     }
+}
+
+export const rotationToNum = (rotation) => {
+    let res = rotation * 180 / Math.PI;
+    if (res < 0) {
+        res += 360;
+    } else if (res > 360) {
+        res -= 360;
+    }
+    return res;
+}
+
+export const rotationParseNum = (num) => {
+    return num * Math.PI / 180;
+}
+
+export const findSpriteByUuid = (uuid) => {
+    return mapContainer?.children?.find(child => child?.data?.uuid === uuid);
+}
+
+export const sensorTypeSelectOptions = (intl) => {
+    let options = [];
+    Object.entries(SENSOR_TYPE).forEach(([key, value]) => {
+        switch (key) {
+            case SENSOR_TYPE.SHELF:
+                options.push({
+                    value: value,
+                    label: intl.formatMessage({ id: 'map.sensor.type.shelf', defaultMessage: '璐ф灦' })
+                })
+                break;
+            case SENSOR_TYPE.AGV:
+                options.push({
+                    value: value,
+                    label: intl.formatMessage({ id: 'map.sensor.type.agv', defaultMessage: '鏃犱汉灏忚溅' })
+                })
+                break;
+            case SENSOR_TYPE.POINT:
+                options.push({
+                    value: value,
+                    label: intl.formatMessage({ id: 'map.sensor.type.point', defaultMessage: '瀹氫綅鐐�' })
+                })
+                break;
+            default:
+                break;
+        }
+
+    })
+    return options;
+}
+
+export const fetchMapData = async (intl) => {
+    clearMapData();
+    await Http.doPostPromise('api/map/list', {}, (res) => {
+        const mapItemList = res.data.itemList;
+        mapItemList.forEach(item => {
+            let sprite;
+            switch (item.type) {
+                case SENSOR_TYPE.SHELF:
+                    sprite = PIXI.Sprite.from(shelf);
+                    break;
+                case SENSOR_TYPE.AGV:
+                    sprite = PIXI.Sprite.from(agv);
+                    break;
+                case SENSOR_TYPE.POINT:
+                    sprite = PIXI.Sprite.from(point);
+                    break;
+                default:
+                    break;
+            }
+            if (sprite) {
+                initSprite(sprite, item.type);
+                // data
+                sprite.data.uuid = item.uuid;
+                sprite.data.no = item.no;
+
+                // graph
+                sprite.position.set(item.positionX, item.positionY);
+                sprite.scale.set(item.scaleX, item.scaleY);
+                sprite.rotation = rotationParseNum(item.rotation);
+                mapContainer.addChild(sprite);
+            }
+        })
+
+    }).catch((error) => {
+        console.error(error);
+    })
+
+}
+
+export const saveMapData = async (intl) => {
+    if (!mapContainer) {
+        return;
+    }
+
+    let mapItemList = [];
+    mapContainer?.children.forEach(child => {
+        if (child.data?.uuid) {
+            mapItemList.push({
+                // data
+                type: child.data.type,
+                uuid: child.data.uuid,
+                no: child.data.no,
+
+                // graph
+                positionX: child.position.x,
+                positionY: child.position.y,
+                scaleX: child.scale.x,
+                scaleY: child.scale.y,
+                rotation: rotationToNum(child.rotation)
+            })
+        }
+    })
+
+    const closeLoading = message.loading({ content: intl.formatMessage({ id: 'common.loading.api.message', defaultMessage: '绛夊緟鏈嶅姟鍣�......' }), duration: API_TIMEOUT });
+    await Http.doPostPromise('api/map/save', { itemList: mapItemList }, (res) => {
+        closeLoading();
+    }).catch((error) => {
+        closeLoading();
+        console.error(error);
+    })
+}
+
+export const clearMapData = (intl) => {
+    if (!mapContainer) {
+        return;
+    }
+    let childList = [];
+    mapContainer.children.forEach(child => {
+        if (child.data?.uuid) {
+            childList.push(child);
+        }
+    })
+    if (childList.length > 0) {
+        childList.forEach(child => {
+            mapContainer.removeChild(child);
+            child.destroy({ children: true, texture: false, baseTexture: false });
+        })
+        childList.forEach((child, index) => {
+            childList[index] = null;
+        });
+        childList = [];
+    }
+}
+
+export const beCenter = (sprite) => {
+    if (!sprite || !app || !mapContainer) {
+        return;
+    }
+
+    mapContainer.scale.set(1);
+    mapContainer.position.set(0, 0);
+
+    let bounds = sprite.getBounds();
+    let centerPoint = {
+        x: bounds.x + bounds.width / 2,
+        y: bounds.y + bounds.height / 2
+    };
+
+    let targetPos = {
+        x: app.renderer.width / 3 - centerPoint.x * mapContainer.scale.x,
+        y: app.renderer.height / 3 - centerPoint.y * mapContainer.scale.y
+    };
+
+    new TWEEDLE.Tween(mapContainer.position).easing(TWEEDLE.Easing.Quadratic.Out)
+        .to(targetPos, 500).start();
 }
\ No newline at end of file
diff --git a/zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/controller/MapController.java b/zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/controller/MapController.java
new file mode 100644
index 0000000..38a6053
--- /dev/null
+++ b/zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/controller/MapController.java
@@ -0,0 +1,38 @@
+package com.zy.asrs.wcs.core.map.controller;
+
+import com.zy.asrs.framework.common.R;
+import com.zy.asrs.wcs.core.map.controller.param.MapDataParam;
+import com.zy.asrs.wcs.core.map.service.MapService;
+import com.zy.asrs.wcs.system.controller.BaseController;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Created by vincent on 3/15/2024
+ */
+@RestController
+@RequestMapping("/api/map")
+public class MapController extends BaseController {
+
+    @Autowired
+    private MapService mapService;
+
+    //    @PreAuthorize("hasAuthority('core:map:list')")
+    @PostMapping("/list")
+    public R mapList() {
+        return R.ok().add(mapService.getMapData(getLoginUserId()));
+    }
+
+//    @PreAuthorize("hasAuthority('core:map:save')")
+    @PostMapping("/save")
+    @Transactional
+    public R mapSave(@RequestBody MapDataParam param) {
+        mapService.saveMapData(param, getLoginUserId());
+        return R.ok();
+    }
+
+}
diff --git a/zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/controller/param/MapDataParam.java b/zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/controller/param/MapDataParam.java
new file mode 100644
index 0000000..1a3f4ff
--- /dev/null
+++ b/zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/controller/param/MapDataParam.java
@@ -0,0 +1,16 @@
+package com.zy.asrs.wcs.core.map.controller.param;
+
+import com.zy.asrs.wcs.core.map.entity.MapItem;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * Created by vincent on 3/15/2024
+ */
+@Data
+public class MapDataParam {
+
+    public List<MapItem> itemList;
+
+}
diff --git a/zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/entity/MapItem.java b/zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/entity/MapItem.java
new file mode 100644
index 0000000..009a761
--- /dev/null
+++ b/zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/entity/MapItem.java
@@ -0,0 +1,28 @@
+package com.zy.asrs.wcs.core.map.entity;
+
+import lombok.Data;
+
+/**
+ * Created by vincent on 3/15/2024
+ */
+@Data
+public class MapItem {
+
+    private String type;
+
+    private String uuid;
+
+    private String no;
+
+
+    private Double positionX;
+
+    private Double positionY;
+
+    private Double scaleX;
+
+    private Double scaleY;
+
+    private Double rotation;
+
+}
diff --git a/zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/service/MapService.java b/zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/service/MapService.java
new file mode 100644
index 0000000..4ec28c1
--- /dev/null
+++ b/zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/service/MapService.java
@@ -0,0 +1,37 @@
+package com.zy.asrs.wcs.core.map.service;
+
+import com.alibaba.fastjson.JSON;
+import com.zy.asrs.framework.common.Cools;
+import com.zy.asrs.framework.exception.CoolException;
+import com.zy.asrs.wcs.core.map.controller.param.MapDataParam;
+import com.zy.asrs.wcs.system.entity.User;
+import com.zy.asrs.wcs.system.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * Created by vincent on 3/15/2024
+ */
+@Service
+public class MapService {
+
+    @Autowired
+    private UserService userService;
+
+    public MapDataParam getMapData(Long userId) {
+        User user = userService.getById(userId);
+        if (Cools.isEmpty(user.getMemo())) {
+            return new MapDataParam();
+        }
+        return JSON.parseObject(user.getMemo(), MapDataParam.class);
+    }
+
+    public void saveMapData(MapDataParam param, Long userId) {
+        User user = userService.getById(userId);
+        user.setMemo(JSON.toJSONString(param));
+        if (!userService.updateById(user)) {
+            throw new CoolException("鏈嶅姟鍣ㄥ唴閮ㄩ敊璇�");
+        }
+    }
+
+}

--
Gitblit v1.9.1