Browse Source

fix: merge next

wzyyy 3 years ago
parent
commit
34ae819cd0

+ 14 - 14
config/config.ts

@@ -29,9 +29,9 @@ export default defineConfig({
   locale: {
   locale: {
     // default zh-CN
     // default zh-CN
     default: 'zh-CN',
     default: 'zh-CN',
-    antd: true,
+    antd: false,
     // default true, when it is true, will use `navigator.language` overwrite default
     // default true, when it is true, will use `navigator.language` overwrite default
-    baseNavigator: true,
+    baseNavigator: false,
   },
   },
   dynamicImport: {
   dynamicImport: {
     loading: '@ant-design/pro-layout/es/PageLoading',
     loading: '@ant-design/pro-layout/es/PageLoading',
@@ -62,18 +62,18 @@ export default defineConfig({
   // Fast Refresh 热更新
   // Fast Refresh 热更新
   fastRefresh: {},
   fastRefresh: {},
   openAPI: [
   openAPI: [
-    {
-      requestLibPath: "import { request } from 'umi'",
-      // 或者使用在线的版本
-      // schemaPath: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json"
-      schemaPath: join(__dirname, 'oneapi.json'),
-      mock: false,
-    },
-    {
-      requestLibPath: "import { request } from 'umi'",
-      schemaPath: 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json',
-      projectName: 'swagger',
-    },
+    // {
+    //   requestLibPath: "import { request } from 'umi'",
+    //   // 或者使用在线的版本
+    //   // schemaPath: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json"
+    //   schemaPath: join(__dirname, 'oneapi.json'),
+    //   mock: false,
+    // },
+    // {
+    //   requestLibPath: "import { request } from 'umi'",
+    //   schemaPath: 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json',
+    //   projectName: 'swagger',
+    // },
   ],
   ],
   nodeModulesTransform: { type: 'none' },
   nodeModulesTransform: { type: 'none' },
   // mfsu: {},
   // mfsu: {},

+ 1 - 1
src/components/FMonacoEditor/index.tsx

@@ -2,7 +2,7 @@ import MonacoEditor from 'react-monaco-editor';
 import { connect, mapProps } from '@formily/react';
 import { connect, mapProps } from '@formily/react';
 import { useState } from 'react';
 import { useState } from 'react';
 
 
-const JMonacoEditor = (props: any) => {
+export const JMonacoEditor = (props: any) => {
   const [loading, setLoading] = useState(false);
   const [loading, setLoading] = useState(false);
 
 
   return (
   return (

+ 54 - 0
src/components/ProTableCard/CardItems/edge/Resource.tsx

@@ -0,0 +1,54 @@
+import { Ellipsis, TableCard } from '@/components';
+import { StatusColorEnum } from '@/components/BadgeStatus';
+import '@/style/common.less';
+import '../../index.less';
+import React from 'react';
+
+export interface ResourceCardProps extends Partial<ResourceItem> {
+  detail?: React.ReactNode;
+  actions?: React.ReactNode[];
+  avatarSize?: number;
+  className?: string;
+  content?: React.ReactNode[];
+  onClick?: () => void;
+  showTool?: boolean;
+}
+
+const defaultImage = require('/public/images/device-type-3-big.png');
+
+export default (props: ResourceCardProps) => {
+  return (
+    <TableCard
+      showMask={false}
+      detail={props.detail}
+      actions={props.actions}
+      status={props.state?.value}
+      statusText={props.state?.text}
+      statusNames={{
+        enabled: StatusColorEnum.success,
+        disabled: StatusColorEnum.error,
+      }}
+    >
+      <div className={'pro-table-card-item'}>
+        <div className={'card-item-avatar'}>
+          <img width={88} height={88} src={defaultImage} alt={''} />
+        </div>
+        <div className={'card-item-body'}>
+          <div className={'card-item-header'}>
+            <Ellipsis title={props.name} titleClassName={'card-item-header-name'} />
+          </div>
+          <div className={'card-item-content'}>
+            <div>
+              <label>通讯协议</label>
+              <Ellipsis title={props?.category || ''} />
+            </div>
+            <div>
+              <label>所属边缘网关</label>
+              <Ellipsis title={props?.category || ''} />
+            </div>
+          </div>
+        </div>
+      </div>
+    </TableCard>
+  );
+};

+ 2 - 2
src/global.less

@@ -154,8 +154,8 @@ input[type='tel'] {
 #driver-page-overlay {
 #driver-page-overlay {
   opacity: 0.35 !important;
   opacity: 0.35 !important;
 }
 }
-.ant-pagination.mini .ant-pagination-jump-prev,
-.ant-pagination.mini .ant-pagination-jump-next {
+.ant-pagination-mini .ant-pagination-jump-prev,
+.ant-pagination-mini .ant-pagination-jump-next {
   display: none;
   display: none;
 }
 }
 
 

+ 111 - 0
src/pages/edge/Resource/Issue/Result.tsx

@@ -0,0 +1,111 @@
+import SystemConst from '@/utils/const';
+import Token from '@/utils/token';
+import { downloadObject } from '@/utils/util';
+import { Col, Input, Modal, Row } from 'antd';
+import { EventSourcePolyfill } from 'event-source-polyfill';
+import { useEffect, useState } from 'react';
+import { DeviceInstance } from '@/pages/device/Instance/typings';
+
+interface Props {
+  list: Partial<DeviceInstance>[];
+  data: Partial<ResourceItem>;
+  close: () => void;
+}
+
+const Publish = (props: Props) => {
+  const [count, setCount] = useState<number>(0);
+  const [countErr, setCountErr] = useState<number>(0);
+  const [flag, setFlag] = useState<boolean>(true);
+  const [errMessage, setErrMessage] = useState<any[]>([]);
+
+  const getData = () => {
+    let dt = 0;
+    let et = 0;
+    const errMessages: any[] = [];
+    const _terms = {
+      deviceId: (props.list || []).map((item) => item.id),
+      name: props.data.name,
+      targetId: props.data.targetId,
+      targetType: props.data.targetType,
+      category: props.data.category,
+      metadata: encodeURIComponent(props.data?.metadata || ''),
+    };
+    const url = new URLSearchParams();
+    Object.keys(_terms).forEach((key) => {
+      if (Array.isArray(_terms[key]) && _terms[key].length) {
+        _terms[key].map((item: string) => {
+          url.append(key, item);
+        });
+      } else {
+        url.append(key, _terms[key]);
+      }
+    });
+    const source = new EventSourcePolyfill(
+      `/${
+        SystemConst.API_BASE
+      }/edge/operations/entity-template-save/invoke/_batch?:X_Access_Token=${Token.get()}&${url.toString()}`,
+    );
+    source.onmessage = (e: any) => {
+      const res = JSON.parse(e.data);
+      if (res.successful) {
+        dt += 1;
+        setCount(dt);
+      } else {
+        et += 1;
+        setCountErr(et);
+        setFlag(false);
+        if (errMessages.length <= 5) {
+          errMessages.push({ ...res });
+          setErrMessage([...errMessages]);
+        }
+      }
+    };
+    source.onerror = () => {
+      source.close();
+    };
+    source.onopen = () => {};
+  };
+
+  useEffect(() => {
+    getData();
+  }, []);
+
+  return (
+    <Modal
+      title={'下发结果'}
+      maskClosable={false}
+      open
+      onCancel={props.close}
+      onOk={props.close}
+      width={900}
+    >
+      <Row gutter={24} style={{ marginBottom: 20 }}>
+        <Col span={8}>
+          <div>成功: {count}</div>
+          <div>
+            失败: {countErr}
+            {errMessage.length > 0 && (
+              <a
+                style={{ marginLeft: 20 }}
+                onClick={() => {
+                  downloadObject(errMessage || '', '下发失败原因');
+                }}
+              >
+                下载
+              </a>
+            )}
+          </div>
+        </Col>
+        <Col span={8}>下发设备数量: {props.list?.length || 0}</Col>
+        <Col span={8}>已下发数量: {countErr + count}</Col>
+      </Row>
+      {!flag && (
+        <div>
+          <Input.TextArea rows={10} value={JSON.stringify(errMessage)} />
+        </div>
+      )}
+    </Modal>
+  );
+};
+
+export default Publish;

+ 182 - 7
src/pages/edge/Resource/Issue/index.tsx

@@ -1,23 +1,198 @@
-import { Modal } from 'antd';
+import { Badge, Modal } from 'antd';
+import { DeviceInstance } from '@/pages/device/Instance/typings';
+import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import { Key, useRef, useState } from 'react';
+import moment from 'moment';
+import { service } from '../index';
+import SearchComponent from '@/components/SearchComponent';
+import { statusMap } from '@/pages/device/Instance';
+import styles from '@/pages/link/AccessConfig/Detail/components/Network/index.less';
+import { InfoCircleOutlined } from '@ant-design/icons';
+import { onlyMessage } from '@/utils/util';
+import Result from './Result';
 interface Props {
 interface Props {
-  data: any;
+  data: Partial<ResourceItem>;
   cancel: () => void;
   cancel: () => void;
 }
 }
 
 
 export default (props: Props) => {
 export default (props: Props) => {
+  const intl = useIntl();
+  const actionRef = useRef<ActionType>();
+  const [searchParams, setSearchParams] = useState<any>({});
+
+  const columns: ProColumns<DeviceInstance>[] = [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      width: 200,
+      ellipsis: true,
+      fixed: 'left',
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.table.productName',
+        defaultMessage: '产品名称',
+      }),
+      dataIndex: 'productName',
+      ellipsis: true,
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.table.deviceName',
+        defaultMessage: '设备名称',
+      }),
+      dataIndex: 'name',
+      ellipsis: true,
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.device.instance.registrationTime',
+        defaultMessage: '注册时间',
+      }),
+      dataIndex: 'registryTime',
+      width: '200px',
+      render: (text: any) => (text ? moment(text).format('YYYY-MM-DD HH:mm:ss') : ''),
+      sorter: true,
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.searchTable.titleStatus',
+        defaultMessage: '状态',
+      }),
+      dataIndex: 'state',
+      width: '90px',
+      valueType: 'select',
+      renderText: (record) =>
+        record ? <Badge status={statusMap.get(record.value)} text={record.text} /> : '',
+      valueEnum: {
+        notActive: {
+          text: intl.formatMessage({
+            id: 'pages.device.instance.status.notActive',
+            defaultMessage: '禁用',
+          }),
+          status: 'notActive',
+        },
+        offline: {
+          text: intl.formatMessage({
+            id: 'pages.device.instance.status.offLine',
+            defaultMessage: '离线',
+          }),
+          status: 'offline',
+        },
+        online: {
+          text: intl.formatMessage({
+            id: 'pages.device.instance.status.onLine',
+            defaultMessage: '在线',
+          }),
+          status: 'online',
+        },
+      },
+      filterMultiple: false,
+    },
+  ];
+
+  const [data, setData] = useState<Partial<DeviceInstance>[]>([]);
+  const [visible, setVisible] = useState<boolean>(false);
+
   return (
   return (
     <Modal
     <Modal
       open
       open
-      title={'下发'}
-      onOk={() => {
-        props.cancel();
+      title={'下发设备'}
+      onOk={async () => {
+        if (data.length) {
+          setVisible(true);
+        } else {
+          onlyMessage('请选择设备', 'error');
+        }
       }}
       }}
       onCancel={() => {
       onCancel={() => {
         props.cancel();
         props.cancel();
       }}
       }}
-      width={700}
+      width={1000}
     >
     >
-      下发
+      <div className={styles.alert}>
+        <InfoCircleOutlined style={{ marginRight: 10 }} />
+        离线设备无法进行设备模板下发
+      </div>
+      <SearchComponent<DeviceInstance>
+        field={columns}
+        enableSave={false}
+        model="simple"
+        target="edge-resource-issue"
+        onSearch={(param) => {
+          actionRef.current?.reset?.();
+          setSearchParams(param);
+        }}
+      />
+      <ProTable<DeviceInstance>
+        tableAlertRender={false}
+        rowSelection={{
+          type: 'checkbox',
+          onSelect: (selectedRow: any, selected: any) => {
+            let newSelectKeys = [...data];
+            if (selected) {
+              newSelectKeys.push({ ...selectedRow });
+            } else {
+              newSelectKeys = newSelectKeys.filter((item) => item.id !== selectedRow.id);
+            }
+            setData(newSelectKeys);
+          },
+          onSelectAll: (selected: boolean, _: any, changeRows: any) => {
+            let newSelectKeys = [...data];
+            if (selected) {
+              changeRows.forEach((item: any) => {
+                newSelectKeys.push({ ...item });
+              });
+            } else {
+              newSelectKeys = newSelectKeys.filter((a) => {
+                return !changeRows.some((b: any) => b.id === a.id);
+              });
+            }
+            setData(newSelectKeys);
+          },
+          selectedRowKeys: data?.map((item) => item.id) as Key[],
+        }}
+        params={searchParams}
+        toolBarRender={false}
+        rowKey="id"
+        pagination={{
+          pageSize: 10,
+        }}
+        search={false}
+        columnEmptyText={''}
+        columns={columns}
+        actionRef={actionRef}
+        request={(params) =>
+          service.queryDeviceList({
+            ...params,
+            terms: [
+              ...(params?.terms || []),
+              {
+                terms: [
+                  {
+                    termType: 'eq',
+                    column: 'productId$product-info',
+                    value: 'accessProvider is official-edge-gateway',
+                  },
+                ],
+                type: 'and',
+              },
+            ],
+            sorts: [{ name: 'createTime', order: 'desc' }],
+          })
+        }
+      />
+      {visible && (
+        <Result
+          data={props.data}
+          list={data}
+          close={() => {
+            setVisible(false);
+            props.cancel();
+          }}
+        />
+      )}
     </Modal>
     </Modal>
   );
   );
 };
 };

+ 23 - 11
src/pages/edge/Resource/Save/index.tsx

@@ -1,13 +1,20 @@
 import { Modal } from 'antd';
 import { Modal } from 'antd';
-import MonacoEditor from 'react-monaco-editor';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
+import { JMonacoEditor } from '@/components/FMonacoEditor';
+import { service } from '@/pages/edge/Resource';
+import { onlyMessage } from '@/utils/util';
 interface Props {
 interface Props {
-  data: any;
+  data: Partial<ResourceItem>;
   cancel: () => void;
   cancel: () => void;
+  reload: () => void;
 }
 }
 
 
 export default (props: Props) => {
 export default (props: Props) => {
-  const [monacoValue, setMonacoValue] = useState<string>(props.data);
+  const [monacoValue, setMonacoValue] = useState<string>(props.data?.metadata || '{}');
+
+  useEffect(() => {
+    setMonacoValue(props.data?.metadata || '{}');
+  }, [props.data]);
 
 
   const editorDidMountHandle = (editor: any) => {
   const editorDidMountHandle = (editor: any) => {
     editor.getAction('editor.action.formatDocument').run();
     editor.getAction('editor.action.formatDocument').run();
@@ -20,21 +27,26 @@ export default (props: Props) => {
     <Modal
     <Modal
       open
       open
       title={'编辑'}
       title={'编辑'}
-      onOk={() => {
-        props.cancel();
+      onOk={async () => {
+        if (props.data?.id) {
+          const resp = await service.modify(props.data.id, { metadata: monacoValue });
+          if (resp.status === 200) {
+            props.reload();
+            onlyMessage('操作成功', 'success');
+          }
+        }
       }}
       }}
       onCancel={() => {
       onCancel={() => {
         props.cancel();
         props.cancel();
       }}
       }}
       width={700}
       width={700}
     >
     >
-      <MonacoEditor
-        width={'100%'}
-        height={400}
-        theme="vs-dark"
+      <JMonacoEditor
+        height={350}
+        theme="vs"
         language={'json'}
         language={'json'}
         value={monacoValue}
         value={monacoValue}
-        onChange={(newValue) => {
+        onChange={(newValue: any) => {
           setMonacoValue(newValue);
           setMonacoValue(newValue);
         }}
         }}
         editorDidMount={editorDidMountHandle}
         editorDidMount={editorDidMountHandle}

+ 263 - 384
src/pages/edge/Resource/index.tsx

@@ -1,394 +1,273 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import { PageContainer } from '@ant-design/pro-layout';
-// import {DeviceInstance} from "@/pages/device/Instance/typings";
-// import SearchComponent from "@/components/SearchComponent";
-// import {ActionType, ProColumns} from "@jetlinks/pro-table";
-// import moment from "moment";
-// import {Badge, Button, Tooltip} from "antd";
-// import {service as categoryService} from "@/pages/device/Category";
-// import {InstanceModel, service, statusMap} from "@/pages/device/Instance";
-// import {useIntl} from "@@/plugin-locale/localeExports";
-// import {
-//   DeleteOutlined,
-//   DownSquareOutlined,
-//   EditOutlined,
-//   EyeOutlined,
-//   PlayCircleOutlined,
-//   StopOutlined,
-// } from "@ant-design/icons";
-// import {PermissionButton, ProTableCard} from "@/components";
-// import {useRef, useState} from "react";
-// import DeviceCard from "@/components/ProTableCard/CardItems/device";
-// import {onlyMessage} from "@/utils/util";
-// import {getMenuPathByParams, MENUS_CODE} from "@/utils/menu";
-// import {useHistory} from "umi";
-// import Save from './Save';
+import SearchComponent from '@/components/SearchComponent';
+import { ActionType, ProColumns } from '@jetlinks/pro-table';
+import { Badge } from 'antd';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import {
+  DeleteOutlined,
+  DownSquareOutlined,
+  EditOutlined,
+  PlayCircleOutlined,
+  StopOutlined,
+} from '@ant-design/icons';
+import { PermissionButton, ProTableCard } from '@/components';
+import { useRef, useState } from 'react';
+import { onlyMessage } from '@/utils/util';
+import Save from './Save';
+import Issue from './Issue';
+import Service from './service';
+import ResourceCard from '@/components/ProTableCard/CardItems/edge/Resource';
+import moment from 'moment';
+
+export const service = new Service('entity/template');
 
 
 export default () => {
 export default () => {
-  // const intl = useIntl();
-  // const actionRef = useRef<ActionType>();
-  // const [searchParams, setSearchParams] = useState<any>({});
-  // const [current, setCurrent] = useState<Partial<DeviceInstance>>({});
-  // const [visible, setVisible] = useState<boolean>(false);
-  // const history = useHistory<Record<string, string>>();
+  const intl = useIntl();
+  const actionRef = useRef<ActionType>();
+  const [searchParams, setSearchParams] = useState<any>({});
+  const [current, setCurrent] = useState<Partial<ResourceItem>>({});
+  const [visible, setVisible] = useState<boolean>(false);
+  const [issueVisible, setIssueVisible] = useState<boolean>(false);
 
 
-  // const tools = (record: DeviceInstance, type: 'card' | 'list') => [
-  //   type === 'list' && <Button
-  //     type={'link'}
-  //     style={{ padding: 0 }}
-  //     key={'detail'}
-  //     onClick={() => {
-  //       InstanceModel.current = record;
-  //       const url = getMenuPathByParams(MENUS_CODE['device/Instance/Detail'], record.id);
-  //       history.push(url);
-  //     }}
-  //   >
-  //     <Tooltip
-  //       title={intl.formatMessage({
-  //         id: 'pages.data.option.detail',
-  //         defaultMessage: '查看',
-  //       })}
-  //     >
-  //       <EyeOutlined />
-  //     </Tooltip>
-  //   </Button>,
-  //   <PermissionButton
-  //     type={'link'}
-  //     isPermission={true}
-  //     onClick={() => {
-  //       setCurrent(record);
-  //       setVisible(true);
-  //     }}
-  //     tooltip={{
-  //       title: type === 'list' ? '编辑' : '',
-  //     }}
-  //     style={{ padding: 0 }}
-  //     key={'edit'}
-  //   >
-  //     <EditOutlined />
-  //     {type === 'list' ? '' : '编辑'}
-  //   </PermissionButton>,
-  //   <PermissionButton
-  //     type={'link'}
-  //     onClick={() => {
-  //     }}
-  //     tooltip={{
-  //       title: type !== 'list' ? '' : '下发'
-  //     }}
-  //     style={{ padding: 0 }}
-  //     isPermission={true}
-  //     key={'reset'}
-  //   >
-  //     <DownSquareOutlined />
-  //     {type === 'list' ? '' : '下发'}
-  //   </PermissionButton>,
-  //   <PermissionButton
-  //     type={'link'}
-  //     key={'state'}
-  //     style={{ padding: 0 }}
-  //     popConfirm={{
-  //       title: intl.formatMessage({
-  //         id: `pages.data.option.${
-  //           record.state.value !== 'notActive' ? 'disabled' : 'enabled'
-  //         }.tips`,
-  //         defaultMessage: '确认禁用?',
-  //       }),
-  //       onConfirm: () => {
-  //         if (record.state.value !== 'notActive') {
-  //           service.undeployDevice(record.id).then((resp: any) => {
-  //             if (resp.status === 200) {
-  //               onlyMessage(
-  //                 intl.formatMessage({
-  //                   id: 'pages.data.option.success',
-  //                   defaultMessage: '操作成功!',
-  //                 }),
-  //               );
-  //               actionRef.current?.reload();
-  //             }
-  //           });
-  //         } else {
-  //           service.deployDevice(record.id).then((resp: any) => {
-  //             if (resp.status === 200) {
-  //               onlyMessage(
-  //                 intl.formatMessage({
-  //                   id: 'pages.data.option.success',
-  //                   defaultMessage: '操作成功!',
-  //                 }),
-  //               );
-  //               actionRef.current?.reload();
-  //             }
-  //           });
-  //         }
-  //       },
-  //     }}
-  //     isPermission={true}
-  //     tooltip={{
-  //       title: intl.formatMessage({
-  //         id: `pages.data.option.${record.state.value !== 'notActive' ? 'disabled' : 'enabled'}`,
-  //         defaultMessage: record.state.value !== 'notActive' ? '禁用' : '启用',
-  //       }),
-  //     }}
-  //   >
-  //     {record.state.value !== 'notActive' ? <StopOutlined /> : <PlayCircleOutlined />}
-  //     {record.state.value !== 'notActive' ? (type === 'list' ? '' : '禁用') : (type === 'list' ? '' : '启用')}
-  //   </PermissionButton>,
-  //   <PermissionButton
-  //     type={'link'}
-  //     key={'delete'}
-  //     style={{ padding: 0 }}
-  //     isPermission={true}
-  //     tooltip={
-  //       record.state.value !== 'notActive'
-  //         ? { title: intl.formatMessage({ id: 'pages.device.instance.deleteTip' }) }
-  //         : undefined
-  //     }
-  //     disabled={record.state.value !== 'notActive'}
-  //     popConfirm={{
-  //       title: intl.formatMessage({
-  //         id: 'pages.data.option.remove.tips',
-  //       }),
-  //       disabled: record.state.value !== 'notActive',
-  //       onConfirm: async () => {
-  //         if (record.state.value === 'notActive') {
-  //           await service.remove(record.id);
-  //           onlyMessage(
-  //             intl.formatMessage({
-  //               id: 'pages.data.option.success',
-  //               defaultMessage: '操作成功!',
-  //             }),
-  //           );
-  //           actionRef.current?.reload();
-  //         } else {
-  //           onlyMessage(intl.formatMessage({ id: 'pages.device.instance.deleteTip' }), 'error');
-  //         }
-  //       },
-  //     }}
-  //   >
-  //     <DeleteOutlined />
-  //   </PermissionButton>,
-  // ];
-  // const columns: ProColumns<DeviceInstance>[] = [
-  //   {
-  //     title: 'ID',
-  //     dataIndex: 'id',
-  //     width: 200,
-  //     ellipsis: true,
-  //     fixed: 'left',
-  //   },
-  //   {
-  //     title: intl.formatMessage({
-  //       id: 'pages.table.deviceName',
-  //       defaultMessage: '设备名称',
-  //     }),
-  //     dataIndex: 'name',
-  //     ellipsis: true,
-  //     width: 200,
-  //   },
-  //   {
-  //     title: intl.formatMessage({
-  //       id: 'pages.table.productName',
-  //       defaultMessage: '产品名称',
-  //     }),
-  //     dataIndex: 'productId',
-  //     width: 200,
-  //     ellipsis: true,
-  //     valueType: 'select',
-  //     request: async () => {
-  //       const res = await service.getProductList();
-  //       if (res.status === 200) {
-  //         return res.result.map((pItem: any) => ({ label: pItem.name, value: pItem.id }));
-  //       }
-  //       return [];
-  //     },
-  //     render: (_, row) => row.productName,
-  //     filterMultiple: true,
-  //   },
-  //   {
-  //     title: intl.formatMessage({
-  //       id: 'pages.device.instance.registrationTime',
-  //       defaultMessage: '注册时间',
-  //     }),
-  //     dataIndex: 'registryTime',
-  //     width: '200px',
-  //     valueType: 'dateTime',
-  //     render: (_: any, row) => {
-  //       return row.registryTime ? moment(row.registryTime).format('YYYY-MM-DD HH:mm:ss') : '';
-  //     },
-  //     sorter: true,
-  //   },
-  //   {
-  //     title: intl.formatMessage({
-  //       id: 'pages.searchTable.titleStatus',
-  //       defaultMessage: '状态',
-  //     }),
-  //     dataIndex: 'state',
-  //     width: '90px',
-  //     valueType: 'select',
-  //     renderText: (record) =>
-  //       record ? <Badge status={statusMap.get(record.value)} text={record.text} /> : '',
-  //     valueEnum: {
-  //       notActive: {
-  //         text: intl.formatMessage({
-  //           id: 'pages.device.instance.status.notActive',
-  //           defaultMessage: '禁用',
-  //         }),
-  //         status: 'notActive',
-  //       },
-  //       offline: {
-  //         text: intl.formatMessage({
-  //           id: 'pages.device.instance.status.offLine',
-  //           defaultMessage: '离线',
-  //         }),
-  //         status: 'offline',
-  //       },
-  //       online: {
-  //         text: intl.formatMessage({
-  //           id: 'pages.device.instance.status.onLine',
-  //           defaultMessage: '在线',
-  //         }),
-  //         status: 'online',
-  //       },
-  //     },
-  //     filterMultiple: false,
-  //   },
-  //   {
-  //     dataIndex: 'classifiedId',
-  //     title: '产品分类',
-  //     valueType: 'treeSelect',
-  //     hideInTable: true,
-  //     fieldProps: {
-  //       fieldNames: {
-  //         label: 'name',
-  //         value: 'id',
-  //       },
-  //     },
-  //     request: () =>
-  //       categoryService
-  //         .queryTree({
-  //           paging: false,
-  //         })
-  //         .then((resp: any) => resp.result),
-  //   },
-  //   {
-  //     dataIndex: 'productId$product-info',
-  //     title: '接入方式',
-  //     valueType: 'select',
-  //     hideInTable: true,
-  //     request: () =>
-  //       service.queryGatewayList().then((resp: any) =>
-  //         resp.result.map((item: any) => ({
-  //           label: item.name,
-  //           value: `accessId is ${item.id}`,
-  //         })),
-  //       ),
-  //   },
-  //   {
-  //     dataIndex: 'deviceType',
-  //     title: '设备类型',
-  //     valueType: 'select',
-  //     hideInTable: true,
-  //     valueEnum: {
-  //       device: {
-  //         text: '直连设备',
-  //         status: 'device',
-  //       },
-  //       childrenDevice: {
-  //         text: '网关子设备',
-  //         status: 'childrenDevice',
-  //       },
-  //       gateway: {
-  //         text: '网关设备',
-  //         status: 'gateway',
-  //       },
-  //     },
-  //   },
-  //   {
-  //     title: intl.formatMessage({
-  //       id: 'pages.table.description',
-  //       defaultMessage: '说明',
-  //     }),
-  //     dataIndex: 'describe',
-  //     width: '15%',
-  //     ellipsis: true,
-  //     hideInSearch: true,
-  //   },
-  //   {
-  //     title: intl.formatMessage({
-  //       id: 'pages.data.option',
-  //       defaultMessage: '操作',
-  //     }),
-  //     valueType: 'option',
-  //     width: 250,
-  //     fixed: 'right',
-  //     render: (text, record) => tools(record, 'list'),
-  //   },
-  // ];
+  const tools = (record: ResourceItem, type: 'card' | 'list') => [
+    <PermissionButton
+      type={'link'}
+      isPermission={true}
+      onClick={() => {
+        setCurrent(record);
+        setVisible(true);
+      }}
+      tooltip={{
+        title: type === 'list' ? '编辑' : '',
+      }}
+      style={{ padding: 0 }}
+      key={'edit'}
+    >
+      <EditOutlined />
+      {type === 'list' ? '' : '编辑'}
+    </PermissionButton>,
+    <PermissionButton
+      type={'link'}
+      onClick={() => {
+        setCurrent(record);
+        setIssueVisible(true);
+      }}
+      tooltip={{
+        title: type !== 'list' ? '' : '下发',
+      }}
+      style={{ padding: 0 }}
+      isPermission={true}
+      key={'reset'}
+    >
+      <DownSquareOutlined />
+      {type === 'list' ? '' : '下发'}
+    </PermissionButton>,
+    <PermissionButton
+      type={'link'}
+      key={'state'}
+      style={{ padding: 0 }}
+      popConfirm={{
+        title: intl.formatMessage({
+          id: `pages.data.option.${record.state?.value}.tips`,
+          defaultMessage: '确认禁用?',
+        }),
+        onConfirm: () => {
+          if (record.state?.value !== 'disabled') {
+            service._stop([record.id]).then((resp: any) => {
+              if (resp.status === 200) {
+                onlyMessage(
+                  intl.formatMessage({
+                    id: 'pages.data.option.success',
+                    defaultMessage: '操作成功!',
+                  }),
+                );
+                actionRef.current?.reload();
+              }
+            });
+          } else {
+            service._start([record.id]).then((resp: any) => {
+              if (resp.status === 200) {
+                onlyMessage(
+                  intl.formatMessage({
+                    id: 'pages.data.option.success',
+                    defaultMessage: '操作成功!',
+                  }),
+                );
+                actionRef.current?.reload();
+              }
+            });
+          }
+        },
+      }}
+      isPermission={true}
+      tooltip={{
+        title: type === 'list' ? (record.state.value !== 'disabled' ? '禁用' : '启用') : '',
+      }}
+    >
+      {record.state.value !== 'disabled' ? <StopOutlined /> : <PlayCircleOutlined />}
+      {record.state.value !== 'disabled'
+        ? type === 'list'
+          ? ''
+          : '禁用'
+        : type === 'list'
+        ? ''
+        : '启用'}
+    </PermissionButton>,
+    <PermissionButton
+      type={'link'}
+      key={'delete'}
+      style={{ padding: 0 }}
+      isPermission={true}
+      tooltip={record.state.value !== 'notActive' ? { title: '请先禁用,再删除。' } : undefined}
+      disabled={record.state.value !== 'notActive'}
+      popConfirm={{
+        title: intl.formatMessage({
+          id: 'pages.data.option.remove.tips',
+        }),
+        disabled: record.state.value !== 'notActive',
+        onConfirm: async () => {
+          if (record.state.value === 'notActive') {
+            await service.remove(record.id);
+            onlyMessage(
+              intl.formatMessage({
+                id: 'pages.data.option.success',
+                defaultMessage: '操作成功!',
+              }),
+            );
+            actionRef.current?.reload();
+          } else {
+            onlyMessage(intl.formatMessage({ id: 'pages.device.instance.deleteTip' }), 'error');
+          }
+        },
+      }}
+    >
+      <DeleteOutlined />
+    </PermissionButton>,
+  ];
+  const columns: ProColumns<ResourceItem>[] = [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      width: 200,
+      ellipsis: true,
+      fixed: 'left',
+    },
+    {
+      title: '名称',
+      dataIndex: 'name',
+      width: 150,
+      ellipsis: true,
+    },
+    {
+      title: '通信协议',
+      dataIndex: 'category',
+      width: 150,
+      ellipsis: true,
+    },
+    {
+      title: '所属边缘网关',
+      width: 150,
+      dataIndex: 'sourceId',
+      ellipsis: true,
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'createTime',
+      width: 200,
+      valueType: 'dateTime',
+      render: (_: any, row) => {
+        return row.createTime ? moment(row.createTime).format('YYYY-MM-DD HH:mm:ss') : '';
+      },
+      sorter: true,
+    },
+    {
+      title: '状态',
+      dataIndex: 'state',
+      width: '90px',
+      valueType: 'select',
+      renderText: (text) =>
+        text ? (
+          <Badge status={text.value === 'enabled' ? 'success' : 'error'} text={text?.text || ''} />
+        ) : (
+          ''
+        ),
+      valueEnum: {
+        enabled: {
+          text: '正常',
+          status: 'enabled',
+        },
+        disabled: {
+          text: '异常',
+          status: 'disabled',
+        },
+      },
+      filterMultiple: false,
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.data.option',
+        defaultMessage: '操作',
+      }),
+      valueType: 'option',
+      width: 200,
+      fixed: 'right',
+      render: (text, record) => tools(record, 'list'),
+    },
+  ];
 
 
   return (
   return (
     <PageContainer>
     <PageContainer>
-      开发中。。。。
-      {/*<SearchComponent<DeviceInstance>*/}
-      {/*  field={columns}*/}
-      {/*  target="edge-resource"*/}
-      {/*  onSearch={(data) => {*/}
-      {/*    actionRef.current?.reset?.();*/}
-      {/*    setSearchParams(data);*/}
-      {/*  }}*/}
-      {/*/>*/}
-      {/*<ProTableCard<DeviceInstance>*/}
-      {/*  columns={columns}*/}
-      {/*  scroll={{ x: 1366 }}*/}
-      {/*  actionRef={actionRef}*/}
-      {/*  params={searchParams}*/}
-      {/*  options={{ fullScreen: true }}*/}
-      {/*  columnEmptyText={''}*/}
-      {/*  request={(params) =>*/}
-      {/*    service.query({*/}
-      {/*      ...params,*/}
-      {/*      terms: [*/}
-      {/*        ...(params?.terms || []),*/}
-      {/*        {*/}
-      {/*          terms: [*/}
-      {/*            {*/}
-      {/*              column: 'productId$product-info',*/}
-      {/*              value: 'accessProvider is official-edge-gateway',*/}
-      {/*            },*/}
-      {/*          ],*/}
-      {/*          type: 'and',*/}
-      {/*        },*/}
-      {/*      ],*/}
-      {/*      sorts: [*/}
-      {/*        {*/}
-      {/*          name: 'createTime',*/}
-      {/*          order: 'desc',*/}
-      {/*        },*/}
-      {/*      ],*/}
-      {/*    })*/}
-      {/*  }*/}
-      {/*  rowKey="id"*/}
-      {/*  search={false}*/}
-      {/*  pagination={{ pageSize: 10 }}*/}
-      {/*  cardRender={(record) => (*/}
-      {/*    <DeviceCard*/}
-      {/*      {...record}*/}
-      {/*      detail={*/}
-      {/*        <div*/}
-      {/*          style={{ padding: 8, fontSize: 24 }}*/}
-      {/*          onClick={() => {*/}
-      {/*            InstanceModel.current = record;*/}
-      {/*            const url = getMenuPathByParams(MENUS_CODE['device/Instance/Detail'], record.id);*/}
-      {/*            history.push(url);*/}
-      {/*          }}*/}
-      {/*        >*/}
-      {/*          <EyeOutlined />*/}
-      {/*        </div>*/}
-      {/*      }*/}
-      {/*      actions={tools(record, 'card')}*/}
-      {/*    />*/}
-      {/*  )}*/}
-      {/*/>*/}
-      {/*{*/}
-      {/*  visible && <Save data={current} cancel={() => {setVisible(false)}} />*/}
-      {/*}*/}
+      <SearchComponent<ResourceItem>
+        field={columns}
+        target="edge-resource"
+        onSearch={(data) => {
+          actionRef.current?.reset?.();
+          setSearchParams(data);
+        }}
+      />
+      <ProTableCard<ResourceItem>
+        columns={columns}
+        scroll={{ x: 1366 }}
+        actionRef={actionRef}
+        params={searchParams}
+        options={{ fullScreen: true }}
+        columnEmptyText={''}
+        request={(params) =>
+          service.query({
+            ...params,
+            sorts: [
+              {
+                name: 'createTime',
+                order: 'desc',
+              },
+            ],
+          })
+        }
+        rowKey="id"
+        search={false}
+        pagination={{ pageSize: 10 }}
+        cardRender={(record) => <ResourceCard {...record} actions={tools(record, 'card')} />}
+      />
+      {visible && (
+        <Save
+          data={current}
+          cancel={() => {
+            setVisible(false);
+          }}
+          reload={() => {
+            actionRef.current?.reload();
+            setVisible(false);
+          }}
+        />
+      )}
+      {issueVisible && (
+        <Issue
+          data={current}
+          cancel={() => {
+            setIssueVisible(false);
+          }}
+        />
+      )}
     </PageContainer>
     </PageContainer>
   );
   );
 };
 };

+ 23 - 0
src/pages/edge/Resource/service.ts

@@ -0,0 +1,23 @@
+import BaseService from '@/utils/BaseService';
+import { request } from '@@/plugin-request/request';
+import SystemConst from '@/utils/const';
+
+class Service extends BaseService<ResourceItem> {
+  _start = (data: any) =>
+    request(`/${SystemConst.API_BASE}/entity/template/start/_batch`, {
+      method: 'POST',
+      data,
+    });
+  _stop = (data: any) =>
+    request(`/${SystemConst.API_BASE}/entity/template/stop/_batch`, {
+      method: 'POST',
+      data,
+    });
+  queryDeviceList = (data: any) =>
+    request(`/${SystemConst.API_BASE}/device-instance/detail/_query`, {
+      method: 'POST',
+      data,
+    });
+}
+
+export default Service;

+ 8 - 66
src/pages/edge/Resource/typings.d.ts

@@ -1,74 +1,16 @@
-export type DeviceInstance = {
+type ResourceItem = {
   id: string;
   id: string;
   name: string;
   name: string;
-  describe: string;
-  description: string;
-  productId: string;
-  productName: string;
-  protocolName: string;
-  security: any;
-  deriveMetadata: string;
+  targetId: string;
+  targetType: string;
+  sourceId: string;
+  sourceType: string;
   metadata: string;
   metadata: string;
-  binds: any;
   state: {
   state: {
     value: string;
     value: string;
     text: string;
     text: string;
   };
   };
-  creatorId: string;
-  creatorName: string;
-  createTime: number;
-  registryTime: string;
-  disabled?: boolean;
-  aloneConfiguration?: boolean;
-  deviceType: {
-    value: string;
-    text: string;
-  };
-  transportProtocol: string;
-  messageProtocol: string;
-  orgId: string;
-  orgName: string;
-  configuration: Record<string, any>;
-  relations?: any[];
-  cachedConfiguration: any;
-  transport: string;
-  protocol: string;
-  address: string;
-  registerTime: number;
-  onlineTime: string | number;
-  offlineTime: string | number;
-  tags: any;
-  photoUrl: string;
-  independentMetadata?: boolean;
-  accessProvider?: string;
-  accessId?: string;
-  features?: any[];
-  parentId?: string;
-  classifiedName?: string;
-};
-
-type Unit = {
-  id: string;
-  name: string;
-  symbol: string;
-  text: string;
-  type: string;
-  value: string;
-  description: string;
-};
-
-type PropertyData = {
-  data: {
-    value?:
-      | {
-          formatValue: string;
-          property: string;
-          value: any;
-        }
-      | any;
-    timeString: string;
-    timestamp: number;
-    formatValue: string;
-    property: string;
-  };
+  properties: any;
+  category?: string;
+  createTime: string | number;
 };
 };

+ 2 - 1
src/pages/rule-engine/Scene/Save/action/ListItem/FilterCondition.tsx

@@ -105,7 +105,8 @@ export default observer((props: FilterProps) => {
     queryBuiltInParams(FormModel.current, _params).then((res: any) => {
     queryBuiltInParams(FormModel.current, _params).then((res: any) => {
       if (res.status === 200) {
       if (res.status === 200) {
         const params = handleTreeData(
         const params = handleTreeData(
-          res.result.filter((item: any) => !item.id.includes(`action_${props.action}`)),
+          // res.result.filter((item: any) => !item.id.includes(`action_${props.action}`)),
+          res.result,
         );
         );
         setColumnOptions(params);
         setColumnOptions(params);
         setBuiltInOptions(params);
         setBuiltInOptions(params);

+ 3 - 2
src/pages/rule-engine/Scene/Save/action/ListItem/Item.tsx

@@ -55,7 +55,8 @@ export default (props: ItemProps) => {
       case 'dingTalk':
       case 'dingTalk':
         return (
         return (
           <div>
           <div>
-            向<span>{options?.notifierName || data?.notify?.notifierId}</span>
+            向<span className={'notify-text-highlight'}>{options?.orgName || ''}</span>
+            <span className={'notify-text-highlight'}>{options?.sendTo || ''}</span>
             通过
             通过
             <span className={'notify-img-highlight'}>
             <span className={'notify-img-highlight'}>
               <img width={18} src={itemNotifyIconMap.get(data?.notify?.notifyType)} />
               <img width={18} src={itemNotifyIconMap.get(data?.notify?.notifyType)} />
@@ -189,7 +190,7 @@ export default (props: ItemProps) => {
     if (props?.data?.alarm?.mode === 'trigger') {
     if (props?.data?.alarm?.mode === 'trigger') {
       return (
       return (
         <div className={'item-options-content'}>
         <div className={'item-options-content'}>
-          满足条件后将触发关联
+          满足条件后将触发
           <a
           <a
             onClick={(e) => {
             onClick={(e) => {
               e.stopPropagation();
               e.stopPropagation();

+ 34 - 3
src/pages/rule-engine/Scene/Save/action/notify/VariableDefinitions.tsx

@@ -7,6 +7,7 @@ import Tag from './components/variableItem/tag';
 import BuildIn from './components/variableItem/buildIn';
 import BuildIn from './components/variableItem/buildIn';
 import InputFile from './components/variableItem/inputFile';
 import InputFile from './components/variableItem/inputFile';
 import { forwardRef, useCallback, useImperativeHandle } from 'react';
 import { forwardRef, useCallback, useImperativeHandle } from 'react';
+import { onlyMessage } from '@/utils/util';
 
 
 interface Props {
 interface Props {
   name: number;
   name: number;
@@ -122,10 +123,40 @@ export default forwardRef((props: Props, ref) => {
         const formData = await form.validateFields().catch(() => {
         const formData = await form.validateFields().catch(() => {
           resolve(false);
           resolve(false);
         });
         });
-        if (formData) {
-          resolve(formData);
+        if (
+          NotifyModel.notify.notifyType &&
+          ['dingTalk', 'weixin'].includes(NotifyModel.notify.notifyType)
+        ) {
+          const arr = NotifyModel.variable.map((item) => {
+            return { type: item.expands?.businessType || item?.type, id: item.id };
+          });
+          const org = arr.find((i) => i.type === 'org')?.id;
+          const user = arr.find((i) => i.type === 'user')?.id;
+          if (org && user) {
+            if (
+              (formData[org]?.source && formData[org]?.value) ||
+              (!formData[org]?.source && formData[org]) ||
+              (formData[user]?.source && (formData[user]?.value || formData[user]?.relation)) ||
+              (!formData[user]?.source && formData[user])
+            ) {
+              resolve(formData);
+            } else {
+              onlyMessage('收信人和收信部门必填一个', 'error');
+              resolve(false);
+            }
+          } else {
+            if (formData) {
+              resolve(formData);
+            } else {
+              resolve(false);
+            }
+          }
         } else {
         } else {
-          resolve(false);
+          if (formData) {
+            resolve(formData);
+          } else {
+            resolve(false);
+          }
         }
         }
       } else {
       } else {
         resolve({});
         resolve({});

+ 4 - 0
src/pages/rule-engine/Scene/Save/components/Buttons/ParamsDropdown.tsx

@@ -72,6 +72,7 @@ export default (props: ParamsDropdownProps) => {
         value: value,
         value: value,
         source: activeKey,
         source: activeKey,
       };
       };
+      setOpen(false);
       props.onChange?.(changeValue, _label);
       props.onChange?.(changeValue, _label);
     },
     },
     [activeKey],
     [activeKey],
@@ -112,6 +113,7 @@ export default (props: ParamsDropdownProps) => {
               options={_options}
               options={_options}
               onChange={(v, l) => {
               onChange={(v, l) => {
                 onValueChange(v === 'true' ? true : false, l);
                 onValueChange(v === 'true' ? true : false, l);
+                setOpen(false);
               }}
               }}
             />
             />
           );
           );
@@ -122,6 +124,7 @@ export default (props: ParamsDropdownProps) => {
               onChange={(_: any, timeString: string) => {
               onChange={(_: any, timeString: string) => {
                 console.log('timeString', timeString);
                 console.log('timeString', timeString);
                 onValueChange(timeString, timeString);
                 onValueChange(timeString, timeString);
+                setOpen(false);
               }}
               }}
             />
             />
           );
           );
@@ -136,6 +139,7 @@ export default (props: ParamsDropdownProps) => {
                     titleKey = props.showLabelKey;
                     titleKey = props.showLabelKey;
                   }
                   }
                   onValueChange(selectedKeys[0], e.node[titleKey]);
                   onValueChange(selectedKeys[0], e.node[titleKey]);
+                  setOpen(false);
                 }}
                 }}
                 style={{ width: 300 }}
                 style={{ width: 300 }}
                 treeData={props.BuiltInOptions}
                 treeData={props.BuiltInOptions}

+ 8 - 2
src/pages/rule-engine/Scene/Save/device/TopCard.tsx

@@ -1,6 +1,8 @@
 import classNames from 'classnames';
 import classNames from 'classnames';
 import { useEffect, useState } from 'react';
 import { useEffect, useState } from 'react';
 import './index.less';
 import './index.less';
+import { QuestionCircleOutlined } from '@ant-design/icons';
+import { Popover } from 'antd';
 
 
 interface Props {
 interface Props {
   typeList: any[];
   typeList: any[];
@@ -43,8 +45,12 @@ const TopCard = (props: Props) => {
           }}
           }}
         >
         >
           <div className={'way-item-title'}>
           <div className={'way-item-title'}>
-            <p>{item.label}</p>
-            {item.tip && <span>{item.tip}</span>}
+            <span className={'way-item-label'}>{item.label}</span>
+            {item.tip && (
+              <Popover content={item.tip}>
+                <QuestionCircleOutlined className={'way-item-icon'} />
+              </Popover>
+            )}
           </div>
           </div>
           <div className={'way-item-image'}>
           <div className={'way-item-image'}>
             <img width={48} src={item.image} />
             <img width={48} src={item.image} />

+ 1 - 1
src/pages/rule-engine/Scene/Save/device/addModel.tsx

@@ -247,7 +247,7 @@ export default observer((props: AddProps) => {
     <Modal
     <Modal
       visible
       visible
       title="执行规则"
       title="执行规则"
-      width={800}
+      width={810}
       onCancel={() => {
       onCancel={() => {
         props.onCancel?.();
         props.onCancel?.();
         TriggerDeviceModel.stepNumber = 0;
         TriggerDeviceModel.stepNumber = 0;

+ 24 - 10
src/pages/rule-engine/Scene/Save/device/deviceList.tsx

@@ -18,7 +18,6 @@ export default observer(() => {
     pageIndex: TriggerDeviceModel.devicePage,
     pageIndex: TriggerDeviceModel.devicePage,
     pageSize: TriggerDeviceModel.devicePageSize,
     pageSize: TriggerDeviceModel.devicePageSize,
   });
   });
-  const [isFirst, setIsFirst] = useState(true);
 
 
   const [loading, setLoading] = useState(true);
   const [loading, setLoading] = useState(true);
 
 
@@ -212,6 +211,8 @@ export default observer(() => {
       <Observer>
       <Observer>
         {() => (
         {() => (
           <ProTableCard<DeviceInstance>
           <ProTableCard<DeviceInstance>
+            noPadding
+            cardScrollY={400}
             actionRef={actionRef}
             actionRef={actionRef}
             columns={columns}
             columns={columns}
             rowKey="id"
             rowKey="id"
@@ -222,16 +223,29 @@ export default observer(() => {
             tableAlertRender={false}
             tableAlertRender={false}
             rowSelection={{
             rowSelection={{
               selectedRowKeys: [...TriggerDeviceModel.deviceKeys],
               selectedRowKeys: [...TriggerDeviceModel.deviceKeys],
-              onChange(selectedRowKeys, selectedRows) {
-                console.log(selectedRowKeys);
-                if (!isFirst) {
-                  TriggerDeviceModel.deviceKeys = selectedRows.map((item) => item.id);
-                  TriggerDeviceModel.selectorValues = selectedRows.map((item) => ({
-                    name: item.name,
-                    value: item.id,
-                  }));
+              onSelect: (record, selected) => {
+                console.log(record);
+                if (selected) {
+                  TriggerDeviceModel.deviceKeys.push(record.id);
+                  if (TriggerDeviceModel.selectorValues) {
+                    TriggerDeviceModel.selectorValues?.push({
+                      name: record.name,
+                      value: record.id,
+                    });
+                  } else {
+                    TriggerDeviceModel.selectorValues = [
+                      {
+                        name: record.name,
+                        value: record.id,
+                      },
+                    ];
+                  }
                 } else {
                 } else {
-                  setIsFirst(false);
+                  const newArray = TriggerDeviceModel.selectorValues?.filter(
+                    (item) => item.value !== record.id,
+                  );
+                  TriggerDeviceModel.deviceKeys = newArray?.map((item) => item.value) || [];
+                  TriggerDeviceModel.selectorValues = newArray || [];
                 }
                 }
               },
               },
             }}
             }}

+ 25 - 35
src/pages/rule-engine/Scene/Save/device/index.less

@@ -20,41 +20,47 @@
 
 
   .trigger-way-item {
   .trigger-way-item {
     display: flex;
     display: flex;
+    align-items: center;
     justify-content: space-between;
     justify-content: space-between;
     width: 237px;
     width: 237px;
     //width: 100%;
     //width: 100%;
-    padding: 16px;
-    border: 1px solid #e0e4e8;
+    padding: 12px 16px;
+    border: 1px solid rgba(#e0e4e8, 0.6);
     border-radius: 2px;
     border-radius: 2px;
     cursor: pointer;
     cursor: pointer;
-    opacity: 0.6;
     transition: all 0.3s;
     transition: all 0.3s;
 
 
     .way-item-title {
     .way-item-title {
-      p {
-        margin-bottom: 8px;
-        font-weight: bold;
+      span {
         font-size: 16px;
         font-size: 16px;
       }
       }
+      .way-item-label {
+        padding-right: 6px;
+        color: rgba(#000, 0.64);
+      }
 
 
-      span {
-        color: rgba(#000, 0.35);
-        font-size: 12px;
+      .way-item-icon {
+        color: rgba(#000, 0.5);
       }
       }
     }
     }
 
 
     .way-item-image {
     .way-item-image {
       margin: 0 !important;
       margin: 0 !important;
+      opacity: 0.6;
     }
     }
 
 
     &:hover {
     &:hover {
-      color: @primary-color-hover;
-      opacity: 0.8;
+      //color: @primary-color-hover;
+      .way-item-image {
+        opacity: 0.8;
+      }
     }
     }
 
 
     &.active {
     &.active {
       border-color: @primary-color-active;
       border-color: @primary-color-active;
-      opacity: 1;
+      .way-item-image {
+        opacity: 1;
+      }
     }
     }
 
 
     &.labelBottom {
     &.labelBottom {
@@ -104,30 +110,14 @@
 
 
 .trigger-options-content {
 .trigger-options-content {
   .trigger-options-name {
   .trigger-options-name {
+    .ellipsisFn(1, 30%);
+  }
+
+  .trigger-options-type {
     .ellipsisFn(1, 10%);
     .ellipsisFn(1, 10%);
   }
   }
 
 
-  //.trigger-options-type {
-  //
-  //}
-  //
-  //.trigger-options-when {
-  //
-  //}
-  //
-  //.trigger-options-time {
-  //
-  //}
-  //
-  //.trigger-options-extraTime {
-  //
-  //}
-  //
-  //.trigger-options-action {
-  //
-  //}
-  //
-  //.trigger-options-type {
-  //
-  //}
+  .trigger-options-when {
+    .ellipsisFn(1, 15%);
+  }
 }
 }

+ 4 - 0
src/pages/rule-engine/Scene/Save/device/org.tsx

@@ -48,6 +48,10 @@ export default observer(() => {
         actionRef={actionRef}
         actionRef={actionRef}
         columnEmptyText={''}
         columnEmptyText={''}
         tableAlertRender={false}
         tableAlertRender={false}
+        options={false}
+        scroll={{
+          y: 350,
+        }}
         rowSelection={{
         rowSelection={{
           type: 'radio',
           type: 'radio',
           selectedRowKeys: [TriggerDeviceModel.orgId],
           selectedRowKeys: [TriggerDeviceModel.orgId],

+ 44 - 49
src/pages/rule-engine/Scene/Save/device/product.tsx

@@ -176,7 +176,7 @@ export default observer(() => {
           .queryOrgThree({
           .queryOrgThree({
             paging: false,
             paging: false,
           })
           })
-          .then((resp) => {
+          .then((resp: any) => {
             const formatValue = (list: any[]) => {
             const formatValue = (list: any[]) => {
               const _list: any[] = [];
               const _list: any[] = [];
               list.forEach((item) => {
               list.forEach((item) => {
@@ -224,55 +224,50 @@ export default observer(() => {
         }}
         }}
         target="department-assets-product"
         target="department-assets-product"
       />
       />
-      <div
-        style={{
-          height: 'calc(100vh - 440px)',
-          overflowY: 'auto',
+      <ProTableCard<ProductItem>
+        noPadding
+        cardScrollY={460}
+        actionRef={actionRef}
+        columns={columns}
+        rowKey="id"
+        search={false}
+        gridColumn={2}
+        columnEmptyText={''}
+        onlyCard={true}
+        tableAlertRender={false}
+        rowSelection={{
+          type: 'radio',
+          selectedRowKeys: [TriggerDeviceModel.productId],
+          onChange: (_, selectedRows) => {
+            console.log('onChange', selectedRows);
+            TriggerDeviceModel.productId = selectedRows.map((item) => item.id)[0];
+            TriggerDeviceModel.productDetail = selectedRows?.[0];
+            handleMetadata(TriggerDeviceModel.productDetail.metadata);
+            // 初始化选择设备类型以及触发类型
+            TriggerDeviceModel.deviceKeys = [];
+            TriggerDeviceModel.orgId = '';
+            TriggerDeviceModel.selector = 'fixed';
+            TriggerDeviceModel.operation = {
+              operator: 'online',
+            };
+          },
         }}
         }}
-      >
-        <ProTableCard<ProductItem>
-          actionRef={actionRef}
-          columns={columns}
-          rowKey="id"
-          search={false}
-          gridColumn={2}
-          columnEmptyText={''}
-          onlyCard={true}
-          tableAlertRender={false}
-          rowSelection={{
-            type: 'radio',
-            selectedRowKeys: [TriggerDeviceModel.productId],
-            onChange: (_, selectedRows) => {
-              console.log(selectedRows);
-              TriggerDeviceModel.productId = selectedRows.map((item) => item.id)[0];
-              TriggerDeviceModel.productDetail = selectedRows?.[0];
-              handleMetadata(TriggerDeviceModel.productDetail.metadata);
-              // 初始化选择设备类型以及触发类型
-              TriggerDeviceModel.deviceKeys = [];
-              TriggerDeviceModel.orgId = '';
-              TriggerDeviceModel.selector = 'fixed';
-              TriggerDeviceModel.operation = {
-                operator: 'online',
-              };
-            },
-          }}
-          onPageChange={(page, size) => {
-            TriggerDeviceModel.productPage = page;
-            TriggerDeviceModel.productPageSize = size;
-          }}
-          request={(params) =>
-            service.query({
-              ...params,
-              sorts: [{ name: 'createTime', order: 'desc' }],
-            })
-          }
-          params={searchParam}
-          cardRender={(record) => (
-            <SceneProductCard showBindBtn={false} showTool={false} {...record} />
-          )}
-          height={'none'}
-        />
-      </div>
+        onPageChange={(page, size) => {
+          TriggerDeviceModel.productPage = page;
+          TriggerDeviceModel.productPageSize = size;
+        }}
+        request={(params) =>
+          service.query({
+            ...params,
+            sorts: [{ name: 'createTime', order: 'desc' }],
+          })
+        }
+        params={searchParam}
+        cardRender={(record) => (
+          <SceneProductCard showBindBtn={false} showTool={false} {...record} />
+        )}
+        height={'none'}
+      />
     </div>
     </div>
   );
   );
 });
 });

+ 7 - 1
src/pages/rule-engine/Scene/Save/device/type.tsx

@@ -162,7 +162,13 @@ export default forwardRef((props: Props, ref) => {
   }));
   }));
 
 
   return (
   return (
-    <div>
+    <div
+      style={{
+        maxHeight: 'calc(100vh - 350px)',
+        overflowY: 'auto',
+        overflowX: 'hidden',
+      }}
+    >
       <Form form={form} layout={'vertical'}>
       <Form form={form} layout={'vertical'}>
         <Form.Item name="operator" label="触发类型" required initialValue={'online'}>
         <Form.Item name="operator" label="触发类型" required initialValue={'online'}>
           <TopCard typeList={TypeList} labelBottom={true} />
           <TopCard typeList={TypeList} labelBottom={true} />

+ 29 - 17
src/pages/rule-engine/Scene/Save/index.tsx

@@ -16,7 +16,7 @@ import { useHistory } from 'umi';
 import { getMenuPathByCode } from '@/utils/menu';
 import { getMenuPathByCode } from '@/utils/menu';
 import { cloneDeep } from 'lodash';
 import { cloneDeep } from 'lodash';
 
 
-const defaultBranches = [
+export const defaultBranches = [
   {
   {
     when: [
     when: [
       {
       {
@@ -72,6 +72,7 @@ export default observer(() => {
   const triggerType = location?.query?.triggerType || '';
   const triggerType = location?.query?.triggerType || '';
   const id = location?.query?.id || '';
   const id = location?.query?.id || '';
   const history = useHistory();
   const history = useHistory();
+  const [form] = Form.useForm();
 
 
   const FormModelInit = () => {
   const FormModelInit = () => {
     FormModel.current = {
     FormModel.current = {
@@ -116,8 +117,9 @@ export default observer(() => {
             branches = branches.map((bItem: ActionBranchesProps, bIndex: number) => {
             branches = branches.map((bItem: ActionBranchesProps, bIndex: number) => {
               if (!bItem.key) {
               if (!bItem.key) {
                 bItem.key = `branches_${new Date().getTime() + bIndex}`;
                 bItem.key = `branches_${new Date().getTime() + bIndex}`;
-                if (bItem.then && bItem.then) {
-                  bItem.then = bItem.then.map((tItem) => {
+                if (bItem.then && bItem.then.length) {
+                  bItem.then = bItem.then.map((tItem, tIndex) => {
+                    tItem.key = `then_${new Date().getTime() + tIndex}`;
                     if (tItem.actions) {
                     if (tItem.actions) {
                       tItem.actions = tItem.actions.map((aItem, index) => {
                       tItem.actions = tItem.actions.map((aItem, index) => {
                         aItem.key = `${aItem.executor}_${new Date().getTime() + index}`;
                         aItem.key = `${aItem.executor}_${new Date().getTime() + index}`;
@@ -127,7 +129,7 @@ export default observer(() => {
                     return tItem;
                     return tItem;
                   });
                   });
                 }
                 }
-                if (bItem.when) {
+                if (bItem.when && bItem.when.length) {
                   bItem.when = bItem.when.map((wItem, index) => {
                   bItem.when = bItem.when.map((wItem, index) => {
                     wItem.key = `when_${new Date().getTime() + index}`;
                     wItem.key = `when_${new Date().getTime() + index}`;
                     if (wItem.terms) {
                     if (wItem.terms) {
@@ -143,27 +145,31 @@ export default observer(() => {
               return bItem;
               return bItem;
             });
             });
           }
           }
-          branches.push({
-            when: [],
-            key: 'branckes_2',
-            shakeLimit: {
-              enabled: false,
-              time: 0,
-              threshold: 0,
-              alarmFirst: false,
-            },
-            then: [],
-          });
+          if (branches[0]?.when?.length) {
+            branches.push({
+              when: [],
+              key: 'branckes_2',
+              shakeLimit: {
+                enabled: false,
+                time: 0,
+                threshold: 0,
+                alarmFirst: false,
+              },
+              then: [],
+            });
+          }
           FormModel.current = {
           FormModel.current = {
             ...resp.result,
             ...resp.result,
           };
           };
           FormModel.current.options = { ...defaultOptions, ...resp.result.options };
           FormModel.current.options = { ...defaultOptions, ...resp.result.options };
           FormModel.current.trigger = resp.result.trigger || {};
           FormModel.current.trigger = resp.result.trigger || {};
           FormModel.current.branches = branches;
           FormModel.current.branches = branches;
+
+          form.setFieldValue('description', resp.result.description);
         }
         }
       });
       });
     }
     }
-  }, [id]);
+  }, [id, form]);
 
 
   const triggerRender = (type: string) => {
   const triggerRender = (type: string) => {
     FormModel.current.trigger!.type = type;
     FormModel.current.trigger!.type = type;
@@ -192,6 +198,7 @@ export default observer(() => {
   };
   };
 
 
   const submit = useCallback(() => {
   const submit = useCallback(() => {
+    const baseData = form.getFieldsValue();
     const FormData = cloneDeep(FormModel.current);
     const FormData = cloneDeep(FormModel.current);
     const { options, branches } = FormData;
     const { options, branches } = FormData;
     if (options?.terms) {
     if (options?.terms) {
@@ -207,6 +214,11 @@ export default observer(() => {
     if (branches) {
     if (branches) {
       FormData.branches = branches.filter((item) => !!item.then.length || !!item.when.length);
       FormData.branches = branches.filter((item) => !!item.then.length || !!item.when.length);
     }
     }
+
+    if (baseData) {
+      FormData.description = baseData.description;
+    }
+
     service.updateScene(FormData).then((res) => {
     service.updateScene(FormData).then((res) => {
       if (res.status === 200) {
       if (res.status === 200) {
         onlyMessage('操作成功', 'success');
         onlyMessage('操作成功', 'success');
@@ -219,7 +231,7 @@ export default observer(() => {
   return (
   return (
     <PageContainer>
     <PageContainer>
       <Card>
       <Card>
-        <Form name="timer" layout={'vertical'}>
+        <Form name="timer" layout={'vertical'} form={form}>
           {triggerRender(triggerType)}
           {triggerRender(triggerType)}
           <Form.Item
           <Form.Item
             label={<TitleComponent style={{ fontSize: 14 }} data={'说明'} />}
             label={<TitleComponent style={{ fontSize: 14 }} data={'说明'} />}

+ 89 - 21
src/pages/rule-engine/Scene/Save/terms/index.tsx

@@ -1,11 +1,14 @@
-import { useEffect } from 'react';
+import { useEffect, useState } from 'react';
 import { TitleComponent } from '@/components';
 import { TitleComponent } from '@/components';
 import { observer, Observer } from '@formily/react';
 import { observer, Observer } from '@formily/react';
 import { model } from '@formily/reactive';
 import { model } from '@formily/reactive';
-import { FormModel } from '@/pages/rule-engine/Scene/Save';
+import { FormModel, defaultBranches } from '@/pages/rule-engine/Scene/Save';
 import BranchItem from './branchItem';
 import BranchItem from './branchItem';
 import { service } from '@/pages/rule-engine/Scene/index';
 import { service } from '@/pages/rule-engine/Scene/index';
+import { Switch } from 'antd';
 import type { TriggerType } from '@/pages/rule-engine/Scene/typings';
 import type { TriggerType } from '@/pages/rule-engine/Scene/typings';
+import Actions from '@/pages/rule-engine/Scene/Save/action';
+import { cloneDeep } from 'lodash';
 
 
 interface TermsModelProps {
 interface TermsModelProps {
   columnOptions: any[];
   columnOptions: any[];
@@ -16,12 +19,56 @@ export const TermsModel = model<TermsModelProps>({
 });
 });
 
 
 export default observer(() => {
 export default observer(() => {
+  const [open, setOpen] = useState(true);
+
+  useEffect(() => {
+    console.log('terms-effect', FormModel.current.branches);
+    if (FormModel.current.branches && FormModel.current.branches.length === 1) {
+      setOpen(false);
+    }
+  }, [FormModel.current.branches]);
+
   const queryColumn = (data: TriggerType) => {
   const queryColumn = (data: TriggerType) => {
     service.getParseTerm({ trigger: data }).then((res: any) => {
     service.getParseTerm({ trigger: data }).then((res: any) => {
       TermsModel.columnOptions = res;
       TermsModel.columnOptions = res;
     });
     });
   };
   };
 
 
+  const openChange = (checked: boolean) => {
+    console.log('terms-effect-change');
+    setOpen(checked);
+    if (checked) {
+      FormModel.current.branches = cloneDeep([
+        ...defaultBranches,
+        {
+          when: [],
+          key: 'branches_2',
+          shakeLimit: {
+            enabled: false,
+            time: 0,
+            threshold: 0,
+            alarmFirst: false,
+          },
+          then: [],
+        },
+      ]);
+    } else {
+      FormModel.current.branches = [
+        {
+          when: [],
+          key: 'branches_' + new Date().getTime(),
+          shakeLimit: {
+            enabled: false,
+            time: 0,
+            threshold: 0,
+            alarmFirst: false,
+          },
+          then: [],
+        },
+      ];
+    }
+  };
+
   useEffect(() => {
   useEffect(() => {
     if (FormModel.current.trigger?.device) {
     if (FormModel.current.trigger?.device) {
       queryColumn(FormModel.current.trigger);
       queryColumn(FormModel.current.trigger);
@@ -30,26 +77,47 @@ export default observer(() => {
 
 
   return (
   return (
     <div className="actions-terms">
     <div className="actions-terms">
-      <TitleComponent style={{ fontSize: 14 }} data="触发条件" />
-      <Observer>
-        {() =>
-          FormModel.current.branches?.map((item, index) => {
-            const isFrist = index === 0;
-            return (
-              <BranchItem
-                data={item}
-                isFrist={isFrist}
-                name={index}
-                paramsOptions={TermsModel.columnOptions}
-                onDelete={() => {
-                  FormModel.current.branches?.splice(index, 1);
-                  FormModel.current.options?.when?.splice(index, 1);
-                }}
-              />
-            );
-          })
+      <TitleComponent
+        style={{ fontSize: 14 }}
+        data={
+          <span>
+            触发条件{' '}
+            <Switch
+              checked={open}
+              onChange={openChange}
+              checkedChildren={'开'}
+              unCheckedChildren={'关'}
+            />
+          </span>
         }
         }
-      </Observer>
+      />
+      {open ? (
+        <Observer>
+          {() =>
+            FormModel.current.branches?.map((item, index) => {
+              const isFrist = index === 0;
+              return (
+                <BranchItem
+                  data={item}
+                  isFrist={isFrist}
+                  name={index}
+                  paramsOptions={TermsModel.columnOptions}
+                  onDelete={() => {
+                    FormModel.current.branches?.splice(index, 1);
+                    FormModel.current.options?.when?.splice(index, 1);
+                  }}
+                />
+              );
+            })
+          }
+        </Observer>
+      ) : (
+        <Actions
+          openShakeLimit={true}
+          name={0}
+          thenOptions={FormModel.current.branches ? FormModel.current.branches[0].then : []}
+        />
+      )}
     </div>
     </div>
   );
   );
 });
 });

+ 58 - 46
src/pages/system/User/Save/index.tsx

@@ -59,6 +59,7 @@ const Save = (props: Props) => {
 
 
   registerValidateRules({
   registerValidateRules({
     checkStrength(value: string) {
     checkStrength(value: string) {
+      if (!value || value.length < 8 || value.length > 64) return true;
       if (/^[0-9]+$/.test(value) || /^[a-zA-Z]+$/.test(value) || /^[~!@#$%^&*]+$/.test(value)) {
       if (/^[0-9]+$/.test(value) || /^[a-zA-Z]+$/.test(value) || /^[~!@#$%^&*]+$/.test(value)) {
         return {
         return {
           type: 'error',
           type: 'error',
@@ -177,6 +178,7 @@ const Save = (props: Props) => {
                 triggerType: 'onBlur',
                 triggerType: 'onBlur',
                 validator: (value: string) => {
                 validator: (value: string) => {
                   return new Promise((resolve) => {
                   return new Promise((resolve) => {
+                    if (!value) resolve('');
                     service
                     service
                       .validateField('username', value)
                       .validateField('username', value)
                       .then((resp) => {
                       .then((resp) => {
@@ -214,17 +216,17 @@ const Save = (props: Props) => {
           placeholder: '请输入密码',
           placeholder: '请输入密码',
         },
         },
         'x-visible': model === 'add',
         'x-visible': model === 'add',
-        'x-reactions': [
-          {
-            dependencies: ['.confirmPassword'],
-            fulfill: {
-              state: {
-                selfErrors:
-                  '{{$deps[0] && $self.value && $self.value !==$deps[0] ? "两次密码输入不一致" : ""}}',
-              },
-            },
-          },
-        ],
+        // 'x-reactions': [
+        //   {
+        //     dependencies: ['.confirmPassword'],
+        //     fulfill: {
+        //       state: {
+        //         selfErrors:
+        //           '{{$deps[0].length > 8 && $deps[0].length > 8 && $self.value && $self.value !==$deps[0] ? "两次密码输入不一致" : ""}}',
+        //       },
+        //     },
+        //   },
+        // ],
         name: 'password',
         name: 'password',
         'x-validator': [
         'x-validator': [
           {
           {
@@ -240,40 +242,10 @@ const Save = (props: Props) => {
             message: '请输入密码',
             message: '请输入密码',
           },
           },
           {
           {
-            checkStrength: true,
-          },
-        ],
-      },
-      confirmPassword: {
-        type: 'string',
-        title: intl.formatMessage({
-          id: 'pages.system.confirmPassword',
-          defaultMessage: '确认密码?',
-        }),
-        'x-decorator': 'FormItem',
-        'x-component': 'Password',
-        'x-component-props': {
-          checkStrength: true,
-          placeholder: '请再次输入密码',
-        },
-        'x-visible': model === 'add',
-        'x-validator': [
-          {
-            max: 64,
-            message: '密码最多可输入64位',
-          },
-          {
-            min: 8,
-            message: '密码不能少于8位',
-          },
-          {
-            required: model === 'add',
-            message: '请输入确认密码',
-          },
-          {
             triggerType: 'onBlur',
             triggerType: 'onBlur',
             validator: (value: string) => {
             validator: (value: string) => {
               return new Promise((resolve) => {
               return new Promise((resolve) => {
+                if (!value || value.length < 8 || value.length > 64) resolve('');
                 service
                 service
                   .validateField('password', value)
                   .validateField('password', value)
                   .then((resp) => {
                   .then((resp) => {
@@ -292,11 +264,41 @@ const Save = (props: Props) => {
               });
               });
             },
             },
           },
           },
+          // {
+          //   checkStrength: true,
+          // },
+        ],
+      },
+      confirmPassword: {
+        type: 'string',
+        title: intl.formatMessage({
+          id: 'pages.system.confirmPassword',
+          defaultMessage: '确认密码?',
+        }),
+        'x-decorator': 'FormItem',
+        'x-component': 'Password',
+        'x-component-props': {
+          checkStrength: true,
+          placeholder: '请再次输入密码',
+        },
+        'x-visible': model === 'add',
+        'x-validator': [
+          // {
+          //   max: 64,
+          //   message: '密码最多可输入64位',
+          // },
+          // {
+          //   min: 8,
+          //   message: '密码不能少于8位',
+          // },
           {
           {
-            checkStrength: true,
+            required: model === 'add',
+            message: '请输入确认密码',
           },
           },
+          // {
+          //   checkStrength: true,
+          // },
         ],
         ],
-
         'x-reactions': [
         'x-reactions': [
           {
           {
             dependencies: ['.password'],
             dependencies: ['.password'],
@@ -410,7 +412,12 @@ const Save = (props: Props) => {
             title: '手机号',
             title: '手机号',
             'x-decorator': 'FormItem',
             'x-decorator': 'FormItem',
             'x-component': 'Input',
             'x-component': 'Input',
-            'x-validator': 'phone',
+            'x-validator': [
+              {
+                format: 'phone',
+                message: '请输入正确的手机号',
+              },
+            ],
             'x-decorator-props': {
             'x-decorator-props': {
               gridSpan: 1,
               gridSpan: 1,
             },
             },
@@ -422,7 +429,12 @@ const Save = (props: Props) => {
             title: '邮箱',
             title: '邮箱',
             'x-decorator': 'FormItem',
             'x-decorator': 'FormItem',
             'x-component': 'Input',
             'x-component': 'Input',
-            'x-validator': 'email',
+            'x-validator': [
+              {
+                format: 'email',
+                message: '请输入正确的邮箱',
+              },
+            ],
             'x-decorator-props': {
             'x-decorator-props': {
               gridSpan: 1,
               gridSpan: 1,
             },
             },

+ 1 - 1
src/utils/util.ts

@@ -52,7 +52,7 @@ export const downloadFileByUrl = (url: string, name: string, type: string) => {
 export const downloadObject = (record: Record<string, any>, fileName: string) => {
 export const downloadObject = (record: Record<string, any>, fileName: string) => {
   // 创建隐藏的可下载链接
   // 创建隐藏的可下载链接
   const ghostLink = document.createElement('a');
   const ghostLink = document.createElement('a');
-  ghostLink.download = `${record?.name}${fileName}_${moment(new Date()).format(
+  ghostLink.download = `${record?.name || ''}${fileName}_${moment(new Date()).format(
     'YYYY/MM/DD HH:mm:ss',
     'YYYY/MM/DD HH:mm:ss',
   )}.json`;
   )}.json`;
   ghostLink.style.display = 'none';
   ghostLink.style.display = 'none';