Procházet zdrojové kódy

feat(merge): merge xyh

Next xyh
Lind před 3 roky
rodič
revize
60230ddd2c

+ 5 - 54
src/pages/device/Instance/Detail/Functions/index.tsx

@@ -1,67 +1,16 @@
-import { Button, Card, Tabs } from 'antd';
+import { Card, Tabs } from 'antd';
 import { InstanceModel } from '@/pages/device/Instance';
 import type { FunctionMetadata } from '@/pages/device/Product/typings';
 import FnForm from './form';
 import AModel from './AdvancedMode';
-import { Empty, PermissionButton } from '@/components';
 import { useDomFullHeight } from '@/hooks';
-import { getMenuPathByParams } from '@/utils/menu';
-import useHistory from '@/hooks/route/useHistory';
+import Empty from '@/pages/device/components/Empty';
 
 const Functions = () => {
   const functionList = JSON.parse(InstanceModel.detail.metadata || '{}')
     .functions as FunctionMetadata[];
-  const history = useHistory();
 
   const { minHeight } = useDomFullHeight(`.device-detail-function`);
-  const { permission } = PermissionButton.usePermission('device/Product');
-
-  const empty = () => {
-    const isIndependent = InstanceModel.detail?.independentMetadata;
-    const path = isIndependent
-      ? getMenuPathByParams('device/Product/Detail', InstanceModel.detail?.productId)
-      : getMenuPathByParams('device/Instance/Detail', InstanceModel.detail?.id);
-
-    let description = <></>;
-    if (isIndependent) {
-      // 物模型解绑
-      if (!permission.update) {
-        description = <span>请联系管理员配置物模型属性</span>;
-      } else {
-        description = (
-          <span>
-            暂无数据, 请前往产品配置
-            <Button
-              style={{ margin: '0 6px' }}
-              type={'link'}
-              onClick={() => {
-                history.push(`${path}?key=metadata`);
-              }}
-            >
-              物模型-功能定义
-            </Button>
-          </span>
-        );
-      }
-    } else {
-      description = (
-        <span>
-          暂无数据,请配置
-          <Button
-            style={{ margin: '0 6px' }}
-            type={'link'}
-            onClick={() => {
-              history.push(`${path}?key=metadata`);
-            }}
-          >
-            物模型-功能定义
-          </Button>
-        </span>
-      );
-    }
-
-    return <Empty description={description} />;
-  };
 
   return (
     <Card className={'device-detail-function'} style={{ minHeight: minHeight }}>
@@ -93,7 +42,9 @@ const Functions = () => {
           </Tabs.TabPane>
         </Tabs>
       ) : (
-        <div style={{ height: minHeight - 150 }}>{empty()}</div>
+        <div style={{ height: minHeight - 150 }}>
+          <Empty />
+        </div>
       )}
     </Card>
   );

+ 75 - 73
src/pages/device/Instance/Detail/MetadataMap/index.tsx

@@ -1,11 +1,11 @@
-import { Card } from 'antd';
+import { Button, Card, Empty } from 'antd';
 import { useEffect, useState } from 'react';
-import { service } from '@/pages/device/Instance';
+import { InstanceModel, service } from '@/pages/device/Instance';
 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, Empty } from '@/components';
+import { PermissionButton } from '@/components';
 import { useDomFullHeight } from '@/hooks';
 
 interface Props {
@@ -55,92 +55,94 @@ const MetadataMap = (props: Props) => {
     const dmetadata = JSON.parse(data?.metadata || '{}');
     const height = minHeight - 150;
     if (product) {
+      // 是否有物模型属性
       const flag =
         (type === 'device' &&
           (metadata?.properties || []).length === 0 &&
           (dmetadata?.properties || []).length === 0) ||
         (type === 'product' && (dmetadata?.properties || []).length === 0);
-      if (!product.accessId && flag) {
+
+      const isIndependent = InstanceModel.detail?.independentMetadata;
+      let description = undefined;
+
+      if (type === 'device' && isIndependent && flag) {
+        description = (
+          <span>
+            暂无数据,请配置
+            <Button
+              style={{ margin: 0, padding: '0 4px' }}
+              type={'link'}
+              onClick={() => {
+                InstanceModel.active = 'metadata';
+              }}
+            >
+              物模型属性
+            </Button>
+          </span>
+        );
+      } else if (!product.accessId && flag) {
         if (!permission.update) {
-          return (
-            <div style={{ height }}>
-              <Empty
-                description={<span>请联系管理员配置物模型属性,并选择对应产品的设备接入方式</span>}
-              />
-            </div>
-          );
+          description = <span>请联系管理员配置物模型属性,并选择对应产品的设备接入方式</span>;
         } else {
-          return (
-            <div style={{ height }}>
-              <Empty
-                description={
-                  <span>
-                    请先配置对应产品的
-                    <a
-                      onClick={() => {
-                        checkUrl('metadata');
-                      }}
-                    >
-                      物模型属性
-                    </a>
-                    ,并选择对应产品的
-                    <a
-                      onClick={() => {
-                        checkUrl('access');
-                      }}
-                    >
-                      设备接入方式
-                    </a>
-                  </span>
-                }
-              />
-            </div>
+          description = (
+            <span>
+              请先配置对应产品的
+              <a
+                onClick={() => {
+                  checkUrl('metadata');
+                }}
+              >
+                物模型属性
+              </a>
+              ,并选择对应产品的
+              <a
+                onClick={() => {
+                  checkUrl('access');
+                }}
+              >
+                设备接入方式
+              </a>
+            </span>
           );
         }
       } else if (flag && product.accessId) {
-        return (
-          <div style={{ height }}>
-            <Empty
-              description={
-                !permission.update ? (
-                  <span>请联系管理员配置物模型属性</span>
-                ) : (
-                  <span>
-                    请配置对应产品的
-                    <a
-                      onClick={() => {
-                        checkUrl('metadata');
-                      }}
-                    >
-                      物模型属性
-                    </a>
-                  </span>
-                )
-              }
-            />
-          </div>
+        description = !permission.update ? (
+          <span>请联系管理员配置物模型属性</span>
+        ) : (
+          <span>
+            请配置对应产品的
+            <a
+              onClick={() => {
+                checkUrl('metadata');
+              }}
+            >
+              物模型属性
+            </a>
+          </span>
         );
       } else if (!flag && !product.accessId) {
+        description = (
+          <span>
+            请选择对应产品的
+            <a
+              onClick={() => {
+                checkUrl('access');
+              }}
+            >
+              设备接入方式
+            </a>
+          </span>
+        );
+      }
+
+      if (!description) {
+        return <EditableTable data={data} type={type} />;
+      } else {
         return (
           <div style={{ height }}>
-            <Empty
-              description={
-                <span>
-                  请选择对应产品的
-                  <a
-                    onClick={() => {
-                      checkUrl('access');
-                    }}
-                  >
-                    设备接入方式
-                  </a>
-                </span>
-              }
-            />
+            <Empty description={description} />
           </div>
         );
-      } else {
-        return <EditableTable data={data} type={type} />;
       }
     }
     return (

+ 7 - 13
src/pages/device/Instance/Detail/Running/index.tsx

@@ -1,13 +1,16 @@
 import { InstanceModel } from '@/pages/device/Instance';
-import { Card, Empty, Input, Tabs } from 'antd';
+import { Card, Input, Tabs } from 'antd';
 import type { DeviceMetadata } from '@/pages/device/Product/typings';
 import Property from '@/pages/device/Instance/Detail/Running/Property';
 import Event from '@/pages/device/Instance/Detail/Running/Event';
 import { useEffect, useState } from 'react';
+import Empty from '@/pages/device/components/Empty';
+import { useDomFullHeight } from '@/hooks';
 
 const Running = () => {
   const metadata = JSON.parse((InstanceModel.detail?.metadata || '{}') as string) as DeviceMetadata;
   const [list, setList] = useState<any[]>([]);
+  const { minHeight } = useDomFullHeight(`.device-detail-running`);
 
   useEffect(() => {
     setList(metadata?.events || []);
@@ -32,27 +35,18 @@ const Running = () => {
   );
 
   return (
-    <Card>
+    <Card className={'device-detail-running'} style={{ minHeight }}>
       {list?.length === 0 && (metadata?.properties || [])?.length === 0 ? (
         <div
           style={{
-            height: 480,
-            display: 'flex',
-            alignItems: 'center',
-            width: '100%',
-            justifyContent: 'center',
+            height: minHeight - 150,
           }}
         >
           <Empty />
         </div>
       ) : (
         <div className="tabs-full-active">
-          <Tabs
-            defaultActiveKey="1"
-            tabPosition="left"
-            style={{ minHeight: 600 }}
-            tabBarExtraContent={{ left: operations() }}
-          >
+          <Tabs defaultActiveKey="1" tabPosition="left" tabBarExtraContent={{ left: operations() }}>
             <Tabs.TabPane tab="属性" key="1">
               <Property data={metadata?.properties || []} />
             </Tabs.TabPane>

+ 12 - 8
src/pages/device/Instance/Detail/index.tsx

@@ -36,7 +36,7 @@ deviceStatus.set('notActive', <Badge status="processing" text={'未启用'} />);
 
 const InstanceDetail = observer(() => {
   const intl = useIntl();
-  const [tab, setTab] = useState<string>('detail');
+  // const [tab, setTab] = useState<string>('detail');
   const params = useParams<{ id: string }>();
   const service = new Service('device-instance');
   const { permission } = PermissionButton.usePermission('device/Instance');
@@ -225,7 +225,8 @@ const InstanceDetail = observer(() => {
     if (!InstanceModel.current && !params.id) {
       history.goBack();
     } else {
-      setTab('detail');
+      // setTab('detail');
+      InstanceModel.active = 'detail';
       getDetail(params?.id || InstanceModel.current?.id || '');
     }
     return () => {
@@ -234,9 +235,9 @@ const InstanceDetail = observer(() => {
   }, [params.id]);
 
   useEffect(() => {
-    console.log(location.query);
     if ((location as any).query?.key) {
-      setTab((location as any).query?.key || 'detail');
+      // setTab((location as any).query?.key || 'detail');
+      InstanceModel.active = (location as any).query?.key || 'detail';
     }
     const subscription = Store.subscribe(SystemConst.BASE_UPDATE_DATA, (data) => {
       if ((window as any).onTabSaveSuccess) {
@@ -250,7 +251,8 @@ const InstanceDetail = observer(() => {
   useEffect(() => {
     const { state } = location;
     if (state && state?.tab) {
-      setTab(state?.tab);
+      // setTab(state?.tab);
+      InstanceModel.active = state?.tab;
     }
   }, [location]);
 
@@ -258,9 +260,11 @@ const InstanceDetail = observer(() => {
     <PageContainer
       className={'page-title-show'}
       onBack={history.goBack}
-      onTabChange={setTab}
+      onTabChange={(e) => {
+        InstanceModel.active = e;
+      }}
       tabList={list}
-      tabActiveKey={tab}
+      tabActiveKey={InstanceModel.active}
       content={
         <Descriptions size="small" column={4}>
           <Descriptions.Item label={'ID'}>{InstanceModel.detail?.id}</Descriptions.Item>
@@ -363,7 +367,7 @@ const InstanceDetail = observer(() => {
       //   </Button>,
       // ]}
     >
-      {list.find((k) => k.key === tab)?.component}
+      {list.find((k) => k.key === InstanceModel.active)?.component}
     </PageContainer>
   );
 });

+ 1 - 0
src/pages/device/Instance/index.tsx

@@ -55,6 +55,7 @@ export const InstanceModel = model<{
   detail: {},
   config: {},
   metadataItem: {},
+  active: 'detail',
   params: new Set<string>(['test']),
 });
 export const service = new Service('device-instance');

+ 54 - 0
src/pages/device/components/Empty/index.tsx

@@ -0,0 +1,54 @@
+import { InstanceModel } from '@/pages/device/Instance';
+import { getMenuPathByParams } from '@/utils/menu';
+import { Button } from 'antd';
+import { Empty, PermissionButton } from '@/components';
+import useHistory from '@/hooks/route/useHistory';
+
+export default () => {
+  const isIndependent = InstanceModel.detail?.independentMetadata;
+  const path = getMenuPathByParams('device/Product/Detail', InstanceModel.detail?.productId);
+  const { permission } = PermissionButton.usePermission('device/Product');
+  const history = useHistory();
+
+  let description = <></>;
+
+  if (!isIndependent) {
+    if (!permission.update) {
+      description = <span>请联系管理员配置物模型属性</span>;
+    } else {
+      description = (
+        <span>
+          暂无数据, 请前往产品配置
+          <Button
+            style={{ margin: 0, padding: '0 4px' }}
+            type={'link'}
+            onClick={() => {
+              history.push(`${path}?key=metadata`);
+            }}
+          >
+            物模型
+          </Button>
+        </span>
+      );
+    }
+  } else {
+    // 物模型解绑
+    description = (
+      <span>
+        暂无数据,请配置
+        <Button
+          style={{ margin: 0, padding: '0 4px' }}
+          type={'link'}
+          onClick={() => {
+            InstanceModel.active = 'metadata';
+            // history.push(`${path}?key=metadata`);
+          }}
+        >
+          物模型
+        </Button>
+      </span>
+    );
+  }
+
+  return <Empty description={description} />;
+};

+ 0 - 4
src/pages/media/Device/Channel/Tree/index.less

@@ -1,9 +1,5 @@
 .channel-tree {
   height: 100%;
-  margin-right: 16px;
-  padding: 20px;
-  background-color: #fff;
-  border-radius: 2px;
 
   .channel-tree-query {
     margin-bottom: 16px;

+ 2 - 0
src/pages/media/Device/Channel/Tree/index.tsx

@@ -9,6 +9,7 @@ import { debounce } from 'lodash';
 interface TreeProps {
   deviceId: string;
   onSelect: (id: React.Key) => void;
+  onTreeLoad: (type: boolean) => void;
 }
 
 export default (props: TreeProps) => {
@@ -19,6 +20,7 @@ export default (props: TreeProps) => {
     formatResult: (res) => res.result,
     onSuccess: (res) => {
       treeData[0].children = res.result || [];
+      props.onTreeLoad(treeData[0].children.length > 10);
       setTreeData(treeData);
     },
   });

+ 39 - 2
src/pages/media/Device/Channel/index.less

@@ -1,8 +1,45 @@
 .device-channel-warp {
   display: flex;
 
-  .left {
-    width: 300px;
+  .left-warp {
+    position: relative;
+    margin-right: 16px;
+    padding: 20px;
+    background-color: #fff;
+    border-radius: 2px;
+
+    .left-content {
+      width: 0;
+      height: 100%;
+      overflow: hidden;
+
+      &.active {
+        width: 260px;
+      }
+    }
+
+    .left-warp--btn {
+      position: absolute;
+      top: 50%;
+      right: 0;
+      padding: 20px 4px;
+      color: rgba(#000, 0.3);
+      background-color: rgba(#f0f0f0, 6);
+      border-radius: ~'100% 0 0 100% / 50% 0 0 50%';
+      cursor: pointer;
+
+      &:hover {
+        color: rgba(#000, 0.5);
+        background-color: rgba(#f0f0f0, 8);
+      }
+
+      &.active {
+        right: 50%;
+        background-color: transparent;
+        border-radius: 0;
+        transform: translateX(50%) rotate(180deg);
+      }
+    }
   }
 
   .right {

+ 31 - 17
src/pages/media/Device/Channel/index.tsx

@@ -13,6 +13,7 @@ import {
   DeleteOutlined,
   EditOutlined,
   PlusOutlined,
+  LeftOutlined,
   VideoCameraAddOutlined,
   VideoCameraOutlined,
 } from '@ant-design/icons';
@@ -23,6 +24,7 @@ import Live from './Live';
 import { getButtonPermission, getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
 import Tree from './Tree';
 import { useDomFullHeight } from '@/hooks';
+import classnames from 'classnames';
 
 export const service = new Service('media');
 
@@ -37,6 +39,7 @@ export default () => {
   const [channelId, setChannelId] = useState('');
   const [type, setType] = useState('');
   const { minHeight } = useDomFullHeight(`.channelDevice`, 24);
+  const [show, setShow] = useState(false);
 
   const location = useLocation();
   const history = useHistory();
@@ -211,24 +214,35 @@ export default () => {
     <PageContainer>
       <div className={'device-channel-warp'}>
         {type === ProviderValue.GB281 && (
-          <div className={'left'}>
-            <Tree
-              deviceId={deviceId}
-              onSelect={(key) => {
-                if (key === deviceId && actionRef.current?.reset) {
-                  actionRef.current?.reset();
-                } else {
-                  setQueryParam({
-                    terms: [
-                      {
-                        column: 'parentId',
-                        value: key,
-                      },
-                    ],
-                  });
-                }
+          <div className={classnames('left-warp')}>
+            <div className={classnames('left-content', { active: show })}>
+              <Tree
+                deviceId={deviceId}
+                onSelect={(key) => {
+                  if (key === deviceId && actionRef.current?.reset) {
+                    actionRef.current?.reset();
+                  } else {
+                    setQueryParam({
+                      terms: [
+                        {
+                          column: 'parentId',
+                          value: key,
+                        },
+                      ],
+                    });
+                  }
+                }}
+                onTreeLoad={setShow}
+              />
+            </div>
+            <div
+              className={classnames('left-warp--btn', { active: !show })}
+              onClick={() => {
+                setShow(!show);
               }}
-            />
+            >
+              <LeftOutlined />
+            </div>
           </div>
         )}
         <div className={'right'}>

+ 72 - 113
src/pages/rule-engine/Scene/Save/action/VariableItems/user.tsx

@@ -1,13 +1,14 @@
 // 收信人
 import { useEffect, useState } from 'react';
 import { ItemGroup } from '@/pages/rule-engine/Scene/Save/components';
-import { Input, Select } from 'antd';
+import { Input, Select, TreeSelect } from 'antd';
 import {
   queryDingTalkUsers,
   queryPlatformUsers,
   queryRelationUsers,
   queryWechatUsers,
 } from '@/pages/rule-engine/Scene/Save/action/service';
+import { forkJoin, filter, from, defer, map } from 'rxjs';
 
 type ChangeType = {
   source?: string;
@@ -26,8 +27,11 @@ interface UserProps {
 export default (props: UserProps) => {
   const [source, setSource] = useState(props.value?.source);
   const [value, setValue] = useState<string | undefined>();
-  const [userList, setUserList] = useState({ platform: [], relation: [] });
   const [relationList, setRelationList] = useState([]);
+  const [treeData, setTreeData] = useState([
+    { name: '平台用户', id: 'p1', selectable: false, children: [] },
+    { name: '关系用户', id: 'p2', selectable: false, children: [] },
+  ]);
 
   useEffect(() => {
     setSource(props.value?.source);
@@ -48,31 +52,26 @@ export default (props: UserProps) => {
   }, [props.value]);
 
   const getPlatformUser = async () => {
-    const _userList: any = {
-      platform: [],
-      relation: [],
-    };
-    const resp1 = await queryPlatformUsers();
-    if (resp1.status === 200) {
-      _userList.platform = resp1.result.map((item: any) => ({
-        label: item.name,
-        value: item.id,
-        username: item.username,
-      }));
-    }
-
-    const resp2 = await queryRelationUsers();
-    if (resp2.status === 200) {
-      _userList.relation = resp2.result.map((item: any) => ({
-        label: item.name,
-        value: item.relation,
-        username: '',
-      }));
-    }
-
-    setUserList(_userList);
+    forkJoin(
+      defer(() => from(queryPlatformUsers())).pipe(
+        filter((item) => item.status === 200),
+        map((resp) => resp.result),
+      ),
+      defer(() => from(queryRelationUsers())).pipe(
+        filter((item) => item.status === 200),
+        map((resp) => resp.result),
+      ),
+    ).subscribe((res) => {
+      const newTree = [...treeData];
+      res.forEach((item, index) => {
+        newTree[index].children = item;
+      });
+      setTreeData(newTree);
+    });
   };
 
+  console.log('treeData', treeData);
+
   const getRelationUsers = async (notifyType: string, configId: string) => {
     if (notifyType === 'dingTalk') {
       const resp = await queryDingTalkUsers(configId);
@@ -170,55 +169,56 @@ export default (props: UserProps) => {
     } else {
       obj.value = _value;
     }
-    console.log(obj);
+
     if (props.onChange) {
       props.onChange(obj);
     }
   };
 
   const filterOption = (input: string, option: any) => {
-    return option.label ? option.label.toLowerCase().includes(input.toLowerCase()) : false;
+    return option.name ? option.name.toLowerCase().includes(input.toLowerCase()) : false;
+  };
+
+  const createTreeNode = (data: any): React.ReactNode => {
+    return data.map((item: any) => {
+      if (item.children) {
+        return (
+          <TreeSelect.TreeNode value={item.id} title={item.name} selectable={false}>
+            {createTreeNode(item.children)}
+          </TreeSelect.TreeNode>
+        );
+      } else {
+        return (
+          <TreeSelect.TreeNode
+            name={item.name}
+            value={item.id}
+            title={
+              <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                <span>{item.name}</span>
+                <span style={{ color: '#cfcfcf' }}>{item.username}</span>
+              </div>
+            }
+          />
+        );
+      }
+    });
   };
 
   const userSelect =
     source === 'relation' ? (
-      <Select
+      <TreeSelect
         showSearch
         value={value}
-        onChange={(key, node) => {
+        dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
+        placeholder={'请选择收信人'}
+        onSelect={(key: any, node: any) => {
           setValue(key);
           onchange(source, key, node.isRelation);
         }}
-        placeholder={'请选择收信人'}
-        listHeight={200}
-        filterOption={filterOption}
-        optionLabelProp="label"
+        filterTreeNode={filterOption}
       >
-        {userList.platform.length ? (
-          <Select.OptGroup label={'平台用户'}>
-            {userList.platform.map((item: any) => (
-              <Select.Option value={item.value} isRelation={false} label={item.label}>
-                <div style={{ display: 'flex', justifyContent: 'space-between' }}>
-                  <span>{item.label}</span>
-                  <span style={{ color: '#cfcfcf' }}>{item.username}</span>
-                </div>
-              </Select.Option>
-            ))}
-          </Select.OptGroup>
-        ) : null}
-        {userList.relation.length ? (
-          <Select.OptGroup label={'关系用户'}>
-            {userList.relation.map((item: any) => (
-              <Select.Option value={item.value} isRelation={false} label={item.label}>
-                <div style={{ display: 'flex', justifyContent: 'space-between' }}>
-                  <span>{item.label}</span>
-                  <span style={{ color: '#cfcfcf' }}>{item.username}</span>
-                </div>
-              </Select.Option>
-            ))}
-          </Select.OptGroup>
-        ) : null}
-      </Select>
+        {createTreeNode(treeData)}
+      </TreeSelect>
     ) : (
       <Select
         showSearch
@@ -239,43 +239,19 @@ export default (props: UserProps) => {
 
   const emailSelect =
     source === 'relation' ? (
-      <Select
+      <TreeSelect
         showSearch
         value={value}
-        onChange={(key, node) => {
+        dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
+        placeholder={'请选择收信人'}
+        onSelect={(key: any, node: any) => {
           setValue(key);
           onchange(source, key, node.isRelation);
         }}
-        placeholder={'请选择收信人'}
-        listHeight={200}
-        filterOption={filterOption}
-        optionLabelProp="label"
+        filterTreeNode={filterOption}
       >
-        {userList.platform.length ? (
-          <Select.OptGroup label={'平台用户'}>
-            {userList.platform.map((item: any) => (
-              <Select.Option value={item.value} isRelation={false} label={item.label}>
-                <div style={{ display: 'flex', justifyContent: 'space-between' }}>
-                  <span>{item.label}</span>
-                  <span style={{ color: '#cfcfcf' }}>{item.username}</span>
-                </div>
-              </Select.Option>
-            ))}
-          </Select.OptGroup>
-        ) : null}
-        {userList.relation.length ? (
-          <Select.OptGroup label={'关系用户'}>
-            {userList.relation.map((item: any) => (
-              <Select.Option value={item.value} isRelation={true} label={item.label}>
-                <div style={{ display: 'flex', justifyContent: 'space-between' }}>
-                  <span>{item.label}</span>
-                  <span style={{ color: '#cfcfcf' }}>{item.username}</span>
-                </div>
-              </Select.Option>
-            ))}
-          </Select.OptGroup>
-        ) : null}
-      </Select>
+        {createTreeNode(treeData)}
+      </TreeSelect>
     ) : (
       <Input
         value={value}
@@ -288,36 +264,19 @@ export default (props: UserProps) => {
 
   const voiceSelect =
     source === 'relation' ? (
-      <Select
+      <TreeSelect
         showSearch
         value={value}
-        onChange={(key, node) => {
+        dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
+        placeholder={'请选择收信人'}
+        onSelect={(key: any, node: any) => {
           setValue(key);
           onchange(source, key, node.isRelation);
         }}
-        placeholder={'请选择收信人'}
-        listHeight={200}
-        filterOption={filterOption}
+        filterTreeNode={filterOption}
       >
-        {userList.platform.length ? (
-          <Select.OptGroup label={'平台用户'}>
-            {userList.platform.map((item: any) => (
-              <Select.Option value={item.value} isRelation={false}>
-                {item.label}
-              </Select.Option>
-            ))}
-          </Select.OptGroup>
-        ) : null}
-        {userList.relation.length ? (
-          <Select.OptGroup label={'关系用户'}>
-            {userList.relation.map((item: any) => (
-              <Select.Option value={item.value} isRelation={true}>
-                {item.label}
-              </Select.Option>
-            ))}
-          </Select.OptGroup>
-        ) : null}
-      </Select>
+        {createTreeNode(treeData)}
+      </TreeSelect>
     ) : (
       <Input
         value={value}