Explorar o código

fix: 422下午

sun-chaochao %!s(int64=3) %!d(string=hai) anos
pai
achega
c600774263

+ 21 - 10
src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.tsx

@@ -27,6 +27,7 @@ interface EditableCellProps {
   dataIndex: string;
   record: any;
   list: any[];
+  properties: any[];
   handleSave: (record: any) => void;
 }
 
@@ -37,10 +38,12 @@ const EditableCell = ({
   dataIndex,
   record,
   list,
+  properties,
   handleSave,
   ...restProps
 }: EditableCellProps) => {
   const form: any = useContext(EditableContext);
+  const [temp, setTemp] = useState<any>({});
 
   const save = async () => {
     try {
@@ -54,6 +57,7 @@ const EditableCell = ({
   useEffect(() => {
     if (record) {
       form.setFieldsValue({ [dataIndex]: record[dataIndex] });
+      setTemp(properties.find((i) => i.id === record.originalId));
     }
   }, [record]);
 
@@ -71,6 +75,11 @@ const EditableCell = ({
           }
         >
           <Select.Option value={record.metadataId}>使用原始属性</Select.Option>
+          {record.originalId !== record.metadataId && (
+            <Select.Option value={record.originalId}>
+              {temp?.name}({temp?.id})
+            </Select.Option>
+          )}
           {list.length > 0 &&
             list.map((item: any) => (
               <Select.Option key={item?.id} value={item?.id}>
@@ -98,7 +107,7 @@ const EditableTable = (props: Props) => {
     },
     {
       title: '设备上报属性',
-      dataIndex: 'metadataId',
+      dataIndex: 'originalId',
       width: '30%',
       editable: true,
     },
@@ -136,7 +145,7 @@ const EditableTable = (props: Props) => {
     },
   };
 
-  const initData = async () => {
+  const initData = async (lists: any[]) => {
     let resp = null;
     if (props.type === 'device') {
       resp = await service.queryDeviceMetadata(props.data.id);
@@ -147,10 +156,10 @@ const EditableTable = (props: Props) => {
       const data = resp.result;
       const obj: any = {};
       data.map((i: any) => {
-        obj[i?.originalId] = i;
+        obj[i?.metadataId] = i;
       });
-      if (protocolMetadata.length > 0) {
-        setPmList(protocolMetadata.filter((i) => !_.map(data, 'metadataId').includes(i.id)));
+      if (lists.length > 0) {
+        setPmList(lists.filter((i) => !_.map(data, 'originalId').includes(i.id)));
       } else {
         setPmList([]);
       }
@@ -184,8 +193,9 @@ const EditableTable = (props: Props) => {
         )
         .then((resp) => {
           if (resp.status === 200) {
-            setProtocolMetadata(JSON.parse(resp.result || '{}')?.properties || []);
-            initData();
+            const list = JSON.parse(resp.result || '{}')?.properties || [];
+            setProtocolMetadata(list);
+            initData(list);
           }
         });
     }
@@ -201,15 +211,15 @@ const EditableTable = (props: Props) => {
       ](props.data?.id, [
         {
           metadataType: 'property',
-          metadataId: row.metadataId === row.id ? row.metadataId : row.id,
-          originalId: row.metadataId === row.id ? row.id : '',
+          metadataId: row.id,
+          originalId: row.metadataId !== row.id ? row.metadataId : '',
           others: {},
         },
       ]);
       if (resp.status === 200) {
         message.success('操作成功!');
         // 刷新
-        initData();
+        initData(protocolMetadata);
       }
     }
   };
@@ -253,6 +263,7 @@ const EditableTable = (props: Props) => {
         dataIndex: col.dataIndex,
         title: col.title,
         list: pmList,
+        properties: protocolMetadata,
         handleSave: handleSave,
       }),
     };

+ 99 - 0
src/pages/rule-engine/Alarm/Log/Logger/index.tsx

@@ -0,0 +1,99 @@
+import SearchComponent from '@/components/SearchComponent';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+import { useRef, useState } from 'react';
+import { service } from '@/pages/media/Cascade';
+import { PageContainer } from '@ant-design/pro-layout';
+
+interface Props {
+  data: string;
+  close: () => void;
+}
+
+const Logger = (props: Props) => {
+  const [param, setParam] = useState<any>({
+    terms: [
+      {
+        column: 'id',
+        termType: 'cascade_channel$not',
+        value: props.data,
+        type: 'and',
+      },
+      {
+        column: 'catalogType',
+        termType: 'eq',
+        value: 'device',
+        type: 'and',
+      },
+    ],
+    sorts: [
+      {
+        name: 'name',
+        order: 'asc',
+      },
+    ],
+  });
+  const actionRef = useRef<ActionType>();
+
+  const columns: ProColumns<any>[] = [
+    {
+      dataIndex: 'deviceName',
+      title: '告警时间',
+    },
+    {
+      dataIndex: 'name',
+      title: '告警名称',
+    },
+    {
+      dataIndex: 'address',
+      title: '告警设备',
+    },
+    {
+      dataIndex: 'manufacturer',
+      title: '说明',
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <SearchComponent<any>
+        field={columns}
+        target="bind-channel"
+        enableSave={false}
+        onSearch={(data) => {
+          actionRef.current?.reload();
+          const terms = [
+            {
+              column: 'id',
+              termType: 'cascade_channel$not',
+              value: props.data,
+              type: 'and',
+            },
+            {
+              column: 'catalogType',
+              termType: 'eq',
+              value: 'device',
+              type: 'and',
+            },
+          ];
+          setParam({
+            ...param,
+            terms: data?.terms ? [...data?.terms, ...terms] : [...terms],
+          });
+        }}
+      />
+      <ProTable<any>
+        actionRef={actionRef}
+        params={param}
+        columns={columns}
+        search={false}
+        headerTitle={'记录列表'}
+        request={async (params) => {
+          return service.queryChannel({ ...params, sorts: [{ name: 'name', order: 'desc' }] });
+        }}
+        rowKey="id"
+      />
+    </PageContainer>
+  );
+};
+export default Logger;

+ 19 - 0
src/pages/rule-engine/Alarm/Log/SolveComponent/index.tsx

@@ -0,0 +1,19 @@
+import { Form, Input, Modal } from 'antd';
+
+const SolveComponent = () => {
+  return (
+    <Modal title="告警处理" visible onOk={() => {}} onCancel={() => {}}>
+      <Form name="basic" layout="vertical" onFinish={() => {}}>
+        <Form.Item
+          label="处理结果"
+          name="username"
+          rules={[{ required: true, message: '请输入处理结果!' }]}
+        >
+          <Input.TextArea rows={8} placeholder="请输入处理结果" />
+        </Form.Item>
+      </Form>
+    </Modal>
+  );
+};
+
+export default SolveComponent;

+ 99 - 0
src/pages/rule-engine/Alarm/Log/SolveLog/index.tsx

@@ -0,0 +1,99 @@
+import SearchComponent from '@/components/SearchComponent';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+import { Modal } from 'antd';
+import { useRef, useState } from 'react';
+import { service } from '@/pages/media/Cascade';
+
+interface Props {
+  data: string;
+  close: () => void;
+}
+
+const SolveLog = (props: Props) => {
+  const [param, setParam] = useState<any>({
+    terms: [
+      {
+        column: 'id',
+        termType: 'cascade_channel$not',
+        value: props.data,
+        type: 'and',
+      },
+      {
+        column: 'catalogType',
+        termType: 'eq',
+        value: 'device',
+        type: 'and',
+      },
+    ],
+    sorts: [
+      {
+        name: 'name',
+        order: 'asc',
+      },
+    ],
+  });
+  const actionRef = useRef<ActionType>();
+
+  const columns: ProColumns<any>[] = [
+    {
+      dataIndex: 'deviceName',
+      title: '处理时间',
+    },
+    {
+      dataIndex: 'name',
+      title: '处理类型',
+    },
+    {
+      dataIndex: 'address',
+      title: '告警时间',
+    },
+    {
+      dataIndex: 'manufacturer',
+      title: '告警处理',
+    },
+  ];
+
+  return (
+    <Modal title={'处理记录'} visible onCancel={props.close} onOk={() => {}} width={1200}>
+      <SearchComponent<any>
+        field={columns}
+        target="bind-channel"
+        enableSave={false}
+        onSearch={(data) => {
+          actionRef.current?.reload();
+          const terms = [
+            {
+              column: 'id',
+              termType: 'cascade_channel$not',
+              value: props.data,
+              type: 'and',
+            },
+            {
+              column: 'catalogType',
+              termType: 'eq',
+              value: 'device',
+              type: 'and',
+            },
+          ];
+          setParam({
+            ...param,
+            terms: data?.terms ? [...data?.terms, ...terms] : [...terms],
+          });
+        }}
+      />
+      <ProTable<any>
+        actionRef={actionRef}
+        params={param}
+        columns={columns}
+        search={false}
+        headerTitle={'记录列表'}
+        request={async (params) => {
+          return service.queryChannel({ ...params, sorts: [{ name: 'name', order: 'desc' }] });
+        }}
+        rowKey="id"
+      />
+    </Modal>
+  );
+};
+export default SolveLog;

+ 135 - 0
src/pages/rule-engine/Alarm/Log/TabComponent/index.less

@@ -0,0 +1,135 @@
+@import '~antd/es/style/themes/default.less';
+
+.ellipsis {
+  width: 100%;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
+
+.alarm-log-card {
+  .alarm-log-item {
+    display: flex;
+    margin-bottom: 20px;
+    box-shadow: 0 2px 16px rgba(0, 0, 0, 0.1);
+
+    .alarm-log-title {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 13%;
+      padding: 10px;
+      color: @primary-color;
+      background-color: #f0f2f3;
+      div {
+        .ellipsis();
+      }
+    }
+    .alarm-log-content {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      width: 87%;
+      padding: 20px;
+      background: url('/images/alarm/background.png') no-repeat;
+      background-size: 100% 100%;
+
+      .alarm-log-image {
+        display: flex;
+        align-items: center;
+
+        .alarm-type {
+          max-width: 120px;
+          .name {
+            color: #000;
+            font-size: 18px;
+          }
+
+          .text {
+            margin-top: 8px;
+            color: #666;
+            font-size: 14px;
+            .ellipsis();
+          }
+        }
+      }
+      .alarm-log-time,
+      .alarm-log-level {
+        max-width: 165px;
+        margin: 0 10px;
+        .log-title {
+          margin-top: 8px;
+          color: #666;
+          font-size: 12px;
+        }
+
+        .context {
+          margin-top: 8px;
+          color: rgba(#000, 0.85);
+          font-size: 14px;
+          .ellipsis();
+        }
+      }
+
+      .alarm-log-status {
+        width: 134px;
+        height: 44px;
+        color: #666;
+        font-size: 16px;
+        line-height: 44px;
+        text-align: center;
+        background-color: #f7f7f7;
+        .icon {
+          margin-right: 10px;
+          color: #666;
+          font-size: 20px;
+        }
+      }
+
+      .status-active {
+        color: #f53434;
+        background-color: rgba(245, 52, 52, 0.08);
+        .icon {
+          color: #f53434;
+        }
+      }
+
+      .alarm-log-actions {
+        .alarm-log-action {
+          display: flex;
+          justify-content: center;
+          width: 72px;
+          height: 72px;
+          background-color: #fff;
+          border: 1px solid @primary-color;
+
+          .btn {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+            width: 72px;
+            height: 72px;
+            .icon {
+              margin-bottom: 5px;
+              color: @primary-color;
+              font-size: 25px;
+            }
+
+            div {
+              color: @primary-color;
+              font-size: 12px;
+            }
+          }
+        }
+        .alarm-log-action:hover {
+          background-color: @primary-color;
+          .icon,
+          div {
+            color: #fff;
+          }
+        }
+      }
+    }
+  }
+}

+ 220 - 0
src/pages/rule-engine/Alarm/Log/TabComponent/index.tsx

@@ -0,0 +1,220 @@
+import SearchComponent from '@/components/SearchComponent';
+import { BellFilled, FileFilled, FileTextFilled, ToolFilled } from '@ant-design/icons';
+import type { ProColumns } from '@jetlinks/pro-table';
+import { Button, Card, Col, Empty, Pagination, Row, Space } from 'antd';
+import classNames from 'classnames';
+import moment from 'moment';
+import { useEffect, useState } from 'react';
+import './index.less';
+import SolveComponent from '../SolveComponent';
+import { AlarmLogModel } from '../model';
+import Service from '../service';
+
+export const service = new Service('alarm/record');
+
+interface Props {
+  type: string;
+}
+const defaultImage = require('/public/images/alarm/log.png');
+
+const imgMap = new Map();
+imgMap.set(1, require('/public/images/alarm/level_1.png'));
+imgMap.set(2, require('/public/images/alarm/level_2.png'));
+imgMap.set(3, require('/public/images/alarm/level_3.png'));
+imgMap.set(4, require('/public/images/alarm/level_4.png'));
+imgMap.set(5, require('/public/images/alarm/level_5.png'));
+
+const TabComponent = (props: Props) => {
+  const columns: ProColumns<any>[] = [
+    {
+      title: '名称',
+      dataIndex: 'name',
+    },
+    // {
+    //     title: '状态',
+    //     dataIndex: 'state',
+    //     valueType: 'select',
+    //     valueEnum: {
+    //         disabled: {
+    //             text: '已停止',
+    //             status: 'disabled',
+    //         },
+    //         enabled: {
+    //             text: '已启动',
+    //             status: 'enabled',
+    //         },
+    //     },
+    // },
+    // {
+    //     title: '说明',
+    //     dataIndex: 'description',
+    // },
+  ];
+
+  const [param, setParam] = useState<any>({ pageSize: 10, terms: [] });
+
+  const [dataSource, setDataSource] = useState<any>({
+    data: [],
+    pageSize: 10,
+    pageIndex: 0,
+    total: 0,
+  });
+
+  const handleSearch = (params: any) => {
+    setParam(params);
+    service
+      .query({
+        ...params,
+        terms: [
+          ...params.terms,
+          {
+            termType: 'eq',
+            column: 'targetType',
+            value: props.type,
+            type: 'and',
+          },
+        ],
+        sorts: [{ name: 'createTime', order: 'desc' }],
+      })
+      .then((resp) => {
+        if (resp.status === 200) {
+          setDataSource(resp.result);
+        }
+      });
+  };
+
+  useEffect(() => {
+    handleSearch(param);
+  }, [props.type]);
+
+  return (
+    <div className="alarm-log-card">
+      <SearchComponent<any>
+        field={columns}
+        target="alarm-log"
+        onSearch={(data) => {
+          const dt = {
+            pageSize: 10,
+            terms: [...data?.terms],
+          };
+          handleSearch(dt);
+        }}
+      />
+      <Card>
+        {dataSource?.data.length > 0 ? (
+          <Row gutter={24} style={{ marginTop: 10 }}>
+            {(dataSource?.data || []).map((item: any, index: number) => (
+              <Col key={item.id} span={24}>
+                <div className="alarm-log-item">
+                  <div className="alarm-log-title">
+                    <div>{item.name}</div>
+                  </div>
+                  <div className="alarm-log-content">
+                    <div className="alarm-log-image">
+                      <img
+                        width={88}
+                        height={88}
+                        src={defaultImage}
+                        alt={''}
+                        style={{ marginRight: 20 }}
+                      />
+                      <div className="alarm-type">
+                        <div className="name">产品</div>
+                        <div className="text">海康烟感A海康烟感A</div>
+                      </div>
+                    </div>
+                    <div className="alarm-log-time">
+                      <div className="log-title">最近告警时间</div>
+                      <div className="context">
+                        {moment(item.timestamp).format('YYYY-MM-DD HH:mm:ss')}
+                      </div>
+                    </div>
+                    <div className="alarm-log-level">
+                      <div className="log-title">告警级别</div>
+                      <div className="context">
+                        <img src={imgMap.get(index + 1)} alt={''} style={{ marginRight: 5 }} />
+                        {item.level}
+                      </div>
+                    </div>
+                    <div
+                      className={classNames(
+                        'alarm-log-status',
+                        item.status.value ? 'status-active' : '',
+                      )}
+                    >
+                      <BellFilled className="icon" />
+                      {item.status.text}
+                    </div>
+                    <div className="alarm-log-actions">
+                      <Space>
+                        <div className="alarm-log-action">
+                          <Button
+                            type={'link'}
+                            onClick={() => {
+                              AlarmLogModel.solveVisible = true;
+                            }}
+                          >
+                            <div className="btn">
+                              <ToolFilled className="icon" />
+                              <div>告警处理</div>
+                            </div>
+                          </Button>
+                        </div>
+                        <div className="alarm-log-action">
+                          <Button type={'link'}>
+                            <div className="btn">
+                              <FileFilled className="icon" />
+                              <div>告警日志</div>
+                            </div>
+                          </Button>
+                        </div>
+                        <div className="alarm-log-action">
+                          <Button type={'link'}>
+                            <div className="btn">
+                              <FileTextFilled className="icon" />
+                              <div>处理记录</div>
+                            </div>
+                          </Button>
+                        </div>
+                      </Space>
+                    </div>
+                  </div>
+                </div>
+              </Col>
+            ))}
+          </Row>
+        ) : (
+          <Empty />
+        )}
+        {dataSource.data.length > 0 && (
+          <div style={{ display: 'flex', marginTop: 20, justifyContent: 'flex-end' }}>
+            <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>
+        )}
+      </Card>
+      {AlarmLogModel.solveVisible && <SolveComponent />}
+    </div>
+  );
+};
+
+export default TabComponent;

+ 6 - 5
src/pages/rule-engine/Alarm/Log/index.tsx

@@ -1,6 +1,7 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import { observer } from '@formily/reactive-react';
 import { AlarmLogModel } from './model';
+import TabComponent from './TabComponent';
 
 const Log = observer(() => {
   const list = [
@@ -13,7 +14,7 @@ const Log = observer(() => {
       tab: '设备',
     },
     {
-      key: 'department',
+      key: 'org',
       tab: '部门',
     },
     {
@@ -23,13 +24,13 @@ const Log = observer(() => {
   ];
   return (
     <PageContainer
-      // onTabChange={(key: 'product' | 'device' | 'department' | 'other') => {
-      //     AlarmLogModel.tab = key
-      // }}
+      onTabChange={(key: string) => {
+        AlarmLogModel.tab = key;
+      }}
       tabList={list}
       tabActiveKey={AlarmLogModel.tab}
     >
-      test
+      <TabComponent type={AlarmLogModel.tab} />
     </PageContainer>
   );
 });

+ 3 - 1
src/pages/rule-engine/Alarm/Log/model.ts

@@ -1,7 +1,9 @@
 import { model } from '@formily/reactive';
 
 export const AlarmLogModel = model<{
-  tab: 'product' | 'device' | 'department' | 'other';
+  tab: string;
+  solveVisible: boolean;
 }>({
   tab: 'product',
+  solveVisible: false,
 });

+ 12 - 0
src/pages/rule-engine/Alarm/Log/service.ts

@@ -0,0 +1,12 @@
+import BaseService from '@/utils/BaseService';
+import { request } from 'umi';
+import SystemConst from '@/utils/const';
+
+class Service extends BaseService<AlarmLogItem> {
+  getTypes = () =>
+    request(`/${SystemConst.API_BASE}/relation/types`, {
+      method: 'GET',
+    });
+}
+
+export default Service;

+ 12 - 0
src/pages/rule-engine/Alarm/Log/typings.d.ts

@@ -0,0 +1,12 @@
+type AlarmLogItem = {
+  name: string;
+  alarmConfigId: string;
+  alarmName: string;
+  targetType: string;
+  targetName: string;
+  targetTypeName: string;
+  alarmDate: number;
+  level: number;
+  description?: string;
+  state: Record<string, any>;
+};