Wzyyy98 3 лет назад
Родитель
Сommit
47c0b3b560

+ 17 - 10
src/components/ProTableCard/CardItems/networkCard.tsx

@@ -5,7 +5,7 @@ import '../index.less';
 import { NetworkItem } from '@/pages/link/Type/typings';
 import { networkMap } from '@/pages/link/Type';
 import { StatusColorEnum } from '@/components/BadgeStatus';
-import { Tooltip } from 'antd';
+// import { Tooltip } from 'antd';
 
 export interface NoticeCardProps extends NetworkItem {
   detail?: React.ReactNode;
@@ -34,14 +34,21 @@ export default (props: NoticeCardProps) => {
             item.configuration?.publicPort || item.configuration?.remotePort
           }`,
       );
+      const str = log
+        .map((item) => {
+          return `${networkMap[record.type]}${item}`;
+        })
+        .join(',');
+
       return (
         <>
-          {log.map((item) => (
-            <div key={item}>
-              {networkMap[record.type]}
-              {item}
-            </div>
-          ))}
+          {/*{[...log, ...log].slice(0, 2).map((item) => (*/}
+          {/*  <div key={item} className={'ellipsis-type-item'}>*/}
+          {/*    {networkMap[record.type]}*/}
+          {/*    {item}*/}
+          {/*  </div>*/}
+          {/*))}*/}
+          {str}
         </>
       );
     }
@@ -79,9 +86,9 @@ export default (props: NoticeCardProps) => {
             </div>
             <div>
               <label>详情</label>
-              <Tooltip title={createDetail()}>
-                <div className={'ellipsis'}>{createDetail()}</div>
-              </Tooltip>
+              {/*<Tooltip title={createDetail()}>*/}
+              <div className={'ellipsis-type'}>{createDetail()}</div>
+              {/*</Tooltip>*/}
             </div>
           </div>
         </div>

+ 12 - 0
src/components/ProTableCard/index.less

@@ -108,6 +108,18 @@
             font-weight: bold;
             font-size: 14px;
           }
+
+          .ellipsis-type {
+            display: -webkit-box;
+            width: 100%;
+            overflow: hidden;
+            font-weight: bold;
+            font-size: 14px;
+            text-overflow: ellipsis;
+            word-break: break-all;
+            -webkit-box-orient: vertical;
+            -webkit-line-clamp: 2;
+          }
         }
       }
     }

+ 2 - 2
src/pages/link/DataCollect/Dashboard/index.tsx

@@ -165,8 +165,8 @@ const DeviceBoard = () => {
         grid: {
           top: '2%',
           bottom: '5%',
-          left: '50px',
-          right: '50px',
+          // left: '50px',
+          // right: '50px',
         },
         series: [
           {

+ 14 - 3
src/pages/link/DataCollect/DataGathering/index.tsx

@@ -10,16 +10,18 @@ import { Empty } from '@/components';
 
 const DataCollectModel = model<{
   id: Partial<string>;
-  type: 'channel' | 'device';
+  type: 'channel' | 'device' | undefined;
   provider: 'OPC_UA' | 'MODBUS_TCP';
   data: any;
   reload: boolean;
+  refresh: boolean;
 }>({
   type: 'channel',
   id: '',
   provider: 'MODBUS_TCP',
   data: {},
   reload: false,
+  refresh: false,
 });
 
 export default observer(() => {
@@ -34,6 +36,7 @@ export default observer(() => {
         type={false}
         id={DataCollectModel.id}
         provider={DataCollectModel.provider}
+        refresh={DataCollectModel.refresh}
       />
     ),
     device: (
@@ -48,16 +51,24 @@ export default observer(() => {
           <div className={styles.left}>
             <ChannelTree
               change={(key, type, provider, data) => {
+                DataCollectModel.type = undefined;
                 DataCollectModel.id = key;
-                DataCollectModel.type = type;
                 DataCollectModel.provider = provider;
                 DataCollectModel.data = data || {};
+                setTimeout(() => {
+                  DataCollectModel.type = type;
+                }, 0);
               }}
               reload={DataCollectModel.reload}
+              onReload={() => {
+                DataCollectModel.refresh = !DataCollectModel.refresh;
+              }}
             />
           </div>
           {DataCollectModel?.id ? (
-            <div className={styles.right}>{obj[DataCollectModel.type]}</div>
+            <div className={styles.right}>
+              {DataCollectModel.type ? obj[DataCollectModel.type] : ''}
+            </div>
           ) : (
             <Empty style={{ marginTop: 100 }} />
           )}

+ 8 - 1
src/pages/link/DataCollect/components/Device/index.tsx

@@ -18,6 +18,7 @@ interface Props {
   id?: any;
   provider?: 'OPC_UA' | 'MODBUS_TCP';
   reload?: () => void;
+  refresh?: boolean;
 }
 
 const CollectorModel = model<{
@@ -177,7 +178,7 @@ export default observer((props: Props) => {
 
   useEffect(() => {
     handleSearch(param);
-  }, [props.id]);
+  }, [props.id, props.refresh]);
 
   const getState = (record: Partial<CollectorItem>) => {
     if (record) {
@@ -273,6 +274,9 @@ export default observer((props: Props) => {
                                 if (resp.status === 200) {
                                   onlyMessage('操作成功!');
                                   handleSearch(param);
+                                  if (props?.reload) {
+                                    props.reload();
+                                  }
                                 } else {
                                   onlyMessage('操作失败!', 'error');
                                 }
@@ -317,6 +321,9 @@ export default observer((props: Props) => {
                                     }),
                                   );
                                   handleSearch(param);
+                                  if (props?.reload) {
+                                    props.reload();
+                                  }
                                 } else {
                                   onlyMessage(
                                     intl.formatMessage({ id: 'pages.device.instance.deleteTip' }),

+ 13 - 8
src/pages/link/DataCollect/components/Point/CollectorCard/index.less

@@ -62,13 +62,12 @@
     .card-item-content {
       display: flex;
       overflow: hidden;
-      .card-item-content-item {
+      .card-item-content-item-left,
+      .card-item-content-item-right {
         flex-direction: row-reverse;
         align-items: flex-start;
         justify-content: space-around;
-        min-width: calc(50% - 40px);
-        max-width: calc(50% - 20px);
-
+        height: 105px;
         .card-item-content-item-empty {
           margin-top: 10px;
           color: rgba(0, 0, 0, 0.45);
@@ -76,7 +75,6 @@
             color: rgba(0, 0, 0, 0.45);
           }
         }
-
         .card-item-content-item-header {
           display: flex;
           align-items: center;
@@ -89,13 +87,13 @@
             font-size: 14px;
           }
           .card-item-content-item-header-title {
-            width: cala(100% - 40px);
+            min-width: cala(100% - 40px);
             color: #000;
             font-size: 20px;
             opacity: 0.85;
           }
           .card-item-content-item-header-action {
-            min-width: 40px;
+            max-width: 40px;
             color: rgba(0, 0, 0, 0.45);
           }
         }
@@ -108,14 +106,21 @@
         .card-item-content-item-tags {
           display: flex;
           margin-top: 10px;
+          //flex-wrap: wrap;
           .card-item-content-item-tag {
-            margin-right: 5px;
+            margin: 0 5px 0 0;
             padding: 3px 12px;
             color: rgba(0, 0, 0, 0.6);
             background: #f3f3f3;
           }
         }
       }
+      .card-item-content-item-left {
+        min-width: 15%;
+      }
+      //.card-item-content-item-right {
+      //
+      //}
       .content-item-border-right {
         height: 50px;
         margin: 20px 20px 0;

+ 68 - 60
src/pages/link/DataCollect/components/Point/CollectorCard/index.tsx

@@ -121,32 +121,30 @@ const CollectorCard = (props: PointCardProps) => {
             </div>
             <div className={'card-item-content'}>
               {dataValue ? (
-                <div className={'card-item-content-item'}>
+                <div className={'card-item-content-item-left'}>
                   <div className={'card-item-content-item-header'}>
                     <div className={'card-item-content-item-header-title'}>
                       <Ellipsis title={`${dataValue?.parseData}(${dataValue?.dataType})`} />
                     </div>
                     <div className={'card-item-content-item-header-action'}>
-                      {item.accessModes?.length === 1 &&
-                        item.accessModes.map((i) => i.value)?.includes('write') && (
-                          <EditOutlined
-                            style={{ marginLeft: 15 }}
-                            onClick={(e) => {
-                              e?.stopPropagation();
-                              props.update(item, true);
-                            }}
-                          />
-                        )}
-                      {item.accessModes?.length === 1 &&
-                        item.accessModes.map((i) => i.value)?.includes('read') && (
-                          <RedoOutlined
-                            style={{ marginLeft: 15 }}
-                            onClick={(e) => {
-                              e?.stopPropagation();
-                              read();
-                            }}
-                          />
-                        )}
+                      {item.accessModes && item.accessModes.map((i) => i.value)?.includes('write') && (
+                        <EditOutlined
+                          style={{ marginLeft: 15 }}
+                          onClick={(e) => {
+                            e?.stopPropagation();
+                            props.update(item, true);
+                          }}
+                        />
+                      )}
+                      {item.accessModes && item.accessModes.map((i) => i.value)?.includes('read') && (
+                        <RedoOutlined
+                          style={{ marginLeft: 15 }}
+                          onClick={(e) => {
+                            e?.stopPropagation();
+                            read();
+                          }}
+                        />
+                      )}
                     </div>
                   </div>
                   <div className={'card-item-content-item-text'}>
@@ -163,57 +161,67 @@ const CollectorCard = (props: PointCardProps) => {
                   </div>
                 </div>
               ) : (
-                <div className={'card-item-content-item'}>
+                <div className={'card-item-content-item-left'}>
                   <div className={'card-item-content-item-empty'}>
                     <span className={'action'} style={{ fontWeight: 600, color: '#000' }}>
                       --
                     </span>
-                    {item.accessModes?.length === 1 &&
-                      item.accessModes.map((i) => i.value)?.includes('write') && (
-                        <EditOutlined
-                          className={'action'}
-                          style={{ marginLeft: 15 }}
-                          onClick={(e) => {
-                            e?.stopPropagation();
-                            props.update(item, true);
-                          }}
-                        />
-                      )}
-                    {item.accessModes?.length === 1 &&
-                      item.accessModes.map((i) => i.value)?.includes('read') && (
-                        <RedoOutlined
-                          style={{ marginLeft: 15 }}
-                          className={'action'}
-                          onClick={(e) => {
-                            e?.stopPropagation();
-                            read();
-                          }}
-                        />
-                      )}
+                    {item.accessModes && item.accessModes.map((i) => i.value)?.includes('write') && (
+                      <EditOutlined
+                        className={'action'}
+                        style={{ marginLeft: 15 }}
+                        onClick={(e) => {
+                          e?.stopPropagation();
+                          props.update(item, true);
+                        }}
+                      />
+                    )}
+                    {item.accessModes && item.accessModes.map((i) => i.value)?.includes('read') && (
+                      <RedoOutlined
+                        style={{ marginLeft: 15 }}
+                        className={'action'}
+                        onClick={(e) => {
+                          e?.stopPropagation();
+                          read();
+                        }}
+                      />
+                    )}
                   </div>
                 </div>
               )}
               <div className={'content-item-border-right'}></div>
-              <div className={'card-item-content-item'}>
+              <div className={'card-item-content-item-right'}>
                 <div className={'card-item-content-item-header'}>
-                  <div className={'card-item-content-item-header-item'}>
-                    <div>
-                      <Ellipsis title={item.configuration?.parameter?.quantity} />
+                  {item.configuration?.parameter?.quantity && (
+                    <div className={'card-item-content-item-header-item'}>
+                      <div>
+                        <Ellipsis title={item.configuration?.parameter?.quantity} />
+                      </div>
+                      <div style={{ width: 85, opacity: 0.75 }} className={'ellipsis'}>
+                        (读取寄存器)
+                      </div>
                     </div>
-                    <div style={{ width: 85, opacity: 0.75 }}>(读取寄存器)</div>
-                  </div>
-                  <div className={'card-item-content-item-header-item'}>
-                    <div>
-                      <Ellipsis title={item.configuration?.parameter?.address} />
+                  )}
+                  {item.configuration?.parameter?.address && (
+                    <div className={'card-item-content-item-header-item'}>
+                      <div>
+                        <Ellipsis title={item.configuration?.parameter?.address} />
+                      </div>
+                      <div style={{ width: 50, opacity: 0.75 }} className={'ellipsis'}>
+                        (地址)
+                      </div>
                     </div>
-                    <div style={{ width: 40, opacity: 0.75 }}>(地址)</div>
-                  </div>
-                  <div className={'card-item-content-item-header-item'}>
-                    <div>
-                      <Ellipsis title={item.configuration?.codec?.configuration?.scaleFactor} />
+                  )}
+                  {item.configuration?.codec?.configuration?.scaleFactor && (
+                    <div className={'card-item-content-item-header-item'}>
+                      <div>
+                        <Ellipsis title={item.configuration?.codec?.configuration?.scaleFactor} />
+                      </div>
+                      <div style={{ width: 72, opacity: 0.75 }} className={'ellipsis'}>
+                        (缩放因子)
+                      </div>
                     </div>
-                    <div style={{ width: 72, opacity: 0.75 }}>(缩放因子)</div>
-                  </div>
+                  )}
                 </div>
                 <div className={'card-item-content-item-tags'}>
                   <div className={'card-item-content-item-tag'}>

+ 3 - 3
src/pages/link/DataCollect/components/Point/Save/BatchUpdate.tsx

@@ -169,10 +169,10 @@ export default (props: Props) => {
           onClick={async () => {
             const value = await form.submit<any>();
             const obj = props.data[0];
-            if (value.accessModes.length) {
+            if (value?.accessModes && value?.accessModes?.length) {
               obj.accessModes = value.accessModes;
             }
-            if (value.features.length) {
+            if (value?.features && value?.features?.length) {
               obj.features = value.features;
             }
             if (Number(value.interval) >= 0) {
@@ -185,7 +185,7 @@ export default (props: Props) => {
               return {
                 ...item,
                 ...obj,
-                pointKey: obj?.configuration?.nodeId,
+                pointKey: item?.pointKey || item?.configuration?.nodeId,
               };
             });
             const response = await service.savePointBatch(arr);

+ 35 - 0
src/pages/link/DataCollect/components/Point/Save/components/RemoveData.tsx

@@ -0,0 +1,35 @@
+import { DeleteOutlined } from '@ant-design/icons';
+import { ArrayItems } from '@formily/antd';
+import { Popconfirm } from 'antd';
+import { useField } from '@formily/react';
+
+interface Props {
+  onDelete: (item: any) => void;
+}
+
+const RemoveData = (props: Props) => {
+  const index = ArrayItems.useIndex!();
+  const self = useField();
+  const array = ArrayItems.useArray!();
+  const item = ArrayItems.useRecord!()();
+  if (!array) return null;
+  if (array.field?.pattern !== 'editable') return null;
+
+  return (
+    <div>
+      <Popconfirm
+        title={'确认删除'}
+        onConfirm={() => {
+          if (self?.disabled) return;
+          array.field?.remove?.(index);
+          array.props?.onRemove?.(index);
+          props.onDelete(item);
+        }}
+      >
+        <DeleteOutlined />
+      </Popconfirm>
+    </div>
+  );
+};
+
+export default RemoveData;

+ 2 - 2
src/pages/link/DataCollect/components/Point/Save/modbus.tsx

@@ -83,10 +83,10 @@ export default (props: Props) => {
           message: '最多可输入64个字符',
         };
       }
-      if (!(Number(value) % 1 === 0) || Number(value) <= 0) {
+      if (!(Number(value) % 1 === 0) || Number(value) < 0) {
         return {
           type: 'error',
-          message: '请输入0正整数',
+          message: '请输入0正整数',
         };
       }
       return '';

+ 3 - 10
src/pages/link/DataCollect/components/Point/Save/opc-ua.tsx

@@ -83,10 +83,10 @@ export default (props: Props) => {
           message: '最多可输入64个字符',
         };
       }
-      if (!(value % 1 === 0)) {
+      if (!(Number(value) % 1 === 0) || Number(value) < 0) {
         return {
           type: 'error',
-          message: '请输入0正整数',
+          message: '请输入0正整数',
         };
       }
       return '';
@@ -165,17 +165,10 @@ export default (props: Props) => {
               gridSpan: 2,
             },
             default: 3000,
-            // 'x-reactions': {
-            //   dependencies: ['..accessModes'],
-            //   fulfill: {
-            //     state: {
-            //       visible: '{{($deps[0] || []).includes("subscribe")}}',
-            //     },
-            //   },
-            // },
             'x-component-props': {
               placeholder: '请输入采集频率',
               addonAfter: '毫秒',
+              stringMode: true,
               style: {
                 width: '100%',
               },

+ 165 - 83
src/pages/link/DataCollect/components/Point/Save/scan.tsx

@@ -1,10 +1,10 @@
-import { Button, Col, Modal, Row, Spin, Tree } from 'antd';
-import { useEffect, useState } from 'react';
+import { Button, Col, Modal, Row, Spin, Tree, Checkbox } from 'antd';
+import { useEffect, useMemo, useState } from 'react';
 import service from '@/pages/link/DataCollect/service';
 import './scan.less';
 import { onlyMessage } from '@/utils/util';
 import { createSchemaField, FormProvider } from '@formily/react';
-import { ArrayTable, Editable, FormItem, Input, NumberPicker } from '@formily/antd';
+import { ArrayTable, FormItem, Input, Select } from '@formily/antd';
 import MyInput from '@/pages/link/DataCollect/components/Point/Save/components/MyInput';
 import MySelect from '@/pages/link/DataCollect/components/Point/Save/components/MySelect';
 import {
@@ -14,6 +14,7 @@ import {
   onFieldValueChange,
   registerValidateRules,
 } from '@formily/core';
+import RemoveData from './components/RemoveData';
 
 interface Props {
   collector?: any;
@@ -25,10 +26,11 @@ interface Props {
 export default (props: Props) => {
   const [checkedKeys, setCheckedKeys] = useState<any>();
   const [treeData, setTreeData] = useState<any[]>([]);
+  const [treeAllData, setTreeAllData] = useState<any[]>([]);
   const [loading, setLoading] = useState<boolean>(false);
   const [spinning, setSpinning] = useState<boolean>(false);
-  const [dataSource, setDataSource] = useState<any[]>([]);
   const [selectKeys, setSelectKeys] = useState<any[]>([]);
+  const [isSelected, setIsSelected] = useState<boolean>(false);
 
   useEffect(() => {
     setSelectKeys(props.data.map((item) => item.pointKey));
@@ -51,7 +53,7 @@ export default (props: Props) => {
                 disabled: item?.folder,
               };
             });
-            setTreeData(list);
+            setTreeAllData(list);
           }
           setLoading(false);
         });
@@ -97,7 +99,7 @@ export default (props: Props) => {
             isLeaf: !item?.folder,
           };
         });
-        setTreeData((origin) => updateTreeData(origin, node.key, [...list]));
+        setTreeAllData((origin) => updateTreeData(origin, node.key, [...list]));
       }
       resolve();
     });
@@ -105,62 +107,66 @@ export default (props: Props) => {
   const SchemaField = createSchemaField({
     components: {
       FormItem,
-      Editable,
       Input,
       ArrayTable,
-      NumberPicker,
       MyInput,
       MySelect,
+      RemoveData,
+      Select,
     },
   });
 
-  const form = createForm({
-    initialValues: { array: [] },
-    effects: () => {
-      onFieldValueChange('array.*.accessModes', (field, f) => {
-        const nextPath = FormPath.transform(field.path, /\d+/, (index) => {
-          return `array.${parseInt(index + 1)}.accessModes`;
-        });
-        const nextValue = (field.query(nextPath).take() as Field)?.value;
-        if (nextValue && nextValue.check) {
-          f.setFieldState(nextPath, (state) => {
-            state.value = {
-              ...field.value,
-              check: nextValue.check,
-            };
+  const form = useMemo(
+    () =>
+      createForm({
+        initialValues: { array: [] },
+        effects: () => {
+          onFieldValueChange('array.*.accessModes', (field, f) => {
+            const nextPath = FormPath.transform(field.path, /\d+/, (index) => {
+              return `array.${parseInt(index + 1)}.accessModes`;
+            });
+            const nextValue = (field.query(nextPath).take() as Field)?.value;
+            if (nextValue && nextValue.check) {
+              f.setFieldState(nextPath, (state) => {
+                state.value = {
+                  ...field.value,
+                  check: nextValue.check,
+                };
+              });
+            }
           });
-        }
-      });
-      onFieldValueChange('array.*.configuration.interval', (field, f) => {
-        const nextPath = FormPath.transform(field.path, /\d+/, (index) => {
-          return `array.${parseInt(index + 1)}.configuration.interval`;
-        });
-        const nextValue = (field.query(nextPath).take() as Field)?.value;
-        if (nextValue && nextValue.check) {
-          f.setFieldState(nextPath, (state) => {
-            state.value = {
-              ...field.value,
-              check: nextValue.check,
-            };
+          onFieldValueChange('array.*.configuration.interval', (field, f) => {
+            const nextPath = FormPath.transform(field.path, /\d+/, (index) => {
+              return `array.${parseInt(index + 1)}.configuration.interval`;
+            });
+            const nextValue = (field.query(nextPath).take() as Field)?.value;
+            if (nextValue && nextValue.check) {
+              f.setFieldState(nextPath, (state) => {
+                state.value = {
+                  ...field.value,
+                  check: nextValue.check,
+                };
+              });
+            }
           });
-        }
-      });
-      onFieldValueChange('array.*.features', (field, f) => {
-        const nextPath = FormPath.transform(field.path, /\d+/, (index) => {
-          return `array.${parseInt(index + 1)}.features`;
-        });
-        const nextValue = (field.query(nextPath).take() as Field)?.value;
-        if (nextValue && nextValue.check) {
-          f.setFieldState(nextPath, (state) => {
-            state.value = {
-              ...field.value,
-              check: nextValue.check,
-            };
+          onFieldValueChange('array.*.features', (field, f) => {
+            const nextPath = FormPath.transform(field.path, /\d+/, (index) => {
+              return `array.${parseInt(index + 1)}.features`;
+            });
+            const nextValue = (field.query(nextPath).take() as Field)?.value;
+            if (nextValue && nextValue.check) {
+              f.setFieldState(nextPath, (state) => {
+                state.value = {
+                  ...field.value,
+                  check: nextValue.check,
+                };
+              });
+            }
           });
-        }
-      });
-    },
-  });
+        },
+      }),
+    [],
+  );
 
   registerValidateRules({
     checkLength(value) {
@@ -179,14 +185,13 @@ export default (props: Props) => {
       return '';
     },
     checkAccessModes(value) {
-      if (value.value && value.value.length) {
-        return '';
-      } else {
+      if (!value?.value.length) {
         return {
           type: 'error',
           message: '请选择访问类型',
         };
       }
+      return '';
     },
   });
 
@@ -210,8 +215,8 @@ export default (props: Props) => {
               'x-component-props': { title: '名称' },
               properties: {
                 name: {
-                  type: 'string',
                   'x-component': 'Input',
+                  'x-decorator': 'FormItem',
                   'x-component-props': {
                     placeholder: '请输入点位名称',
                   },
@@ -249,6 +254,7 @@ export default (props: Props) => {
               properties: {
                 accessModes: {
                   'x-component': 'MySelect',
+                  'x-decorator': 'FormItem',
                   'x-component-props': {
                     placeholder: '请选择访问类型',
                     mode: 'multiple',
@@ -308,40 +314,67 @@ export default (props: Props) => {
                 },
               },
             },
+            column6: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {
+                title: '操作',
+                dataIndex: 'operations',
+                width: 50,
+              },
+              properties: {
+                item: {
+                  type: 'void',
+                  'x-component': 'FormItem',
+                  properties: {
+                    remove: {
+                      type: 'number',
+                      'x-component': 'RemoveData',
+                      'x-component-props': {
+                        onDelete: (item: any) => {
+                          setCheckedKeys({
+                            ...checkedKeys,
+                            checked: checkedKeys.checked.filter((i: any) => {
+                              return i !== item.id;
+                            }),
+                          });
+                        },
+                      },
+                    },
+                  },
+                },
+              },
+            },
           },
         },
       },
     },
   };
 
-  useEffect(() => {
-    const one = (dataSource || [])[0];
-    form.setValues({
-      array: (dataSource || []).map((item) => {
+  const handleData = (arr: any[]): any[] => {
+    const data = arr.filter((item) => {
+      return (isSelected && !selectKeys.includes(item.id)) || !isSelected;
+    });
+    return data.map((item) => {
+      if (item.children && item.children?.length) {
         return {
-          ...one,
-          features: {
-            value: (one?.features || []).includes('changedOnly'),
-            check: true,
-          },
-          id: item.id,
-          name: item.name,
-          accessModes: {
-            value: one?.accessModes || [],
-            check: true,
-          },
-          configuration: {
-            ...one.configuration,
-            interval: {
-              value: one?.configuration?.interval || 3000,
-              check: true,
-            },
-            nodeId: item.id,
-          },
+          ...item,
+          children: handleData(item.children),
         };
-      }),
+      } else {
+        return item;
+      }
     });
-  }, [dataSource]);
+  };
+
+  useEffect(() => {
+    if (isSelected) {
+      const arr = handleData(treeAllData);
+      setTreeData(arr);
+    } else {
+      setTreeData(treeAllData);
+    }
+  }, [isSelected, treeAllData]);
 
   return (
     <Modal
@@ -359,7 +392,7 @@ export default (props: Props) => {
           key={2}
           loading={spinning}
           onClick={async () => {
-            const data: any = await form.submit();
+            const data = await form.submit<any>();
             const arr = data?.array || [];
             if (!arr.length) {
               onlyMessage('请选择目标数据', 'error');
@@ -395,6 +428,17 @@ export default (props: Props) => {
       <Spin spinning={loading}>
         <Row gutter={24}>
           <Col span={6}>
+            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
+              <h4>数据源</h4>
+              <Checkbox
+                checked={isSelected}
+                onChange={(e) => {
+                  setIsSelected(e.target.checked);
+                }}
+              >
+                隐藏已有节点
+              </Checkbox>
+            </div>
             <div style={{ padding: '10px 0' }}>
               <Tree
                 blockNode
@@ -406,7 +450,45 @@ export default (props: Props) => {
                 loadData={onLoadData}
                 onCheck={(checkedKeysValue, info) => {
                   setCheckedKeys(checkedKeysValue);
-                  setDataSource(info.checkedNodes);
+                  const one: any = { ...info.node };
+                  const list = form.getState().values?.array || [];
+                  if (info.checked) {
+                    const last: any = list.length ? list[list.length - 1] : undefined;
+                    if (list.map((i: any) => i?.id).includes(one.id)) {
+                      return;
+                    }
+                    const item = {
+                      features: {
+                        value: last
+                          ? last?.features?.value
+                          : (one?.features || []).includes('changedOnly'),
+                        check: true,
+                      },
+                      id: one?.id || '',
+                      name: one?.name || '',
+                      accessModes: {
+                        value: last ? last?.accessModes?.value : one?.accessModes || [],
+                        check: true,
+                      },
+                      configuration: {
+                        ...one?.configuration,
+                        interval: {
+                          value: last
+                            ? last?.configuration?.interval?.value
+                            : one?.configuration?.interval || 3000,
+                          check: true,
+                        },
+                        nodeId: one?.id,
+                      },
+                    };
+                    form.setValues({
+                      array: [...list, item],
+                    });
+                  } else {
+                    form.setValues({
+                      array: list.filter((item: any) => item?.id !== one.id),
+                    });
+                  }
                 }}
                 titleRender={(nodeData) => {
                   return (

+ 256 - 170
src/pages/link/DataCollect/components/Point/index.tsx

@@ -6,7 +6,7 @@ import { useDomFullHeight } from '@/hooks';
 import service from '@/pages/link/DataCollect/service';
 import CollectorCard from './CollectorCard/index';
 import { Empty, PermissionButton } from '@/components';
-import { Card, Col, Pagination, Row } from 'antd';
+import { Button, Card, Checkbox, Col, Dropdown, Menu, Pagination, Row } from 'antd';
 import { model } from '@formily/reactive';
 import ModbusSave from '@/pages/link/DataCollect/components/Point/Save/modbus';
 import Scan from '@/pages/link/DataCollect/components/Point/Save/scan';
@@ -16,6 +16,7 @@ import OpcSave from '@/pages/link/DataCollect/components/Point/Save/opc-ua';
 import WritePoint from '@/pages/link/DataCollect/components/Point/CollectorCard/WritePoint';
 import BatchUpdate from './Save/BatchUpdate';
 import { onlyMessage } from '@/utils/util';
+import { DeleteOutlined, EditOutlined, RedoOutlined } from '@ant-design/icons';
 interface Props {
   type: boolean; // true: 综合查询  false: 数据采集
   data?: Partial<CollectorItem>;
@@ -27,6 +28,7 @@ interface PointCardProps {
   data?: Partial<CollectorItem>;
   provider?: 'OPC_UA' | 'MODBUS_TCP';
   reload: boolean; // 变化时刷新
+  columns: ProColumns<PointItem>[];
 }
 
 const PointModel = model<{
@@ -40,6 +42,7 @@ const PointModel = model<{
   list: any[];
   selectKey: string[];
   arr: any[];
+  checkAll: boolean;
 }>({
   m_visible: false,
   p_visible: false,
@@ -51,6 +54,7 @@ const PointModel = model<{
   list: [],
   selectKey: [],
   arr: [],
+  checkAll: false,
 });
 
 const PointCard = observer((props: PointCardProps) => {
@@ -82,157 +86,6 @@ const PointCard = observer((props: PointCardProps) => {
     }
   };
 
-  const columns: ProColumns<PointItem>[] = props.type
-    ? [
-        {
-          title: '名称',
-          dataIndex: 'name',
-        },
-        {
-          title: '通讯协议',
-          dataIndex: 'provider',
-          valueType: 'select',
-          valueEnum: {
-            OPC_UA: {
-              text: 'OPC_UA',
-              status: 'OPC_UA',
-            },
-            MODBUS_TCP: {
-              text: 'MODBUS_TCP',
-              status: 'MODBUS_TCP',
-            },
-          },
-        },
-        {
-          title: '访问类型',
-          dataIndex: 'accessModes',
-          valueType: 'select',
-          valueEnum: {
-            read: {
-              text: '读',
-              status: 'read',
-            },
-            write: {
-              text: '写',
-              status: 'write',
-            },
-            subscribe: {
-              text: '订阅',
-              status: 'subscribe',
-            },
-          },
-        },
-        {
-          title: '状态',
-          dataIndex: 'state',
-          valueType: 'select',
-          valueEnum: {
-            enabled: {
-              text: '正常',
-              status: 'enabled',
-            },
-            disabled: {
-              text: '禁用',
-              status: 'disabled',
-            },
-          },
-        },
-        {
-          title: '运行状态',
-          dataIndex: 'runningState',
-          valueType: 'select',
-          valueEnum: {
-            running: {
-              text: '运行中',
-              status: 'running',
-            },
-            partialError: {
-              text: '部分错误',
-              status: 'partialError',
-            },
-            failed: {
-              text: '错误',
-              status: 'failed',
-            },
-            stopped: {
-              text: '已停止',
-              status: 'stopped',
-            },
-          },
-        },
-        {
-          title: '说明',
-          dataIndex: 'description',
-        },
-      ]
-    : [
-        {
-          title: '名称',
-          dataIndex: 'name',
-        },
-        {
-          title: '访问类型',
-          dataIndex: 'accessModes',
-          valueType: 'select',
-          valueEnum: {
-            read: {
-              text: '读',
-              status: 'read',
-            },
-            write: {
-              text: '写',
-              status: 'write',
-            },
-            subscribe: {
-              text: '订阅',
-              status: 'subscribe',
-            },
-          },
-        },
-        {
-          title: '状态',
-          dataIndex: 'state',
-          valueType: 'select',
-          valueEnum: {
-            enabled: {
-              text: '正常',
-              status: 'enabled',
-            },
-            disabled: {
-              text: '禁用',
-              status: 'disabled',
-            },
-          },
-        },
-        {
-          title: '运行状态',
-          dataIndex: 'runningState',
-          valueType: 'select',
-          valueEnum: {
-            running: {
-              text: '运行中',
-              status: 'running',
-            },
-            partialError: {
-              text: '部分错误',
-              status: 'partialError',
-            },
-            failed: {
-              text: '错误',
-              status: 'failed',
-            },
-            stopped: {
-              text: '已停止',
-              status: 'stopped',
-            },
-          },
-        },
-        {
-          title: '说明',
-          dataIndex: 'description',
-        },
-      ];
-
   const subRef = useRef<any>(null);
 
   const subscribeProperty = (list: any) => {
@@ -250,6 +103,9 @@ const PointCard = observer((props: PointCardProps) => {
       });
   };
   const handleSearch = (params: any) => {
+    PointModel.checkAll = false;
+    PointModel.selectKey = [];
+    PointModel.list = [];
     setLoading(true);
     setParam(params);
     service
@@ -283,10 +139,62 @@ const PointCard = observer((props: PointCardProps) => {
     };
   }, []);
 
+  const menu = (
+    <Menu>
+      <Menu.Item key="1">
+        <PermissionButton
+          isPermission={permission.update}
+          icon={<EditOutlined />}
+          onClick={() => {
+            if (PointModel.list.length) {
+              PointModel.batch_visible = true;
+            } else {
+              onlyMessage('请选择点位', 'error');
+            }
+          }}
+          key="batch"
+        >
+          编辑
+        </PermissionButton>
+      </Menu.Item>
+      <Menu.Item key="2">
+        <PermissionButton
+          key="delete"
+          isPermission={permission.delete}
+          icon={<DeleteOutlined />}
+          popConfirm={{
+            title: '确认删除?',
+            onConfirm: async () => {
+              const resp = await service.batchDeletePoint(PointModel.selectKey);
+              if (resp.status === 200) {
+                handleSearch(param);
+                onlyMessage('操作成功!');
+              }
+            },
+          }}
+        >
+          删除
+        </PermissionButton>
+      </Menu.Item>
+      <Menu.Item key="3">
+        <Button
+          icon={<RedoOutlined />}
+          onClick={() => {
+            PointModel.selectKey = [];
+            PointModel.list = [];
+            PointModel.checkAll = false;
+          }}
+        >
+          取消
+        </Button>
+      </Menu.Item>
+    </Menu>
+  );
+
   return (
     <div>
       <SearchComponent<PointItem>
-        field={columns}
+        field={props.columns}
         target="data-collect-point"
         onSearch={(data) => {
           const dt = {
@@ -303,6 +211,7 @@ const PointCard = observer((props: PointCardProps) => {
               <div style={{ width: '100%', display: 'flex', justifyContent: 'flex-start' }}>
                 <PermissionButton
                   isPermission={permission.add}
+                  style={{ marginRight: 10 }}
                   onClick={async () => {
                     if (props.provider === 'OPC_UA') {
                       const resp = await service.queryPointNoPaging({ paging: false });
@@ -321,30 +230,40 @@ const PointCard = observer((props: PointCardProps) => {
                   {props?.provider === 'OPC_UA' ? '扫描' : '新增'}
                 </PermissionButton>
                 {props.provider === 'OPC_UA' && (
-                  <PermissionButton
-                    style={{ marginLeft: 15 }}
-                    isPermission={permission.update}
-                    onClick={() => {
-                      if (PointModel.list.length) {
-                        PointModel.batch_visible = true;
-                      } else {
-                        onlyMessage('请选择点位', 'error');
-                      }
-                    }}
-                    key="batch"
-                  >
-                    批量编辑
-                  </PermissionButton>
+                  <Dropdown key={'more'} overlay={menu} placement="bottom">
+                    <Button>批量操作</Button>
+                  </Dropdown>
                 )}
               </div>
             )}
             {dataSource?.data.length ? (
               <>
-                <Row gutter={[18, 18]} style={{ marginTop: 10 }}>
+                {props.provider === 'OPC_UA' && (
+                  <div style={{ margin: '20px 0' }}>
+                    <Checkbox
+                      checked={PointModel.checkAll}
+                      onChange={(e) => {
+                        PointModel.checkAll = e.target.checked;
+                        if (e.target.checked) {
+                          PointModel.selectKey = [...dataSource?.data.map((item: any) => item.id)];
+                          PointModel.list = [...dataSource?.data];
+                        } else {
+                          PointModel.selectKey = [];
+                          PointModel.list = [];
+                        }
+                      }}
+                    >
+                      全选
+                    </Checkbox>
+                  </div>
+                )}
+                <Row gutter={[24, 24]} style={{ marginTop: 10 }}>
                   {(dataSource?.data || []).map((record: any) => (
                     <Col
                       key={record.id}
-                      span={12}
+                      xl={props.type ? 12 : 24}
+                      xxl={12}
+                      span={24}
                       onClick={() => {
                         if (props?.provider === 'OPC_UA') {
                           if (PointModel.selectKey.includes(record.id)) {
@@ -356,6 +275,11 @@ const PointCard = observer((props: PointCardProps) => {
                             PointModel.selectKey.push(record.id);
                             PointModel.list.push(record);
                           }
+                          if (PointModel.selectKey.length === dataSource.data.length) {
+                            PointModel.checkAll = true;
+                          } else {
+                            PointModel.checkAll = false;
+                          }
                         }
                       }}
                     >
@@ -427,9 +351,171 @@ const PointCard = observer((props: PointCardProps) => {
 });
 
 export default observer((props: Props) => {
+  const columns: ProColumns<PointItem>[] = props.type
+    ? [
+        {
+          title: '名称',
+          dataIndex: 'name',
+        },
+        {
+          title: '通讯协议',
+          dataIndex: 'provider',
+          valueType: 'select',
+          valueEnum: {
+            OPC_UA: {
+              text: 'OPC_UA',
+              status: 'OPC_UA',
+            },
+            MODBUS_TCP: {
+              text: 'MODBUS_TCP',
+              status: 'MODBUS_TCP',
+            },
+          },
+        },
+        {
+          title: '访问类型',
+          dataIndex: 'accessModes',
+          valueType: 'select',
+          valueEnum: {
+            read: {
+              text: '读',
+              status: 'read',
+            },
+            write: {
+              text: '写',
+              status: 'write',
+            },
+            subscribe: {
+              text: '订阅',
+              status: 'subscribe',
+            },
+          },
+        },
+        {
+          title: '状态',
+          dataIndex: 'state',
+          valueType: 'select',
+          valueEnum: {
+            enabled: {
+              text: '正常',
+              status: 'enabled',
+            },
+            disabled: {
+              text: '禁用',
+              status: 'disabled',
+            },
+          },
+        },
+        {
+          title: '运行状态',
+          dataIndex: 'runningState',
+          valueType: 'select',
+          valueEnum: {
+            running: {
+              text: '运行中',
+              status: 'running',
+            },
+            partialError: {
+              text: '部分错误',
+              status: 'partialError',
+            },
+            failed: {
+              text: '错误',
+              status: 'failed',
+            },
+            stopped: {
+              text: '已停止',
+              status: 'stopped',
+            },
+          },
+        },
+        {
+          title: '说明',
+          dataIndex: 'description',
+        },
+      ]
+    : [
+        {
+          title: '名称',
+          dataIndex: 'name',
+        },
+        {
+          title: '访问类型',
+          dataIndex: 'accessModes',
+          valueType: 'select',
+          valueEnum:
+            props?.provider === 'MODBUS_TCP'
+              ? {
+                  read: {
+                    text: '读',
+                    status: 'read',
+                  },
+                  write: {
+                    text: '写',
+                    status: 'write',
+                  },
+                }
+              : {
+                  read: {
+                    text: '读',
+                    status: 'read',
+                  },
+                  write: {
+                    text: '写',
+                    status: 'write',
+                  },
+                  subscribe: {
+                    text: '订阅',
+                    status: 'subscribe',
+                  },
+                },
+        },
+        {
+          title: '状态',
+          dataIndex: 'state',
+          valueType: 'select',
+          valueEnum: {
+            enabled: {
+              text: '正常',
+              status: 'enabled',
+            },
+            disabled: {
+              text: '禁用',
+              status: 'disabled',
+            },
+          },
+        },
+        {
+          title: '运行状态',
+          dataIndex: 'runningState',
+          valueType: 'select',
+          valueEnum: {
+            running: {
+              text: '运行中',
+              status: 'running',
+            },
+            partialError: {
+              text: '部分错误',
+              status: 'partialError',
+            },
+            failed: {
+              text: '错误',
+              status: 'failed',
+            },
+            stopped: {
+              text: '已停止',
+              status: 'stopped',
+            },
+          },
+        },
+        {
+          title: '说明',
+          dataIndex: 'description',
+        },
+      ];
   return (
     <div>
-      <PointCard {...props} reload={PointModel.reload} />
+      <PointCard columns={columns} {...props} reload={PointModel.reload} />
       {PointModel.m_visible && (
         <ModbusSave
           data={PointModel.current}

+ 3 - 1
src/pages/link/DataCollect/components/Tree/index.tsx

@@ -46,6 +46,7 @@ interface Props {
     data?: any,
   ) => void;
   reload?: boolean;
+  onReload: () => void;
 }
 
 export default observer((props: Props) => {
@@ -245,6 +246,7 @@ export default observer((props: Props) => {
                                   if (resp.status === 200) {
                                     TreeModel.param = {};
                                     handleSearch(TreeModel.param);
+                                    props.onReload();
                                     onlyMessage('操作成功');
                                   } else {
                                     onlyMessage('操作失败!', 'error');
@@ -260,7 +262,7 @@ export default observer((props: Props) => {
                                 </Tooltip>
                               </Popconfirm>
                               <Popconfirm
-                                title={'该操作将会删除下属采集器与点位,确定删除?'}
+                                title={'该操作将会删除下属点位,确定删除?'}
                                 onConfirm={async () => {
                                   const resp = await service.removeCollector(i.id);
                                   if (resp.status === 200) {

+ 5 - 0
src/pages/link/DataCollect/service.ts

@@ -164,6 +164,11 @@ class Service {
       method: 'POST',
       data,
     });
+  public batchDeletePoint = (params: any) =>
+    request(`/${SystemConst.API_BASE}/data-collect/point/batch/_delete`, {
+      method: 'POST',
+      data: params,
+    });
 }
 
 const service = new Service();

+ 4 - 0
src/pages/link/Protocol/save/index.tsx

@@ -20,6 +20,7 @@ interface Props {
 const Save = (props: Props) => {
   const [data, setData] = useState<ProtocolItem | undefined>(props.data);
   const { permission } = PermissionButton.usePermission('link/Protocol');
+  const [loading, setLoading] = useState(false);
   // const [count, setCount] = useState<number>(0);
 
   useEffect(() => {
@@ -245,9 +246,11 @@ const Save = (props: Props) => {
 
   const save = async () => {
     const value = await form.submit<ProtocolItem>();
+    setLoading(true);
     const response: any = props.data?.id
       ? await service.savePatch({ ...props.data, ...value })
       : await service.save({ ...props.data, ...value });
+    setLoading(false);
     if (response && response?.status === 200) {
       onlyMessage('操作成功');
       props.reload();
@@ -272,6 +275,7 @@ const Save = (props: Props) => {
         <Button
           type="primary"
           key={2}
+          loading={loading}
           onClick={() => {
             save();
           }}