Explorar o código

fix: 场景联动添加右侧说明文档

xieyonghong %!s(int64=3) %!d(string=hai) anos
pai
achega
da58c3d629

+ 4 - 2
src/pages/Northbound/DuerOS/Detail/index.tsx

@@ -61,12 +61,13 @@ const Save = () => {
     return _productTypes?.find((item: any) => item.id === _id);
   };
 
-  const getProduct = () =>
-    service.getProduct().then((resp) => {
+  const getProduct = (f: Field) => {
+    return service.getProduct(f?.value).then((resp) => {
       const _temp = resp.result.map((item: any) => ({ label: item.name, value: item.id }));
       Store.set('product-list', _temp);
       return _temp;
     });
+  };
 
   const getTypes = () =>
     service.getTypes().then((resp) => {
@@ -171,6 +172,7 @@ const Save = () => {
             switch (value) {
               case 'date':
                 format.setComponent(DatePicker);
+                format.value = new Date();
                 break;
               case 'string':
                 format.setComponent(Input);

+ 11 - 8
src/pages/Northbound/DuerOS/service.ts

@@ -9,19 +9,22 @@ class Service extends BaseService<DuerOSItem> {
       method: 'GET',
     });
 
-  public getProduct = () =>
-    request(`/${SystemConst.API_BASE}/device-product/_query/no-paging`, {
+  public getProduct = (id?: string) => {
+    const defaultData = {
+      column: 'id$dueros-product$not',
+      value: 1,
+    };
+
+    const data = id ? [defaultData, { column: 'id', type: 'or', value: id }] : [defaultData];
+
+    return request(`/${SystemConst.API_BASE}/device-product/_query/no-paging`, {
       method: 'POST',
       data: {
         paging: false,
-        terms: [
-          {
-            column: 'id$dueros-product$not',
-            value: 1,
-          },
-        ],
+        terms: data,
       },
     });
+  };
 
   public changeState = (id: string, state: 'enable' | 'disable') =>
     request(`/${SystemConst.API_BASE}/dueros/product/${id}/_${state}`, { method: 'POST' });

+ 52 - 0
src/pages/rule-engine/Scene/Save/Explanation.less

@@ -0,0 +1,52 @@
+@import '~antd/es/style/themes/default.less';
+
+.doc {
+  padding: 24px;
+  color: rgba(#000, 0.8);
+  font-size: 14px;
+  background-color: #fafafa;
+
+  .url {
+    padding: 8px 16px;
+    color: #2f54eb;
+    background-color: rgba(#a7bdf7, 0.2);
+  }
+
+  h1 {
+    margin: 16px 0;
+    color: rgba(#000, 0.85);
+    font-weight: bold;
+    font-size: 14px;
+
+    &:first-child {
+      margin-top: 0;
+    }
+  }
+
+  h2 {
+    margin: 6px 0;
+    color: rgba(0, 0, 0, 0.8);
+    font-size: 14px;
+  }
+
+  .image {
+    margin: 16px 0;
+  }
+
+  div {
+    margin: 12px 0;
+    border-radius: 2px;
+  }
+}
+
+.notice {
+  padding: 8px 12px;
+  color: rgba(#000, 0.8);
+  background-color: #f0f5ff;
+  border: 1px solid @primary-color;
+
+  > span {
+    padding-right: 12px;
+    color: @primary-color;
+  }
+}

+ 79 - 0
src/pages/rule-engine/Scene/Save/Explanation.tsx

@@ -0,0 +1,79 @@
+// 说明文档
+import styles from './Explanation.less';
+import { ExclamationCircleFilled } from '@ant-design/icons';
+
+export default () => {
+  return (
+    <div className={styles.doc} style={{ marginLeft: 12 }}>
+      <h1>1.概述</h1>
+      <div>
+        场景联动是规则引擎中,一种业务逻辑的可视化编程方式,您可以通过可视化的方式定义设备之间联动规则。当触发条件指定的事件或属性变化事件发生时,系统通过判断执行条件是否已满足,来决定是否执行规则中定义的执行动作。如果满足执行条件,则执行定义的执行动作;反之则不执行。
+      </div>
+      <div>场景联动的规则可用于告警规则的引用,达到触发条件时将生成对应的告警记录。</div>
+      <h1>2.配置说明</h1>
+      <h2>1、触发方式</h2>
+      <div>
+        包含<b>手动触发</b>、<b>定时触发</b>、<b>设备触发</b>3种方式
+      </div>
+      <h3>a. 手动触发</h3>
+      <div>适用于第三方平台向物联网平台下发指令控制设备。</div>
+      <div>
+        <b>例如:</b>用户通过智能家居APP开启或关闭设备。
+      </div>
+
+      <h3>b. 定时触发</h3>
+      <div>
+        <b>例如:</b>每天早上9点打开空调,并将空调开到26度。
+      </div>
+
+      <h3>c. 设备触发</h3>
+      <div>
+        适用于多个不同设备间执行动作的联动。选择具体设备时支持指定产品下属的固定设备、全部设备或选择产品下归属于具体部门的设备
+      </div>
+      <div>
+        <b>例如:</b>打开综合办部门房间门的时候,打开电灯、空调。
+      </div>
+
+      <h2>2、触发条件</h2>
+      <div>
+        选择设备触发时,可对设备触发条件进行详细配置。触发条件支持多组条件,条件之间支持并且/或者的关系配置。每一个分组内可以添加多个条件,条件之间支持并且/或者的关系配置。
+      </div>
+      <div>
+        触发条件支持以属性指标(需在产品物模型tab页进行属性指标定义,定义后在对应设备的运行状态页填写阈值)作为判断阈值。
+      </div>
+
+      <h2>3、执行动作</h2>
+      <div>支持消息通知、设备输出、延迟执行3种方式,延迟执行只在执行动作为串行时显示。</div>
+      <h3>a.消息通知</h3>
+      <div>
+        通过系统外的渠道给相关人员发送对应的通知模板内容。若钉钉消息、微信企业消息的通知模板中没有定义固定通知人,可在当前页面进行配置。同时“收信人”字段支持同步平台与微信、钉钉用户映射关系(对应类型的通知配置列表页配置),实现选择平台用户即可通知到对应的微信、钉钉用户。
+      </div>
+      <div className={styles.notice}>
+        <ExclamationCircleFilled />
+        注:只有设备触发时,收信人可选择关系用户。
+      </div>
+      <div>
+        <b>例如:</b>
+        设备下线时,以微信或钉钉的方式通知该“设备负责人”(需管理员先在“关系配置”菜单中定义设备与用户的关系,然后到设备实例信息页面填写值)。
+      </div>
+
+      <h3>b.设备输出</h3>
+      <div>
+        联动其他设备进行功能调用、读取属性、设置属性操作。
+        选择设备时支持固定设备、按标签(需在设备物模型中自定义配置)、按关系(需管理员先在“关系配置”菜单中定义设备与用户的关系,然后到设备实例信息页面填写值)3种方式。
+      </div>
+      <div className={styles.notice}>
+        <ExclamationCircleFilled />
+        注:只有设备触发时,被联动设备支持按关系选择。
+      </div>
+      <div>
+        <b>例如:</b>
+        {`温度传感器>30度时,与该设备相同“设备负责人”(自定义关系名)的空调执行打开操作。即可实现张三的温度传感器>30度时自动打开张三的空调,李四的温度传感器>30度时自动打开李四的空调。`}
+      </div>
+
+      <h3>c.延迟执行</h3>
+      <div>设置延时时间后,设备执行动作将保持当前状态,时间结束后再执行下一条动作。</div>
+      <div>房间门打开时,摄像头开启录像,延迟执行5s后,停止录像。</div>
+    </div>
+  );
+};

+ 202 - 171
src/pages/rule-engine/Scene/Save/index.tsx

@@ -1,5 +1,17 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import { Button, Card, Form, Input, InputNumber, Radio, Space, Switch, Tooltip } from 'antd';
+import {
+  Button,
+  Card,
+  Col,
+  Form,
+  Input,
+  InputNumber,
+  Radio,
+  Row,
+  Space,
+  Switch,
+  Tooltip,
+} from 'antd';
 import { useIntl, useLocation } from 'umi';
 import { useCallback, useEffect, useRef, useState } from 'react';
 import { PermissionButton, TitleComponent } from '@/components';
@@ -14,6 +26,7 @@ import './index.less';
 import { model } from '@formily/reactive';
 import type { FormModelType } from '@/pages/rule-engine/Scene/typings';
 import { onlyMessage } from '@/utils/util';
+import Explanation from './Explanation';
 
 type ShakeLimitType = {
   enabled: boolean;
@@ -196,181 +209,199 @@ export default () => {
   return (
     <PageContainer>
       <Card>
-        <Form
-          scrollToFirstError={{
-            behavior: 'smooth',
-            block: 'end',
-          }}
-          form={form}
-          colon={false}
-          name="basicForm"
-          layout={'vertical'}
-          preserve={false}
-          className={'scene-save'}
-          initialValues={{
-            actions: [undefined],
-          }}
-          onValuesChange={(changeValue, allValues) => {
-            if (changeValue.trigger) {
-              if (changeValue.trigger.device) {
-                if (
-                  changeValue.trigger.device.productId ||
-                  changeValue.trigger.device.selectorValues ||
-                  (changeValue.trigger.device.operation &&
-                    hasKeyInObject(
-                      ['operator', 'eventId', 'functionId'],
-                      changeValue.trigger.device.operation,
-                    ))
-                ) {
-                  setTriggerValue([]);
-                  setRequestParams({ trigger: allValues.trigger });
-                  setTriggerDatas(allValues.trigger);
-                }
+        <Row>
+          <Col span={16}>
+            <Form
+              scrollToFirstError={{
+                behavior: 'smooth',
+                block: 'end',
+              }}
+              form={form}
+              colon={false}
+              name="basicForm"
+              layout={'vertical'}
+              preserve={false}
+              className={'scene-save'}
+              initialValues={{
+                actions: [undefined],
+              }}
+              onValuesChange={(changeValue, allValues) => {
+                if (changeValue.trigger) {
+                  if (changeValue.trigger.device) {
+                    if (
+                      changeValue.trigger.device.productId ||
+                      changeValue.trigger.device.selectorValues ||
+                      (changeValue.trigger.device.operation &&
+                        hasKeyInObject(
+                          ['operator', 'eventId', 'functionId'],
+                          changeValue.trigger.device.operation,
+                        ))
+                    ) {
+                      setTriggerValue([]);
+                      setRequestParams({ trigger: allValues.trigger });
+                      setTriggerDatas(allValues.trigger);
+                    }
 
-                if (
-                  hasKeyInObject(['productId'], changeValue.trigger.device) ||
-                  (changeValue.trigger.device.operation &&
-                    hasKeyInObject(
-                      ['operator', 'eventId', 'functionId'],
-                      changeValue.trigger.device.operation,
-                    ))
-                ) {
-                  setActionParams({ trigger: allValues.trigger }); // 用于内置参数请求
+                    if (
+                      hasKeyInObject(['productId'], changeValue.trigger.device) ||
+                      (changeValue.trigger.device.operation &&
+                        hasKeyInObject(
+                          ['operator', 'eventId', 'functionId'],
+                          changeValue.trigger.device.operation,
+                        ))
+                    ) {
+                      setActionParams({ trigger: allValues.trigger }); // 用于内置参数请求
+                    }
+                  } else if (['timer', 'manual'].includes(changeValue.trigger.type)) {
+                    setActionParams({ trigger: allValues.trigger }); // 用于内置参数请求
+                  }
                 }
-              } else if (['timer', 'manual'].includes(changeValue.trigger.type)) {
-                setActionParams({ trigger: allValues.trigger }); // 用于内置参数请求
-              }
-            }
 
-            if (allValues.actions) {
-              setActionsData(allValues.actions);
-            }
-            FormModel = { ...allValues };
-          }}
-        >
-          <Form.Item
-            name={'name'}
-            label={<TitleComponent data={'名称'} style={{ margin: 0 }} />}
-            rules={[
-              { required: true, message: '请输入名称' },
-              {
-                max: 64,
-                message: intl.formatMessage({
-                  id: 'pages.form.tip.max64',
-                  defaultMessage: '最多输入64个字符',
-                }),
-              },
-            ]}
-          >
-            <Input placeholder={'请输入名称'} />
-          </Form.Item>
-          <Form.Item label={<TitleComponent data={'触发方式'} style={{ margin: 0 }} />} required>
-            <Form.Item
-              name={['trigger', 'type']}
-              rules={[{ required: true, message: '请选择触发方式' }]}
+                if (allValues.actions) {
+                  setActionsData(allValues.actions);
+                }
+                FormModel = { ...allValues };
+              }}
             >
-              <TriggerWay onSelect={setTriggerType} disabled={isEdit} />
-            </Form.Item>
-            {triggerType === TriggerWayType.timing && (
-              <TimingTrigger name={['trigger']} form={form} className={'trigger-type-content'} />
-            )}
-            {triggerType === TriggerWayType.device && (
-              <TriggerDevice value={triggerDatas} className={'trigger-type-content'} form={form} />
-            )}
-          </Form.Item>
-          {triggerType === TriggerWayType.device &&
-          requestParams &&
-          requestParams.trigger?.device?.productId ? (
-            <Form.Item label={AntiShake}>
-              <TriggerTerm ref={triggerRef} params={requestParams} value={triggerValue} />
-            </Form.Item>
-          ) : null}
-          <Form.Item hidden name={'parallel'} initialValue={true}>
-            <Input />
-          </Form.Item>
-          <Form.Item
-            label={
-              <Space>
-                <TitleComponent data={<span>执行动作</span>} style={{ margin: 0 }} />
-                <Tooltip
-                  title={
-                    <div>
-                      <div>串行:按顺序依次执行动作</div>
-                      <div>并行:同时执行所有动作</div>
-                    </div>
-                  }
+              <Form.Item
+                name={'name'}
+                label={<TitleComponent data={'名称'} style={{ margin: 0 }} />}
+                rules={[
+                  { required: true, message: '请输入名称' },
+                  {
+                    max: 64,
+                    message: intl.formatMessage({
+                      id: 'pages.form.tip.max64',
+                      defaultMessage: '最多输入64个字符',
+                    }),
+                  },
+                ]}
+              >
+                <Input placeholder={'请输入名称'} />
+              </Form.Item>
+              <Form.Item
+                label={<TitleComponent data={'触发方式'} style={{ margin: 0 }} />}
+                required
+              >
+                <Form.Item
+                  name={['trigger', 'type']}
+                  rules={[{ required: true, message: '请选择触发方式' }]}
                 >
-                  <QuestionCircleOutlined style={{ margin: '0 8px', fontSize: 14 }} />
-                </Tooltip>
-                <Radio.Group
-                  value={parallel}
-                  options={[
-                    { label: '串行', value: false },
-                    { label: '并行', value: true },
-                  ]}
-                  optionType="button"
-                  onChange={(e) => {
-                    setParallel(e.target.value);
-                    form.setFieldsValue({ parallel: e.target.value });
-                  }}
-                ></Radio.Group>
-              </Space>
-            }
-          >
-            <Form.List name="actions">
-              {(fields, { add, remove }, { errors }) => (
-                <>
-                  <div className={'scene-actions'} style={{ paddingBottom: 24 }}>
-                    {fields.map(({ key, name, ...restField }) => (
-                      <ActionItems
-                        key={key}
-                        form={form}
-                        restField={restField}
-                        name={name}
-                        trigger={actionParams}
-                        triggerType={triggerType}
-                        onRemove={() => remove(name)}
-                        actionItemData={actionsData.length && actionsData[name]}
-                        parallel={parallel}
-                      />
-                    ))}
-                    <Form.Item noStyle>
-                      <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
-                        新增
-                      </Button>
-                    </Form.Item>
-                  </div>
-                  <Form.ErrorList errors={errors} />
-                </>
-              )}
-            </Form.List>
-          </Form.Item>
-          <Form.Item
-            label={<TitleComponent data={'说明'} style={{ margin: 0 }} />}
-            name={'description'}
-          >
-            <Input.TextArea showCount maxLength={200} placeholder={'请输入说明'} rows={4} />
-          </Form.Item>
-          {/*{triggerType === TriggerWayType.device &&*/}
-          {/*requestParams &&*/}
-          {/*requestParams.trigger?.device?.productId ? (*/}
-          {/*  <Form.Item hidden name={['trigger','shakeLimit']}>*/}
-          {/*    <Input />*/}
-          {/*  </Form.Item>*/}
-          {/*) : null}*/}
-          <Form.Item hidden name={'id'}>
-            <Input />
-          </Form.Item>
-          <PermissionButton
-            isPermission={getOtherPermission(['add', 'update'])}
-            onClick={saveData}
-            type={'primary'}
-            loading={loading}
-            htmlType="submit"
-          >
-            保存
-          </PermissionButton>
-        </Form>
+                  <TriggerWay onSelect={setTriggerType} disabled={isEdit} />
+                </Form.Item>
+                {triggerType === TriggerWayType.timing && (
+                  <TimingTrigger
+                    name={['trigger']}
+                    form={form}
+                    className={'trigger-type-content'}
+                  />
+                )}
+                {triggerType === TriggerWayType.device && (
+                  <TriggerDevice
+                    value={triggerDatas}
+                    className={'trigger-type-content'}
+                    form={form}
+                  />
+                )}
+              </Form.Item>
+              {triggerType === TriggerWayType.device &&
+              requestParams &&
+              requestParams.trigger?.device?.productId ? (
+                <Form.Item label={AntiShake}>
+                  <TriggerTerm ref={triggerRef} params={requestParams} value={triggerValue} />
+                </Form.Item>
+              ) : null}
+              <Form.Item hidden name={'parallel'} initialValue={true}>
+                <Input />
+              </Form.Item>
+              <Form.Item
+                label={
+                  <Space>
+                    <TitleComponent data={<span>执行动作</span>} style={{ margin: 0 }} />
+                    <Tooltip
+                      title={
+                        <div>
+                          <div>串行:按顺序依次执行动作</div>
+                          <div>并行:同时执行所有动作</div>
+                        </div>
+                      }
+                    >
+                      <QuestionCircleOutlined style={{ margin: '0 8px', fontSize: 14 }} />
+                    </Tooltip>
+                    <Radio.Group
+                      value={parallel}
+                      options={[
+                        { label: '串行', value: false },
+                        { label: '并行', value: true },
+                      ]}
+                      optionType="button"
+                      onChange={(e) => {
+                        setParallel(e.target.value);
+                        form.setFieldsValue({ parallel: e.target.value });
+                      }}
+                    ></Radio.Group>
+                  </Space>
+                }
+              >
+                <Form.List name="actions">
+                  {(fields, { add, remove }, { errors }) => (
+                    <>
+                      <div className={'scene-actions'} style={{ paddingBottom: 24 }}>
+                        {fields.map(({ key, name, ...restField }) => (
+                          <ActionItems
+                            key={key}
+                            form={form}
+                            restField={restField}
+                            name={name}
+                            trigger={actionParams}
+                            triggerType={triggerType}
+                            onRemove={() => remove(name)}
+                            actionItemData={actionsData.length && actionsData[name]}
+                            parallel={parallel}
+                          />
+                        ))}
+                        <Form.Item noStyle>
+                          <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
+                            新增
+                          </Button>
+                        </Form.Item>
+                      </div>
+                      <Form.ErrorList errors={errors} />
+                    </>
+                  )}
+                </Form.List>
+              </Form.Item>
+              <Form.Item
+                label={<TitleComponent data={'说明'} style={{ margin: 0 }} />}
+                name={'description'}
+              >
+                <Input.TextArea showCount maxLength={200} placeholder={'请输入说明'} rows={4} />
+              </Form.Item>
+              {/*{triggerType === TriggerWayType.device &&*/}
+              {/*requestParams &&*/}
+              {/*requestParams.trigger?.device?.productId ? (*/}
+              {/*  <Form.Item hidden name={['trigger','shakeLimit']}>*/}
+              {/*    <Input />*/}
+              {/*  </Form.Item>*/}
+              {/*) : null}*/}
+              <Form.Item hidden name={'id'}>
+                <Input />
+              </Form.Item>
+              <PermissionButton
+                isPermission={getOtherPermission(['add', 'update'])}
+                onClick={saveData}
+                type={'primary'}
+                loading={loading}
+                htmlType="submit"
+              >
+                保存
+              </PermissionButton>
+            </Form>
+          </Col>
+          <Col span={8}>
+            <Explanation />
+          </Col>
+        </Row>
       </Card>
     </PageContainer>
   );