Prechádzať zdrojové kódy

feat(PathNavigator): 新增PathNavigator组件

xieyonghong 3 rokov pred
rodič
commit
9522749f6e

+ 94 - 0
src/components/AMapComponent/PathSimplifier/PathNavigator.tsx

@@ -0,0 +1,94 @@
+import { useCallback, useEffect, useRef } from 'react';
+import { omit } from 'lodash';
+
+interface PathNavigatorProps extends PathNavigatorOptions {
+  __pathSimplifier__?: PathSimplifier;
+  navKey?: number;
+  onCreate?: (nav: PathNavigator) => void;
+  /**
+   * 是否自动播放
+   * @default true
+   */
+  isAuto?: boolean;
+  onStart?: (e: any) => void;
+  onPause?: (e: any) => void;
+  onMove?: (e: any) => void;
+  onStop?: (e: any) => void;
+}
+
+const EventMap = {
+  start: 'onStart',
+  pause: 'onPause',
+  move: 'onMove',
+  stop: 'onStop',
+};
+
+export default (props: PathNavigatorProps) => {
+  const { __pathSimplifier__, navKey, isAuto, onCreate, ...extraProps } = props;
+
+  const PathNavigatorRef = useRef<PathNavigator | null>(null);
+
+  const createEvent = () => {
+    Object.keys(EventMap).forEach((event) => {
+      if (props[EventMap[event]]) {
+        PathNavigatorRef.current?.on(event, props[EventMap[event]]);
+      }
+    });
+  };
+
+  const removeEvent = () => {
+    Object.keys(EventMap).forEach((event) => {
+      if (props[EventMap[event]]) {
+        PathNavigatorRef.current?.off(event, props[EventMap[event]]);
+      }
+    });
+  };
+
+  const createPathNavigator = useCallback(
+    (path?: PathSimplifier) => {
+      if (path) {
+        PathNavigatorRef.current = path.createPathNavigator(navKey!, {
+          speed: props.speed || 10000,
+          ...omit(extraProps, Object.values(EventMap)),
+        });
+
+        if (PathNavigatorRef.current) {
+          createEvent();
+        }
+
+        if (onCreate && PathNavigatorRef.current) {
+          onCreate(PathNavigatorRef.current);
+        }
+
+        if (props.isAuto !== false) {
+          PathNavigatorRef.current?.start();
+        }
+      }
+    },
+    [props],
+  );
+
+  useEffect(() => {
+    if (PathNavigatorRef.current && props.speed !== undefined) {
+      PathNavigatorRef.current?.setSpeed(props.speed);
+    }
+  }, [props.speed]);
+
+  useEffect(() => {
+    if (PathNavigatorRef.current && props.range !== undefined) {
+      PathNavigatorRef.current?.setRange(props.range[0], props.range[1]);
+    }
+  }, [props.range]);
+
+  useEffect(() => {
+    createPathNavigator(props.__pathSimplifier__);
+
+    return () => {
+      if (PathNavigatorRef.current) {
+        removeEvent();
+      }
+    };
+  }, []);
+
+  return null;
+};

+ 33 - 33
src/components/AMapComponent/PathSimplifier/index.tsx

@@ -1,38 +1,19 @@
-import { useCallback, useEffect, useRef } from 'react';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import PathNavigator from './PathNavigator';
 
 interface PathSimplifierProps {
   __map__?: any;
   options?: Omit<PathSimplifierOptions, 'map'>;
-  pathNavigatorOptions?: PathNavigatorOptions;
-  speed?: number;
-  pathName?: string;
-  pathData?: PathDataType;
-  onCreated?: (nav: PathNavigator) => void;
+  pathData?: PathDataItemType[];
+  children?: React.ReactNode;
+  onCreated?: (nav: PathSimplifier) => void;
 }
 
 const PathSimplifier = (props: PathSimplifierProps) => {
-  const { pathData, pathName, __map__, onCreated, speed, pathNavigatorOptions, options } = props;
+  const { pathData, __map__, onCreated, options } = props;
 
   const pathSimplifierRef = useRef<PathSimplifier | null>(null);
-  const pathNavRef = useRef<PathNavigator | undefined>(undefined);
-
-  const createPathNav = (path: number[][], navOptions?: PathNavigatorOptions) => {
-    pathSimplifierRef.current?.setData([
-      {
-        name: pathName,
-        path,
-      },
-    ]);
-    pathNavRef.current = pathSimplifierRef.current?.createPathNavigator(0, {
-      loop: false,
-      speed: 10000,
-      ...navOptions,
-    });
-
-    if (onCreated) {
-      onCreated(pathNavRef.current!);
-    }
-  };
+  const [loading, setLoading] = useState(false);
 
   const pathSimplifier = useCallback(
     (PathObj: PathSimplifier) => {
@@ -48,7 +29,14 @@ const PathSimplifier = (props: PathSimplifierProps) => {
         ...options,
       });
       if (pathData) {
-        createPathNav(pathData, pathNavigatorOptions);
+        pathSimplifierRef.current?.setData(
+          pathData.map((item) => ({ name: item.name || '路线', path: item.path })),
+        );
+        setLoading(true);
+      }
+
+      if (onCreated) {
+        onCreated(pathSimplifierRef.current!);
       }
     },
     [props],
@@ -66,11 +54,21 @@ const PathSimplifier = (props: PathSimplifierProps) => {
     }
   };
 
-  useEffect(() => {
-    if (pathNavRef.current && speed !== undefined) {
-      pathNavRef.current?.setSpeed(speed);
-    }
-  }, [pathNavRef.current, speed]);
+  const renderChildren = () => {
+    return React.Children.map(props.children, (child, index) => {
+      if (child) {
+        if (typeof child === 'string') {
+          return child;
+        } else {
+          return React.cloneElement(child as any, {
+            __pathSimplifier__: pathSimplifierRef.current,
+            navKey: index,
+          });
+        }
+      }
+      return child;
+    });
+  };
 
   useEffect(() => {
     if (__map__) {
@@ -78,7 +76,9 @@ const PathSimplifier = (props: PathSimplifierProps) => {
     }
   }, [__map__]);
 
-  return null;
+  return <>{loading && renderChildren()}</>;
 };
 
+PathSimplifier.PathNavigator = PathNavigator;
+
 export default PathSimplifier;

+ 6 - 1
src/components/AMapComponent/PathSimplifier/types.d.ts

@@ -14,6 +14,11 @@ type PathSimplifierOptions = {
   renderOptions?: {};
 };
 
+type PathDataItemType = {
+  name?: string;
+  path: PathDataType;
+};
+
 interface PathSimplifier {
   new (options: PathSimplifierOptions);
 
@@ -75,7 +80,7 @@ interface PathNavigatorOptions {
   pathNavigatorStyle?: {};
   animInterval?: number;
   dirToPosInMillsecs?: number;
-  range?: number[];
+  range?: [number, number];
 }
 
 interface PathNavigator {

+ 34 - 22
src/pages/demo/AMap/index.tsx

@@ -2,7 +2,7 @@ import { AMap, PathSimplifier } from '@/components';
 import { useState } from 'react';
 
 export default () => {
-  const [speed, setSpeed] = useState(10000);
+  const [speed] = useState(100000);
   return (
     <AMap
       useAMapUI={true}
@@ -13,28 +13,40 @@ export default () => {
     >
       <PathSimplifier
         pathData={[
-          [116.405289, 39.904987],
-          [113.964458, 40.54664],
-          [111.47836, 41.135964],
-          [108.949297, 41.670904],
-          [106.380111, 42.149509],
-          [103.774185, 42.56996],
-          [101.135432, 42.930601],
-          [98.46826, 43.229964],
-          [95.777529, 43.466798],
-          [93.068486, 43.64009],
-          [90.34669, 43.749086],
-          [87.61792, 43.793308],
+          {
+            name: '线路1',
+            path: [
+              [116.405289, 39.904987],
+              [113.964458, 40.54664],
+              [111.47836, 41.135964],
+              [108.949297, 41.670904],
+              [106.380111, 42.149509],
+              [103.774185, 42.56996],
+              [101.135432, 42.930601],
+              [98.46826, 43.229964],
+              [95.777529, 43.466798],
+              [93.068486, 43.64009],
+              [90.34669, 43.749086],
+              [87.61792, 43.793308],
+            ],
+          },
         ]}
-        pathName={'线路1'}
-        speed={speed}
-        onCreated={(nav) => {
-          nav.start();
-          setTimeout(() => {
-            setSpeed(speed * 8);
-          }, 5000);
-        }}
-      />
+      >
+        <PathSimplifier.PathNavigator
+          speed={speed}
+          onStart={() => {
+            console.log('start');
+          }}
+          onCreate={(nav) => {
+            setTimeout(() => {
+              nav.pause();
+            }, 5000);
+            setTimeout(() => {
+              nav.resume(); // 恢复
+            }, 7000);
+          }}
+        />
+      </PathSimplifier>
     </AMap>
   );
 };

+ 15 - 5
src/pages/rule-engine/Scene/Save/action/messageContent.tsx

@@ -27,11 +27,21 @@ export default (props: MessageContentProps) => {
       if (item.required) {
         rules.push({
           validator: async (_: any, value: any) => {
-            if (!value.value) {
-              if (['date'].includes(type)) {
-                return Promise.reject(new Error('请选择' + item.name));
-              } else {
-                return Promise.reject(new Error('请输入' + item.name));
+            if (type === 'file' && !value) {
+              return Promise.reject(new Error('请输入' + item.name));
+            } else {
+              if (!value || !value.value) {
+                if (['date', 'org', 'user'].includes(type)) {
+                  if (
+                    ['sms', 'voice', 'email'].includes(props.notifyType) &&
+                    value.source !== 'relation'
+                  ) {
+                    return Promise.reject(new Error('请输入' + item.name));
+                  }
+                  return Promise.reject(new Error('请选择' + item.name));
+                } else {
+                  return Promise.reject(new Error('请输入' + item.name));
+                }
               }
             }
             return Promise.resolve();

+ 17 - 16
src/pages/rule-engine/Scene/Save/index.tsx

@@ -19,7 +19,7 @@ import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons';
 import { TimingTrigger, TriggerWay } from './components';
 import { TriggerWayType } from './components/TriggerWay';
 import TriggerTerm from '@/pages/rule-engine/Scene/TriggerTerm';
-import TriggerDevice from './trigger/device';
+import TriggerDevice from './trigger';
 import { service } from '../index';
 import './index.less';
 import { model } from '@formily/reactive';
@@ -271,21 +271,22 @@ export default () => {
               </Form.Item>
             )}
             {triggerType === TriggerWayType.device && (
-              <Form.Item
-                name={['trigger', 'device']}
-                rules={[
-                  {
-                    validator: async (_: any, value: any) => {
-                      if (!value) {
-                        return Promise.reject(new Error('请选择产品'));
-                      }
-                      return Promise.resolve();
-                    },
-                  },
-                ]}
-              >
-                <TriggerDevice className={'trigger-type-content'} />
-              </Form.Item>
+              // <Form.Item
+              //   name={['trigger', 'device']}
+              //   rules={[
+              //     {
+              //       validator: async (_: any, value: any) => {
+              //         if (!value) {
+              //           return Promise.reject(new Error('请选择产品'));
+              //         }
+              //         return Promise.resolve();
+              //       },
+              //     },
+              //   ]}
+              // >
+              //   <TriggerDevice className={'trigger-type-content'} />
+              // </Form.Item>
+              <TriggerDevice className={'trigger-type-content'} form={form} />
             )}
           </Form.Item>
           {triggerType === TriggerWayType.device &&

+ 29 - 0
src/pages/rule-engine/Scene/Save/trigger/OrgTreeSelect.tsx

@@ -0,0 +1,29 @@
+import { TreeSelect } from 'antd';
+import type { TreeSelectProps } from 'antd';
+import React, { useEffect, useState } from 'react';
+
+interface OrgTreeSelect extends Omit<TreeSelectProps, 'onChange' | 'value'> {
+  onChange?: (value: any[]) => void;
+  value?: any;
+}
+
+export default (props: OrgTreeSelect) => {
+  const [myValue, setMyValue] = useState(props.value ? props.value[0].id : undefined);
+  const { value, onChange, ...extraProps } = props;
+
+  const onchange = (key: string, label: React.ReactNode[]) => {
+    if (props.onChange) {
+      props.onChange([{ id: key, name: label[0] }]);
+    }
+  };
+
+  useEffect(() => {
+    if (props.value) {
+      setMyValue(props.value[0].id);
+    } else {
+      setMyValue(undefined);
+    }
+  }, [props.value]);
+
+  return <TreeSelect<string> value={myValue} onChange={onchange} {...extraProps} />;
+};

+ 1 - 0
src/pages/rule-engine/Scene/Save/trigger/device.tsx

@@ -267,6 +267,7 @@ export default (props: TriggerProps) => {
         </Col>
       </Row>
       {props.value?.operation?.operator === OperatorEnum.invokeFunction ||
+      props.value?.operation?.operator === OperatorEnum.readProperty ||
       props.value?.operation?.operator === OperatorEnum.writeProperty ? (
         <TimingTrigger
           value={props.value?.operation?.timer}

+ 50 - 38
src/pages/rule-engine/Scene/Save/trigger/index.tsx

@@ -1,7 +1,7 @@
 // 已废弃
 import { useCallback, useEffect, useState } from 'react';
 import type { FormInstance } from 'antd';
-import { Col, Form, Row, Select, TreeSelect } from 'antd';
+import { Col, Form, Row, Select } from 'antd';
 import { ItemGroup, TimingTrigger } from '@/pages/rule-engine/Scene/Save/components';
 import { getProductList } from '@/pages/rule-engine/Scene/Save/action/device/service';
 import { queryOrgTree, querySelector } from '@/pages/rule-engine/Scene/Save/trigger/service';
@@ -10,6 +10,7 @@ import FunctionCall from '@/pages/rule-engine/Scene/Save/action/device/functionC
 import Operation from './operation';
 import classNames from 'classnames';
 import { observer } from '@formily/reactive-react';
+import OrgTreeSelect from './OrgTreeSelect';
 import { FormModel } from '../index';
 import AllDevice from '@/pages/rule-engine/Scene/Save/action/device/AllDevice';
 
@@ -47,6 +48,8 @@ export default observer((props: TriggerProps) => {
   const [functionItem, setFunctionItem] = useState<any[]>([]); // 单个功能-属性列表
   const [orgTree, setOrgTree] = useState<any>([]);
 
+  // const nameValue = Form.useWatch('name', props.form);
+
   const getSelector = () => {
     querySelector().then((resp) => {
       if (resp && resp.status === 200) {
@@ -155,6 +158,7 @@ export default observer((props: TriggerProps) => {
             rules={[{ required: true, message: '请选择产品' }]}
           >
             <Select
+              showSearch
               options={productList}
               placeholder={'请选择产品'}
               style={{ width: '100%' }}
@@ -164,51 +168,59 @@ export default observer((props: TriggerProps) => {
                 props.form?.resetFields([['trigger', 'device', 'selectorValues']]);
                 props.form?.resetFields([['trigger', 'device', 'operation', 'operator']]);
                 productIdChange(key, node.metadata);
+                props.form?.setFieldsValue({
+                  trigger: {
+                    device: {
+                      selector: 'fixed',
+                      selectorValues: undefined,
+                      operation: {},
+                    },
+                    productId: key,
+                  },
+                });
               }}
               fieldNames={{ label: 'name', value: 'id' }}
+              filterOption={(input: string, option: any) =>
+                option.name.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              }
             />
           </Form.Item>
         </Col>
-        <Col span={12}>
-          <Form.Item noStyle>
-            <ItemGroup>
-              <Form.Item
-                name={['trigger', 'device', 'selector']}
-                initialValue={
-                  props.triggerData && props.triggerData.device && props.triggerData.device.selector
-                    ? props.triggerData.device.selector
-                    : 'fixed'
-                }
-              >
-                <Select
-                  options={selectorOptions}
-                  fieldNames={{ label: 'name', value: 'id' }}
-                  style={{ width: 120 }}
-                />
-              </Form.Item>
-              {selector === 'all' && (
-                <Form.Item name={['trigger', 'device', 'selectorValues']}>
-                  <AllDevice productId={productId} />
-                </Form.Item>
-              )}
-              {selector === 'fixed' && (
-                <Form.Item name={['trigger', 'device', 'selectorValues']}>
-                  <Device productId={productId} />
-                </Form.Item>
-              )}
-              {selector === 'org' && (
-                <Form.Item name={['trigger', 'device', 'selectorValues']}>
-                  <TreeSelect
-                    treeData={orgTree}
+        {!!productId && (
+          <Col span={12}>
+            <Form.Item noStyle>
+              <ItemGroup>
+                <Form.Item name={['trigger', 'device', 'selector']} initialValue={'fixed'}>
+                  <Select
+                    options={selectorOptions}
                     fieldNames={{ label: 'name', value: 'id' }}
-                    placeholder={'请选择部门'}
-                    style={{ width: '100%' }}
+                    style={{ width: 120 }}
                   />
                 </Form.Item>
-              )}
-            </ItemGroup>
-          </Form.Item>
-        </Col>
+                {selector === 'all' && (
+                  <Form.Item name={['trigger', 'device', 'selectorValues']}>
+                    <AllDevice productId={productId} />
+                  </Form.Item>
+                )}
+                {selector === 'fixed' && (
+                  <Form.Item name={['trigger', 'device', 'selectorValues']}>
+                    <Device productId={productId} />
+                  </Form.Item>
+                )}
+                {selector === 'org' && (
+                  <Form.Item name={['trigger', 'device', 'selectorValues']}>
+                    <OrgTreeSelect
+                      treeData={orgTree}
+                      fieldNames={{ label: 'name', value: 'id' }}
+                      placeholder={'请选择部门'}
+                      style={{ width: '100%' }}
+                    />
+                  </Form.Item>
+                )}
+              </ItemGroup>
+            </Form.Item>
+          </Col>
+        )}
         <Col span={6}>
           {functions.length || events.length || properties.length ? (
             <Form.Item