xieyonghong 3 lat temu
rodzic
commit
dda00c1f8d
36 zmienionych plików z 766 dodań i 453 usunięć
  1. BIN
      public/images/network.png
  2. 2 0
      src/components/FBraftEditor/index.tsx
  3. 32 33
      src/components/Footer/index.tsx
  4. 4 3
      src/components/ProTableCard/CardItems/AccessConfig/index.less
  5. 3 3
      src/components/ProTableCard/CardItems/AccessConfig/index.tsx
  6. 12 5
      src/components/ProTableCard/CardItems/cascade.tsx
  7. 80 0
      src/components/ProTableCard/CardItems/networkCard.tsx
  8. 1 2
      src/components/ProTableCard/CardItems/ruleInstance.tsx
  9. 78 84
      src/pages/device/Instance/Detail/Config/index.tsx
  10. 8 2
      src/pages/device/Instance/Detail/Log/index.tsx
  11. 44 31
      src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.tsx
  12. 2 1
      src/pages/device/Instance/Detail/Tags/index.tsx
  13. 6 4
      src/pages/device/Instance/Detail/index.tsx
  14. 2 2
      src/pages/device/Instance/service.ts
  15. 2 2
      src/pages/device/Product/Detail/Access/AccessConfig/index.tsx
  16. 1 1
      src/pages/device/components/Metadata/Base/Edit/index.tsx
  17. 32 12
      src/pages/device/components/Metadata/Base/index.tsx
  18. 10 7
      src/pages/device/components/Metadata/Cat/index.tsx
  19. 27 21
      src/pages/link/AccessConfig/index.tsx
  20. 1 1
      src/pages/link/Type/Detail/index.tsx
  21. 92 7
      src/pages/link/Type/index.tsx
  22. 21 10
      src/pages/media/Cascade/Channel/index.tsx
  23. 26 12
      src/pages/media/Cascade/index.tsx
  24. 1 1
      src/pages/notice/Config/Debug/index.tsx
  25. 1 1
      src/pages/notice/Config/Log/index.tsx
  26. 3 1
      src/pages/notice/Config/index.tsx
  27. 2 2
      src/pages/notice/Config/service.ts
  28. 6 3
      src/pages/notice/Template/Debug/index.tsx
  29. 13 8
      src/pages/notice/Template/Detail/index.tsx
  30. 1 1
      src/pages/notice/Template/Log/index.tsx
  31. 3 1
      src/pages/notice/Template/index.tsx
  32. 2 2
      src/pages/notice/Template/service.ts
  33. 73 64
      src/pages/rule-engine/Instance/index.tsx
  34. 1 1
      src/pages/system/Department/save.tsx
  35. 1 1
      src/pages/system/Role/Detail/UserManage/BindUser.tsx
  36. 173 124
      src/pages/system/User/Save/index.tsx

BIN
public/images/network.png


+ 2 - 0
src/components/FBraftEditor/index.tsx

@@ -6,6 +6,7 @@ import { useState } from 'react';
 interface Props extends BraftEditorProps {
   value: any;
   onChange: (data: any) => void;
+  placeholder?: string;
 }
 
 const FBraftEditor = connect((props: Props) => {
@@ -18,6 +19,7 @@ const FBraftEditor = connect((props: Props) => {
       {
         // @ts-ignore
         <BraftEditor
+          placeholder={props.placeholder}
           value={editorState}
           onChange={(state) => {
             setEditorState(state);

+ 32 - 33
src/components/Footer/index.tsx

@@ -1,37 +1,36 @@
-import { useIntl } from 'umi';
-import { GithubOutlined } from '@ant-design/icons';
-import { DefaultFooter } from '@ant-design/pro-layout';
+// import {useIntl} from 'umi';
+// import {GithubOutlined} from '@ant-design/icons';
+// import {DefaultFooter} from '@ant-design/pro-layout';
 
 export default () => {
-  const intl = useIntl();
-  const defaultMessage = intl.formatMessage({
-    id: 'app.copyright.produced',
-    defaultMessage: 'Jetlinks Team',
-  });
+  // const intl = useIntl();
+  // const defaultMessage = intl.formatMessage({
+  //   id: 'app.copyright.produced',
+  //   defaultMessage: 'Jetlinks Team',
+  // });
 
-  return (
-    <DefaultFooter
-      copyright={`2021 ${defaultMessage}`}
-      links={[
-        {
-          key: 'Jetlinks',
-          title: 'Jetlinks',
-          href: 'https://pro.ant.design',
-          blankTarget: true,
-        },
-        {
-          key: 'github',
-          title: <GithubOutlined />,
-          href: 'https://github.com/jetlinks',
-          blankTarget: true,
-        },
-        {
-          key: 'Ant Design',
-          title: 'Ant Design',
-          href: 'https://ant.design',
-          blankTarget: true,
-        },
-      ]}
-    />
-  );
+  // const footer = <DefaultFooter
+  //   copyright={`2021 ${defaultMessage}`}
+  //   links={[
+  //     {
+  //       key: 'Jetlinks',
+  //       title: 'Jetlinks',
+  //       href: 'https://pro.ant.design',
+  //       blankTarget: true,
+  //     },
+  //     {
+  //       key: 'github',
+  //       title: <GithubOutlined/>,
+  //       href: 'https://github.com/jetlinks',
+  //       blankTarget: true,
+  //     },
+  //     {
+  //       key: 'Ant Design',
+  //       title: 'Ant Design',
+  //       href: 'https://ant.design',
+  //       blankTarget: true,
+  //     },
+  //   ]}
+  // />
+  return null;
 };

+ 4 - 3
src/components/ProTableCard/CardItems/AccessConfig/index.less

@@ -24,12 +24,13 @@
     .card {
       display: flex;
       flex-direction: column;
-      width: 100%;
+      flex-grow: 1;
+      width: 0;
       margin-left: 20px;
 
       .header {
         .title {
-          width: 70%;
+          width: calc(100% - 70px);
           overflow: hidden;
           font-weight: 700;
           font-size: 18px;
@@ -42,7 +43,7 @@
         }
 
         .desc {
-          width: 70%;
+          width: 100%;
           margin-top: 10px;
           overflow: hidden;
           color: #666;

+ 3 - 3
src/components/ProTableCard/CardItems/AccessConfig/index.tsx

@@ -38,9 +38,9 @@ export default (props: AccessConfigCardProps) => {
         </div>
         <div className="card">
           <div className="header">
-            <div className="title">
-              <Tooltip title={props.name}>{props.name || '--'}</Tooltip>
-            </div>
+            <Tooltip title={props.name}>
+              <div className="title ellipsis">{props.name || '--'}</div>
+            </Tooltip>
             <div className="desc">{props.description || '--'}</div>
           </div>
           <div className="container">

+ 12 - 5
src/components/ProTableCard/CardItems/cascade.tsx

@@ -35,11 +35,18 @@ export default (props: CascadeCardProps) => {
             <span className={'card-item-header-name ellipsis'}>{props.name}</span>
           </div>
           <div>通道数量: {props?.count || 0}</div>
-          <div>
-            <Badge
-              status={props.onlineStatus?.value === 'offline' ? 'error' : 'success'}
-              text={`sip:${props.sipConfigs[0]?.sipId}@${props.sipConfigs[0]?.hostAndPort}`}
-            />
+          <div style={{ display: 'flex', width: '100%' }}>
+            <Badge status={props.onlineStatus?.value === 'offline' ? 'error' : 'success'} />
+            <div
+              style={{
+                width: '90%',
+                overflow: 'hidden',
+                whiteSpace: 'nowrap',
+                textOverflow: 'ellipsis',
+              }}
+            >
+              sip:{props.sipConfigs[0]?.sipId}@{props.sipConfigs[0]?.hostAndPort}
+            </div>
           </div>
         </div>
       </div>

+ 80 - 0
src/components/ProTableCard/CardItems/networkCard.tsx

@@ -0,0 +1,80 @@
+import React from 'react';
+import { TableCard } from '@/components';
+import '@/style/common.less';
+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';
+
+export interface NoticeCardProps extends NetworkItem {
+  detail?: React.ReactNode;
+  actions?: React.ReactNode[];
+  avatarSize?: number;
+}
+
+const image = require('/public/images/network.png');
+
+export default (props: NoticeCardProps) => {
+  const createDetail = () => {
+    const record = props;
+    if (record.shareCluster) {
+      const publicHost = record.configuration.publicHost;
+      const publicPort = record.configuration.publicPort;
+      return publicHost ? (
+        <>
+          {networkMap[record.type]}
+          {publicHost}:{publicPort}
+        </>
+      ) : null;
+    } else {
+      const log = record.cluster?.map(
+        (item) => `${item.configuration.publicHost}:${item.configuration.publicPort}`,
+      );
+      return (
+        <>
+          {log.map((item) => (
+            <div key={item}>
+              `${networkMap[record.type]}${item}`
+            </div>
+          ))}
+        </>
+      );
+    }
+  };
+  return (
+    <TableCard
+      actions={props.actions}
+      status={props.state.value}
+      statusText={props.state.text}
+      statusNames={{
+        disabled: StatusColorEnum.error,
+        enabled: StatusColorEnum.processing,
+      }}
+      showMask={false}
+    >
+      <div className={'pro-table-card-item'}>
+        <div className={'card-item-avatar'}>
+          <img width={88} height={88} src={image} alt={props.type} />
+        </div>
+        <div className={'card-item-body'}>
+          <div className={'card-item-header'}>
+            <span className={'card-item-header-name ellipsis'}>{props.name}</span>
+          </div>
+          <div className={'card-item-content'}>
+            <div>
+              <label>类型</label>
+              <div className={'ellipsis'}>{props.type}</div>
+            </div>
+            <div>
+              <label>详情</label>
+              <Tooltip title={createDetail()}>
+                <div className={'ellipsis'}>{createDetail()}</div>
+              </Tooltip>
+            </div>
+          </div>
+        </div>
+      </div>
+    </TableCard>
+  );
+};

+ 1 - 2
src/components/ProTableCard/CardItems/ruleInstance.tsx

@@ -21,8 +21,7 @@ export default (props: RuleInstanceCardProps) => {
       statusText={props.state.text}
       statusNames={{
         started: StatusColorEnum.success,
-        stopped: StatusColorEnum.error,
-        disable: StatusColorEnum.processing,
+        disable: StatusColorEnum.error,
       }}
     >
       <div className={'pro-table-card-item'}>

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

@@ -71,7 +71,7 @@ const Config = () => {
           </div>
         );
       } else {
-        return <span>{config[item.property]}</span>;
+        return <div>{config[item.property]}</div>;
       }
     } else {
       return '--';
@@ -80,94 +80,88 @@ const Config = () => {
 
   return (
     <div style={{ width: '100%', marginTop: '20px' }} className="config">
-      <Descriptions
-        layout="vertical"
-        title={[
-          <span key={1}>配置</span>,
-          <Space key={2}>
-            <Button
-              type="link"
-              onClick={async () => {
-                setVisible(true);
+      <div style={{ display: 'flex', marginBottom: 20 }}>
+        <div style={{ fontSize: 16, fontWeight: 700 }}>配置</div>
+        <Space>
+          <Button
+            type="link"
+            onClick={async () => {
+              setVisible(true);
+            }}
+          >
+            <EditOutlined />
+            编辑
+          </Button>
+          {InstanceModel.detail.state?.value !== 'notActive' && (
+            <Popconfirm
+              title="确认重新应用该配置?"
+              onConfirm={async () => {
+                const resp = await service.deployDevice(id || '');
+                if (resp.status === 200) {
+                  message.success('操作成功');
+                  getDetail();
+                }
               }}
             >
-              <EditOutlined />
-              编辑
-            </Button>
-            {InstanceModel.detail.state?.value !== 'notActive' && (
-              <Popconfirm
-                title="确认重新应用该配置?"
-                onConfirm={async () => {
-                  const resp = await service.deployDevice(id || '');
-                  if (resp.status === 200) {
-                    message.success('操作成功');
-                    getDetail();
-                  }
-                }}
-              >
-                <Button type="link">
-                  <CheckOutlined />
-                  应用配置
-                </Button>
-                <Tooltip title="修改配置后需重新应用后才能生效。">
-                  <QuestionCircleOutlined />
-                </Tooltip>
-              </Popconfirm>
-            )}
-            {InstanceModel.detail?.aloneConfiguration && (
-              <Popconfirm
-                title="确认恢复默认配置?"
-                onConfirm={async () => {
-                  const resp = await service.configurationReset(id || '');
-                  if (resp.status === 200) {
-                    message.success('恢复默认配置成功');
-                    getDetail();
-                  }
-                }}
+              <Button type="link">
+                <CheckOutlined />
+                应用配置
+              </Button>
+              <Tooltip title="修改配置后需重新应用后才能生效。">
+                <QuestionCircleOutlined />
+              </Tooltip>
+            </Popconfirm>
+          )}
+          {InstanceModel.detail?.aloneConfiguration && (
+            <Popconfirm
+              title="确认恢复默认配置?"
+              onConfirm={async () => {
+                const resp = await service.configurationReset(id || '');
+                if (resp.status === 200) {
+                  message.success('恢复默认配置成功');
+                  getDetail();
+                }
+              }}
+            >
+              <Button type="link">
+                <UndoOutlined />
+                恢复默认
+              </Button>
+              <Tooltip
+                title={`该设备单独编辑过配置信息,点击此将恢复成默认的配置信息,请谨慎操作。`}
               >
-                <Button type="link">
-                  <UndoOutlined />
-                  恢复默认
-                </Button>
-                <Tooltip
-                  title={`该设备单独编辑过配置信息,点击此将恢复成默认的配置信息,请谨慎操作。`}
-                >
-                  <QuestionCircleOutlined />
-                </Tooltip>
-              </Popconfirm>
-            )}
-          </Space>,
-        ]}
-      >
+                <QuestionCircleOutlined />
+              </Tooltip>
+            </Popconfirm>
+          )}
+        </Space>
+      </div>
+      <div style={{ paddingLeft: 10 }}>
         {(metadata || []).map((i) => (
-          <Descriptions.Item key={i.name} label={<h4>{i.name}</h4>} span={3}>
-            <div style={{ width: '100%' }}>
-              <Descriptions column={2} bordered size="small">
-                {(i?.properties || []).map((item: any) => (
-                  <Descriptions.Item
-                    span={1}
-                    label={
-                      item.description ? (
-                        <div>
-                          <span style={{ marginRight: '10px' }}>{item.name}</span>
-                          <Tooltip title={item.description}>
-                            <QuestionCircleOutlined />
-                          </Tooltip>
-                        </div>
-                      ) : (
-                        item.name
-                      )
-                    }
-                    key={item.property}
-                  >
-                    {renderComponent(item)}
-                  </Descriptions.Item>
-                ))}
-              </Descriptions>
-            </div>
-          </Descriptions.Item>
+          <Descriptions size="small" column={3} key={i.name} bordered title={<h4>{i.name}</h4>}>
+            {(i?.properties || []).map((item: any) => (
+              <Descriptions.Item
+                span={1}
+                label={
+                  item?.description ? (
+                    <div>
+                      <span style={{ marginRight: '10px' }}>{item.name}</span>
+                      <Tooltip title={item.description}>
+                        <QuestionCircleOutlined />
+                      </Tooltip>
+                    </div>
+                  ) : (
+                    item.name
+                  )
+                }
+                key={item.property}
+              >
+                {renderComponent(item)}
+              </Descriptions.Item>
+            ))}
+          </Descriptions>
         ))}
-      </Descriptions>
+      </div>
       {visible && (
         <Edit
           metadata={metadata || []}

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

@@ -1,7 +1,7 @@
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
 import type { LogItem } from '@/pages/device/Instance/Detail/Log/typings';
-import { Card, Modal, Tooltip } from 'antd';
+import { Card, Input, Modal, Tooltip } from 'antd';
 import { SearchOutlined } from '@ant-design/icons';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { InstanceModel, service } from '@/pages/device/Instance';
@@ -70,7 +70,13 @@ const Log = () => {
         return [
           <a
             key="editable"
-            onClick={() => Modal.info({ title: '详细信息', content: <pre>{content}</pre> })}
+            onClick={() =>
+              Modal.info({
+                title: '详细信息',
+                width: 700,
+                content: <Input.TextArea bordered={false} rows={15} value={content} />,
+              })
+            }
           >
             <Tooltip title="查看">
               <SearchOutlined />

+ 44 - 31
src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.tsx

@@ -1,7 +1,7 @@
 import React, { useContext, useEffect, useState } from 'react';
 import { Form, Input, message, Pagination, Select, Table } from 'antd';
 import { service } from '@/pages/device/Instance';
-import { QuestionCircleOutlined } from '@ant-design/icons';
+import _ from 'lodash';
 
 const EditableContext: any = React.createContext(null);
 
@@ -42,7 +42,11 @@ const EditableCell = ({
   const save = async () => {
     try {
       const values = await form.validateFields();
-      handleSave({ ...record, ...values });
+      if (values) {
+        handleSave({ ...record, ...values });
+      } else {
+        console.log(values);
+      }
     } catch (errInfo) {
       console.log('Save failed:', errInfo);
     }
@@ -62,6 +66,7 @@ const EditableCell = ({
         <Select
           onChange={save}
           showSearch
+          allowClear
           optionFilterProp="children"
           filterOption={(input: string, option: any) =>
             (option?.children || '').toLowerCase()?.indexOf(input.toLowerCase()) >= 0
@@ -111,6 +116,7 @@ const EditableTable = (props: Props) => {
     total: properties.length,
   });
   const [protocolMetadata, setProtocolMetadata] = useState<any[]>([]);
+  const [pmList, setPmList] = useState<any[]>([]);
 
   const components = {
     body: {
@@ -132,12 +138,19 @@ const EditableTable = (props: Props) => {
       data.map((i: any) => {
         obj[i?.originalId] = i?.metadataId || '';
       });
-      const list = properties.map((item) => {
-        return {
-          ...item,
-          metadataId: obj[item.id] || '',
-        };
-      });
+      if (protocolMetadata.length > 0) {
+        setPmList(protocolMetadata.filter((i) => !_.map(data, 'metadataId').includes(i.id)));
+      } else {
+        setPmList([]);
+      }
+      const list = (JSON.parse(props?.data?.metadata || '{}')?.properties || []).map(
+        (item: any) => {
+          return {
+            ...item,
+            metadataId: obj[item.id] || '',
+          };
+        },
+      );
       setProperties([...list]);
       setDataSource({
         data: list.slice(
@@ -152,18 +165,20 @@ const EditableTable = (props: Props) => {
   };
 
   useEffect(() => {
-    service
-      .queryProtocolMetadata(
-        props.type === 'device' ? props.data?.protocol : props.data?.messageProtocol,
-        props.type === 'device' ? props.data?.transport : props.data?.transportProtocol,
-      )
-      .then((resp) => {
-        if (resp.status === 200) {
-          setProtocolMetadata(JSON.parse(resp.result || '{}')?.properties || []);
-          initData();
-        }
-      });
-  }, []);
+    if (props.data && Object.keys(props.data).length > 0) {
+      service
+        .queryProtocolMetadata(
+          props.type === 'device' ? props.data?.protocol : props.data?.messageProtocol,
+          props.type === 'device' ? props.data?.transport : props.data?.transportProtocol,
+        )
+        .then((resp) => {
+          if (resp.status === 200) {
+            setProtocolMetadata(JSON.parse(resp.result || '{}')?.properties || []);
+            initData();
+          }
+        });
+    }
+  }, [props.data]);
 
   const handleSave = async (row: any) => {
     const newData = [...dataSource.data];
@@ -231,7 +246,7 @@ const EditableTable = (props: Props) => {
         editable: col.editable,
         dataIndex: col.dataIndex,
         title: col.title,
-        list: protocolMetadata,
+        list: pmList,
         handleSave: handleSave,
       }),
     };
@@ -253,16 +268,14 @@ const EditableTable = (props: Props) => {
             });
           }}
         />
-        <div>
-          <div style={{ color: 'rgba(0, 0, 0, .65)' }}>
-            <QuestionCircleOutlined style={{ margin: 5 }} />
-            该设备已脱离产品物模型映射,修改产品物模型映射对该设备物模型映射无影响
-          </div>
-          <div style={{ color: 'rgba(0, 0, 0, .65)' }}>
-            <QuestionCircleOutlined style={{ margin: 5 }} />
-            设备会默认继承产品的物模型映射,修改设备物模型映射后将脱离产品物模型映射
-          </div>
-        </div>
+        {/* <div style={{ color: 'rgba(0, 0, 0, .65)' }}>
+                    <QuestionCircleOutlined style={{ margin: 5 }} />
+                    {
+                        props?.data?.independentMetadata ?
+                            '该设备已脱离产品物模型映射,修改产品物模型映射对该设备物模型映射无影响' :
+                            '设备会默认继承产品的物模型映射,修改设备物模型映射后将脱离产品物模型映射'
+                    }
+                </div> */}
       </div>
       <Table
         components={components}

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

@@ -23,6 +23,7 @@ const Tags = () => {
       <Descriptions
         style={{ marginBottom: 20 }}
         bordered
+        column={3}
         size="small"
         title={
           <span>
@@ -43,7 +44,7 @@ const Tags = () => {
         }
       >
         {(tags || [])?.map((item: any) => (
-          <Descriptions.Item label={`${item.name}(${item.key})`} key={item.key}>
+          <Descriptions.Item span={1} label={`${item.name}(${item.key})`} key={item.key}>
             {item.value || '--'}
           </Descriptions.Item>
         ))}

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

@@ -1,7 +1,7 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import { InstanceModel, service } from '@/pages/device/Instance';
 import { history, useParams } from 'umi';
-import { Badge, Button, Card, Descriptions, Divider, message, Tooltip } from 'antd';
+import { Badge, Button, Card, Descriptions, Divider, message, Popconfirm, Tooltip } from 'antd';
 import type { ReactNode } from 'react';
 import { useEffect, useState } from 'react';
 import { observer } from '@formily/react';
@@ -70,9 +70,11 @@ const InstanceDetail = observer(() => {
           <Metadata
             type="device"
             tabAction={
-              <Tooltip title="重置后将使用产品的物模型配置">
-                <Button onClick={resetMetadata}>重置操作</Button>
-              </Tooltip>
+              <Popconfirm title="确认重置?" onConfirm={resetMetadata}>
+                <Tooltip title="重置后将使用产品的物模型配置">
+                  <Button>重置操作</Button>
+                </Tooltip>
+              </Popconfirm>
             }
           />
         </Card>

+ 2 - 2
src/pages/device/Instance/service.ts

@@ -195,7 +195,7 @@ class Service extends BaseService<DeviceInstance> {
     });
   // 执行功能
   public executeFunctions = (deviceId: string, functionId: string, data: any) =>
-    request(`/${SystemConst.API_BASE}device/invoked/${deviceId}/function/${functionId}`, {
+    request(`/${SystemConst.API_BASE}/device/invoked/${deviceId}/function/${functionId}`, {
       method: 'POST',
       data,
     });
@@ -207,7 +207,7 @@ class Service extends BaseService<DeviceInstance> {
     });
   // 设置属性
   public settingProperties = (deviceId: string, data: any) =>
-    request(`/${SystemConst.API_BASE}/device/setting/${deviceId}/property`, {
+    request(`/${SystemConst.API_BASE}//device-instance/${deviceId}/property`, {
       method: 'POST',
       data,
     });

+ 2 - 2
src/pages/device/Product/Detail/Access/AccessConfig/index.tsx

@@ -41,7 +41,7 @@ const AccessConfig = (props: Props) => {
     service
       .queryList({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
       .then((resp) => {
-        setDataSource(resp.result);
+        setDataSource(resp?.result);
       });
   };
 
@@ -140,7 +140,7 @@ const AccessConfig = (props: Props) => {
         </div>
       </div>
       <Row gutter={[16, 16]}>
-        {dataSource.data.map((item: any) => (
+        {(dataSource?.data || []).map((item: any) => (
           <Col
             key={item.name}
             span={12}

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

@@ -582,7 +582,7 @@ const Edit = observer((props: Props) => {
             },
           },
           type: {
-            title: '属性类型',
+            title: MetadataModel.type === 'tags' ? '标签类型' : '属性类型',
             required: true,
             'x-decorator': 'FormItem',
             'x-component': 'Select',

+ 32 - 12
src/pages/device/components/Metadata/Base/index.tsx

@@ -51,6 +51,21 @@ const BaseMetadata = observer((props: Props) => {
     }
   };
 
+  const limitsMap = new Map<string, any>();
+  limitsMap.set('events-add', 'eventNotInsertable');
+  limitsMap.set('events-updata', 'eventNotModifiable');
+  limitsMap.set('properties-add', 'propertyNotInsertable');
+  limitsMap.set('properties-updata', 'propertyNotModifiable');
+
+  const operateLimits = (action: 'add' | 'updata', types: MetadataType) => {
+    return (
+      target === 'device' &&
+      (typeMap.get('device')?.features || []).find(
+        (item: { id: string; name: string }) => item.id === limitsMap.get(`${types}-${action}`),
+      )
+    );
+  };
+
   const actions: ProColumns<MetadataItem>[] = [
     {
       title: '操作',
@@ -58,8 +73,10 @@ const BaseMetadata = observer((props: Props) => {
       align: 'center',
       width: 200,
       render: (_: unknown, record: MetadataItem) => [
-        <a
+        <Button
           key="editable"
+          type="link"
+          disabled={operateLimits('updata', type)}
           onClick={() => {
             MetadataModel.edit = true;
             MetadataModel.item = record;
@@ -70,7 +87,7 @@ const BaseMetadata = observer((props: Props) => {
           <Tooltip title="编辑">
             <EditOutlined />
           </Tooltip>
-        </a>,
+        </Button>,
         <a key="delete">
           <Popconfirm
             title="确认删除?"
@@ -144,16 +161,18 @@ const BaseMetadata = observer((props: Props) => {
           },
         }}
         toolBarRender={() => [
-          <Button
-            onClick={() => {
-              MetadataModel.importMetadata = true;
-            }}
-            key="button"
-            icon={<ImportOutlined />}
-            type="ghost"
-          >
-            导入属性
-          </Button>,
+          props.type === 'properties' && (
+            <Button
+              onClick={() => {
+                MetadataModel.importMetadata = true;
+              }}
+              key="button"
+              icon={<ImportOutlined />}
+              type="ghost"
+            >
+              导入属性
+            </Button>
+          ),
           <Button
             onClick={() => {
               MetadataModel.edit = true;
@@ -161,6 +180,7 @@ const BaseMetadata = observer((props: Props) => {
               MetadataModel.type = type;
               MetadataModel.action = 'add';
             }}
+            disabled={operateLimits('add', type)}
             key="button"
             icon={<PlusOutlined />}
             type="primary"

+ 10 - 7
src/pages/device/components/Metadata/Cat/index.tsx

@@ -2,13 +2,14 @@ import { Drawer, Tabs } from 'antd';
 import { useEffect, useState } from 'react';
 import { productModel, service } from '@/pages/device/Product';
 import MonacoEditor from 'react-monaco-editor';
+import { observer } from '@formily/react';
 
 interface Props {
   visible: boolean;
   close: () => void;
 }
 
-const Cat = (props: Props) => {
+const Cat = observer((props: Props) => {
   const [codecs, setCodecs] = useState<{ id: string; name: string }[]>();
   const metadata = productModel.current?.metadata as string;
   const [value, setValue] = useState(metadata);
@@ -23,11 +24,13 @@ const Cat = (props: Props) => {
   const convertMetadata = (key: string) => {
     if (key === 'alink') {
       setValue('');
-      service.convertMetadata('to', 'alink', JSON.parse(metadata)).subscribe({
-        next: (data) => {
-          setValue(JSON.stringify(data));
-        },
-      });
+      if (metadata) {
+        service.convertMetadata('to', 'alink', JSON.parse(metadata)).subscribe({
+          next: (data) => {
+            setValue(JSON.stringify(data));
+          },
+        });
+      }
     } else {
       setValue(metadata);
     }
@@ -65,6 +68,6 @@ const Cat = (props: Props) => {
       </Tabs>
     </Drawer>
   );
-};
+});
 
 export default Cat;

+ 27 - 21
src/pages/link/AccessConfig/index.tsx

@@ -2,7 +2,7 @@ import SearchComponent from '@/components/SearchComponent';
 import { getButtonPermission, getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
 import { PageContainer } from '@ant-design/pro-layout';
 import type { ProColumns } from '@jetlinks/pro-table';
-import { Button, Card, Col, Empty, message, Pagination, Popconfirm, Row } from 'antd';
+import { Button, Card, Col, Empty, message, Pagination, Popconfirm, Row, Tooltip } from 'antd';
 import { useEffect, useState } from 'react';
 import { useHistory } from 'umi';
 import Service from './service';
@@ -13,7 +13,7 @@ export const service = new Service('gateway/device');
 
 const AccessConfig = () => {
   const history = useHistory();
-  const [param, setParam] = useState<any>({ pageSize: 10 });
+  const [param, setParam] = useState<any>({ pageSize: 10, terms: [] });
 
   const columns: ProColumns<any>[] = [
     {
@@ -148,28 +148,34 @@ const AccessConfig = () => {
                         )}
                       </Popconfirm>
                     </Button>,
-                    <Button
+                    <Tooltip
                       key="delete"
-                      type="link"
-                      disabled={getButtonPermission('link/AccessConfig', ['delete'])}
+                      title={
+                        getButtonPermission('link/AccessConfig', ['delete']) ? '没有权限' : '删除'
+                      }
                     >
-                      <Popconfirm
-                        title={'确认删除?'}
-                        onConfirm={() => {
-                          service.remove(item.id).then((resp: any) => {
-                            if (resp.status === 200) {
-                              message.success('操作成功!');
-                              handleSearch(param);
-                            } else {
-                              message.error(resp.message);
-                            }
-                          });
-                        }}
+                      <Button
+                        type="link"
+                        disabled={getButtonPermission('link/AccessConfig', ['delete'])}
                       >
-                        <DeleteOutlined />
-                        删除
-                      </Popconfirm>
-                    </Button>,
+                        <Popconfirm
+                          title={'确认删除?'}
+                          onConfirm={() => {
+                            service.remove(item.id).then((resp: any) => {
+                              if (resp.status === 200) {
+                                message.success('操作成功!');
+                                handleSearch(param);
+                              } else {
+                                message.error(resp?.message || '操作失败');
+                              }
+                            });
+                          }}
+                        >
+                          <DeleteOutlined />
+                          删除
+                        </Popconfirm>
+                      </Button>
+                    </Tooltip>,
                   ]}
                 />
               </Col>

+ 1 - 1
src/pages/link/Type/Detail/index.tsx

@@ -776,7 +776,7 @@ const Save = observer(() => {
     }
   };
   return (
-    <PageContainer onBack={() => history.back()}>
+    <PageContainer className={'page-title-show'} onBack={() => history.back()}>
       <Card>
         <Form form={form} layout="vertical" style={{ padding: 30 }}>
           <SchemaField

+ 92 - 7
src/pages/link/Type/index.tsx

@@ -1,6 +1,5 @@
 import { useRef, useState } from 'react';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
-import ProTable from '@jetlinks/pro-table';
 import { Badge, Button, message, Popconfirm, Tooltip } from 'antd';
 import {
   CloseCircleOutlined,
@@ -17,6 +16,8 @@ import { getButtonPermission, getMenuPathByParams, MENUS_CODE } from '@/utils/me
 import { history } from 'umi';
 import Service from '@/pages/link/service';
 import { Store } from 'jetlinks-store';
+import { ProTableCard } from '@/components';
+import NetworkCard from '@/components/ProTableCard/CardItems/networkCard';
 
 export const service = new Service('network/config');
 
@@ -29,6 +30,16 @@ const pageJump = (id?: string) => {
   history.push(`${getMenuPathByParams(MENUS_CODE['link/Type/Detail'], id)}`);
 };
 
+export const networkMap = {
+  UDP: 'udp://',
+  TCP_SERVER: 'tcp://',
+  WEB_SOCKET_SERVER: 'ws://',
+  MQTT_CLIENT: 'mqtt://',
+  HTTP_SERVER: 'http://',
+  MQTT_SERVER: 'mqtt://',
+  COAP_SERVER: 'coap://',
+};
+
 const Network = () => {
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
@@ -60,11 +71,12 @@ const Network = () => {
         if (record.shareCluster) {
           const publicHost = record.configuration.publicHost;
           const publicPort = record.configuration.publicPort;
-          return (
+          return publicHost ? (
             <>
-              公网: {publicHost}:{publicPort}
+              {networkMap[record.type]}
+              {publicHost}:{publicPort}
             </>
-          );
+          ) : null;
         } else {
           const log = record.cluster?.map(
             (item) => `${item.configuration.publicHost}:${item.configuration.publicPort}`,
@@ -72,7 +84,9 @@ const Network = () => {
           return (
             <>
               {log.map((item) => (
-                <div key={item}>公网:{item}</div>
+                <div key={item}>
+                  `${networkMap[record.type]}${item}`
+                </div>
               ))}
             </>
           );
@@ -114,7 +128,7 @@ const Network = () => {
             pageJump(record.id);
           }}
         >
-          <Tooltip title="查看">
+          <Tooltip title="编辑">
             <EditOutlined />
           </Tooltip>
         </Button>,
@@ -198,7 +212,7 @@ const Network = () => {
           setParam(data);
         }}
       />
-      <ProTable<NetworkItem>
+      <ProTableCard<NetworkItem>
         actionRef={actionRef}
         params={param}
         columns={columns}
@@ -222,6 +236,77 @@ const Network = () => {
         request={async (params) =>
           service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
         }
+        gridColumn={3}
+        cardRender={(record) => (
+          <NetworkCard
+            {...record}
+            actions={[
+              <Button
+                key="edit"
+                onClick={() => {
+                  Store.set('current-network-data', record);
+                  pageJump(record.id);
+                }}
+              >
+                <EditOutlined />
+                编辑
+              </Button>,
+              <Tooltip title={record.state.value === 'enabled' ? '禁用' : '启用'}>
+                <Popconfirm
+                  disabled={getButtonPermission('link/Type', ['action'])}
+                  title={`确认${record.state.value === 'enabled' ? '禁用' : '启用'}?`}
+                  onConfirm={async () => {
+                    // await service.update({
+                    //   id: record.id,
+                    //   status: record.status ? 0 : 1,
+                    // });
+                    const map = {
+                      disabled: 'start',
+                      enabled: 'shutdown',
+                    };
+                    await service.changeState(record.id, map[record.state.value]);
+                    message.success(
+                      intl.formatMessage({
+                        id: 'pages.data.option.success',
+                        defaultMessage: '操作成功!',
+                      }),
+                    );
+                    actionRef.current?.reload();
+                  }}
+                >
+                  <Button
+                    type="link"
+                    style={{ padding: 0 }}
+                    disabled={getButtonPermission('link/Type', ['action'])}
+                    key="changeState"
+                  >
+                    {record.state.value === 'enabled' ? (
+                      <CloseCircleOutlined />
+                    ) : (
+                      <PlayCircleOutlined />
+                    )}
+                    {record.state.value === 'enabled' ? '禁用' : '启用'}
+                  </Button>
+                </Popconfirm>
+              </Tooltip>,
+              <Popconfirm
+                key="delete"
+                title="确认删除?"
+                onConfirm={async () => {
+                  const response: any = await service.remove(record.id);
+                  if (response.status === 200) {
+                    message.success('删除成功');
+                    actionRef.current?.reload();
+                  }
+                }}
+              >
+                <Button key="delete">
+                  <DeleteOutlined />
+                </Button>
+              </Popconfirm>,
+            ]}
+          />
+        )}
       />
     </PageContainer>
   );

+ 21 - 10
src/pages/media/Cascade/Channel/index.tsx

@@ -19,6 +19,7 @@ const Channel = () => {
   const [selectedRowKey, setSelectedRowKey] = useState<string[]>([]);
   const id = location?.query?.id || '';
   const [data, setData] = useState<string>('');
+  const [popVisible, setPopvisible] = useState<string>('');
 
   const unbind = async (list: string[]) => {
     const resp = await service.unbindChannel(id, list);
@@ -43,9 +44,11 @@ const Channel = () => {
           style={{ marginTop: 10, width: '100%' }}
           onClick={async () => {
             if (!!data) {
-              const resp: any = service.editBindInfo(record.id, { gbChannelId: data });
+              const resp: any = await service.editBindInfo(record.id, { gbChannelId: data });
               if (resp.status === 200) {
+                message.success('操作成功');
                 actionRef.current?.reload();
+                setPopvisible('');
               }
             } else {
               message.error('请输入国标ID');
@@ -68,17 +71,23 @@ const Channel = () => {
       title: '通道名称',
     },
     {
-      dataIndex: 'channelId',
+      dataIndex: 'gbChannelId',
       title: '国标ID',
       tooltip: '国标级联有18位、20位两种格式。在当前页面修改不会修改视频设备-通道页面中的国标ID',
       render: (text: any, record: any) => (
         <span>
           {text}
-          <Popover trigger="click" content={content(record)} title="编辑通道ID">
+          <Popover
+            visible={popVisible === record.id}
+            trigger="click"
+            content={content(record)}
+            title="编辑国标ID"
+          >
             <a
               style={{ marginLeft: 10 }}
               onClick={() => {
                 setData('');
+                setPopvisible(record.id);
               }}
             >
               <EditOutlined />
@@ -119,7 +128,7 @@ const Channel = () => {
           key={'unbinds'}
           title="确认解绑"
           onConfirm={() => {
-            unbind([record.id]);
+            unbind([record.channelId]);
           }}
         >
           <a>
@@ -136,7 +145,7 @@ const Channel = () => {
     <PageContainer>
       <SearchComponent<any>
         field={columns}
-        target="unbind-channel"
+        target="bind-channel"
         onSearch={(params) => {
           actionRef.current?.reload();
           setParam({
@@ -157,7 +166,7 @@ const Channel = () => {
             sorts: [{ name: 'createTime', order: 'desc' }],
           })
         }
-        rowKey="id"
+        rowKey="channelId"
         rowSelection={{
           selectedRowKeys: selectedRowKey,
           onChange: (selectedRowKeys) => {
@@ -195,18 +204,20 @@ const Channel = () => {
           >
             绑定通道
           </Button>,
-          <Button
-            onClick={() => {
+          <Popconfirm
+            title={'确认解绑'}
+            onConfirm={() => {
               if (selectedRowKey.length > 0) {
                 unbind(selectedRowKey);
+                setSelectedRowKey([]);
               } else {
                 message.error('请先选择需要解绑的通道列表');
               }
             }}
             key="unbind"
           >
-            批量解绑
-          </Button>,
+            <Button>批量解绑</Button>
+          </Popconfirm>,
         ]}
       />
       {visible && (

+ 26 - 12
src/pages/media/Cascade/index.tsx

@@ -68,21 +68,29 @@ const Cascade = () => {
         选择通道
       </Tooltip>
     </Button>,
-    <Button type={'link'} key={'share'} disabled={record.status.value === 'disabled'}>
-      <Popconfirm
+    <Tooltip
+      key={'share'}
+      title={record.status.value === 'disabled' ? '禁用状态下不可推送' : '推送'}
+    >
+      <Button
+        type={'link'}
         key={'share'}
-        title="确认推送!"
-        onConfirm={() => {
-          setCurrent(record);
-          setVisible(true);
-        }}
+        disabled={
+          getButtonPermission('media/Cascade', ['push']) || record.status.value === 'disabled'
+        }
       >
-        <Tooltip title={record.status.value === 'disabled' ? '禁用状态下不可推送' : '推送'}>
+        <Popconfirm
+          title="确认推送!"
+          onConfirm={() => {
+            setCurrent(record);
+            setVisible(true);
+          }}
+        >
           <ShareAltOutlined />
           推送
-        </Tooltip>
-      </Popconfirm>
-    </Button>,
+        </Popconfirm>
+      </Button>
+    </Tooltip>,
     <Button
       type={'link'}
       key={'operate'}
@@ -262,7 +270,13 @@ const Cascade = () => {
           title={record.status.value === 'disabled' ? '禁用状态下不可推送' : '推送'}
           key={'share'}
         >
-          <Button type="link" style={{ padding: 0 }} disabled={record.status.value === 'disabled'}>
+          <Button
+            type="link"
+            style={{ padding: 0 }}
+            disabled={
+              getButtonPermission('media/Cascade', ['push']) || record.status.value === 'disabled'
+            }
+          >
             <Popconfirm
               onConfirm={() => {
                 setVisible(true);

+ 1 - 1
src/pages/notice/Config/Debug/index.tsx

@@ -164,7 +164,7 @@ const Debug = observer(() => {
     const list = Store.get('notice-template-list');
     const _template = list.find((item: any) => item.id === templateId);
 
-    const resp = await service.debug(state?.current.id, {
+    const resp = await service.debug(state?.current.id, templateId, {
       template: _template,
       context: data.variableDefinitions?.reduce(
         (previousValue: any, currentValue: { id: any; value: any }) => {

+ 1 - 1
src/pages/notice/Config/Log/index.tsx

@@ -57,7 +57,7 @@ const Log = observer(() => {
       visible={state.log && !!state.current?.id}
     >
       <SearchComponent
-        defaultParam={[{ column: 'type$IN', value: id }]}
+        defaultParam={[{ column: 'notifyType$IN', value: id }]}
         field={columns}
         onSearch={(data) => {
           actionRef.current?.reset?.();

+ 3 - 1
src/pages/notice/Config/index.tsx

@@ -242,7 +242,9 @@ const Config = observer(() => {
           </Space>
         }
         gridColumn={3}
-        request={async (params) => service.query(params)}
+        request={async (params) =>
+          service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
+        }
         cardRender={(record) => (
           <NoticeConfig
             {...record}

+ 2 - 2
src/pages/notice/Config/service.ts

@@ -28,8 +28,8 @@ class Service extends BaseService<ConfigItem> {
       data,
     });
 
-  public debug = (id: string, data: Record<string, any>) =>
-    request(`${SystemConst.API_BASE}/notifier/${id}/_send`, {
+  public debug = (id: string, templateId: string, data: Record<string, any>) =>
+    request(`${SystemConst.API_BASE}/notifier/${id}/${templateId}/_send`, {
       method: 'POST',
       data,
     });

+ 6 - 3
src/pages/notice/Template/Debug/index.tsx

@@ -86,7 +86,10 @@ const Debug = observer(() => {
   const getConfig = () =>
     configService
       .queryNoPagingPost({
-        terms: [{ column: 'type$IN', value: id }],
+        terms: [
+          { column: 'type$IN', value: id },
+          { column: 'provider', value: state.current?.provider },
+        ],
       })
       .then((resp: any) => {
         // 缓存通知配置
@@ -166,10 +169,10 @@ const Debug = observer(() => {
   };
 
   const start = async () => {
-    const data: { templateId: string; variableDefinitions: any } = await form.submit();
+    const data: { configId: string; variableDefinitions: any } = await form.submit();
     // 应该取选择的配置信息
     if (!state.current) return;
-    const resp = await service.debug(state?.current.id, {
+    const resp = await service.debug(data.configId, state?.current.id, {
       template: state.current,
       context: data.variableDefinitions?.reduce(
         (previousValue: any, currentValue: { id: any; value: any }) => {

+ 13 - 8
src/pages/notice/Template/Detail/index.tsx

@@ -102,7 +102,10 @@ const Detail = observer(() => {
         effects() {
           onFieldInit('template.message', (field) => {
             if (id === 'email') {
-              field.setComponent(FBraftEditor);
+              field.setComponent(FBraftEditor, {
+                placeholder:
+                  '变量格式:${name};\n 示例:尊敬的${name},${time}有设备触发告警,请注意处理',
+              });
             }
           });
           onFieldValueChange('provider', (field, form1) => {
@@ -132,7 +135,6 @@ const Detail = observer(() => {
                 });
                 break;
               case 'officialMessage':
-                // TODO 通知配置不能为空
                 form1.setFieldState('template.tagid', async (state1) => {
                   state1.dataSource = await getWeixinOfficialTags(value);
                 });
@@ -837,15 +839,15 @@ const Detail = observer(() => {
                     },
                     properties: {
                       code: {
-                        title: '模版ID',
+                        title: '模版',
                         'x-component': 'Select',
                         'x-decorator': 'FormItem',
                         'x-decorator-props': {
-                          tooltip: '请输入模版ID',
+                          tooltip: '阿里云短信平台自定义的模版名称',
                           gridSpan: 1,
                         },
                         'x-component-props': {
-                          placeholder: '请输入模版ID',
+                          placeholder: '请选择模版',
                         },
                         'x-reactions': {
                           dependencies: ['configId'],
@@ -898,7 +900,10 @@ const Detail = observer(() => {
                 'x-decorator': 'FormItem',
                 title: '标题',
                 'x-decorator-props': {
-                  tip: '请输入邮件标题',
+                  tip: '邮件标题',
+                },
+                'x-component-props': {
+                  placeholder: '请输入标题',
                 },
               },
               sendTo: {
@@ -906,7 +911,7 @@ const Detail = observer(() => {
                 'x-decorator': 'FormItem',
                 title: '收件人',
                 'x-decorator-props': {
-                  tip: '请输入收件人邮箱,多个收件人用换行分隔',
+                  tip: '多个收件人用换行分隔 \n最大支持1000个号码',
                 },
               },
               attachments: {
@@ -969,7 +974,7 @@ const Detail = observer(() => {
         'x-component': 'Input.TextArea',
         'x-decorator': 'FormItem',
         'x-decorator-props': {
-          tooltip: '请输入模版内容',
+          tooltip: '发送的内容,支持录入变量',
         },
         required: true,
         'x-reactions': {

+ 1 - 1
src/pages/notice/Template/Log/index.tsx

@@ -59,7 +59,7 @@ const Log = observer(() => {
       visible={state.log}
     >
       <SearchComponent
-        defaultParam={[{ column: 'type$IN', value: id }]}
+        defaultParam={[{ column: 'notifyType$IN', value: id }]}
         field={columns}
         onSearch={(data) => {
           actionRef.current?.reset?.();

+ 3 - 1
src/pages/notice/Template/index.tsx

@@ -282,7 +282,9 @@ const Template = () => {
             ]}
           />
         )}
-        request={async (params) => service.query(params)}
+        request={async (params) =>
+          service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
+        }
       />
       <Debug />
       <Log />

+ 2 - 2
src/pages/notice/Template/service.ts

@@ -31,8 +31,8 @@ class Service extends BaseService<TemplateItem> {
       data,
     });
 
-  public debug = (id: string, data: Record<string, any>) =>
-    request(`${SystemConst.API_BASE}/notifier/${id}/_send`, {
+  public debug = (id: string, templateId: string, data: Record<string, any>) =>
+    request(`${SystemConst.API_BASE}/notifier/${id}/${templateId}/_send`, {
       method: 'POST',
       data,
     });

+ 73 - 64
src/pages/rule-engine/Instance/index.tsx

@@ -117,40 +117,47 @@ const Instance = () => {
         </Tooltip>
       </Popconfirm>
     </Button>,
-    <Button
-      type={'link'}
+    <Tooltip
       key={'delete'}
-      style={{ padding: 0 }}
-      disabled={getButtonPermission('rule-engine/Instance', ['delete'])}
+      title={
+        record.state.value !== 'disable'
+          ? '已启用不能删除'
+          : intl.formatMessage({
+              id: 'pages.data.option.remove',
+              defaultMessage: '删除',
+            })
+      }
     >
-      <Popconfirm
-        title={record.state.value === 'disable' ? '确认删除' : '未停止不能删除'}
-        key={'delete'}
-        onConfirm={async () => {
-          if (record.state.value === 'disable') {
-            await service.remove(record.id);
-            message.success(
-              intl.formatMessage({
-                id: 'pages.data.option.success',
-                defaultMessage: '操作成功!',
-              }),
-            );
-            actionRef.current?.reload();
-          } else {
-            message.error('未停止不能删除');
-          }
-        }}
+      <Button
+        type={'link'}
+        style={{ padding: 0 }}
+        disabled={
+          getButtonPermission('rule-engine/Instance', ['delete']) ||
+          record.state.value !== 'disable'
+        }
       >
-        <Tooltip
-          title={intl.formatMessage({
-            id: 'pages.data.option.remove',
-            defaultMessage: '删除',
-          })}
+        <Popconfirm
+          title={record.state.value === 'disable' ? '确认删除' : '未停止不能删除'}
+          key={'delete'}
+          onConfirm={async () => {
+            if (record.state.value === 'disable') {
+              await service.remove(record.id);
+              message.success(
+                intl.formatMessage({
+                  id: 'pages.data.option.success',
+                  defaultMessage: '操作成功!',
+                }),
+              );
+              actionRef.current?.reload();
+            } else {
+              message.error('未停止不能删除');
+            }
+          }}
         >
           <DeleteOutlined />
-        </Tooltip>
-      </Popconfirm>
-    </Button>,
+        </Popconfirm>
+      </Button>
+    </Tooltip>,
   ];
 
   const columns: ProColumns<InstanceItem>[] = [
@@ -223,7 +230,6 @@ const Instance = () => {
         <Button
           type="link"
           style={{ padding: 0 }}
-          disabled={getButtonPermission('rule-engine/Instance', ['view'])}
           key={'view'}
           onClick={() => {
             window.open(`/${SystemConst.API_BASE}/rule-editor/index.html#flow/${record.id}`);
@@ -279,40 +285,47 @@ const Instance = () => {
             </Tooltip>
           </Popconfirm>
         </Button>,
-        <Button
-          disabled={getButtonPermission('rule-engine/Instance', ['delete'])}
-          type={'link'}
+        <Tooltip
           key={'delete'}
-          style={{ padding: 0 }}
+          title={
+            record.state.value !== 'disable'
+              ? '已启用不能删除'
+              : intl.formatMessage({
+                  id: 'pages.data.option.remove',
+                  defaultMessage: '删除',
+                })
+          }
         >
-          <Popconfirm
-            title={record.state.value === 'disable' ? '确认删除' : '未禁用不能删除'}
-            key={'delete'}
-            onConfirm={async () => {
-              if (record.state.value === 'disable') {
-                await service.remove(record.id);
-                message.success(
-                  intl.formatMessage({
-                    id: 'pages.data.option.success',
-                    defaultMessage: '操作成功!',
-                  }),
-                );
-                actionRef.current?.reload();
-              } else {
-                message.error('未禁用不能删除');
-              }
-            }}
+          <Button
+            disabled={
+              getButtonPermission('rule-engine/Instance', ['delete']) ||
+              record.state.value !== 'disable'
+            }
+            type={'link'}
+            style={{ padding: 0 }}
           >
-            <Tooltip
-              title={intl.formatMessage({
-                id: 'pages.data.option.remove',
-                defaultMessage: '删除',
-              })}
+            <Popconfirm
+              title={record.state.value === 'disable' ? '确认删除' : '未禁用不能删除'}
+              key={'delete'}
+              onConfirm={async () => {
+                if (record.state.value === 'disable') {
+                  await service.remove(record.id);
+                  message.success(
+                    intl.formatMessage({
+                      id: 'pages.data.option.success',
+                      defaultMessage: '操作成功!',
+                    }),
+                  );
+                  actionRef.current?.reload();
+                } else {
+                  message.error('未禁用不能删除');
+                }
+              }}
             >
               <DeleteOutlined />
-            </Tooltip>
-          </Popconfirm>
-        </Button>,
+            </Popconfirm>
+          </Button>
+        </Tooltip>,
       ],
     },
   ];
@@ -373,11 +386,7 @@ const Instance = () => {
               <div
                 style={{ padding: 8, fontSize: 24 }}
                 onClick={() => {
-                  if (!getButtonPermission('rule-engine/Instance', ['view'])) {
-                    window.open(
-                      `/${SystemConst.API_BASE}/rule-editor/index.html#flow/${record.id}`,
-                    );
-                  }
+                  window.open(`/${SystemConst.API_BASE}/rule-editor/index.html#flow/${record.id}`);
                 }}
               >
                 <EyeOutlined />

+ 1 - 1
src/pages/system/Department/save.tsx

@@ -1,5 +1,6 @@
 // Modal 弹窗,用于新增、修改数据
 import React from 'react';
+import type { Field } from '@formily/core';
 import { createForm, onFieldReact } from '@formily/core';
 import { createSchemaField } from '@formily/react';
 import {
@@ -18,7 +19,6 @@ import {
   TreeSelect,
   Upload,
 } from '@formily/antd';
-import type { Field } from '@formily/core';
 import { message, Modal } from 'antd';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import type { ISchema } from '@formily/json-schema';

+ 1 - 1
src/pages/system/Role/Detail/UserManage/BindUser.tsx

@@ -55,7 +55,7 @@ const BindUser = (props: Props) => {
   return (
     <Modal
       title="添加"
-      width={900}
+      width={990}
       visible={props.visible}
       onCancel={() => {
         props.cancel();

+ 173 - 124
src/pages/system/User/Save/index.tsx

@@ -6,7 +6,16 @@ import { createSchemaField } from '@formily/react';
 import React, { useEffect, useState } from 'react';
 import * as ICONS from '@ant-design/icons';
 import { PlusOutlined } from '@ant-design/icons';
-import { Form, FormItem, Input, Password, Select, Switch, TreeSelect } from '@formily/antd';
+import {
+  Form,
+  FormGrid,
+  FormItem,
+  Input,
+  Password,
+  Select,
+  Switch,
+  TreeSelect,
+} from '@formily/antd';
 import type { ISchema } from '@formily/json-schema';
 import { action } from '@formily/reactive';
 import type { Response } from '@/utils/typings';
@@ -75,6 +84,7 @@ const Save = (props: Props) => {
       Switch,
       Select,
       TreeSelect,
+      FormGrid,
     },
     scope: {
       icon(name: any) {
@@ -86,72 +96,93 @@ const Save = (props: Props) => {
   const schema: ISchema = {
     type: 'object',
     properties: {
-      name: {
-        title: intl.formatMessage({
-          id: 'pages.system.name',
-          defaultMessage: '姓名',
-        }),
-        type: 'string',
-        'x-decorator': 'FormItem',
-        'x-component': 'Input',
-        name: 'name',
-        'x-validator': [
-          {
-            max: 64,
-            message: '最多可输入64个字符',
-          },
-          {
-            required: true,
-            message: '请输入姓名',
-          },
-        ],
-        // required: true,
-      },
-      username: {
-        title: intl.formatMessage({
-          id: 'pages.system.username',
-          defaultMessage: '用户名',
-        }),
-        type: 'string',
-        'x-decorator': 'FormItem',
-        'x-component': 'Input',
-        'x-component-props': {
-          disabled: model === 'edit',
+      layout: {
+        type: 'void',
+        'x-decorator': 'FormGrid',
+        'x-decorator-props': {
+          maxColumns: 2,
+          minColumns: 2,
+          columnGap: 24,
         },
-        'x-validator': [
-          {
-            max: 50,
-            message: '最多可输入50个字符',
-          },
-          {
-            required: true,
-            message: '请输入用户名',
+        properties: {
+          name: {
+            title: intl.formatMessage({
+              id: 'pages.system.name',
+              defaultMessage: '姓名',
+            }),
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            'x-component-props': {
+              placeholder: '请输入姓名',
+            },
+            name: 'name',
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入姓名',
+              },
+            ],
+            // required: true,
           },
-          {
-            triggerType: 'onBlur',
-            validator: (value: string) => {
-              return new Promise((resolve) => {
-                service
-                  .validateField('username', value)
-                  .then((resp) => {
-                    if (resp.status === 200) {
-                      if (resp.result.passed) {
+          username: {
+            title: intl.formatMessage({
+              id: 'pages.system.username',
+              defaultMessage: '用户名',
+            }),
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-component-props': {
+              disabled: model === 'edit',
+              placeholder: '请输入用户名',
+            },
+            'x-validator': [
+              {
+                max: 50,
+                message: '最多可输入50个字符',
+              },
+              {
+                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(resp.result.reason);
+                          }
+                        }
                         resolve('');
-                      } else {
-                        resolve(resp.result.reason);
-                      }
-                    }
-                    resolve('');
-                  })
-                  .catch(() => {
-                    return '验证失败!';
+                      })
+                      .catch(() => {
+                        return '验证失败!';
+                      });
                   });
-              });
-            },
+                },
+              },
+            ],
+            name: 'username',
+            required: true,
           },
-        ],
-        name: 'username',
-        required: true,
+        },
       },
       password: {
         type: 'string',
@@ -163,7 +194,7 @@ const Save = (props: Props) => {
         'x-component': 'Password',
         'x-component-props': {
           checkStrength: true,
-          placeholder: '********',
+          placeholder: '请输入密码',
         },
         maxLength: 128,
         minLength: 6,
@@ -204,7 +235,7 @@ const Save = (props: Props) => {
         'x-component': 'Password',
         'x-component-props': {
           checkStrength: true,
-          placeholder: '********',
+          placeholder: '请再次输入密码',
         },
         maxLength: 128,
         minLength: 6,
@@ -236,70 +267,88 @@ const Save = (props: Props) => {
         'x-decorator-props': {},
         name: 'confirmPassword',
       },
-      roleIdList: {
-        title: '角色',
-        'x-decorator': 'FormItem',
-        'x-component': 'Select',
-        'x-component-props': {
-          mode: 'multiple',
-          showArrow: true,
-          filterOption: (input: string, option: any) =>
-            option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
-        },
-        'x-reactions': ['{{useAsyncDataSource(getRole)}}'],
+      layout2: {
+        type: 'void',
+        'x-decorator': 'FormGrid',
         'x-decorator-props': {
-          addonAfter: (
-            <a
-              onClick={() => {
-                const tab: any = window.open(`${origin}/#/system/role?save=true`);
-                tab!.onTabSaveSuccess = (value: any) => {
-                  form.setFieldState('roleIdList', (state) => {
-                    state.dataSource = state.dataSource?.concat([
-                      { label: value.name, value: value.id },
-                    ]);
-                  });
-                };
-              }}
-            >
-              <PlusOutlined />
-            </a>
-          ),
+          maxColumns: 2,
+          minColumns: 2,
+          columnGap: 24,
         },
-      },
-      orgIdList: {
-        title: '部门',
-        'x-decorator': 'FormItem',
-        'x-component': 'TreeSelect',
-        'x-component-props': {
-          multiple: true,
-          showArrow: true,
-          showCheckedStrategy: ATreeSelect.SHOW_ALL,
-          filterOption: (input: string, option: any) =>
-            option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
-          fieldNames: {
-            label: 'name',
-            value: 'id',
+        properties: {
+          roleIdList: {
+            title: '角色',
+            'x-decorator': 'FormItem',
+            'x-component': 'Select',
+            'x-component-props': {
+              mode: 'multiple',
+              showArrow: true,
+              placeholder: '请选择角色',
+              filterOption: (input: string, option: any) =>
+                option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+            },
+            'x-reactions': ['{{useAsyncDataSource(getRole)}}'],
+            'x-decorator-props': {
+              gridSpan: 1,
+              addonAfter: (
+                <a
+                  onClick={() => {
+                    const tab: any = window.open(`${origin}/#/system/role?save=true`);
+                    tab!.onTabSaveSuccess = (value: any) => {
+                      form.setFieldState('roleIdList', (state) => {
+                        state.dataSource = state.dataSource?.concat([
+                          { label: value.name, value: value.id },
+                        ]);
+                      });
+                    };
+                  }}
+                >
+                  <PlusOutlined />
+                </a>
+              ),
+            },
+          },
+          orgIdList: {
+            title: '部门',
+            'x-decorator': 'FormItem',
+            'x-component': 'TreeSelect',
+            'x-component-props': {
+              multiple: true,
+              showArrow: true,
+              placeholder: '请选择角色',
+              showCheckedStrategy: ATreeSelect.SHOW_ALL,
+              filterOption: (input: string, option: any) =>
+                option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+              fieldNames: {
+                label: 'name',
+                value: 'id',
+              },
+              treeNodeFilterProp: 'name',
+            },
+            'x-decorator-props': {
+              gridSpan: 1,
+              addonAfter: (
+                <a
+                  onClick={() => {
+                    const tab: any = window.open(`${origin}/#/system/department?save=true`);
+                    tab!.onTabSaveSuccess = (value: any) => {
+                      console.log(value, 'value');
+                      form.setFieldState('orgIdList', (state) => {
+                        state.dataSource = state.dataSource?.concat({
+                          name: value.name,
+                          id: value.id,
+                        });
+                      });
+                    };
+                  }}
+                >
+                  <PlusOutlined />
+                </a>
+              ),
+            },
+            'x-reactions': ['{{useAsyncDataSource(getOrg)}}'],
           },
-          treeNodeFilterProp: 'name',
-        },
-        'x-decorator-props': {
-          addonAfter: (
-            <a
-              onClick={() => {
-                const tab: any = window.open(`${origin}/#/system/department?save=true`);
-                tab!.onTabSaveSuccess = (value: any) => {
-                  console.log(value, 'value');
-                  form.setFieldState('orgIdList', (state) => {
-                    state.dataSource = state.dataSource?.concat({ name: value.name, id: value.id });
-                  });
-                };
-              }}
-            >
-              <PlusOutlined />
-            </a>
-          ),
         },
-        'x-reactions': ['{{useAsyncDataSource(getOrg)}}'],
       },
     },
   };