Jelajahi Sumber

fix(场景联动): bug#4458、4438、4434、4433、

xieyonghong 3 tahun lalu
induk
melakukan
38effae551

+ 8 - 2
src/components/ProTableCard/CardItems/scene.tsx

@@ -11,6 +11,12 @@ export interface DeviceCardProps extends SceneItem {
 
 const defaultImage = require('/public/images/scene.png');
 
+enum TriggerWayType {
+  manual = '手动触发',
+  timer = '定时触发',
+  device = '设备触发',
+}
+
 export default (props: DeviceCardProps) => {
   return (
     <TableCard
@@ -35,11 +41,11 @@ export default (props: DeviceCardProps) => {
           <div className={'card-item-content'}>
             <div>
               <label>触发方式</label>
-              <div className={'ellipsis'}>{'test'}</div>
+              <div className={'ellipsis'}>{TriggerWayType[props.triggerType]}</div>
             </div>
             <div>
               <label>说明</label>
-              <div className={'ellipsis'}>{props.describe || '--'}</div>
+              <div className={'ellipsis'}>{props.description || '--'}</div>
             </div>
           </div>
         </div>

+ 35 - 39
src/pages/rule-engine/Scene/Save/action/VariableItems/builtIn.tsx

@@ -4,6 +4,7 @@ import { useRequest } from 'umi';
 import { queryBuiltInParams } from '@/pages/rule-engine/Scene/Save/action/service';
 import { ItemGroup } from '@/pages/rule-engine/Scene/Save/components';
 import moment from 'moment';
+import { debounce } from 'lodash';
 
 type ChangeType = {
   source?: string;
@@ -57,7 +58,7 @@ export default (props: BuiltInProps) => {
     if (_upperKey) {
       obj.upperKey = _upperKey;
     }
-
+    console.log(_source, _value);
     if (props.onChange) {
       props.onChange(obj);
     }
@@ -70,45 +71,12 @@ export default (props: BuiltInProps) => {
     [source],
   );
 
-  const inputNodeByType = useCallback(
-    (data: any) => {
-      switch (data.type) {
-        case 'date':
-          return (
-            // @ts-ignore
-            <DatePicker
-              value={value ? moment(value) : undefined}
-              style={{ width: '100%' }}
-              format={data.format || 'YYYY-MM-DD HH:mm:ss'}
-              onChange={(date) => {
-                itemOnChange(date?.format(data.format || 'YYYY-MM-DD HH:mm:ss'));
-              }}
-            />
-          );
-        case 'number':
-          return (
-            <InputNumber
-              value={value}
-              placeholder={`请输入${data.name}`}
-              style={{ width: '100%' }}
-              onChange={itemOnChange}
-            />
-          );
-        default:
-          return (
-            <Input
-              value={value}
-              placeholder={`请输入${data.name}`}
-              onChange={(e) => itemOnChange(e.target.value)}
-            />
-          );
-      }
-    },
-    [value],
-  );
+  const inputChange = (e: any) => {
+    itemOnChange(e.target.value);
+  };
 
   return (
-    <ItemGroup>
+    <ItemGroup compact>
       <Select
         value={source}
         options={[
@@ -132,7 +100,35 @@ export default (props: BuiltInProps) => {
           placeholder={'请选择参数'}
         />
       ) : (
-        <div>{inputNodeByType(props.data)}</div>
+        <div>
+          {
+            // inputNodeByType(props.data)
+            props.data.type === 'date' ? (
+              // @ts-ignore
+              <DatePicker
+                value={value ? moment(value, 'YYYY-MM-DD HH:mm:ss') : undefined}
+                style={{ width: '100%' }}
+                format={'YYYY-MM-DD HH:mm:ss'}
+                onChange={(_: any, dateString) => {
+                  itemOnChange(dateString);
+                }}
+              />
+            ) : props.data.type === 'number' ? (
+              <InputNumber
+                value={value}
+                placeholder={`请输入${props.data.name}`}
+                style={{ width: '100%' }}
+                onChange={itemOnChange}
+              />
+            ) : (
+              <Input
+                value={value}
+                placeholder={`请输入${props.data.name}`}
+                onChange={debounce(inputChange, 300)}
+              />
+            )
+          }
+        </div>
       )}
     </ItemGroup>
   );

+ 1 - 1
src/pages/rule-engine/Scene/Save/action/VariableItems/user.tsx

@@ -169,7 +169,7 @@ export default (props: UserProps) => {
   };
 
   return (
-    <ItemGroup>
+    <ItemGroup compact>
       <Select
         value={source}
         options={options}

+ 71 - 55
src/pages/rule-engine/Scene/Save/action/action.tsx

@@ -1,5 +1,5 @@
 import type { FormInstance } from 'antd';
-import { Button, Form, Select } from 'antd';
+import { Button, Col, Form, Row, Select } from 'antd';
 import { useCallback, useEffect, useState } from 'react';
 import { useRequest } from 'umi';
 import {
@@ -114,6 +114,7 @@ export default observer((props: ActionProps) => {
 
   const handleInit = useCallback(async (data: any) => {
     if (data) {
+      console.log('actions ', data);
       if (data.executor) {
         setType1(data.executor);
       }
@@ -142,41 +143,47 @@ export default observer((props: ActionProps) => {
 
   const MessageNodes = (
     <>
-      <Form.Item {...props.restField} name={[name, 'notify', 'notifyType']}>
-        <Select
-          options={messageType}
-          fieldNames={{ value: 'id', label: 'name' }}
-          placeholder={'请选择通知方式'}
-          style={{ width: 140 }}
-          onChange={async () => {
-            setTemplateData(undefined);
-            props.form.resetFields([['actions', name, 'notify', 'notifierId']]);
-            props.form.resetFields([['actions', name, 'notify', 'templateId']]);
-          }}
-        />
-      </Form.Item>
-      <Form.Item {...props.restField} name={[name, 'notify', 'notifierId']}>
-        <Select
-          options={messageConfig}
-          loading={messageConfigLoading}
-          fieldNames={{ value: 'id', label: 'name' }}
-          onChange={() => {
-            setTemplateData(undefined);
-            props.form.resetFields([['actions', name, 'notify', 'templateId']]);
-          }}
-          style={{ width: 160 }}
-          placeholder={'请选择通知配置'}
-        />
-      </Form.Item>
-      <Form.Item {...props.restField} name={[name, 'notify', 'templateId']}>
-        <Select
-          options={messageTemplate}
-          loading={messageTemplateLoading}
-          fieldNames={{ value: 'id', label: 'name' }}
-          style={{ width: 160 }}
-          placeholder={'请选择通知模板'}
-        />
-      </Form.Item>
+      <Col span={7}>
+        <Form.Item {...props.restField} name={[name, 'notify', 'notifyType']}>
+          <Select
+            options={messageType}
+            fieldNames={{ value: 'id', label: 'name' }}
+            placeholder={'请选择通知方式'}
+            style={{ width: '100%' }}
+            onChange={async () => {
+              setTemplateData(undefined);
+              props.form.resetFields([['actions', name, 'notify', 'notifierId']]);
+              props.form.resetFields([['actions', name, 'notify', 'templateId']]);
+            }}
+          />
+        </Form.Item>
+      </Col>
+      <Col span={7}>
+        <Form.Item {...props.restField} name={[name, 'notify', 'notifierId']}>
+          <Select
+            options={messageConfig}
+            loading={messageConfigLoading}
+            fieldNames={{ value: 'id', label: 'name' }}
+            onChange={() => {
+              setTemplateData(undefined);
+              props.form.resetFields([['actions', name, 'notify', 'templateId']]);
+            }}
+            style={{ width: '100%' }}
+            placeholder={'请选择通知配置'}
+          />
+        </Form.Item>
+      </Col>
+      <Col span={6}>
+        <Form.Item {...props.restField} name={[name, 'notify', 'templateId']}>
+          <Select
+            options={messageTemplate}
+            loading={messageTemplateLoading}
+            fieldNames={{ value: 'id', label: 'name' }}
+            style={{ width: '100%' }}
+            placeholder={'请选择通知模板'}
+          />
+        </Form.Item>
+      </Col>
     </>
   );
 
@@ -201,18 +208,20 @@ export default observer((props: ActionProps) => {
           <DeleteOutlined />
         </Button>
       </div>
-      <div style={{ display: 'flex', gap: 12 }}>
-        <Form.Item {...props.restField} name={[name, 'executor']}>
-          <Select
-            options={[
-              { label: '消息通知', value: 'notify' },
-              { label: '设备输出', value: 'device' },
-              { label: '延迟执行', value: 'delay' },
-            ]}
-            placeholder={'请选择动作方式'}
-            style={{ width: 140 }}
-          />
-        </Form.Item>
+      <Row gutter={24}>
+        <Col span={4}>
+          <Form.Item {...props.restField} name={[name, 'executor']}>
+            <Select
+              options={[
+                { label: '消息通知', value: 'notify' },
+                { label: '设备输出', value: 'device' },
+                { label: '延迟执行', value: 'delay' },
+              ]}
+              placeholder={'请选择动作方式'}
+              style={{ width: '100%' }}
+            />
+          </Form.Item>
+        </Col>
         {type1 === 'notify' && MessageNodes}
         {type1 === 'device' && (
           <DeviceSelect
@@ -227,11 +236,14 @@ export default observer((props: ActionProps) => {
           />
         )}
         {type1 === 'delay' && (
-          <Form.Item name={[name, 'delay']}>
-            <InputNumber />
-          </Form.Item>
+          <Col span={6}>
+            <Form.Item name={[name, 'delay']}>
+              <InputNumber />
+            </Form.Item>
+          </Col>
         )}
-      </div>
+      </Row>
+
       {type1 === 'notify' && templateData ? (
         <MessageContent
           form={props.form}
@@ -252,9 +264,13 @@ export default observer((props: ActionProps) => {
       {type1 === 'device' &&
       deviceMessageType === MessageTypeEnum.READ_PROPERTY &&
       properties.length ? (
-        <Form.Item name={[name, 'device', 'message', 'properties']}>
-          <ReadProperty properties={properties} />
-        </Form.Item>
+        <Row gutter={24}>
+          <Col span={4}>
+            <Form.Item name={[name, 'device', 'message', 'properties']}>
+              <ReadProperty properties={properties} />
+            </Form.Item>
+          </Col>
+        </Row>
       ) : null}
       {type1 === 'device' &&
       deviceMessageType === MessageTypeEnum.INVOKE_FUNCTION &&

+ 1 - 1
src/pages/rule-engine/Scene/Save/action/device/AllDevice.tsx

@@ -23,7 +23,7 @@ export default (props: AllDeviceProps) => {
 
   return (
     <Input
-      style={{ width: 300 }}
+      style={{ width: '100%' }}
       value={props.value ? props.value.map((item: any) => item.name).toString() : undefined}
       readOnly
     />

+ 42 - 35
src/pages/rule-engine/Scene/Save/action/device/WriteProperty/index.tsx

@@ -1,8 +1,9 @@
-import { DatePicker, FormInstance, Input, InputNumber, Select, Space } from 'antd';
+import { Col, DatePicker, FormInstance, Input, InputNumber, Row, Select } from 'antd';
 import { useCallback, useEffect, useState } from 'react';
 import { useRequest } from '@@/plugin-request/request';
 import { queryBuiltInParams } from '@/pages/rule-engine/Scene/Save/action/service';
 import moment from 'moment';
+import { ItemGroup } from '@/pages/rule-engine/Scene/Save/components';
 
 interface WritePropertyProps {
   properties: any[];
@@ -67,7 +68,7 @@ export default (props: WritePropertyProps) => {
         case 'boolean':
           return (
             <Select
-              style={{ width: 300, textAlign: 'left' }}
+              style={{ width: '100%', textAlign: 'left' }}
               value={propertiesValue}
               options={[
                 { label: 'true', value: true },
@@ -85,7 +86,7 @@ export default (props: WritePropertyProps) => {
         case 'double':
           return (
             <InputNumber
-              style={{ width: 300 }}
+              style={{ width: '100%' }}
               value={propertiesValue}
               placeholder={'请输入'}
               onChange={(value) => {
@@ -97,7 +98,7 @@ export default (props: WritePropertyProps) => {
           return (
             // @ts-ignore
             <DatePicker
-              style={{ width: 300 }}
+              style={{ width: '100%' }}
               value={propertiesValue ? moment(propertiesValue, 'YYYY-MM-DD HH:mm:ss') : undefined}
               onChange={(date) => {
                 onChange(propertiesKey, date ? date.format('YYYY-MM-DD HH:mm:ss') : undefined);
@@ -107,7 +108,7 @@ export default (props: WritePropertyProps) => {
         default:
           return (
             <Input
-              style={{ width: 300 }}
+              style={{ width: '100%' }}
               value={propertiesValue}
               placeholder={'请输入'}
               onChange={(e) => onChange(propertiesKey, e.target.value)}
@@ -119,37 +120,43 @@ export default (props: WritePropertyProps) => {
   );
 
   return (
-    <Space>
-      <Select
-        value={propertiesKey}
-        options={props.properties}
-        fieldNames={{ label: 'name', value: 'id' }}
-        style={{ width: 120 }}
-        onSelect={(key: any) => {
-          onChange(key, undefined);
-        }}
-        placeholder={'请选择属性'}
-      ></Select>
-      <Select
-        value={source}
-        options={[
-          { label: '手动输入', value: 'fixed' },
-          { label: '内置参数', value: 'upper' },
-        ]}
-        style={{ width: 120 }}
-        onChange={(key) => {
-          setSource(key);
-        }}
-      />
-      {source === 'upper' ? (
+    <Row gutter={24}>
+      <Col span={4}>
         <Select
-          options={builtInList}
+          value={propertiesKey}
+          options={props.properties}
           fieldNames={{ label: 'name', value: 'id' }}
-          placeholder={'请选择参数'}
-        />
-      ) : (
-        <div>{inputNodeByType(propertiesType)}</div>
-      )}
-    </Space>
+          style={{ width: '100%' }}
+          onSelect={(key: any) => {
+            onChange(key, undefined);
+          }}
+          placeholder={'请选择属性'}
+        ></Select>
+      </Col>
+      <Col span={7}>
+        <ItemGroup compact>
+          <Select
+            value={source}
+            options={[
+              { label: '手动输入', value: 'fixed' },
+              { label: '内置参数', value: 'upper' },
+            ]}
+            style={{ width: 120 }}
+            onChange={(key) => {
+              setSource(key);
+            }}
+          />
+          {source === 'upper' ? (
+            <Select
+              options={builtInList}
+              fieldNames={{ label: 'name', value: 'id' }}
+              placeholder={'请选择参数'}
+            />
+          ) : (
+            <div>{inputNodeByType(propertiesType)}</div>
+          )}
+        </ItemGroup>
+      </Col>
+    </Row>
   );
 };

+ 1 - 1
src/pages/rule-engine/Scene/Save/action/device/deviceModal.tsx

@@ -170,7 +170,7 @@ export default (props: DeviceModelProps) => {
             setSelectKeys([...value]);
           }
         }}
-        style={{ width: 300 }}
+        style={{ width: '100%' }}
         value={value.map((item) => item.name).toString()}
         readOnly
       />

+ 81 - 61
src/pages/rule-engine/Scene/Save/action/device/index.tsx

@@ -1,9 +1,10 @@
 import type { FormInstance } from 'antd';
-import { Form, Input, Select } from 'antd';
+import { Col, Form, Input, Select } from 'antd';
 import { useEffect, useState } from 'react';
 import { getProductList } from '@/pages/rule-engine/Scene/Save/action/device/service';
 import Device from './deviceModal';
 import TagModal from './tagModal';
+import { ItemGroup } from '@/pages/rule-engine/Scene/Save/components';
 
 interface DeviceProps {
   name: number;
@@ -153,70 +154,89 @@ export default (props: DeviceProps) => {
 
   return (
     <>
-      <Form.Item name={[name, 'device', 'productId']}>
-        <Select
-          options={productList}
-          placeholder={'请选择产品'}
-          style={{ width: 220 }}
-          listHeight={220}
-          onChange={() => {
-            props.form?.resetFields([['actions', name, 'device', 'selector']]);
-            props.form?.resetFields([['actions', name, 'device', 'selectorValues']]);
-            props.form?.resetFields([['actions', name, 'device', 'message', 'functionId']]);
-            // setMessageType(MessageTypeEnum.WRITE_PROPERTY)
-          }}
-          fieldNames={{ label: 'name', value: 'id' }}
-        />
-      </Form.Item>
-      <Form.Item
-        name={[name, 'device', 'selector']}
-        initialValue={props.value ? props.value.selector : SourceEnum.fixed}
-      >
-        <Select options={sourceList} style={{ width: 120 }} />
-      </Form.Item>
-      {selector === SourceEnum.fixed && (
-        <Form.Item name={[name, 'device', 'selectorValues']}>
-          <Device productId={productId} />
-        </Form.Item>
-      )}
-      {selector === SourceEnum.tag && (
-        <Form.Item name={[name, 'device', 'selectorValues']}>
-          <TagModal tagData={tagList} />
-        </Form.Item>
-      )}
-      {selector === SourceEnum.relation && (
-        <Form.Item name={[name, 'device', 'selectorValues']}>
-          <Select style={{ width: 300 }} />
-        </Form.Item>
-      )}
-      <Form.Item
-        name={[name, 'device', 'message', 'messageType']}
-        initialValue={
-          props.value && props.value.message && props.value.message.messageType
-            ? props.value.message.messageType
-            : MessageTypeEnum.WRITE_PROPERTY
-        }
-        {...props.restField}
-      >
-        <Select
-          options={[
-            { label: '功能调用', value: MessageTypeEnum.INVOKE_FUNCTION },
-            { label: '读取属性', value: MessageTypeEnum.READ_PROPERTY },
-            { label: '设置属性', value: MessageTypeEnum.WRITE_PROPERTY },
-          ]}
-          style={{ width: 120 }}
-        />
-      </Form.Item>
-      {messageType === MessageTypeEnum.INVOKE_FUNCTION ? (
-        <Form.Item name={[name, 'device', 'message', 'functionId']}>
+      <Col span={5}>
+        <Form.Item name={[name, 'device', 'productId']}>
           <Select
-            options={functionList}
+            showSearch
+            options={productList}
+            placeholder={'请选择产品'}
+            style={{ width: '100%' }}
+            listHeight={220}
+            onChange={() => {
+              props.form?.resetFields([['actions', name, 'device', 'selector']]);
+              props.form?.resetFields([['actions', name, 'device', 'selectorValues']]);
+              props.form?.resetFields([['actions', name, 'device', 'message', 'functionId']]);
+              // setMessageType(MessageTypeEnum.WRITE_PROPERTY)
+            }}
             fieldNames={{ label: 'name', value: 'id' }}
-            style={{ width: 120 }}
-            placeholder={'请选择功能'}
+            filterOption={(input: string, option: any) =>
+              option.name.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }
+          />
+        </Form.Item>
+      </Col>
+      <Col span={7}>
+        <ItemGroup compact>
+          <Form.Item
+            name={[name, 'device', 'selector']}
+            initialValue={props.value ? props.value.selector : SourceEnum.fixed}
+            {...props.restField}
+          >
+            <Select options={sourceList} style={{ width: 120 }} />
+          </Form.Item>
+          {selector === SourceEnum.fixed && (
+            <Form.Item name={[name, 'device', 'selectorValues']} {...props.restField}>
+              <Device productId={productId} />
+            </Form.Item>
+          )}
+          {selector === SourceEnum.tag && (
+            <Form.Item name={[name, 'device', 'selectorValues']} {...props.restField}>
+              <TagModal tagData={tagList} />
+            </Form.Item>
+          )}
+          {selector === SourceEnum.relation && (
+            <Form.Item name={[name, 'device', 'selectorValues']} {...props.restField}>
+              <Select style={{ width: 300 }} />
+            </Form.Item>
+          )}
+        </ItemGroup>
+      </Col>
+      <Col span={4}>
+        <Form.Item
+          name={[name, 'device', 'message', 'messageType']}
+          initialValue={
+            props.value && props.value.message && props.value.message.messageType
+              ? props.value.message.messageType
+              : MessageTypeEnum.WRITE_PROPERTY
+          }
+          {...props.restField}
+        >
+          <Select
+            options={[
+              { label: '功能调用', value: MessageTypeEnum.INVOKE_FUNCTION },
+              { label: '读取属性', value: MessageTypeEnum.READ_PROPERTY },
+              { label: '设置属性', value: MessageTypeEnum.WRITE_PROPERTY },
+            ]}
+            style={{ width: '100%' }}
           />
         </Form.Item>
-      ) : null}
+      </Col>
+      <Col span={4}>
+        {messageType === MessageTypeEnum.INVOKE_FUNCTION ? (
+          <Form.Item name={[name, 'device', 'message', 'functionId']} {...props.restField}>
+            <Select
+              showSearch
+              options={functionList}
+              fieldNames={{ label: 'name', value: 'id' }}
+              style={{ width: '100%' }}
+              placeholder={'请选择功能'}
+              filterOption={(input: string, option: any) =>
+                option.name.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              }
+            />
+          </Form.Item>
+        ) : null}
+      </Col>
       <Form.Item name={[name, 'device', 'source']} hidden>
         <Input />
       </Form.Item>

+ 1 - 1
src/pages/rule-engine/Scene/Save/action/device/readProperty.tsx

@@ -12,7 +12,7 @@ export default (props: ReadPropertyProps) => {
       value={props.value ? props.value[0] : undefined}
       options={props.properties}
       fieldNames={{ label: 'name', value: 'id' }}
-      style={{ width: 120 }}
+      style={{ width: '100%' }}
       onSelect={(key: any) => {
         if (props.onChange) {
           props.onChange([key]);

+ 0 - 1
src/pages/rule-engine/Scene/Save/components/InputNumber.tsx

@@ -39,7 +39,6 @@ export default (props: InputNumberProps) => {
     <InputNumber
       value={time}
       addonAfter={TimeTypeAfter}
-      style={{ width: 150 }}
       min={0}
       max={9999}
       onChange={(value) => {

+ 12 - 2
src/pages/rule-engine/Scene/Save/components/ItemGroup/index.less

@@ -1,10 +1,20 @@
 .group-item-compact {
   display: flex;
-  gap: 12px;
+  gap: 24px;
+  align-items: center;
 
-  > div {
+  > * {
     &:last-child {
       flex: 1;
     }
   }
+
+  &.compact {
+    gap: 0;
+    > * {
+      &:not(:last-child) {
+        margin-right: -1px;
+      }
+    }
+  }
 }

+ 13 - 1
src/pages/rule-engine/Scene/Save/components/ItemGroup/index.tsx

@@ -1,10 +1,22 @@
 import React from 'react';
 import './index.less';
+import classNames from 'classnames';
 
 interface ItemGroupProps {
   children?: React.ReactNode;
+  compact?: boolean;
+  style?: React.CSSProperties;
 }
 
 export default (props: ItemGroupProps) => {
-  return <div className={'group-item-compact'}>{props.children}</div>;
+  return (
+    <div
+      className={classNames('group-item-compact', {
+        compact: 'compact' in props && props.compact !== false,
+      })}
+      style={props.style}
+    >
+      {props.children}
+    </div>
+  );
 };

+ 132 - 114
src/pages/rule-engine/Scene/Save/components/TimingTrigger/index.tsx

@@ -1,9 +1,8 @@
-import { Input, InputNumber, Select, TimePicker } from 'antd';
-import { TimeSelect } from '@/pages/rule-engine/Scene/Save/components';
+import { Col, Input, InputNumber, Row, Select, TimePicker } from 'antd';
+import { ItemGroup, TimeSelect } from '@/pages/rule-engine/Scene/Save/components';
 import { useCallback, useEffect, useState } from 'react';
 import { omit } from 'lodash';
 import moment from 'moment';
-import classNames from 'classnames';
 
 type TimerType = {
   trigger: string;
@@ -48,7 +47,6 @@ export default (props: TimingTrigger) => {
   const [data, setData] = useState<TimerType>(DefaultValue);
 
   useEffect(() => {
-    console.log('timing-trigger', props.value);
     setData(props.value || DefaultValue);
   }, [props.value]);
 
@@ -58,8 +56,6 @@ export default (props: TimingTrigger) => {
     }
   };
 
-  console.log('timing-trigger-data', data);
-
   const type1Select = async (key: string) => {
     if (key !== TriggerEnum.cron) {
       onChange({
@@ -103,6 +99,16 @@ export default (props: TimingTrigger) => {
     [data],
   );
 
+  const cronChange = useCallback(
+    (e: any) => {
+      onChange({
+        trigger: data.trigger,
+        cron: e.target.value,
+      });
+    },
+    [data],
+  );
+
   const TimeTypeAfter = (
     <Select
       value={data.period?.unit || 'seconds'}
@@ -123,121 +129,133 @@ export default (props: TimingTrigger) => {
     />
   );
 
-  const implementNode =
-    data.mod === PeriodModEnum.period ? (
-      <>
-        <TimePicker.RangePicker
-          format={'hh:mm:ss'}
-          value={
-            data.period?.from
-              ? [moment(data.period?.from, 'hh:mm:ss'), moment(data.period?.to, 'hh:mm:ss')]
-              : undefined
-          }
-          onChange={(_, dateString) => {
-            onChange({
-              ...data,
-              period: {
-                ...data.period,
-                from: dateString[0],
-                to: dateString[1],
-              },
-            });
-          }}
-        />
-        <span> 每 </span>
-        <InputNumber
-          value={data.period?.every}
-          placeholder={'请输入时间'}
-          addonAfter={TimeTypeAfter}
-          style={{ width: 180 }}
-          min={0}
-          max={9999}
-          onChange={(e) => {
-            onChange({
-              ...data,
-              period: {
-                ...data.period,
-                every: e,
-              },
-            });
-          }}
-        />
-        <span> 执行一次 </span>
-      </>
-    ) : (
-      <>
-        <TimePicker
-          format={'hh:mm:ss'}
-          value={data.once?.time ? moment(data.once?.time, 'hh:mm:ss') : undefined}
-          onChange={(_, dateString) => {
-            onChange({
-              ...data,
-              once: {
-                time: dateString,
-              },
-            });
-          }}
-        />
-        <span> 执行一次 </span>
-      </>
-    );
-
   return (
-    <div
-      style={{ display: 'flex', gap: 12, alignItems: 'center' }}
-      className={classNames(props.className)}
-    >
-      <Select
-        options={[
-          { label: '按周', value: TriggerEnum.week },
-          { label: '按月', value: TriggerEnum.month },
-          { label: 'cron表达式', value: TriggerEnum.cron },
-        ]}
-        value={data.trigger}
-        onSelect={type1Select}
-        style={{ width: 100 }}
-      />
-      {data.trigger !== TriggerEnum.cron ? (
-        <>
-          <TimeSelect
-            value={data.when}
-            options={
-              data.trigger === TriggerEnum.week
-                ? [
-                    { label: '周一', value: 1 },
-                    { label: '周二', value: 2 },
-                    { label: '周三', value: 3 },
-                    { label: '周四', value: 4 },
-                    { label: '周五', value: 5 },
-                    { label: '周六', value: 6 },
-                    { label: '周末', value: 7 },
-                  ]
-                : new Array(31)
-                    .fill(1)
-                    .map((_, index) => ({ label: index + 1 + '号', value: index + 1 }))
-            }
-            style={{ width: 180 }}
-            onChange={(keys) => {
-              onChange({
-                ...data,
-                when: keys,
-              });
-            }}
-          />
+    <Row gutter={24} className={props.className}>
+      <Col span={data.trigger !== TriggerEnum.cron ? 6 : 8}>
+        <ItemGroup>
           <Select
             options={[
-              { label: '周期执行', value: PeriodModEnum.period },
-              { label: '执行一次', value: PeriodModEnum.once },
+              { label: '按周', value: TriggerEnum.week },
+              { label: '按月', value: TriggerEnum.month },
+              { label: 'cron表达式', value: TriggerEnum.cron },
             ]}
-            value={data.mod}
+            value={data.trigger}
+            onSelect={type1Select}
             style={{ width: 120 }}
-            onSelect={type2Select}
           />
-          {implementNode}
+          {data.trigger !== TriggerEnum.cron ? (
+            <TimeSelect
+              value={data.when}
+              options={
+                data.trigger === TriggerEnum.week
+                  ? [
+                      { label: '周一', value: 1 },
+                      { label: '周二', value: 2 },
+                      { label: '周三', value: 3 },
+                      { label: '周四', value: 4 },
+                      { label: '周五', value: 5 },
+                      { label: '周六', value: 6 },
+                      { label: '周末', value: 7 },
+                    ]
+                  : new Array(31)
+                      .fill(1)
+                      .map((_, index) => ({ label: index + 1 + '号', value: index + 1 }))
+              }
+              style={{ width: '100%' }}
+              onChange={(keys) => {
+                onChange({
+                  ...data,
+                  when: keys,
+                });
+              }}
+            />
+          ) : (
+            <Input
+              value={data.cron}
+              placeholder={'请输入cron表达式'}
+              style={{ width: 400 }}
+              onChange={cronChange}
+            />
+          )}
+        </ItemGroup>
+      </Col>
+      {data.trigger !== TriggerEnum.cron && (
+        <>
+          <Col span={12}>
+            <ItemGroup>
+              <Select
+                options={[
+                  { label: '周期执行', value: PeriodModEnum.period },
+                  { label: '执行一次', value: PeriodModEnum.once },
+                ]}
+                value={data.mod}
+                style={{ width: 120 }}
+                onSelect={type2Select}
+              />
+              {data.mod === PeriodModEnum.period ? (
+                <TimePicker.RangePicker
+                  format={'hh:mm:ss'}
+                  value={
+                    data.period?.from
+                      ? [moment(data.period?.from, 'hh:mm:ss'), moment(data.period?.to, 'hh:mm:ss')]
+                      : undefined
+                  }
+                  onChange={(_, dateString) => {
+                    onChange({
+                      ...data,
+                      period: {
+                        ...data.period,
+                        from: dateString[0],
+                        to: dateString[1],
+                      },
+                    });
+                  }}
+                />
+              ) : (
+                <TimePicker
+                  format={'hh:mm:ss'}
+                  value={data.once?.time ? moment(data.once?.time, 'hh:mm:ss') : undefined}
+                  onChange={(_, dateString) => {
+                    onChange({
+                      ...data,
+                      once: {
+                        time: dateString,
+                      },
+                    });
+                  }}
+                />
+              )}
+            </ItemGroup>
+          </Col>
+          <Col span={6}>
+            <ItemGroup style={{ gap: 16 }}>
+              {data.mod === PeriodModEnum.period ? (
+                <>
+                  <span> 每 </span>
+                  <InputNumber
+                    value={data.period?.every}
+                    placeholder={'请输入时间'}
+                    addonAfter={TimeTypeAfter}
+                    style={{ flex: 1 }}
+                    min={0}
+                    max={9999}
+                    onChange={(e) => {
+                      onChange({
+                        ...data,
+                        period: {
+                          ...data.period,
+                          every: e,
+                        },
+                      });
+                    }}
+                  />
+                </>
+              ) : null}
+              <span style={{ flex: 0, flexBasis: 64, lineHeight: '32px' }}> 执行一次 </span>
+            </ItemGroup>
+          </Col>
         </>
-      ) : (
-        <Input placeholder={'请输入cron表达式'} style={{ width: 400 }} />
       )}
-    </div>
+    </Row>
   );
 };

+ 6 - 0
src/pages/rule-engine/Scene/Save/index.less

@@ -6,4 +6,10 @@
     padding: 24px;
     background-color: @bgColor;
   }
+
+  .trigger-type-content {
+    > .ant-row:not(:last-child) {
+      margin-bottom: 24px;
+    }
+  }
 }

+ 55 - 35
src/pages/rule-engine/Scene/Save/index.tsx

@@ -11,7 +11,7 @@ import {
   Switch,
   Tooltip,
 } from 'antd';
-import { useLocation } from 'umi';
+import { useLocation, useIntl } from 'umi';
 import { useEffect, useRef, useState } from 'react';
 import { PermissionButton, TitleComponent } from '@/components';
 import ActionItems from './action/action';
@@ -19,9 +19,8 @@ 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';
+import TriggerDevice from './trigger/device';
 import { service } from '../index';
-import { useIntl } from '@@/plugin-locale/localeExports';
 import './index.less';
 import { model } from '@formily/reactive';
 import type { FormModelType } from '@/pages/rule-engine/Scene/typings';
@@ -54,17 +53,22 @@ export default () => {
   const [parallel, setParallel] = useState(false); // 是否并行
   const [shakeLimit, setShakeLimit] = useState<ShakeLimitType>(DefaultShakeLimit);
   const [requestParams, setRequestParams] = useState<any>(undefined);
-  const [formDatas, setFormDatas] = useState({});
+  const [actionsData, setActionsData] = useState<any[]>([]);
 
   const getDetail = async (id: string) => {
     const resp = await service.detail(id);
     if (resp.status === 200 && resp.result) {
       const _data: any = resp.result;
-      console.log(_data);
       FormModel = _data;
       form.setFieldsValue(_data);
       setParallel(_data.parallel);
       setShakeLimit(_data.shakeLimit || DefaultShakeLimit);
+      if (_data.trigger?.device?.selectorValues) {
+        setRequestParams({ trigger: _data.trigger });
+      }
+      if (_data.actions) {
+        setActionsData(_data.actions);
+      }
     }
   };
 
@@ -95,7 +99,7 @@ export default () => {
       setLoading(false);
       if (resp.status === 200) {
         message.success('操作成功');
-        form.setFieldsValue({ id: resp.result.id });
+        history.back();
       } else {
         message.error(resp.message);
       }
@@ -162,8 +166,6 @@ export default () => {
     </Space>
   );
 
-  console.log(formDatas);
-
   return (
     <PageContainer>
       <Card>
@@ -179,8 +181,10 @@ export default () => {
             } else {
               setRequestParams(undefined);
             }
+            if (allValues.actions) {
+              setActionsData(allValues.actions);
+            }
             FormModel = { ...allValues };
-            setFormDatas({ ...allValues });
           }}
         >
           <Form.Item
@@ -201,7 +205,10 @@ export default () => {
             <Input placeholder={'请输入名称'} />
           </Form.Item>
           <Form.Item label={<TitleComponent data={'触发方式'} style={{ margin: 0 }} />} required>
-            <Form.Item name={['trigger', 'type']}>
+            <Form.Item
+              name={['trigger', 'type']}
+              rules={[{ required: true, message: '请选择触发方式' }]}
+            >
               <TriggerWay onSelect={setTriggerType} />
             </Form.Item>
             {triggerType === TriggerWayType.timing && (
@@ -210,11 +217,9 @@ export default () => {
               </Form.Item>
             )}
             {triggerType === TriggerWayType.device && (
-              <TriggerDevice
-                className={'trigger-type-content'}
-                form={form}
-                triggerData={FormModel.trigger}
-              />
+              <Form.Item name={['trigger', 'device']}>
+                <TriggerDevice className={'trigger-type-content'} />
+              </Form.Item>
             )}
           </Form.Item>
           {triggerType === TriggerWayType.device &&
@@ -268,26 +273,41 @@ export default () => {
               </Space>
             }
           >
-            <Form.List name="actions">
-              {(fields, { add, remove }) => (
-                <div className={'scene-actions'}>
-                  {fields.map(({ key, name, ...restField }) => (
-                    <ActionItems
-                      key={key}
-                      form={form}
-                      restField={restField}
-                      name={name}
-                      triggerType={triggerType}
-                      onRemove={() => remove(name)}
-                      actionItemData={FormModel.actions && FormModel.actions[name]}
-                    />
-                  ))}
-                  <Form.Item noStyle>
-                    <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
-                      新增
-                    </Button>
-                  </Form.Item>
-                </div>
+            <Form.List
+              name="actions"
+              rules={[
+                {
+                  validator: async (_: any, value: any) => {
+                    if (!value) {
+                      return Promise.reject(new Error('请添加执行动作'));
+                    }
+                    return Promise.resolve();
+                  },
+                },
+              ]}
+            >
+              {(fields, { add, remove }, { errors }) => (
+                <>
+                  <div className={'scene-actions'}>
+                    {fields.map(({ key, name, ...restField }) => (
+                      <ActionItems
+                        key={key}
+                        form={form}
+                        restField={restField}
+                        name={name}
+                        triggerType={triggerType}
+                        onRemove={() => remove(name)}
+                        actionItemData={actionsData.length && actionsData[name]}
+                      />
+                    ))}
+                    <Form.Item noStyle>
+                      <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
+                        新增
+                      </Button>
+                    </Form.Item>
+                  </div>
+                  <Form.ErrorList errors={errors} />
+                </>
               )}
             </Form.List>
           </Form.Item>

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

@@ -0,0 +1,387 @@
+import { useCallback, useEffect, useState } from 'react';
+import { Col, Row, Select, TreeSelect } 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';
+import Device from '@/pages/rule-engine/Scene/Save/action/device/deviceModal';
+import FunctionCall from '@/pages/rule-engine/Scene/Save/action/device/functionCall';
+import Operation from './operation';
+import classNames from 'classnames';
+import { FormModel } from '../index';
+import AllDevice from '@/pages/rule-engine/Scene/Save/action/device/AllDevice';
+
+interface DeviceData {
+  productId?: string;
+  source?: string;
+  selector?: any;
+  selectorValues?: any[];
+  relation?: any;
+  upperKey?: string;
+  operation?: {
+    operator?: string;
+    timer?: any;
+    eventId?: string;
+    readProperties?: string[];
+    writeProperties?: any;
+    functionId?: string;
+    functionParameters?: { name: string; value: any }[];
+  };
+}
+
+interface TriggerProps {
+  value?: DeviceData;
+  onChange?: (value: DeviceData) => void;
+  className?: string;
+}
+
+enum OperatorEnum {
+  'online' = 'online',
+  'offline' = 'offline',
+  'reportEvent' = 'reportEvent',
+  'reportProperty' = 'reportProperty',
+  'readProperty' = 'readProperty',
+  'writeProperty' = 'writeProperty',
+  'invokeFunction' = 'invokeFunction',
+}
+
+export default (props: TriggerProps) => {
+  const [productList, setProductList] = useState<any[]>([]);
+  const [productId, setProductId] = useState('');
+  // const [selector, setSelector] = useState('fixed');
+
+  const [selectorOptions, setSelectorOptions] = useState<any[]>([]);
+  // const [operation, setOperation] = useState<string | undefined>(undefined);
+  const [operatorOptions, setOperatorOptions] = useState<any[]>([]);
+
+  const [properties, setProperties] = useState<any[]>([]); // 属性
+  const [events, setEvents] = useState([]); // 事件
+  const [functions, setFunctions] = useState([]); // 功能列表
+
+  const [functionItem, setFunctionItem] = useState<any[]>([]); // 单个功能-属性列表
+  const [orgTree, setOrgTree] = useState<any>([]);
+
+  const onChange = (value: DeviceData) => {
+    if (props.onChange) {
+      props.onChange(value);
+    }
+  };
+
+  const getSelector = () => {
+    querySelector().then((resp) => {
+      if (resp && resp.status === 200) {
+        setSelectorOptions(resp.result);
+      }
+    });
+  };
+
+  const getOrgTree = useCallback(() => {
+    queryOrgTree(productId).then((resp) => {
+      if (resp && resp.status === 200) {
+        setOrgTree(resp.result);
+      }
+    });
+  }, [queryOrgTree]);
+
+  const handleMetadata = (metadata?: string) => {
+    try {
+      const metadataObj = JSON.parse(metadata || '{}');
+      let newOperator: any[] = [
+        { label: '设备上线', value: OperatorEnum.online },
+        { label: '设备离线', value: OperatorEnum.offline },
+      ];
+      if (metadataObj.properties && metadataObj.properties.length) {
+        newOperator = [
+          ...newOperator,
+          { label: '属性上报', value: OperatorEnum.reportProperty },
+          { label: '属性读取', value: OperatorEnum.readProperty },
+          { label: '修改属性', value: OperatorEnum.writeProperty },
+        ];
+        setProperties(metadataObj.properties);
+      }
+      if (metadataObj.events && metadataObj.events.length) {
+        newOperator = [...newOperator, { label: '事件上报', value: OperatorEnum.reportEvent }];
+        setEvents(metadataObj.events);
+      }
+      if (metadataObj.functions && metadataObj.functions.length) {
+        newOperator = [...newOperator, { label: '功能调用', value: OperatorEnum.invokeFunction }];
+        setFunctions(metadataObj.functions);
+      }
+      setOperatorOptions(newOperator);
+    } catch (err) {
+      console.warn('handleMetadata === ', err);
+    }
+  };
+
+  const productIdChange = useCallback(
+    (id: string, metadata: any) => {
+      setProductId(id);
+      handleMetadata(metadata);
+      if (props.value?.selector === 'org') {
+        getOrgTree();
+      }
+    },
+    [props.value],
+  );
+
+  const getProducts = async () => {
+    const resp = await getProductList({ paging: false });
+    if (resp && resp.status === 200) {
+      setProductList(resp.result);
+      if (FormModel.trigger && FormModel.trigger.device) {
+        const productItem = resp.result.find(
+          (item: any) => item.id === FormModel.trigger!.device.productId,
+        );
+
+        if (productItem) {
+          productIdChange(FormModel.trigger!.device.productId, productItem.metadata);
+        }
+      }
+    }
+  };
+
+  useEffect(() => {
+    getProducts();
+    getSelector();
+  }, []);
+
+  // useEffect(() => {
+  //   const triggerData = props.triggerData;
+  //   console.log('trigger', triggerData);
+  //   if (triggerData && triggerData.device) {
+  //     const _device = triggerData.device;
+  //
+  //     if (_device.selector) {
+  //       if (_device.selector === 'org') {
+  //         getOrgTree();
+  //       }
+  //       setSelector(_device.selector);
+  //     }
+  //
+  //     if (_device.operation && 'operator' in _device.operation) {
+  //       setOperation(_device.operation.operator);
+  //     } else {
+  //       setOperation(undefined)
+  //     }
+  //   }
+  // }, [props.triggerData]);
+
+  return (
+    <div className={classNames(props.className)}>
+      <Row gutter={24}>
+        <Col span={6}>
+          <Select
+            showSearch
+            value={props.value?.productId}
+            options={productList}
+            placeholder={'请选择产品'}
+            style={{ width: '100%' }}
+            listHeight={220}
+            onChange={(key: any, node: any) => {
+              productIdChange(key, node.metadata);
+              onChange({
+                productId: key,
+                selectorValues: undefined,
+                selector: 'fixed',
+                operation: {
+                  operator: undefined,
+                },
+              });
+            }}
+            fieldNames={{ label: 'name', value: 'id' }}
+            filterOption={(input: string, option: any) =>
+              option.name.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }
+          />
+        </Col>
+        {props.value?.productId ? (
+          <Col span={12}>
+            <ItemGroup compact>
+              <Select
+                value={props.value?.selector}
+                options={selectorOptions}
+                fieldNames={{ label: 'name', value: 'id' }}
+                style={{ width: 120 }}
+                onSelect={(value: string) => {
+                  const _value = { ...props.value };
+                  _value.selector = value;
+                  onChange(_value);
+                }}
+              />
+              {props.value?.selector === 'all' && (
+                <AllDevice
+                  value={props.value?.selectorValues}
+                  productId={productId}
+                  onChange={(value) => {
+                    const _value = { ...props.value };
+                    _value.selectorValues = value;
+                    onChange(_value);
+                  }}
+                />
+              )}
+              {props.value?.selector === 'fixed' && (
+                <Device
+                  value={props.value?.selectorValues}
+                  productId={productId}
+                  onChange={(value) => {
+                    const _value = { ...props.value };
+                    _value.selectorValues = value;
+                    onChange(_value);
+                  }}
+                />
+              )}
+              {props.value?.selector === 'org' && (
+                <TreeSelect
+                  value={props.value?.selectorValues}
+                  treeData={orgTree}
+                  fieldNames={{ label: 'name', value: 'id' }}
+                  placeholder={'请选择部门'}
+                  style={{ width: '100%' }}
+                  onChange={(value) => {
+                    const _value = { ...props.value };
+                    _value.selectorValues = value;
+                    onChange(_value);
+                  }}
+                />
+              )}
+            </ItemGroup>
+          </Col>
+        ) : null}
+        <Col span={6}>
+          {functions.length || events.length || properties.length ? (
+            <Select
+              value={props.value?.operation?.operator}
+              placeholder={'请选择触发类型'}
+              options={operatorOptions}
+              style={{ width: '100%' }}
+              onChange={(value) => {
+                const _value = { ...props.value };
+                _value.operation!.operator = value;
+                onChange(_value);
+              }}
+            />
+          ) : null}
+        </Col>
+      </Row>
+      {props.value?.operation?.operator === OperatorEnum.invokeFunction ||
+      props.value?.operation?.operator === OperatorEnum.writeProperty ? (
+        <TimingTrigger
+          value={props.value?.operation?.timer}
+          onChange={(value) => {
+            const _value = { ...props.value };
+            _value.operation!.timer = value;
+            onChange(_value);
+          }}
+        />
+      ) : null}
+      {props.value?.operation?.operator === OperatorEnum.invokeFunction && (
+        <>
+          <Row gutter={24}>
+            <Col span={6}>
+              <Select
+                value={props.value?.operation?.functionId}
+                options={functions}
+                fieldNames={{
+                  label: 'name',
+                  value: 'id',
+                }}
+                style={{ width: '100%' }}
+                placeholder={'请选择功能'}
+                onSelect={(key: any, data: any) => {
+                  const _properties = data.valueType ? data.valueType.properties : data.inputs;
+                  const array = [];
+                  for (const datum of _properties) {
+                    array.push({
+                      id: datum.id,
+                      name: datum.name,
+                      type: datum.valueType ? datum.valueType.type : '-',
+                      format: datum.valueType ? datum.valueType.format : undefined,
+                      options: datum.valueType ? datum.valueType.elements : undefined,
+                      value: undefined,
+                    });
+                  }
+                  setFunctionItem(array);
+                  const _value = { ...props.value };
+                  _value.operation!.functionId = key;
+                  onChange(_value);
+                }}
+              />
+            </Col>
+            <Col span={18}>
+              <span style={{ lineHeight: '32px' }}>定时调用所选功能,功能返回值用于条件配置</span>
+            </Col>
+          </Row>
+          <Row>
+            <Col span={24}>
+              <FunctionCall
+                value={props.value?.operation?.functionParameters}
+                functionData={functionItem}
+                onChange={(value) => {
+                  const _value = { ...props.value };
+                  _value.operation!.functionParameters = value;
+                  onChange(_value);
+                }}
+              />
+            </Col>
+          </Row>
+        </>
+      )}
+      {props.value?.operation?.operator === OperatorEnum.writeProperty && (
+        <Row>
+          <Col span={24}>
+            <Operation
+              value={props.value?.operation?.writeProperties}
+              propertiesList={properties.filter((item) => {
+                if (item.expands) {
+                  return item.expands.type ? item.expands.type.includes('write') : false;
+                }
+                return false;
+              })}
+              onChange={(value) => {
+                const _value = { ...props.value };
+                _value.operation!.writeProperties = value;
+                onChange(_value);
+              }}
+            />
+          </Col>
+        </Row>
+      )}
+      {props.value?.operation?.operator === OperatorEnum.readProperty && (
+        <Row gutter={24}>
+          <Col span={6}>
+            <Select
+              value={props.value?.operation?.readProperties}
+              mode={'multiple'}
+              options={properties.filter((item) => {
+                if (item.expands) {
+                  return item.expands.type ? item.expands.type.includes('read') : false;
+                }
+                return false;
+              })}
+              maxTagCount={0}
+              maxTagPlaceholder={(values) => {
+                return (
+                  <div style={{ maxWidth: 'calc(100% - 8px)' }}>
+                    {values.map((item) => item.label).toString()}
+                  </div>
+                );
+              }}
+              placeholder={'请选择属性'}
+              style={{ width: '100%' }}
+              fieldNames={{ label: 'name', value: 'id' }}
+              onChange={(value) => {
+                if (props.value) {
+                  const _value = { ...props.value };
+                  _value!.operation!.readProperties = value;
+                  onChange(_value);
+                }
+              }}
+            />
+          </Col>
+          <Col span={18}>
+            <span style={{ lineHeight: '32px' }}>定时读取所选属性值,用于条件配置</span>
+          </Col>
+        </Row>
+      )}
+    </div>
+  );
+};

+ 92 - 79
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, Space, TreeSelect } from 'antd';
-import { TimingTrigger } from '@/pages/rule-engine/Scene/Save/components';
+import { Col, Form, Row, Select, TreeSelect } 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';
 import Device from '@/pages/rule-engine/Scene/Save/action/device/deviceModal';
@@ -137,98 +137,107 @@ export default observer((props: TriggerProps) => {
         setSelector(_device.selector);
       }
 
-      if (_device.operation && _device.operation.operator) {
+      if (_device.operation && 'operator' in _device.operation) {
         setOperation(_device.operation.operator);
+      } else {
+        setOperation(undefined);
       }
     }
   }, [props.triggerData]);
 
   return (
     <div className={classNames(props.className)}>
-      <Row>
-        <Col span={24}>
-          <Space>
-            <Form.Item name={['trigger', 'device', 'productId']}>
-              <Select
-                options={productList}
-                placeholder={'请选择产品'}
-                style={{ width: 220 }}
-                listHeight={220}
-                onChange={(key: any, node: any) => {
-                  props.form?.resetFields([['trigger', 'device', 'selector']]);
-                  props.form?.resetFields([['trigger', 'device', 'selectorValues']]);
-                  props.form?.resetFields([['trigger', 'device', 'operation', 'operator']]);
-                  props.form?.resetFields([['trigger', 'device', 'operation', 'operator']]);
-                  setOperation(undefined);
-                  productIdChange(key, node.metadata);
-                }}
-                fieldNames={{ label: 'name', value: 'id' }}
-              />
-            </Form.Item>
+      <Row gutter={24}>
+        <Col span={6}>
+          <Form.Item name={['trigger', 'device', 'productId']}>
+            <Select
+              options={productList}
+              placeholder={'请选择产品'}
+              style={{ width: '100%' }}
+              listHeight={220}
+              onChange={(key: any, node: any) => {
+                props.form?.resetFields([['trigger', 'device', 'selector']]);
+                props.form?.resetFields([['trigger', 'device', 'selectorValues']]);
+                props.form?.resetFields([['trigger', 'device', 'operation', 'operator']]);
+                productIdChange(key, node.metadata);
+              }}
+              fieldNames={{ label: 'name', value: 'id' }}
+            />
+          </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}
+                    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
-              name={['trigger', 'device', 'selector']}
-              initialValue={
-                props.triggerData && props.triggerData.device && props.triggerData.device.selector
-                  ? props.triggerData.device.selector
-                  : 'fixed'
-              }
+              name={['trigger', 'device', 'operation', 'operator']}
+              initialValue={undefined}
             >
               <Select
-                options={selectorOptions}
-                fieldNames={{ label: 'name', value: 'id' }}
-                style={{ width: 120 }}
+                placeholder={'请选择触发类型'}
+                options={operatorOptions}
+                style={{ width: '100%' }}
               />
             </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}
-                  fieldNames={{ label: 'name', value: 'id' }}
-                  placeholder={'请选择部门'}
-                  style={{ width: 300 }}
-                />
-              </Form.Item>
-            )}
-            {functions.length || events.length || properties.length ? (
-              <Form.Item name={['trigger', 'device', 'operation', 'operator']}>
-                <Select
-                  placeholder={'请选择触发类型'}
-                  options={operatorOptions}
-                  style={{ width: 140 }}
-                />
-              </Form.Item>
-            ) : null}
-          </Space>
+          ) : null}
         </Col>
-        {operation === OperatorEnum.invokeFunction || operation === OperatorEnum.writeProperty ? (
-          <Col>
-            <Form.Item name={['trigger', 'device', 'operation', 'timer']}>
-              <TimingTrigger />
-            </Form.Item>
-          </Col>
-        ) : null}
       </Row>
+      {operation === OperatorEnum.invokeFunction || operation === OperatorEnum.writeProperty ? (
+        <Form.Item name={['trigger', 'device', 'operation', 'timer']}>
+          <TimingTrigger />
+        </Form.Item>
+      ) : null}
       {operation === OperatorEnum.invokeFunction && (
         <>
-          <Row>
-            <Col span={12}>
+          <Row gutter={24}>
+            <Col span={6}>
               <Form.Item name={['trigger', 'device', 'operation', 'functionId']}>
                 <Select
+                  showSearch
                   options={functions}
                   fieldNames={{
                     label: 'name',
                     value: 'id',
                   }}
+                  style={{ width: '100%' }}
                   placeholder={'请选择功能'}
                   onSelect={(_: any, data: any) => {
                     const _properties = data.valueType ? data.valueType.properties : data.inputs;
@@ -245,13 +254,14 @@ export default observer((props: TriggerProps) => {
                     }
                     setFunctionItem(array);
                   }}
+                  filterOption={(input: string, option: any) =>
+                    option.name.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  }
                 />
               </Form.Item>
             </Col>
-            <Col span={8}>
-              <span style={{ margin: '0 12px', lineHeight: '32px' }}>
-                定时调用所选功能,功能返回值用于条件配置
-              </span>
+            <Col span={18}>
+              <span style={{ lineHeight: '32px' }}>定时调用所选功能,功能返回值用于条件配置</span>
             </Col>
           </Row>
           <Row>
@@ -280,8 +290,8 @@ export default observer((props: TriggerProps) => {
         </Row>
       )}
       {operation === OperatorEnum.readProperty && (
-        <Row>
-          <Col>
+        <Row gutter={24}>
+          <Col span={6}>
             <Form.Item name={['trigger', 'device', 'operation', 'readProperties']} noStyle>
               <Select
                 mode={'multiple'}
@@ -299,11 +309,14 @@ export default observer((props: TriggerProps) => {
                     </div>
                   );
                 }}
-                style={{ width: 300 }}
+                placeholder={'请选择属性'}
+                style={{ width: '100%' }}
                 fieldNames={{ label: 'name', value: 'id' }}
               />
             </Form.Item>
-            <span style={{ margin: '0 12px' }}>定时读取所选属性值,用于条件配置</span>
+          </Col>
+          <Col span={18}>
+            <span style={{ lineHeight: '32px' }}>定时读取所选属性值,用于条件配置</span>
           </Col>
         </Row>
       )}

+ 6 - 6
src/pages/rule-engine/Scene/Save/trigger/operation.tsx

@@ -55,8 +55,8 @@ export default (props: OperatorProps) => {
   }, [props.value, props.propertiesList]);
 
   return (
-    <Row>
-      <Col span={24}>
+    <Row gutter={24}>
+      <Col span={6}>
         <Select
           options={props.propertiesList || []}
           value={key}
@@ -64,7 +64,7 @@ export default (props: OperatorProps) => {
             label: 'name',
             value: 'id',
           }}
-          style={{ width: 300 }}
+          style={{ width: '100%' }}
           placeholder={'请选择属性'}
           onSelect={(id: any) => {
             // TODO 多选
@@ -73,9 +73,9 @@ export default (props: OperatorProps) => {
             }
           }}
         />
-        <span style={{ margin: '0 12px', lineHeight: '32px' }}>
-          定时调用所选属性,修改后的属性值用于条件配置
-        </span>
+      </Col>
+      <Col span={18}>
+        <span style={{ lineHeight: '32px' }}>定时调用所选属性,修改后的属性值用于条件配置</span>
       </Col>
       {key && (
         <Col span={24}>

+ 35 - 22
src/pages/rule-engine/Scene/index.tsx

@@ -2,7 +2,7 @@ import { PageContainer } from '@ant-design/pro-layout';
 import React, { useRef, useState } from 'react';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import type { SceneItem } from '@/pages/rule-engine/Scene/typings';
-import { Badge, message } from 'antd';
+import { message } from 'antd';
 import {
   DeleteOutlined,
   EditOutlined,
@@ -10,17 +10,22 @@ import {
   PlusOutlined,
   StopOutlined,
 } from '@ant-design/icons';
-import { useIntl } from '@@/plugin-locale/localeExports';
-import { PermissionButton, ProTableCard } from '@/components';
-import { statusMap } from '@/pages/device/Instance';
+import { BadgeStatus, PermissionButton, ProTableCard } from '@/components';
 import SearchComponent from '@/components/SearchComponent';
 import SceneCard from '@/components/ProTableCard/CardItems/scene';
 import Service from './service';
-import { useHistory } from 'umi';
+import { useHistory, useIntl } from 'umi';
 import { getMenuPathByCode } from '@/utils/menu';
+import { StatusColorEnum } from '@/components/BadgeStatus';
 
 export const service = new Service('scene');
 
+enum TriggerWayType {
+  manual = '手动触发',
+  timer = '定时触发',
+  device = '设备触发',
+}
+
 const Scene = () => {
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
@@ -36,7 +41,7 @@ const Scene = () => {
         style={{ padding: 0 }}
         isPermission={permission.update}
         tooltip={
-          type !== 'table'
+          type === 'table'
             ? {
                 title: intl.formatMessage({
                   id: 'pages.data.option.edit',
@@ -96,7 +101,7 @@ const Scene = () => {
           },
         }}
         tooltip={
-          type !== 'table'
+          type === 'table'
             ? {
                 title: intl.formatMessage({
                   id: `pages.data.option.${
@@ -131,7 +136,7 @@ const Scene = () => {
           onConfirm: async () => {},
         }}
         tooltip={
-          type !== 'table'
+          type === 'table'
             ? {
                 title: intl.formatMessage({
                   id: 'pages.device.instance.deleteTip',
@@ -160,14 +165,16 @@ const Scene = () => {
       }),
     },
     {
-      dataIndex: 'triggers',
+      dataIndex: 'triggerType',
       title: intl.formatMessage({
         id: 'pages.ruleEngine.scene.triggers',
         defaultMessage: '触发方式',
       }),
+      width: 120,
+      renderText: (record) => TriggerWayType[record],
     },
     {
-      dataIndex: 'describe',
+      dataIndex: 'description',
       title: intl.formatMessage({
         id: 'pages.system.description',
         defaultMessage: '说明',
@@ -182,21 +189,27 @@ const Scene = () => {
       width: '90px',
       valueType: 'select',
       renderText: (record) =>
-        record ? <Badge status={statusMap.get(record.value)} text={record.text} /> : '',
+        record ? (
+          <BadgeStatus
+            status={record.value}
+            text={record.text}
+            statusNames={{
+              started: StatusColorEnum.processing,
+              disable: StatusColorEnum.error,
+              notActive: StatusColorEnum.warning,
+            }}
+          />
+        ) : (
+          ''
+        ),
       valueEnum: {
-        offline: {
-          text: intl.formatMessage({
-            id: 'pages.device.instance.status.offLine',
-            defaultMessage: '离线',
-          }),
+        disable: {
+          text: '禁用',
           status: 'offline',
         },
-        online: {
-          text: intl.formatMessage({
-            id: 'pages.device.instance.status.onLine',
-            defaultMessage: '在线',
-          }),
-          status: 'online',
+        started: {
+          text: '正常',
+          status: 'started',
         },
       },
     },

+ 2 - 1
src/pages/rule-engine/Scene/typings.d.ts

@@ -17,7 +17,8 @@ interface SceneItem {
   triggers: Trigger[];
   id: string;
   name: string;
-  describe: string;
+  description: string;
+  triggerType: string;
 }
 
 type TriggerType = {