Junjie
2024-03-19 ec2e79e0d510568d51714a7121e4a32c026e6d44
Merge remote-tracking branch 'origin/master'
11个文件已添加
17个文件已修改
1576 ■■■■ 已修改文件
zy-asrs-flow/config/config.ts 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/package.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/public/favicon.jpg 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/public/img/map/point.svg 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/App.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/components/RightContent/AvatarDropdown.jsx 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/config/setting.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/locales/en-US.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/locales/en-US/map.ts 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/pages/map/components/configSettings.jsx 128 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/pages/map/components/device.jsx 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/pages/map/components/mapCopySettings.jsx 166 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/pages/map/components/mapSettings.jsx 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/pages/map/components/settings.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/pages/map/drawer/agv/index.jsx 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/pages/map/drawer/index.jsx 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/pages/map/drawer/point/index.jsx 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/pages/map/drawer/shelf/index.jsx 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/pages/map/drawer/showJson.jsx 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/pages/map/header/search.jsx 164 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/pages/map/index.css 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/pages/map/index.jsx 200 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/pages/map/player.js 91 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-flow/src/pages/map/utils.js 204 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/controller/MapController.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/controller/param/MapDataParam.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/entity/MapItem.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/service/MapService.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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'
  ]
});
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",
zy-asrs-flow/public/favicon.jpg
zy-asrs-flow/public/img/map/point.svg
New file
@@ -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>
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) => {
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: '退出登录' }),
    },
  ];
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';
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...',
  '':'',
  '':'',
  '':'',
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!',
    '': '',
    '': '',
    '': '',
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>
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>
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>
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 => {
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>
zy-asrs-flow/src/pages/map/drawer/agv/index.jsx
New file
@@ -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;
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>
        </>
    )
}
zy-asrs-flow/src/pages/map/drawer/point/index.jsx
New file
@@ -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;
zy-asrs-flow/src/pages/map/drawer/shelf/index.jsx
New file
@@ -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;
zy-asrs-flow/src/pages/map/drawer/showJson.jsx
New file
@@ -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;
zy-asrs-flow/src/pages/map/header/search.jsx
New file
@@ -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;
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;
}
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}
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();
        });
    }
}
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();
}
zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/controller/MapController.java
New file
@@ -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();
    }
}
zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/controller/param/MapDataParam.java
New file
@@ -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;
}
zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/entity/MapItem.java
New file
@@ -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;
}
zy-asrs-wcs/src/main/java/com/zy/asrs/wcs/core/map/service/MapService.java
New file
@@ -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("服务器内部错误");
        }
    }
}