浏览代码

fix: 告警绑定场景联动

100011797 3 年之前
父节点
当前提交
d9db43f362

+ 58 - 11
src/components/ProTableCard/CardItems/scene.tsx

@@ -4,26 +4,77 @@ import { Ellipsis, TableCard } from '@/components';
 import '@/style/common.less';
 import '../index.less';
 import type { SceneItem } from '@/pages/rule-engine/Scene/typings';
+import { CheckOutlined } from '@ant-design/icons';
 
-export interface DeviceCardProps extends SceneItem {
-  tools: React.ReactNode[];
+const defaultImage = require('/public/images/scene.png');
+
+// @ts-ignore
+export interface SceneCardProps extends SceneItem {
   detail?: React.ReactNode;
+  actions?: React.ReactNode[];
+  avatarSize?: number;
+  className?: string;
+  onUnBind?: (e: any) => void;
+  showBindBtn?: boolean;
+  cardType?: 'bind' | 'unbind';
+  showTool?: boolean;
+  onClick?: () => void;
 }
 
-const defaultImage = require('/public/images/scene.png');
-
 enum TriggerWayType {
   manual = '手动触发',
   timer = '定时触发',
   device = '设备触发',
 }
 
-export default (props: DeviceCardProps) => {
+export const ExtraSceneCard = (props: SceneCardProps) => {
+  return (
+    <TableCard
+      status={props.state.value}
+      statusText={props.state.text}
+      statusNames={{
+        started: StatusColorEnum.success,
+        disable: StatusColorEnum.error,
+        notActive: StatusColorEnum.warning,
+      }}
+      showTool={props.showTool}
+      showMask={false}
+      actions={props.actions}
+      onClick={props.onClick}
+      className={props.className}
+    >
+      <div className={'pro-table-card-item context-access'}>
+        <div className={'card-item-avatar'}>
+          <img width={88} height={88} src={defaultImage} alt={''} />
+        </div>
+        <div className={'card-item-body'}>
+          <div className={'card-item-header'}>
+            <Ellipsis title={props.name} titleClassName={'card-item-header-name'} />
+          </div>
+          <div>
+            <div>
+              <label>触发方式</label>
+              <Ellipsis title={TriggerWayType[props.triggerType]} />
+            </div>
+          </div>
+        </div>
+      </div>
+      <div className={'checked-icon'}>
+        <div>
+          <CheckOutlined />
+        </div>
+      </div>
+    </TableCard>
+  );
+};
+
+export default (props: SceneCardProps) => {
   return (
     <TableCard
-      // showMask={false}
+      showMask={false}
       detail={props.detail}
-      actions={props.tools}
+      showTool={props.showTool}
+      actions={props.actions}
       status={props.state.value}
       statusText={props.state.text}
       statusNames={{
@@ -45,10 +96,6 @@ export default (props: DeviceCardProps) => {
               <label>触发方式</label>
               <Ellipsis title={TriggerWayType[props.triggerType]} />
             </div>
-            {/*<div>*/}
-            {/*  <label>说明</label>*/}
-            {/*  <Ellipsis title={props.description} />*/}
-            {/*</div>*/}
           </div>
         </div>
       </div>

src/pages/rule-engine/Alarm/Configuration/Save/index.less → src/pages/rule-engine/Alarm/Configuration/Save/Base/index.less


+ 212 - 0
src/pages/rule-engine/Alarm/Configuration/Save/Base/index.tsx

@@ -0,0 +1,212 @@
+import Service from '@/pages/rule-engine/Alarm/Configuration/service';
+import { Typography } from 'antd';
+import { service as ConfigService } from '@/pages/rule-engine/Alarm/Config';
+import { useMemo } from 'react';
+import { createForm, onFormInit } from '@formily/core';
+import { createSchemaField } from '@formily/react';
+import { Form, FormButtonGroup, FormGrid, FormItem, Input, Radio, Select } from '@formily/antd';
+import { onlyMessage, useAsyncDataSource } from '@/utils/util';
+import { PermissionButton } from '@/components';
+import { ISchema } from '@formily/json-schema';
+import styles from '@/pages/rule-engine/Alarm/Configuration/Save/Base/index.less';
+import useLocation from '@/hooks/route/useLocation';
+import { getMenuPathByCode } from '@/utils/menu';
+import { useHistory } from 'umi';
+
+const alarm1 = require('/public/images/alarm/alarm1.png');
+const alarm2 = require('/public/images/alarm/alarm2.png');
+const alarm3 = require('/public/images/alarm/alarm3.png');
+const alarm4 = require('/public/images/alarm/alarm4.png');
+const alarm5 = require('/public/images/alarm/alarm5.png');
+
+const service = new Service('alarm/config');
+
+const createImageLabel = (image: string, text: string) => {
+  return (
+    <div style={{ textAlign: 'center', marginTop: 10, fontSize: '15px', width: '90px' }}>
+      <img alt="" height="40px" src={image} />
+      <Typography.Text style={{ maxWidth: '50px', marginBottom: 10 }} ellipsis={{ tooltip: text }}>
+        {text}
+      </Typography.Text>
+    </div>
+  );
+};
+
+export default () => {
+  const { getOtherPermission } = PermissionButton.usePermission('rule-engine/Alarm/Configuration');
+  const history = useHistory();
+  const location = useLocation();
+  const id = location?.query?.id || '';
+
+  const LevelMap = {
+    1: alarm1,
+    2: alarm2,
+    3: alarm3,
+    4: alarm4,
+    5: alarm5,
+  };
+
+  const getLevel = () => {
+    return ConfigService.queryLevel().then((resp) => {
+      if (resp.status === 200) {
+        return resp.result?.levels
+          ?.filter((i: any) => i?.level && i?.title)
+          .map((item: { level: number; title: string }) => ({
+            label: createImageLabel(LevelMap[item.level], item.title),
+            value: item.level,
+          }));
+      }
+    });
+  };
+
+  const form = useMemo(
+    () =>
+      createForm({
+        initialValues: {},
+        validateFirst: true,
+        effects() {
+          onFormInit(async (form1) => {
+            if (!id) return;
+            const resp = await service.detail(id);
+            form1.setInitialValues({ ...resp.result });
+          });
+        },
+      }),
+    [id],
+  );
+
+  const getSupports = () => service.getTargetTypes();
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Input,
+      Select,
+      FormGrid,
+      Radio,
+    },
+  });
+
+  const handleSave = async () => {
+    const data: any = await form.submit();
+    const resp: any = await service.update({ ...data });
+    if (resp.status === 200) {
+      onlyMessage('操作成功');
+      const url = getMenuPathByCode('rule-engine/Alarm/Configuration');
+      history.push(`${url}`);
+    }
+  };
+
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      layout: {
+        type: 'void',
+        'x-decorator': 'FormGrid',
+        'x-decorator-props': {
+          maxColumns: 2,
+          minColumns: 2,
+          columnGap: 24,
+        },
+        properties: {
+          name: {
+            title: '名称',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            'x-component-props': {
+              placeholder: '请输入名称',
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入名称',
+              },
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+            ],
+          },
+          targetType: {
+            title: '类型',
+            'x-decorator': 'FormItem',
+            'x-component': 'Select',
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            required: true,
+            'x-reactions': '{{useAsyncDataSource(getSupports)}}',
+            'x-component-props': {
+              placeholder: '请选择类型',
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择类型',
+              },
+            ],
+          },
+        },
+      },
+      level: {
+        title: '级别',
+        'x-decorator': 'FormItem',
+        'x-component': 'Radio.Group',
+        'x-component-props': {
+          optionType: 'button',
+          placeholder: '请选择类型',
+        },
+        required: true,
+        'x-reactions': '{{useAsyncDataSource(getLevel)}}',
+        'x-decorator-props': {
+          gridSpan: 1,
+        },
+        'x-validator': [
+          {
+            required: true,
+            message: '请选择级别',
+          },
+        ],
+      },
+      description: {
+        title: '说明',
+        'x-decorator': 'FormItem',
+        'x-component': 'Input.TextArea',
+        'x-decorator-props': {
+          gridSpan: 1,
+        },
+        'x-component-props': {
+          placeholder: '请输入说明',
+        },
+        'x-validator': [
+          {
+            max: 200,
+            message: '最多可输入200个字符',
+          },
+        ],
+      },
+    },
+  };
+
+  return (
+    <div>
+      <Form className={styles.form} form={form} layout="vertical">
+        <SchemaField schema={schema} scope={{ useAsyncDataSource, getSupports, getLevel }} />
+        <FormButtonGroup.Sticky>
+          <FormButtonGroup.FormItem>
+            <PermissionButton
+              type="primary"
+              isPermission={getOtherPermission(['add', 'update'])}
+              onClick={() => handleSave()}
+            >
+              保存
+            </PermissionButton>
+          </FormButtonGroup.FormItem>
+        </FormButtonGroup.Sticky>
+      </Form>
+    </div>
+  );
+};

+ 10 - 0
src/pages/rule-engine/Alarm/Configuration/Save/Log/index.tsx

@@ -0,0 +1,10 @@
+import TabComponent from '@/pages/rule-engine/Alarm/Log/TabComponent';
+import useLocation from '@/hooks/route/useLocation';
+import { Empty } from '@/components';
+
+export default () => {
+  const location = useLocation();
+  const id = location?.query?.id || '';
+
+  return <div>{id ? <TabComponent type={'detail'} id={id} /> : <Empty />}</div>;
+};

+ 167 - 0
src/pages/rule-engine/Alarm/Configuration/Save/Scene/Save/index.tsx

@@ -0,0 +1,167 @@
+import { Modal, ProTableCard } from '@/components';
+import SearchComponent from '@/components/SearchComponent';
+import { SceneItem } from '@/pages/rule-engine/Scene/typings';
+import { ExtraSceneCard } from '@/components/ProTableCard/CardItems/scene';
+import { service as sceneService } from '@/pages/rule-engine/Scene';
+import { ActionType, ProColumns } from '@jetlinks/pro-table';
+import { useRef, useState } from 'react';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import { onlyMessage } from '@/utils/util';
+
+interface Props {
+  close: () => void;
+  id: string;
+}
+
+export default (props: Props) => {
+  const { id } = props;
+  const intl = useIntl();
+  const actionRef = useRef<ActionType>();
+  const [searchParams, setSearchParams] = useState<any>({});
+  const [selectKeys, setSelectKeys] = useState<any[]>([]);
+
+  const columns: ProColumns<SceneItem>[] = [
+    {
+      dataIndex: 'name',
+      fixed: 'left',
+      title: intl.formatMessage({
+        id: 'pages.table.name',
+        defaultMessage: '名称',
+      }),
+    },
+    {
+      dataIndex: 'triggerType',
+      title: intl.formatMessage({
+        id: 'pages.ruleEngine.scene.triggers',
+        defaultMessage: '触发方式',
+      }),
+      valueType: 'select',
+      valueEnum: {
+        manual: {
+          text: '手动触发',
+          status: 'manual',
+        },
+        timer: {
+          text: '定时触发',
+          status: 'timer',
+        },
+        device: {
+          text: '设备触发',
+          status: 'device',
+        },
+      },
+    },
+    {
+      dataIndex: 'state',
+      title: intl.formatMessage({
+        id: 'pages.searchTable.titleStatus',
+        defaultMessage: '状态',
+      }),
+      valueType: 'select',
+      valueEnum: {
+        started: {
+          text: '正常',
+          status: 'started',
+        },
+        disable: {
+          text: '禁用',
+          status: 'disable',
+        },
+      },
+    },
+  ];
+
+  return (
+    <Modal
+      title={'新增'}
+      maskClosable={false}
+      open
+      onCancel={() => {
+        props.close();
+      }}
+      onOk={async () => {
+        if (selectKeys.length > 0) {
+          props.close();
+        } else {
+          onlyMessage('请选择至少一条数据', 'error');
+        }
+      }}
+      width={1000}
+    >
+      <SearchComponent<SceneItem>
+        field={columns}
+        enableSave={false}
+        model={'simple'}
+        target={'alarm-scene-unbind'}
+        onSearch={(data) => {
+          actionRef.current?.reset?.();
+          setSearchParams(data);
+        }}
+      />
+      <div
+        style={{
+          height: 'calc(100vh - 400px)',
+          overflowY: 'auto',
+        }}
+      >
+        <ProTableCard<SceneItem>
+          actionRef={actionRef}
+          columns={columns}
+          rowKey="id"
+          search={false}
+          onlyCard={true}
+          gridColumn={2}
+          columnEmptyText={''}
+          // @ts-ignore
+          request={(params) => {
+            if (!id) {
+              return {
+                code: 200,
+                result: {
+                  data: [],
+                  pageIndex: 0,
+                  pageSize: 0,
+                  total: 0,
+                },
+                status: 200,
+              };
+            }
+            return sceneService.query({
+              ...params,
+              terms: [
+                ...(params?.terms || []),
+                {
+                  terms: [
+                    {
+                      column: 'sceneId',
+                      value: id,
+                    },
+                  ],
+                  type: 'and',
+                },
+              ],
+              sorts: [
+                {
+                  name: 'createTime',
+                  order: 'desc',
+                },
+              ],
+            });
+          }}
+          rowSelection={{
+            selectedRowKeys: selectKeys,
+            onChange: (selectedRowKeys) => {
+              console.log(selectedRowKeys);
+              setSelectKeys(selectedRowKeys);
+            },
+          }}
+          cardRender={(record) => (
+            <ExtraSceneCard {...record} showTool={false} showBindBtn={false} cardType={'bind'} />
+          )}
+          params={searchParams}
+          height={'none'}
+        />
+      </div>
+    </Modal>
+  );
+};

+ 192 - 0
src/pages/rule-engine/Alarm/Configuration/Save/Scene/index.tsx

@@ -0,0 +1,192 @@
+import { SceneItem } from '@/pages/rule-engine/Scene/typings';
+import { PermissionButton, ProTableCard } from '@/components';
+import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
+import SceneCard from '@/components/ProTableCard/CardItems/scene';
+import { useRef, useState } from 'react';
+import { ActionType, ProColumns } from '@jetlinks/pro-table';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import useLocation from '@/hooks/route/useLocation';
+// import { service } from "@/pages/rule-engine/Alarm/Configuration";
+import { service as sceneService } from '@/pages/rule-engine/Scene';
+import Save from './Save';
+// import SearchComponent from "@/components/SearchComponent";
+
+export default () => {
+  const intl = useIntl();
+  const actionRef = useRef<ActionType>();
+  const { permission } = PermissionButton.usePermission('rule-engine/Alarm/Configuration');
+  const location = useLocation();
+  const id = location?.query?.id || '';
+  const [visible, setVisible] = useState<boolean>(false);
+  const [searchParams] = useState<any>({});
+
+  const columns: ProColumns<SceneItem>[] = [
+    {
+      dataIndex: 'name',
+      fixed: 'left',
+      ellipsis: true,
+      width: 300,
+      title: intl.formatMessage({
+        id: 'pages.table.name',
+        defaultMessage: '名称',
+      }),
+    },
+    {
+      dataIndex: 'triggerType',
+      title: intl.formatMessage({
+        id: 'pages.ruleEngine.scene.triggers',
+        defaultMessage: '触发方式',
+      }),
+      valueType: 'select',
+      valueEnum: {
+        manual: {
+          text: '手动触发',
+          status: 'manual',
+        },
+        timer: {
+          text: '定时触发',
+          status: 'timer',
+        },
+        device: {
+          text: '设备触发',
+          status: 'device',
+        },
+      },
+    },
+    {
+      dataIndex: 'description',
+      title: intl.formatMessage({
+        id: 'pages.system.description',
+        defaultMessage: '说明',
+      }),
+      hideInSearch: true,
+    },
+    {
+      dataIndex: 'state',
+      title: intl.formatMessage({
+        id: 'pages.searchTable.titleStatus',
+        defaultMessage: '状态',
+      }),
+      valueType: 'select',
+      valueEnum: {
+        started: {
+          text: '正常',
+          status: 'started',
+        },
+        disable: {
+          text: '禁用',
+          status: 'disable',
+        },
+      },
+    },
+  ];
+
+  return (
+    <>
+      {/*<SearchComponent*/}
+      {/*  field={columns}*/}
+      {/*  target={'alarm-scene'}*/}
+      {/*  onSearch={(data) => {*/}
+      {/*    actionRef.current?.reset?.();*/}
+      {/*    setSearchParams(data);*/}
+      {/*  }}*/}
+      {/*/>*/}
+      <ProTableCard<SceneItem>
+        columns={columns}
+        actionRef={actionRef}
+        scroll={{ x: 1366 }}
+        params={searchParams}
+        columnEmptyText={''}
+        // @ts-ignore
+        request={(params) => {
+          if (!id) {
+            return {
+              code: 200,
+              result: {
+                data: [],
+                pageIndex: 0,
+                pageSize: 0,
+                total: 0,
+              },
+              status: 200,
+            };
+          }
+          return sceneService.query({
+            ...params,
+            terms: [
+              ...(params?.terms || []),
+              {
+                terms: [
+                  {
+                    column: 'sceneId',
+                    value: id,
+                  },
+                ],
+                type: 'and',
+              },
+            ],
+            sorts: [
+              {
+                name: 'createTime',
+                order: 'desc',
+              },
+            ],
+          });
+        }}
+        rowKey="id"
+        search={false}
+        onlyCard={true}
+        headerTitle={[
+          <PermissionButton
+            key="button"
+            icon={<PlusOutlined />}
+            type="primary"
+            isPermission={permission.update}
+            onClick={() => {
+              setVisible(true);
+            }}
+          >
+            {intl.formatMessage({
+              id: 'pages.data.option.add',
+              defaultMessage: '新增',
+            })}
+          </PermissionButton>,
+        ]}
+        cardRender={(record) => (
+          <SceneCard
+            {...record}
+            showBindBtn={false}
+            cardType={'bind'}
+            actions={[
+              <PermissionButton
+                key={'unbind'}
+                type={'link'}
+                style={{ padding: 0 }}
+                isPermission={permission.update}
+                popConfirm={{
+                  title: '确认解绑?',
+                  onConfirm: () => {
+                    console.log(record.id);
+                  },
+                }}
+                tooltip={{
+                  title: <span>解绑</span>,
+                }}
+              >
+                <DeleteOutlined />
+              </PermissionButton>,
+            ]}
+          />
+        )}
+      />
+      {visible && (
+        <Save
+          id={id}
+          close={() => {
+            setVisible(false);
+          }}
+        />
+      )}
+    </>
+  );
+};

+ 27 - 298
src/pages/rule-engine/Alarm/Configuration/Save/index.tsx

@@ -1,306 +1,35 @@
-import { Modal, Typography } from 'antd';
-import { useMemo } from 'react';
-import { createForm, Field, onFieldInit, onFieldValueChange } from '@formily/core';
-import { createSchemaField } from '@formily/react';
-import { Form, FormGrid, FormItem, Input, Radio, Select } from '@formily/antd';
-import { ISchema } from '@formily/json-schema';
-import { PermissionButton } from '@/components';
-import { PlusOutlined } from '@ant-design/icons';
-import Service from '@/pages/rule-engine/Alarm/Configuration/service';
-import { onlyMessage, useAsyncDataSource } from '@/utils/util';
-import styles from './index.less';
-import { service as ConfigService } from '../../Config';
-import { Store } from 'jetlinks-store';
-import encodeQuery from '@/utils/encodeQuery';
+import { PageContainer } from '@ant-design/pro-layout';
+import { Card, Tabs } from 'antd';
+import Scene from './Scene';
+import Base from './Base';
+import Log from './Log';
 
-interface Props {
-  visible: boolean;
-  close: () => void;
-  data: any;
-}
-
-const alarm1 = require('/public/images/alarm/alarm1.png');
-const alarm2 = require('/public/images/alarm/alarm2.png');
-const alarm3 = require('/public/images/alarm/alarm3.png');
-const alarm4 = require('/public/images/alarm/alarm4.png');
-const alarm5 = require('/public/images/alarm/alarm5.png');
-
-const service = new Service('alarm/config');
-
-const createImageLabel = (image: string, text: string) => {
+export default () => {
   return (
-    <div style={{ textAlign: 'center', marginTop: 10, fontSize: '15px', width: '90px' }}>
-      <img alt="" height="40px" src={image} />
-      <Typography.Text style={{ maxWidth: '50px', marginBottom: 10 }} ellipsis={{ tooltip: text }}>
-        {text}
-      </Typography.Text>
-    </div>
-  );
-};
-
-const Save = (props: Props) => {
-  const { visible, close } = props;
-
-  const LevelMap = {
-    1: alarm1,
-    2: alarm2,
-    3: alarm3,
-    4: alarm4,
-    5: alarm5,
-  };
-  const getLevel = () => {
-    return ConfigService.queryLevel().then((resp) => {
-      if (resp.status === 200) {
-        return resp.result?.levels
-          ?.filter((i: any) => i?.level && i?.title)
-          .map((item: { level: number; title: string }) => ({
-            label: createImageLabel(LevelMap[item.level], item.title),
-            value: item.level,
-          }));
-      }
-    });
-  };
-
-  const form = useMemo(
-    () =>
-      createForm({
-        initialValues: props.data,
-        validateFirst: true,
-        effects() {
-          onFieldInit('*(sceneId,targetType)', async (field) => {
-            if (!props?.data?.id) return;
-            const resp = await service.getAlarmCountById(props.data.id);
-            field.setComponentProps({
-              disabled: resp.result > 0,
-            });
-          });
-          onFieldValueChange('targetType', async (field: Field, f) => {
-            if (field.modified) {
-              f.setFieldState(field.query('.sceneId'), (state) => {
-                state.value = undefined;
-              });
-            }
-          });
-        },
-      }),
-    [props.data, props.visible],
-  );
-
-  const getScene = () => {
-    const map = {
-      product: 'device',
-      device: 'device',
-      org: 'device',
-      other: undefined,
-    };
-    return service
-      .getScene(
-        encodeQuery({
-          sorts: { createTime: 'desc' },
-          terms: {
-            triggerType: map[form.getValuesIn('targetType')],
-          },
-        }),
-      )
-      .then((resp) => {
-        Store.set('scene-data', resp.result);
-        return form.getValuesIn('targetType')
-          ? resp.result.map((item: { id: string; name: string }) => ({
-              label: item.name,
-              value: item.id,
-            }))
-          : [];
-      });
-  };
-
-  const getSupports = () => service.getTargetTypes();
-
-  const SchemaField = createSchemaField({
-    components: {
-      FormItem,
-      Input,
-      Select,
-      FormGrid,
-      Radio,
-    },
-  });
-
-  const handleSave = async () => {
-    const data: any = await form.submit();
-    const list = Store.get('scene-data');
-    const scene = list.find((item: any) => item.id === data.sceneId);
-
-    const resp: any = await service.update({
-      ...data,
-      // state: 'disabled',
-      sceneTriggerType: scene.triggerType,
-      sceneName: scene.name,
-    });
-
-    if (resp.status === 200) {
-      onlyMessage('操作成功');
-      props.close();
-    }
-  };
-
-  const { permission } = PermissionButton.usePermission('rule-engine/Scene');
-
-  const schema: ISchema = {
-    type: 'object',
-    properties: {
-      layout: {
-        type: 'void',
-        'x-decorator': 'FormGrid',
-        'x-decorator-props': {
-          maxColumns: 2,
-          minColumns: 2,
-          columnGap: 24,
-        },
-        properties: {
-          name: {
-            title: '名称',
-            'x-decorator': 'FormItem',
-            'x-component': 'Input',
-            'x-decorator-props': {
-              gridSpan: 1,
-            },
-            'x-component-props': {
-              placeholder: '请输入名称',
+    <PageContainer>
+      <Card style={{ minHeight: 600 }}>
+        <Tabs
+          defaultActiveKey="1"
+          onChange={() => {}}
+          items={[
+            {
+              label: `基础配置`,
+              key: '1',
+              children: <Base />,
             },
-            'x-validator': [
-              {
-                required: true,
-                message: '请输入名称',
-              },
-              {
-                max: 64,
-                message: '最多可输入64个字符',
-              },
-            ],
-          },
-          targetType: {
-            title: '类型',
-            'x-decorator': 'FormItem',
-            'x-component': 'Select',
-            'x-decorator-props': {
-              gridSpan: 1,
+            {
+              label: `关联场景联动`,
+              key: '2',
+              children: <Scene />,
             },
-            required: true,
-            'x-reactions': '{{useAsyncDataSource(getSupports)}}',
-            'x-component-props': {
-              placeholder: '请选择类型',
+            {
+              label: `告警记录`,
+              key: '3',
+              children: <Log />,
             },
-            'x-validator': [
-              {
-                required: true,
-                message: '请选择类型',
-              },
-            ],
-          },
-        },
-      },
-      level: {
-        title: '级别',
-        'x-decorator': 'FormItem',
-        'x-component': 'Radio.Group',
-        'x-component-props': {
-          optionType: 'button',
-          placeholder: '请选择类型',
-        },
-        required: true,
-        'x-reactions': '{{useAsyncDataSource(getLevel)}}',
-        'x-decorator-props': {
-          gridSpan: 1,
-        },
-        'x-validator': [
-          {
-            required: true,
-            message: '请选择级别',
-          },
-        ],
-      },
-      sceneId: {
-        title: '关联触发场景',
-        'x-decorator': 'FormItem',
-        'x-component': 'Select',
-        'x-reactions': '{{useAsyncDataSource(getScene)}}',
-        'x-validator': [
-          {
-            required: true,
-            message: '请选择关联触发场景',
-          },
-        ],
-        'x-component-props': {
-          placeholder: '请选择关联触发场景',
-          showSearch: true,
-          allowClear: true,
-          showArrow: true,
-          filterOption: (input: string, option: any) =>
-            option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
-        },
-        'x-decorator-props': {
-          gridSpan: 1,
-          addonAfter: (
-            <PermissionButton
-              type="link"
-              style={{ padding: 0 }}
-              isPermission={permission.add}
-              onClick={() => {
-                const targetType = form.values.targetType;
-                let queryStr = '';
-                if (targetType && targetType !== 'other') {
-                  queryStr = '?targetType=device';
-                }
-                const tab: any = window.open(`${origin}/#/iot/rule-engine/scene/Save${queryStr}`);
-                tab!.onTabSaveSuccess = (value: any) => {
-                  form.setFieldState('sceneId', async (state) => {
-                    state.dataSource = await getScene();
-                    state.value = value?.result?.id;
-                  });
-                };
-              }}
-            >
-              <PlusOutlined />
-            </PermissionButton>
-          ),
-        },
-      },
-      description: {
-        title: '说明',
-        'x-decorator': 'FormItem',
-        'x-component': 'Input.TextArea',
-        'x-decorator-props': {
-          gridSpan: 1,
-        },
-        'x-component-props': {
-          placeholder: '请输入说明',
-        },
-        'x-validator': [
-          {
-            max: 200,
-            message: '最多可输入200个字符',
-          },
-        ],
-      },
-    },
-  };
-
-  return (
-    <Modal
-      width="40vw"
-      visible={visible}
-      onOk={handleSave}
-      forceRender={true}
-      onCancel={() => close()}
-      title={`${props.data ? '编辑' : '新增'}告警`}
-    >
-      <Form className={styles.form} form={form} layout="vertical">
-        <SchemaField
-          schema={schema}
-          scope={{ useAsyncDataSource, getSupports, getLevel, getScene }}
+          ]}
         />
-      </Form>
-    </Modal>
+      </Card>
+    </PageContainer>
   );
 };
-export default Save;

+ 7 - 18
src/pages/rule-engine/Alarm/Configuration/index.tsx

@@ -14,7 +14,6 @@ import { useIntl } from '@@/plugin-locale/localeExports';
 import { useEffect, useRef, useState } from 'react';
 import { Badge, Space, Tooltip } from 'antd';
 import ProTableCard from '@/components/ProTableCard';
-import Save from './Save';
 import Service from '@/pages/rule-engine/Alarm/Configuration/service';
 import AlarmConfig from '@/components/ProTableCard/CardItems/AlarmConfig';
 import { Store } from 'jetlinks-store';
@@ -23,16 +22,14 @@ import { useHistory } from 'umi';
 import { onlyMessage } from '@/utils/util';
 import encodeQuery from '@/utils/encodeQuery';
 
-const service = new Service('alarm/config');
+export const service = new Service('alarm/config');
 
 const Configuration = () => {
   const intl = useIntl();
   const history = useHistory();
-  const [visible, setVisible] = useState<boolean>(false);
   const actionRef = useRef<ActionType>();
   const { permission } = PermissionButton.usePermission('rule-engine/Alarm/Configuration');
 
-  const [current, setCurrent] = useState<any>();
   const columns: ProColumns<ConfigurationItem>[] = [
     {
       dataIndex: 'name',
@@ -223,8 +220,8 @@ const Configuration = () => {
           }}
           type="link"
           onClick={() => {
-            setVisible(true);
-            setCurrent(record);
+            const url = getMenuPathByCode('rule-engine/Alarm/Configuration/Save');
+            history.push(`${url}?id=${record.id}`);
           }}
         >
           <EditOutlined />
@@ -355,8 +352,8 @@ const Configuration = () => {
                 key="edit"
                 type="link"
                 onClick={() => {
-                  setCurrent(record);
-                  setVisible(true);
+                  const url = getMenuPathByCode('rule-engine/Alarm/Configuration/Save');
+                  history.push(`${url}?id=${record.id}`);
                 }}
               >
                 <EditOutlined />
@@ -431,8 +428,8 @@ const Configuration = () => {
             <PermissionButton
               isPermission={permission.add}
               onClick={() => {
-                setCurrent(undefined);
-                setVisible(true);
+                const url = getMenuPathByCode('rule-engine/Alarm/Configuration/Save');
+                history.push(`${url}`);
               }}
               key="button"
               icon={<PlusOutlined />}
@@ -446,14 +443,6 @@ const Configuration = () => {
           </Space>
         }
       />
-      <Save
-        data={current}
-        visible={visible}
-        close={() => {
-          setVisible(false);
-          actionRef.current?.reset?.();
-        }}
-      />
     </PageContainer>
   );
 };

+ 58 - 57
src/pages/rule-engine/Alarm/Log/TabComponent/index.less

@@ -2,80 +2,81 @@
 
 @card-content-padding-top: 30px;
 
-.alarm-log-context {
-  display: flex;
-  align-items: center;
-  width: 100%;
-
-  .context-left {
+.alarm-log-card {
+  .alarm-log-context {
     display: flex;
     align-items: center;
-    justify-content: space-between;
-    width: calc(40% - 40px);
-    margin-right: 10%;
+    width: 100%;
 
-    .context-img {
-      margin-right: 20px;
-    }
+    .context-left {
+      display: flex;
+      align-items: center;
+      width: 40%;
+      .context-img {
+        margin-right: 20px;
+      }
 
-    .left-item {
-      width: calc(100% - 90px);
+      .left-item {
+        width: calc(100% - 90px);
+        padding-right: 10px;
 
-      .left-item-title {
-        font-size: 18px;
-      }
+        .left-item-title {
+          font-size: 18px;
+        }
 
-      .left-item-value {
-        color: #666;
-        font-size: 14px;
+        .left-item-value {
+          color: #666;
+          font-size: 14px;
+        }
       }
     }
-  }
 
-  .context-right {
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    width: 60%;
-    margin-right: 10px;
-    padding-left: 10%;
-    border-left: 1px solid rgba(0, 0, 0, 0.09);
+    .context-right {
+      display: flex;
+      align-items: center;
+      width: 60%;
+      padding-left: 10px;
+      border-left: 1px solid rgba(0, 0, 0, 0.09);
 
-    .right-item {
-      .right-item-title {
-        color: #666;
-        font-size: 12px;
-      }
+      .right-item {
+        display: flex;
+        flex-direction: column;
+        border: none;
+        .right-item-title {
+          color: #666;
+          font-size: 12px;
+        }
 
-      .right-item-value {
-        color: #000;
-        font-size: 14px;
+        .right-item-value {
+          color: #000;
+          font-size: 14px;
+        }
       }
     }
   }
-}
 
-.alarm-log-state {
-  position: absolute;
-  top: @card-content-padding-top;
-  right: -12px;
-  display: flex;
-  justify-content: center;
-  width: 100px;
-  padding: 2px 0;
-  color: #fff;
-  background-color: rgba(#5995f5, 0.15);
-  transform: skewX(45deg);
+  .alarm-log-state {
+    position: absolute;
+    top: @card-content-padding-top;
+    right: -12px;
+    display: flex;
+    justify-content: center;
+    width: 100px;
+    padding: 2px 0;
+    color: #fff;
+    background-color: rgba(#5995f5, 0.15);
+    transform: skewX(45deg);
 
-  .card-state-content {
-    transform: skewX(-45deg);
+    .card-state-content {
+      transform: skewX(-45deg);
 
-    .stateText {
-      width: 70px;
-      overflow: hidden;
-      white-space: nowrap;
-      text-align: center;
-      text-overflow: ellipsis;
+      .stateText {
+        width: 70px;
+        overflow: hidden;
+        white-space: nowrap;
+        text-align: center;
+        text-overflow: ellipsis;
+      }
     }
   }
 }

+ 136 - 127
src/pages/rule-engine/Alarm/Log/TabComponent/index.tsx

@@ -16,9 +16,11 @@ import classNames from 'classnames';
 import { useDomFullHeight } from '@/hooks';
 import PermissionButton from '@/components/PermissionButton';
 import { Ellipsis } from '@/components';
+import { Store } from 'jetlinks-store';
 
 interface Props {
   type: string;
+  id?: string;
 }
 
 const imgMap = new Map();
@@ -44,6 +46,17 @@ const TabComponent = observer((props: Props) => {
   const { minHeight } = useDomFullHeight(`.alarmLog`, 36);
   const { permission } = PermissionButton.usePermission('rule-engine/Alarm/Log');
 
+  useEffect(() => {
+    if (AlarmLogModel.defaultLevel.length === 0) {
+      service.queryDefaultLevel().then((resp) => {
+        if (resp.status === 200) {
+          Store.set('default-level', resp.result?.levels || []);
+          AlarmLogModel.defaultLevel = resp.result?.levels || [];
+        }
+      });
+    }
+  }, []);
+
   const columns: ProColumns<any>[] = [
     {
       title: '名称',
@@ -135,7 +148,7 @@ const TabComponent = observer((props: Props) => {
   const handleSearch = (params: any) => {
     setParam(params);
     const terms = [...params.terms];
-    if (props.type !== 'all') {
+    if (props.type !== 'all' && !props.id) {
       terms.push({
         termType: 'eq',
         column: 'targetType',
@@ -143,6 +156,14 @@ const TabComponent = observer((props: Props) => {
         type: 'and',
       });
     }
+    if (props.id) {
+      terms.push({
+        termType: 'eq',
+        column: 'alarmConfigId',
+        value: props.id,
+        type: 'and',
+      });
+    }
     service
       .query({
         ...params,
@@ -153,9 +174,9 @@ const TabComponent = observer((props: Props) => {
         if (resp.status === 200) {
           const res = await service.getOrgList();
           if (res.status === 200) {
-            resp.result.data.map((item) => {
+            resp.result.data.map((item: any) => {
               if (item.targetType === 'org') {
-                res.result.forEach((item2) => {
+                res.result.forEach((item2: any) => {
                   if (item2.id === item.targetId) {
                     item.targetName = item2.name;
                   }
@@ -174,9 +195,6 @@ const TabComponent = observer((props: Props) => {
 
   useEffect(() => {
     handleSearch(param);
-    if (props.type === 'prodcut') {
-    }
-    console.log(props.type);
   }, [props.type]);
 
   const tools = (record: any) => [
@@ -230,7 +248,7 @@ const TabComponent = observer((props: Props) => {
       处理记录
     </PermissionButton>,
 
-    // <Button
+    // <ButtontargetType
     //   type={'link'}
     //   style={{ padding: 0 }}
     //   key={'detail'}
@@ -261,11 +279,9 @@ const TabComponent = observer((props: Props) => {
       });
   };
 
-  const defaultImage = require('/public/images/device-access.png');
-
   return (
     <div className="alarm-log-card">
-      {props.type === 'all' && (
+      {['all', 'detail'].includes(props.type) && (
         <SearchComponent<any>
           field={columns}
           target="alarm-log"
@@ -278,7 +294,7 @@ const TabComponent = observer((props: Props) => {
           }}
         />
       )}
-      {props.type === 'product' && (
+      {['product', 'other'].includes(props.type) && (
         <SearchComponent<any>
           field={productCol}
           target="alarm-log"
@@ -317,139 +333,132 @@ const TabComponent = observer((props: Props) => {
           }}
         />
       )}
-      {props.type === 'other' && (
-        <SearchComponent<any>
-          field={productCol}
-          target="alarm-log"
-          onSearch={(data) => {
-            const dt = {
-              pageSize: 10,
-              terms: [...data?.terms],
-            };
-            handleSearch(dt);
-          }}
-        />
-      )}
 
-      <Card>
-        <div className="alarmLog" style={{ minHeight, position: 'relative' }}>
+      <Card bordered={false}>
+        <div className="alarm-log-card" style={{ minHeight, position: 'relative' }}>
           <div style={{ height: '100%', paddingBottom: 48 }}>
-            {dataSource?.data.length > 0 ? (
-              <Row gutter={[24, 24]} style={{ marginTop: 10 }}>
-                {(dataSource?.data || []).map((item: any) => (
-                  <Col key={item.id} span={12}>
-                    <div className={classNames('iot-card')}>
-                      <div className={'card-warp'}>
-                        <div className={classNames('card-content')}>
-                          <div style={{ width: 'calc(100% - 90px)' }}>
-                            <Ellipsis title={item.alarmName} titleStyle={{ color: '#2F54EB' }} />
-                            {/*<Tooltip title={item.alarmName}>*/}
-                            {/*  <a style={{ cursor: 'default' }}>{item.alarmName}</a>*/}
-                            {/*</Tooltip>*/}
-                          </div>
-                          <div className="alarm-log-context">
-                            <div className="context-left">
-                              <div className="context-img">
-                                <img width={70} height={70} src={defaultImage} alt={''} />
-                              </div>
-                              <div className="left-item">
-                                <div className="left-item-title">
-                                  {titleMap.get(item.targetType)}
+            {dataSource?.data.length ? (
+              <>
+                <Row gutter={[24, 24]} style={{ marginTop: 10 }}>
+                  {(dataSource?.data || []).map((item: any) => (
+                    <Col key={item.id} span={12}>
+                      <div className={classNames('iot-card')}>
+                        <div className={'card-warp'}>
+                          <div className={classNames('card-content')}>
+                            <div style={{ width: 'calc(100% - 90px)', marginBottom: 10 }}>
+                              <Ellipsis
+                                title={item.alarmName}
+                                titleStyle={{ color: '#2F54EB', fontWeight: 500 }}
+                              />
+                            </div>
+                            <div className="alarm-log-context">
+                              <div className="context-left">
+                                <div className="context-img">
+                                  <img
+                                    width={70}
+                                    height={70}
+                                    src={imgMap.get(item.targetType || 'other')}
+                                    alt={''}
+                                  />
                                 </div>
-                                <div className="left-item-value ellipsis">
-                                  <Tooltip placement="topLeft" title={item.targetName}>
-                                    {item.targetName}
-                                  </Tooltip>
+                                <div className="left-item">
+                                  <div className="left-item-title">
+                                    {titleMap.get(item.targetType)}
+                                  </div>
+                                  <div className="left-item-value ellipsis">
+                                    <Tooltip placement="topLeft" title={item.targetName}>
+                                      {item.targetName}
+                                    </Tooltip>
+                                  </div>
                                 </div>
                               </div>
-                            </div>
-                            <div className="context-right">
-                              <div className="right-item">
-                                <div className="right-item-title">最近告警时间</div>
-                                <div className="right-item-value ellipsis">
-                                  {moment(item.alarmTime).format('YYYY-MM-DD HH:mm:ss')}
+                              <div className="context-right">
+                                <div className="right-item">
+                                  <div className="right-item-title">最近告警时间</div>
+                                  <div className="right-item-value ellipsis">
+                                    {moment(item.alarmTime).format('YYYY-MM-DD HH:mm:ss')}
+                                  </div>
                                 </div>
-                              </div>
-                              <div className="right-item">
-                                <div className="right-item-title">状态</div>
-                                <div className="right-item-value">
-                                  <Badge
-                                    status={item.state.value === 'warning' ? 'error' : 'default'}
-                                  />
-                                  <span
-                                    style={{
-                                      color: item.state.value === 'warning' ? '#E50012' : 'black',
-                                    }}
-                                  >
-                                    {item.state.text}
-                                  </span>
+                                <div className="right-item">
+                                  <div className="right-item-title">状态</div>
+                                  <div className="right-item-value">
+                                    <Badge
+                                      status={item.state.value === 'warning' ? 'error' : 'default'}
+                                    />
+                                    <span
+                                      style={{
+                                        color: item.state.value === 'warning' ? '#E50012' : 'black',
+                                        marginLeft: 5,
+                                      }}
+                                    >
+                                      {item.state.text}
+                                    </span>
+                                  </div>
                                 </div>
                               </div>
                             </div>
-                          </div>
-                          <div
-                            className={'alarm-log-state'}
-                            style={{ backgroundColor: colorMap.get(item.level) }}
-                          >
-                            <div className={'card-state-content'}>
-                              <Tooltip
-                                placement="topLeft"
-                                title={
-                                  AlarmLogModel.defaultLevel.find((i) => i.level === item.level)
-                                    ?.title || item.level
-                                }
-                              >
-                                <div className="ellipsis" style={{ maxWidth: 70 }}>
-                                  {AlarmLogModel.defaultLevel.find((i) => i.level === item.level)
-                                    ?.title || item.level}
-                                </div>
-                              </Tooltip>
+                            <div
+                              className={'alarm-log-state'}
+                              style={{ backgroundColor: colorMap.get(item.level) }}
+                            >
+                              <div className={'card-state-content'}>
+                                <Tooltip
+                                  placement="topLeft"
+                                  title={
+                                    AlarmLogModel.defaultLevel.find((i) => i.level === item.level)
+                                      ?.title || item.level
+                                  }
+                                >
+                                  <div className="ellipsis" style={{ maxWidth: 70 }}>
+                                    {AlarmLogModel.defaultLevel.find((i) => i.level === item.level)
+                                      ?.title || item.level}
+                                  </div>
+                                </Tooltip>
+                              </div>
                             </div>
                           </div>
                         </div>
+                        <div className={'card-tools'}>{getAction(tools(item))}</div>
                       </div>
-                      <div className={'card-tools'}>{getAction(tools(item))}</div>
-                    </div>
-                  </Col>
-                ))}
-              </Row>
+                    </Col>
+                  ))}
+                </Row>
+                <div
+                  style={{
+                    display: 'flex',
+                    justifyContent: 'flex-end',
+                    position: 'absolute',
+                    bottom: 0,
+                    width: '100%',
+                  }}
+                >
+                  <Pagination
+                    showSizeChanger
+                    size="small"
+                    className={'pro-table-card-pagination'}
+                    total={dataSource?.total || 0}
+                    current={dataSource?.pageIndex + 1}
+                    onChange={(page, size) => {
+                      handleSearch({
+                        ...param,
+                        pageIndex: page - 1,
+                        pageSize: size,
+                      });
+                    }}
+                    pageSizeOptions={[10, 20, 50, 100]}
+                    pageSize={dataSource?.pageSize}
+                    showTotal={(num) => {
+                      const minSize = dataSource?.pageIndex * dataSource?.pageSize + 1;
+                      const MaxSize = (dataSource?.pageIndex + 1) * dataSource?.pageSize;
+                      return `第 ${minSize} - ${MaxSize > num ? num : MaxSize} 条/总共 ${num} 条`;
+                    }}
+                  />
+                </div>
+              </>
             ) : (
               <Empty style={{ marginTop: '10%' }} />
             )}
           </div>
-          {dataSource.data.length > 0 && (
-            <div
-              style={{
-                display: 'flex',
-                justifyContent: 'flex-end',
-                position: 'absolute',
-                bottom: 0,
-                width: '100%',
-              }}
-            >
-              <Pagination
-                showSizeChanger
-                size="small"
-                className={'pro-table-card-pagination'}
-                total={dataSource?.total || 0}
-                current={dataSource?.pageIndex + 1}
-                onChange={(page, size) => {
-                  handleSearch({
-                    ...param,
-                    pageIndex: page - 1,
-                    pageSize: size,
-                  });
-                }}
-                pageSizeOptions={[10, 20, 50, 100]}
-                pageSize={dataSource?.pageSize}
-                showTotal={(num) => {
-                  const minSize = dataSource?.pageIndex * dataSource?.pageSize + 1;
-                  const MaxSize = (dataSource?.pageIndex + 1) * dataSource?.pageSize;
-                  return `第 ${minSize} - ${MaxSize > num ? num : MaxSize} 条/总共 ${num} 条`;
-                }}
-              />
-            </div>
-          )}
         </div>
       </Card>
       {AlarmLogModel.solveVisible && (

+ 3 - 2
src/pages/rule-engine/Scene/Save/action/Modal/add.tsx

@@ -58,9 +58,10 @@ export default (props: Props) => {
       }}
       onOk={async () => {
         const values = await form.validateFields();
-        console.log(values.type);
         setActionType(values.type);
-        // props.save({ ...props.data, type: values.type });
+        if (values.type === 'relieve' || values.type === 'trigger') {
+          props.save({ ...props.data, type: values.type });
+        }
       }}
     >
       <Form form={form} layout={'vertical'}>

+ 1 - 1
src/pages/rule-engine/Scene/index.tsx

@@ -24,7 +24,7 @@ import Save from './Save/save';
 
 export const service = new Service('scene');
 
-enum TriggerWayType {
+export enum TriggerWayType {
   manual = '手动触发',
   timer = '定时触发',
   device = '设备触发',

+ 3 - 0
src/utils/menu/index.ts

@@ -37,6 +37,9 @@ const extraRouteObj = {
       { code: 'Save2', name: '测试详情' },
     ],
   },
+  'rule-engine/Alarm/Configuration': {
+    children: [{ code: 'Save', name: '详情' }],
+  },
   'device/Firmware': {
     children: [{ code: 'Task', name: '升级任务' }],
   },

+ 2 - 0
src/utils/menu/router.ts

@@ -72,6 +72,7 @@ export enum MENUS_CODE {
   'rule-engine/Scene/Save' = 'rule-engine/Scene/Save',
   'rule-engine/Scene/Save2' = 'rule-engine/Scene/Save2',
   'rule-engine/Alarm/Configuration' = 'rule-engine/Alarm/Configuration',
+  'rule-engine/Alarm/Configuration/Save' = 'rule-engine/Alarm/Configuration/Save',
   'simulator/Device' = 'simulator/Device',
   'system/DataSource' = 'system/DataSource',
   'system/DataSource/Management' = 'system/DataSource/Management',
@@ -212,6 +213,7 @@ export const CommunityCodeList = [
   'home',
   'rule-engine/DashBoard',
   'rule-engine/Alarm/Configuration',
+  'rule-engine/Alarm/Configuration/Save',
   'rule-engine/Alarm/Log',
   'rule-engine/Alarm/Log/Detail',
   'device/DashBoard',