lind пре 3 година
родитељ
комит
9e92cff4f9

+ 4 - 4
src/pages/device/Instance/Detail/Config/index.tsx

@@ -7,7 +7,7 @@ import {
   CheckOutlined,
   EditOutlined,
   QuestionCircleOutlined,
-  UndoOutlined,
+  SyncOutlined,
 } from '@ant-design/icons';
 import Edit from './Edit';
 import { PermissionButton } from '@/components';
@@ -66,14 +66,14 @@ const Config = () => {
       if (isExit(item.property)) {
         return (
           <div>
-            <span style={{ marginRight: '10px' }}>{config[item.property]}</span>
+            <span style={{ marginRight: '10px' }}>{config[item.property] || '--'}</span>
             <Tooltip title={`有效值:${config[item.property]}`}>
               <QuestionCircleOutlined />
             </Tooltip>
           </div>
         );
       } else {
-        return <div>{config[item.property]}</div>;
+        return <div>{config[item.property] || '--'}</div>;
       }
     } else {
       return '--';
@@ -132,7 +132,7 @@ const Config = () => {
               type="link"
               isPermission={permission.update}
             >
-              <UndoOutlined />
+              <SyncOutlined />
               恢复默认
               <Tooltip
                 title={`该设备单独编辑过配置信息,点击此将恢复成默认的配置信息,请谨慎操作。`}

+ 11 - 8
src/pages/device/Instance/Detail/Diagnose/Status/index.tsx

@@ -74,6 +74,7 @@ const Status = observer((props: Props) => {
   const productPermission = PermissionButton.usePermission('device/Product').permission;
   const networkPermission = PermissionButton.usePermission('link/Type').permission;
   const devicePermission = PermissionButton.usePermission('device/Instance').permission;
+
   const [diagnoseVisible, setDiagnoseVisible] = useState<boolean>(false);
   const [artificialVisible, setArtificialVisible] = useState<boolean>(false);
   const [diagnoseData, setDiagnoseData] = useState<any>({});
@@ -676,15 +677,17 @@ const Status = observer((props: Props) => {
   };
 
   useEffect(() => {
-    if (!props.flag) {
-      handleSearch();
-    } else {
-      const dt = Store.get('diagnose-status');
-      DiagnoseStatusModel.status = dt?.status;
-      DiagnoseStatusModel.list = dt?.list || [];
-      props.onChange('success');
+    if (devicePermission.view) {
+      if (!props.flag) {
+        handleSearch();
+      } else {
+        const dt = Store.get('diagnose-status');
+        DiagnoseStatusModel.status = dt?.status;
+        DiagnoseStatusModel.list = dt?.list || [];
+        props.onChange('success');
+      }
     }
-  }, []);
+  }, [devicePermission]);
 
   return (
     <Row gutter={24}>

+ 65 - 26
src/pages/device/Instance/Detail/MetadataMap/index.tsx

@@ -5,6 +5,8 @@ import EditableTable from './EditableTable';
 import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 import type { ProductItem } from '@/pages/device/Product/typings';
 import { useParams } from 'umi';
+import { PermissionButton } from '@/components';
+
 interface Props {
   type: 'device' | 'product';
 }
@@ -14,6 +16,7 @@ const MetadataMap = (props: Props) => {
   const [product, setProduct] = useState<Partial<ProductItem>>();
   const [data, setData] = useState<any>({});
   const params = useParams<{ id: string }>();
+  const { permission } = PermissionButton.usePermission('device/Product');
 
   const handleSearch = async () => {
     if (props.type === 'product') {
@@ -45,33 +48,22 @@ const MetadataMap = (props: Props) => {
   };
 
   const renderComponent = () => {
+    const metadata = JSON.parse(product?.metadata || '{}');
+    const dmetadata = JSON.parse(data?.metadata || '{}');
     if (product) {
-      if (!product.accessId) {
-        return (
-          <Empty
-            description={
-              <span>
-                请配置对应产品的
-                <a
-                  onClick={() => {
-                    checkUrl('access');
-                  }}
-                >
-                  设备接入方式
-                </a>
-              </span>
-            }
-          />
-        );
-      } else {
-        const metadata = JSON.parse(product?.metadata || '{}');
-        const dmetadata = JSON.parse(data?.metadata || '{}');
-        if (
-          (type === 'device' &&
-            (metadata?.properties || []).length === 0 &&
-            (dmetadata?.properties || []).length === 0) ||
-          (type === 'product' && (dmetadata?.properties || []).length === 0)
-        ) {
+      const flag =
+        (type === 'device' &&
+          (metadata?.properties || []).length === 0 &&
+          (dmetadata?.properties || []).length === 0) ||
+        (type === 'product' && (dmetadata?.properties || []).length === 0);
+      if (!product.accessId && flag) {
+        if (!permission.update) {
+          return (
+            <Empty
+              description={<span>请联系管理员配置物模型属性,并选择对应产品的设备接入方式</span>}
+            />
+          );
+        } else {
           return (
             <Empty
               description={
@@ -84,11 +76,58 @@ const MetadataMap = (props: Props) => {
                   >
                     物模型属性
                   </a>
+                  ,并选择对应产品的
+                  <a
+                    onClick={() => {
+                      checkUrl('access');
+                    }}
+                  >
+                    设备接入方式
+                  </a>
                 </span>
               }
             />
           );
         }
+      } else if (flag && product.accessId) {
+        return (
+          <Empty
+            description={
+              !permission.update ? (
+                <span>请联系管理员配置物模型属性</span>
+              ) : (
+                <span>
+                  请配置对应产品的
+                  <a
+                    onClick={() => {
+                      checkUrl('metadata');
+                    }}
+                  >
+                    物模型属性
+                  </a>
+                </span>
+              )
+            }
+          />
+        );
+      } else if (!flag && !product.accessId) {
+        return (
+          <Empty
+            description={
+              <span>
+                请选择对应产品的
+                <a
+                  onClick={() => {
+                    checkUrl('access');
+                  }}
+                >
+                  设备接入方式
+                </a>
+              </span>
+            }
+          />
+        );
+      } else {
         return <EditableTable data={data} type={type} />;
       }
     }

+ 3 - 14
src/pages/device/Instance/Detail/Reation/index.tsx

@@ -1,7 +1,7 @@
 import { Descriptions, Tooltip } from 'antd';
 import { InstanceModel, service } from '@/pages/device/Instance';
 import { useEffect, useState } from 'react';
-import { history, useParams } from 'umi';
+import { useParams } from 'umi';
 import { EditOutlined, QuestionCircleOutlined } from '@ant-design/icons';
 import Edit from './Edit';
 import { PermissionButton } from '@/components';
@@ -9,17 +9,6 @@ import _ from 'lodash';
 
 const Reation = () => {
   const params = useParams<{ id: string }>();
-  useEffect(() => {
-    const id = InstanceModel.current?.id || params.id;
-    if (id) {
-      service.getConfigMetadata(id).then((response) => {
-        InstanceModel.config = response?.result;
-      });
-    } else {
-      history.goBack();
-    }
-  }, []);
-
   const [data, setData] = useState<any[]>([]);
   const [visible, setVisible] = useState<boolean>(false);
   const { permission } = PermissionButton.usePermission('device/Instance');
@@ -38,7 +27,7 @@ const Reation = () => {
     if (id) {
       setData(InstanceModel.detail?.relations || []);
     }
-  }, [id]);
+  }, [InstanceModel.detail?.relations]);
 
   return (
     <div style={{ width: '100%', marginTop: '20px' }}>
@@ -68,7 +57,7 @@ const Reation = () => {
       >
         {(data || [])?.map((item: any) => (
           <Descriptions.Item span={1} label={item.relationName} key={item.objectId}>
-            {_.map(item?.related || [], 'name').join(',')}
+            {item?.related ? _.map(item?.related || [], 'name').join(',') : '--'}
           </Descriptions.Item>
         ))}
       </Descriptions>

+ 35 - 2
src/pages/device/Instance/Detail/index.tsx

@@ -1,7 +1,7 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import { InstanceModel } from '@/pages/device/Instance';
 import { history, useParams } from 'umi';
-import { Badge, Card, Descriptions, Divider, Tooltip } from 'antd';
+import { Badge, Card, Descriptions, Divider, message, Space, Tooltip } from 'antd';
 import type { ReactNode } from 'react';
 import { useEffect, useState } from 'react';
 import { observer } from '@formily/react';
@@ -35,6 +35,7 @@ const InstanceDetail = observer(() => {
   const [tab, setTab] = useState<string>('detail');
   const params = useParams<{ id: string }>();
   const service = new Service('device-instance');
+  const { permission } = PermissionButton.usePermission('device/Instance');
 
   // const resetMetadata = async () => {
   //   const resp = await service.deleteMetadata(params.id);
@@ -260,7 +261,39 @@ const InstanceDetail = observer(() => {
         <>
           {InstanceModel.detail?.name}
           <Divider type="vertical" />
-          {deviceStatus.get(InstanceModel.detail?.state?.value)}
+          <Space>
+            {deviceStatus.get(InstanceModel.detail?.state?.value)}
+            <PermissionButton
+              type={'link'}
+              key={'state'}
+              popConfirm={{
+                title:
+                  InstanceModel.detail?.state?.value !== 'notActive'
+                    ? '确认断开连接'
+                    : '确认启用设备',
+                onConfirm: async () => {
+                  if (InstanceModel.detail?.state?.value !== 'notActive') {
+                    await service.undeployDevice(params.id);
+                  } else {
+                    await service.deployDevice(params.id);
+                  }
+                  message.success(
+                    intl.formatMessage({
+                      id: 'pages.data.option.success',
+                      defaultMessage: '操作成功!',
+                    }),
+                  );
+                  getDetail(params.id);
+                },
+              }}
+              isPermission={permission.action}
+              tooltip={{
+                title: InstanceModel.detail?.state?.value !== 'notActive' ? '断开连接' : '启用设备',
+              }}
+            >
+              {InstanceModel.detail?.state?.value !== 'notActive' ? '断开连接' : '启用设备'}
+            </PermissionButton>
+          </Space>
         </>
       }
       // extra={[

+ 5 - 3
src/pages/device/Instance/Export/index.tsx

@@ -49,9 +49,10 @@ const Export = (props: Props) => {
         type: 'void',
         'x-component': 'FormLayout',
         'x-component-props': {
-          labelCol: 4,
-          wrapperCol: 18,
-          labelAlign: 'right',
+          // labelCol: 4,
+          // wrapperCol: 18,
+          // labelAlign: 'right',
+          layout: 'vertical',
         },
         properties: {
           product: {
@@ -103,6 +104,7 @@ const Export = (props: Props) => {
     } else {
       downloadFile(`/${SystemConst.API_BASE}/device/instance/export.${values.fileType}`, params);
     }
+    close();
   };
   return (
     <Modal

+ 6 - 5
src/pages/device/Instance/Import/index.tsx

@@ -88,7 +88,7 @@ const NormalUpload = (props: any) => {
           dt += temp;
           setCount(dt);
         } else {
-          setErrMessage(res.message);
+          setErrMessage(res.message || '失败');
         }
       };
       source.onerror = () => {
@@ -221,9 +221,10 @@ const Import = (props: Props) => {
         type: 'void',
         'x-component': 'FormLayout',
         'x-component-props': {
-          labelCol: 4,
-          wrapperCol: 18,
-          labelAlign: 'right',
+          // labelCol: 6,
+          // wrapperCol: 18,
+          // labelAlign: 'right',
+          layout: 'vertical',
         },
         properties: {
           product: {
@@ -264,7 +265,7 @@ const Import = (props: Props) => {
       visible={visible}
       onCancel={() => close()}
       width="35vw"
-      title="导"
+      title="导"
       onOk={() => close()}
       footer={[
         <Button key="cancel" onClick={() => close()}>

+ 4 - 3
src/pages/device/Product/Detail/PropertyImport/index.tsx

@@ -138,9 +138,10 @@ const PropertyImport = (props: Props) => {
         type: 'void',
         'x-component': 'FormLayout',
         'x-component-props': {
-          labelCol: 4,
-          wrapperCol: 18,
-          labelAlign: 'right',
+          // labelCol: 4,
+          // wrapperCol: 18,
+          // labelAlign: 'right',
+          layout: 'vertical',
         },
         properties: {
           fileType: {

+ 13 - 4
src/pages/device/components/Metadata/Base/Edit/index.tsx

@@ -26,7 +26,7 @@ import {
   FileTypeList,
   PropertySource,
 } from '@/pages/device/data';
-import { useMemo } from 'react';
+import { useMemo, useState } from 'react';
 import { productModel } from '@/pages/device/Product';
 import { service } from '@/pages/device/components/Metadata';
 import { Store } from 'jetlinks-store';
@@ -53,6 +53,7 @@ interface Props {
 
 const Edit = observer((props: Props) => {
   const intl = useIntl();
+  const [loading, setLoading] = useState<boolean>(false);
   const form = useMemo(
     () =>
       createForm({
@@ -973,6 +974,7 @@ const Edit = observer((props: Props) => {
   const { type } = MetadataModel;
 
   const saveMetadata = async (deploy?: boolean) => {
+    setLoading(true);
     const params = (await form.submit()) as MetadataItem;
 
     if (!typeMap.get(props.type)) return;
@@ -1014,7 +1016,6 @@ const Edit = observer((props: Props) => {
     // const result = await saveMap.get(props.type);
     const result = await asyncUpdateMedata(props.type, _data);
     if (result.status === 200) {
-      message.success('操作成功!');
       if ((window as any).onTabSaveSuccess) {
         if (result) {
           (window as any).onTabSaveSuccess(result);
@@ -1024,6 +1025,8 @@ const Edit = observer((props: Props) => {
         Store.set(SystemConst.REFRESH_METADATA_TABLE, true);
         if (deploy) {
           Store.set('product-deploy', deploy);
+        } else {
+          message.success('操作成功!');
         }
         MetadataModel.edit = false;
         MetadataModel.item = {};
@@ -1031,6 +1034,7 @@ const Edit = observer((props: Props) => {
     } else {
       message.error('操作失败!');
     }
+    setLoading(false);
   };
 
   const menu = (
@@ -1062,11 +1066,16 @@ const Edit = observer((props: Props) => {
         placement={'right'}
         extra={
           props.type === 'product' ? (
-            <Dropdown.Button type="primary" onClick={() => saveMetadata()} overlay={menu}>
+            <Dropdown.Button
+              loading={loading}
+              type="primary"
+              onClick={() => saveMetadata()}
+              overlay={menu}
+            >
               保存数据
             </Dropdown.Button>
           ) : (
-            <Button type="primary" onClick={() => saveMetadata()}>
+            <Button loading={loading} type="primary" onClick={() => saveMetadata()}>
               保存数据
             </Button>
           )

+ 32 - 25
src/pages/link/Protocol/save/index.tsx

@@ -1,4 +1,4 @@
-import { Button, message } from 'antd';
+import { Button, message, Modal } from 'antd';
 import { createForm, registerValidateRules } from '@formily/core';
 import { createSchemaField } from '@formily/react';
 import React, { useEffect, useState } from 'react';
@@ -6,9 +6,9 @@ import * as ICONS from '@ant-design/icons';
 import { Form, FormGrid, FormItem, Input, Select } from '@formily/antd';
 import type { ISchema } from '@formily/json-schema';
 import { service } from '@/pages/link/Protocol';
-import { Modal } from '@/components';
 import FileUpload from '../FileUpload';
 import type { ProtocolItem } from '@/pages/link/Protocol/typings';
+import { PermissionButton } from '@/components';
 
 interface Props {
   data: ProtocolItem | undefined;
@@ -18,6 +18,7 @@ interface Props {
 
 const Save = (props: Props) => {
   const [data, setData] = useState<ProtocolItem | undefined>(props.data);
+  const { permission } = PermissionButton.usePermission('link/Protocol');
 
   useEffect(() => {
     setData(props.data);
@@ -223,29 +224,35 @@ const Save = (props: Props) => {
       visible
       onCancel={props.close}
       width={700}
-      permissionCode={'link/Protocol'}
-      permission={['add', 'edit']}
-      footer={
-        <>
-          <Button onClick={props.close}>取消</Button>
-          <Button
-            type="primary"
-            onClick={() => {
-              save(false);
-            }}
-          >
-            保存
-          </Button>
-          <Button
-            type="primary"
-            onClick={() => {
-              save(true);
-            }}
-          >
-            保存并发布
-          </Button>
-        </>
-      }
+      footer={[
+        <Button key={1} onClick={props.close}>
+          取消
+        </Button>,
+        <Button
+          type="primary"
+          key={2}
+          onClick={() => {
+            save(false);
+          }}
+          disabled={props.data?.id ? !permission.update : !permission.add}
+        >
+          保存
+        </Button>,
+        <Button
+          key={3}
+          type="primary"
+          onClick={() => {
+            save(true);
+          }}
+          disabled={
+            props.data?.id
+              ? !permission.update && !permission.action
+              : !permission.add && !permission.action
+          }
+        >
+          保存并发布
+        </Button>,
+      ]}
     >
       <Form form={form} layout="vertical">
         <SchemaField schema={schema} />

+ 111 - 0
src/pages/notice/Config/BindUser/index.tsx

@@ -0,0 +1,111 @@
+import { createForm } from '@formily/core';
+import { createSchemaField } from '@formily/react';
+import { Form, FormItem, Select } from '@formily/antd';
+import { message, Modal } from 'antd';
+import { service, state } from '..';
+import { useEffect, useState } from 'react';
+
+interface Props {
+  close: () => void;
+  data: any;
+  id: string;
+  reload: () => void;
+}
+
+const BindUser = (props: Props) => {
+  const form = createForm({
+    validateFirst: true,
+    initialValues: { user: props.data?.userId || '' },
+  });
+  const [list, setList] = useState<any[]>([]);
+
+  const getUsers = async (id: string) => {
+    const resp = await service.syncUser.noBindUser({
+      pagign: false,
+      terms: [
+        {
+          column: `id$user-third${state.current?.type}_${state.current?.provider}$not`,
+          value: id,
+        },
+      ],
+    });
+    const data = resp.result?.map((item: Record<string, unknown>) => ({
+      label: item.name,
+      value: item.id,
+    }));
+    setList(data);
+  };
+
+  useEffect(() => {
+    if (props.data?.id) {
+      getUsers(props.data.id);
+    }
+  }, [props.data]);
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Select,
+    },
+  });
+
+  const schema = {
+    type: 'object',
+    properties: {
+      user: {
+        type: 'string',
+        title: '用户',
+        'x-decorator': 'FormItem',
+        'x-component': 'Select',
+        'x-component-props': {
+          placeholder: '请选择用户',
+          filterOption: (input: string, option: any) =>
+            option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+        },
+        enum: [...list],
+        'x-validator': [
+          {
+            required: true,
+            message: '请选择用户',
+          },
+        ],
+      },
+    },
+  };
+
+  return (
+    <Modal
+      title="绑定用户 "
+      onCancel={() => {
+        props.close();
+      }}
+      visible
+      width={500}
+      onOk={async () => {
+        const values: any = (await form.submit()) as any;
+        const resp = await service.syncUser.bindUser(
+          props.id,
+          state.current?.provider || '',
+          state.current?.id || '',
+          [
+            {
+              userId: values.user,
+              providerName: props.data?.name,
+              thirdPartyUserId: props.data?.id,
+            },
+          ],
+        );
+        if (resp.status === 200) {
+          message.success('操作成功!');
+          props.reload();
+        }
+      }}
+    >
+      <Form form={form} layout="vertical">
+        <SchemaField schema={schema} />
+      </Form>
+    </Modal>
+  );
+};
+
+export default BindUser;

+ 87 - 67
src/pages/notice/Config/SyncUser/index.tsx

@@ -1,52 +1,90 @@
-import { Button, Col, Input, Modal, Row, Tree } from 'antd';
+import { Badge, Button, Col, Input, message, Modal, Popconfirm, Row, Tooltip, Tree } from 'antd';
 import { observer } from '@formily/react';
 import { service, state } from '..';
-import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
 import { useEffect, useRef, useState } from 'react';
 import { history, useLocation } from 'umi';
-import { PermissionButton } from '@/components';
 import { DisconnectOutlined, EditOutlined } from '@ant-design/icons';
+import BindUser from '../BindUser';
 
 const SyncUser = observer(() => {
   const [dept, setDept] = useState<string>();
   const location = useLocation<{ id: string }>();
   const id = (location as any).query?.id;
+  const [visible, setVisible] = useState<boolean>(false);
+  const [current, setCurrent] = useState<any>({});
 
   const idMap = {
     dingTalk: '钉钉',
     weixin: '微信',
   };
+
+  const actionRef = useRef<ActionType>();
+
   const columns: ProColumns<any>[] = [
     {
       dataIndex: 'id',
-      title: `${idMap[id]}ID`,
+      title: `${idMap[id]}用户名`,
+      render: (text: any, record: any) => (
+        <span>
+          {text}({record?.name})
+        </span>
+      ),
     },
     {
-      dataIndex: 'name',
-      title: `${idMap[id]}用户名`,
+      dataIndex: 'userId',
+      title: `用户`,
+      render: (text: any, record: any) => (
+        <span>{record?.userId ? `${record?.username}(${record?.userName})` : '--'}</span>
+      ),
     },
     {
-      dataIndex: 'action',
+      dataIndex: 'status',
       title: '绑定状态',
-      render: () => [
-        <PermissionButton
-          tooltip={{
-            title: '绑定用户',
-          }}
-        >
-          <EditOutlined />
-        </PermissionButton>,
-        <PermissionButton
-          tooltip={{
-            title: '解绑用户',
-          }}
-        >
-          <DisconnectOutlined />
-        </PermissionButton>,
+      render: (text: any, record: any) => (
+        <Badge
+          status={record?.userId ? 'success' : 'error'}
+          text={record?.userId ? '已绑定' : '未绑定'}
+        />
+      ),
+    },
+    {
+      dataIndex: 'action',
+      title: '操作',
+      render: (text: any, record: any) => [
+        <Tooltip title={'绑定用户'} key="bind">
+          <Button
+            type="link"
+            onClick={() => {
+              setCurrent(record);
+              setVisible(true);
+            }}
+          >
+            <EditOutlined />
+          </Button>
+        </Tooltip>,
+        <Tooltip title={'解绑用户'} key="unbind">
+          <Button type="link">
+            <Popconfirm
+              title={'确认解绑'}
+              onConfirm={async () => {
+                if (record?.bindingId) {
+                  const resp = await service.syncUser.unBindUser(record.bindingId);
+                  if (resp.status === 200) {
+                    message.success('操作成功!');
+                    actionRef.current?.reload();
+                  }
+                }
+              }}
+            >
+              <DisconnectOutlined />
+            </Popconfirm>
+          </Button>
+        </Tooltip>,
       ],
     },
   ];
-  const actionRef = useRef<ActionType>();
 
   const [treeData, setTreeData] = useState([]);
 
@@ -60,7 +98,7 @@ const SyncUser = observer(() => {
           if (resp.status === 200) {
             setTreeData(resp.result);
             setDept(resp.result[0].id);
-            console.log(resp.result[0].id, 'id');
+            // console.log(resp.result[0].id, 'id');
           }
         });
       } else if (id === 'weixin') {
@@ -68,7 +106,7 @@ const SyncUser = observer(() => {
           if (resp.status === 200) {
             setTreeData(resp.result);
             setDept(resp.result[0].id);
-            console.log(resp.result[0].id, 'id~~');
+            // console.log(resp.result[0].id, 'id~~');
           }
         });
       }
@@ -82,40 +120,6 @@ const SyncUser = observer(() => {
     getDepartment();
   }, [id]);
 
-  // const updateTreeData = (list: any[], key: React.Key, children: any[]): any[] => {
-  //   return list.map((node) => {
-  //     if (node.id === key) {
-  //       return {
-  //         ...node,
-  //         children: node.children ? [...node.children, ...children] : children,
-  //       };
-  //     }
-  //
-  //     if (node.children) {
-  //       return {
-  //         ...node,
-  //         children: updateTreeData(node.children, key, children),
-  //       };
-  //     }
-  //     return node;
-  //   });
-  // };
-
-  // const getParentKey = (key: any, tree: string | any[]): any => {
-  //   let parentKey;
-  //   for (let i = 0; i < tree.length; i++) {
-  //     const node = tree[i];
-  //     if (node.children) {
-  //       if (node.children.some((item: { key: any; }) => item.key === key)) {
-  //         parentKey = node.key;
-  //       } else if (getParentKey(key, node.children)) {
-  //         parentKey = getParentKey(key, node.children);
-  //       }
-  //     }
-  //   }
-  //   return parentKey;
-  // };
-
   return (
     <Modal
       title="同步用户"
@@ -137,7 +141,6 @@ const SyncUser = observer(() => {
                 setDept(key[0] as string);
               }}
               treeData={treeData}
-              // loadData={onLoadData}
             />
           </div>
         </Col>
@@ -149,26 +152,28 @@ const SyncUser = observer(() => {
               search={false}
               columns={columns}
               params={{ dept: dept }}
-              request={async (params) =>
-                service.syncUser
-                  .getDeptUser(
+              request={(params) =>
+                service
+                  .queryZipSyncUser(
                     {
                       dingTalk: 'dingtalk',
                       weixin: 'wechat',
                     }[id],
+                    id,
+                    state.current?.provider || '',
                     state.current?.id || '',
                     params.dept || '',
                   )
-                  .then((resp) => {
+                  .then((resp: any) => {
                     return {
-                      code: resp.message,
+                      code: '',
                       result: {
-                        data: resp.result || [],
+                        data: resp || [],
                         pageIndex: 0,
                         pageSize: 0,
                         total: 0,
                       },
-                      status: resp.status,
+                      status: 200,
                     };
                   })
               }
@@ -177,6 +182,21 @@ const SyncUser = observer(() => {
           )}
         </Col>
       </Row>
+      {visible && (
+        <BindUser
+          id={id}
+          close={() => {
+            setCurrent({});
+            setVisible(false);
+          }}
+          data={current}
+          reload={() => {
+            setCurrent({});
+            setVisible(false);
+            actionRef.current?.reload();
+          }}
+        />
+      )}
     </Modal>
   );
 });

+ 46 - 0
src/pages/notice/Config/service.ts

@@ -1,6 +1,7 @@
 import BaseService from '@/utils/BaseService';
 import { request } from 'umi';
 import SystemConst from '@/utils/const';
+import { from, lastValueFrom, map, mergeMap, toArray, zip } from 'rxjs';
 
 class Service extends BaseService<ConfigItem> {
   public getTypes = () =>
@@ -64,6 +65,10 @@ class Service extends BaseService<ConfigItem> {
         method: 'PATCH',
         data,
       }),
+    bindUserThirdParty: (type: string, provider: string, configId: string) =>
+      request(`${SystemConst.API_BASE}/user/third-party/${type}_${provider}/${configId}`, {
+        method: 'GET',
+      }),
     getUserBindInfo: () =>
       request(`${SystemConst.API_BASE}/user/third-party/me`, { method: 'GET' }),
     unBindUser: (bindId: string) =>
@@ -71,6 +76,47 @@ class Service extends BaseService<ConfigItem> {
         method: 'DELETE',
       }),
   };
+
+  public queryZipSyncUser = (
+    type: 'wechat' | 'dingTalk',
+    _type: string,
+    provider: string,
+    configId: string,
+    departmentId: string,
+  ) =>
+    lastValueFrom(
+      zip(
+        from(this.syncUser.getDeptUser(type, configId, departmentId)),
+        from(this.syncUser.bindUserThirdParty(_type, provider, configId)),
+        from(this.syncUser.noBindUser({ paging: false })),
+      ).pipe(
+        map((resp) => resp.map((i) => i.result)),
+        mergeMap((res) => {
+          const [resp1, resp2, resp3] = res;
+          const list = resp1.map((item: { id: string; name: string }) => {
+            const data =
+              resp2.find(
+                (i: { userId: string; providerName: string; thirdPartyUserId: string }) =>
+                  i.thirdPartyUserId === item.id,
+              ) || {};
+            let _user: Partial<UserItem> = {};
+            if (data) {
+              _user = resp3.find((i: UserItem) => i.id === data.userId);
+            }
+            return {
+              ..._user,
+              ...data,
+              ...item,
+              bindingId: data?.id,
+              userId: _user?.id,
+              userName: _user?.name,
+            };
+          });
+          return list;
+        }),
+        toArray(),
+      ),
+    );
 }
 
 export default Service;

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

@@ -203,6 +203,7 @@ const TabComponent = observer((props: Props) => {
                               <div className="btn">
                                 <ToolFilled className="icon" />
                                 <div>告警处理</div>
+                                {/* action */}
                               </div>
                             </Button>
                           </div>

+ 27 - 23
src/pages/system/Relationship/Save/index.tsx

@@ -41,7 +41,21 @@ const Save = (props: Props) => {
 
   const form = createForm({
     validateFirst: true,
-    initialValues: props.data,
+    initialValues: {
+      ...props.data,
+      object: props.data?.objectType
+        ? JSON.stringify({
+            objectType: props.data.objectType,
+            objectTypeName: props.data.objectTypeName,
+          })
+        : undefined,
+      target: props.data?.targetType
+        ? JSON.stringify({
+            targetType: props.data.targetType,
+            targetTypeName: props.data.targetTypeName,
+          })
+        : undefined,
+    },
   });
 
   const SchemaField = createSchemaField({
@@ -114,28 +128,6 @@ const Save = (props: Props) => {
                 required: true,
                 message: '请输入标识',
               },
-              // {
-              //   triggerType: 'onBlur',
-              //   // validator: (value: string) => {
-              //   //   return new Promise((resolve) => {
-              //   //     service
-              //   //       .validateField('username', value)
-              //   //       .then((resp) => {
-              //   //         if (resp.status === 200) {
-              //   //           if (resp.result.passed) {
-              //   //             resolve('');
-              //   //           } else {
-              //   //             resolve(model === 'edit' ? '' : resp.result.reason);
-              //   //           }
-              //   //         }
-              //   //         resolve('');
-              //   //       })
-              //   //       .catch(() => {
-              //   //         return '验证失败!';
-              //   //       });
-              //   //   });
-              //   // },
-              // },
             ],
             name: 'relation',
             required: true,
@@ -156,6 +148,12 @@ const Save = (props: Props) => {
                 option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
             },
             required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择关联方',
+              },
+            ],
             'x-reactions': ['{{useAsyncDataSource(getTypes)}}'],
           },
           target: {
@@ -179,6 +177,12 @@ const Save = (props: Props) => {
                 },
               },
             },
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择被关联方',
+              },
+            ],
             required: true,
           },
           description: {