Ver código fonte

fix: 固件升级页面

100011797 3 anos atrás
pai
commit
c45262d7ca

+ 11 - 0
config/routes.ts

@@ -31,6 +31,17 @@
       },
     ],
   },
+  {
+    path: '/init-home',
+    layout: false,
+    routes: [
+      {
+        name: '初始化',
+        path: '/init-home',
+        component: './init-home',
+      },
+    ],
+  },
   // {
   //   path: '/analysis',
   //   name: 'analysis',

BIN
public/images/firmware/error.png


BIN
public/images/firmware/finish.png


BIN
public/images/firmware/loading.png


BIN
public/images/firmware/waiting.png


BIN
public/images/init-home/background.png


src/pages/device/Firmware/Detail/History/index.tsx → src/pages/device/Firmware/Detail copy/History/index.tsx


src/pages/device/Firmware/Detail/Task/Detail/index.tsx → src/pages/device/Firmware/Detail copy/Task/Detail/index.tsx


src/pages/device/Firmware/Detail/Task/Release/index.tsx → src/pages/device/Firmware/Detail copy/Task/Release/index.tsx


src/pages/device/Firmware/Detail/Task/Save/index.tsx → src/pages/device/Firmware/Detail copy/Task/Save/index.tsx


+ 3 - 3
src/pages/device/Firmware/Detail/Task/index.tsx

@@ -11,10 +11,10 @@ import {
   PlusOutlined,
 } from '@ant-design/icons';
 import { useIntl, useParams } from 'umi';
-import Save from '@/pages/device/Firmware/Detail/Task/Save';
+import Save from './Save';
 import { observer } from '@formily/react';
-import Release from '@/pages/device/Firmware/Detail/Task/Release';
-import Detail from '@/pages/device/Firmware/Detail/Task/Detail';
+import Release from './Release';
+import Detail from './Detail';
 
 const Task = observer(() => {
   const intl = useIntl();

+ 1 - 1
src/pages/device/Firmware/Detail/index.tsx

@@ -5,7 +5,7 @@ import { service, state } from '@/pages/device/Firmware';
 import History from './History';
 import { useEffect, useState } from 'react';
 import type { FirmwareItem } from '@/pages/device/Firmware/typings';
-import Task from '@/pages/device/Firmware/Detail/Task';
+import Task from './Task';
 import { observer } from '@formily/react';
 
 const Detail = observer(() => {

+ 191 - 9
src/pages/device/Firmware/Save/index.tsx

@@ -1,7 +1,7 @@
 import { Modal } from 'antd';
 import type { FirmwareItem } from '@/pages/device/Firmware/typings';
 import { createSchemaField } from '@formily/react';
-import { Form, FormGrid, FormItem, Input, Select } from '@formily/antd';
+import { Form, FormGrid, FormItem, Input, Select, ArrayTable } from '@formily/antd';
 import type { Field } from '@formily/core';
 import { createForm } from '@formily/core';
 import type { ISchema } from '@formily/json-schema';
@@ -46,6 +46,7 @@ const Save = (props: Props) => {
       Input,
       FUpload,
       Select,
+      ArrayTable,
     },
   });
 
@@ -71,26 +72,90 @@ const Save = (props: Props) => {
           maxColumns: 2,
         },
         properties: {
-          productId: {
-            title: '产品',
-            'x-decorator': 'FormItem',
-            'x-component': 'Select',
-            'x-reactions': ['{{useAsyncDataSource(loadData)}}'],
-          },
           name: {
             title: '名称',
             'x-decorator': 'FormItem',
             'x-component': 'Input',
+            'x-component-props': {
+              placeholder: '请输入固件名称',
+            },
+            required: true,
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入固件名称',
+              },
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+            ],
+          },
+          productId: {
+            title: '所属产品',
+            'x-decorator': 'FormItem',
+            'x-component': 'Select',
+            'x-reactions': ['{{useAsyncDataSource(loadData)}}'],
+            'x-component-props': {
+              placeholder: '请选择所属产品',
+            },
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择所属产品',
+              },
+            ],
           },
           version: {
             title: '版本号',
             'x-decorator': 'FormItem',
             'x-component': 'Input',
+            'x-component-props': {
+              placeholder: '请输入版本号',
+            },
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入版本号',
+              },
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+            ],
           },
           versionOrder: {
             title: '版本序号',
             'x-decorator': 'FormItem',
             'x-component': 'Input',
+            'x-component-props': {
+              placeholder: '请输入版本序号',
+            },
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入版本号',
+              },
+              {
+                maximum: 99999,
+                minimum: 1,
+              },
+            ],
           },
           signMethod: {
             title: '签名方式',
@@ -100,11 +165,38 @@ const Save = (props: Props) => {
               { label: 'MD5', value: 'MD5' },
               { label: 'SHA256', value: 'SHA256' },
             ],
+            'x-component-props': {
+              placeholder: '请选择签名方式',
+            },
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择签名方式',
+              },
+            ],
           },
           sign: {
             title: '签名',
             'x-decorator': 'FormItem',
             'x-component': 'Input',
+            'x-component-props': {
+              placeholder: '请输入签名',
+            },
+            'x-decorator-props': {
+              tooltip: '请输入本地文件进行签名加密后的值',
+              gridSpan: 1,
+            },
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入签名',
+              },
+            ],
           },
           '{url,size}': {
             title: '文件上传',
@@ -113,6 +205,96 @@ const Save = (props: Props) => {
             'x-component-props': {
               type: 'file',
             },
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请上传文件',
+              },
+            ],
+          },
+          array: {
+            type: 'array',
+            'x-decorator': 'FormItem',
+            'x-component': 'ArrayTable',
+            title: '其他配置',
+            'x-component-props': {
+              pagination: { pageSize: 10 },
+              scroll: { x: '100%' },
+            },
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            items: {
+              type: 'object',
+              properties: {
+                column1: {
+                  type: 'void',
+                  'x-component': 'ArrayTable.Column',
+                  'x-component-props': { title: 'KEY' },
+                  properties: {
+                    a1: {
+                      type: 'string',
+                      'x-decorator': 'Editable',
+                      'x-component': 'Input',
+                    },
+                  },
+                },
+                column2: {
+                  type: 'void',
+                  'x-component': 'ArrayTable.Column',
+                  'x-component-props': { title: 'VALUE' },
+                  properties: {
+                    a2: {
+                      type: 'string',
+                      'x-decorator': 'FormItem',
+                      'x-component': 'Input',
+                    },
+                  },
+                },
+                column3: {
+                  type: 'void',
+                  'x-component': 'ArrayTable.Column',
+                  'x-component-props': {
+                    title: '操作',
+                    dataIndex: 'operations',
+                  },
+                  properties: {
+                    item: {
+                      type: 'void',
+                      'x-component': 'FormItem',
+                      properties: {
+                        remove: {
+                          type: 'void',
+                          'x-component': 'ArrayTable.Remove',
+                        },
+                      },
+                    },
+                  },
+                },
+              },
+            },
+            properties: {
+              add: {
+                type: 'void',
+                'x-component': 'ArrayTable.Addition',
+                title: '添加条目',
+              },
+            },
+          },
+        },
+        description: {
+          title: '说明',
+          'x-decorator': 'FormItem',
+          'x-component': 'Input.TextArea',
+          'x-component-props': {
+            rows: 3,
+            showCount: true,
+            maxLength: 200,
+            placeholder: '请输入说明',
           },
         },
       },
@@ -123,12 +305,12 @@ const Save = (props: Props) => {
     <Modal
       maskClosable={false}
       width="50vw"
-      title="新增固件版本"
+      title="新增"
       onCancel={() => close()}
       onOk={() => save()}
       visible={visible}
     >
-      <Form form={form} labelCol={5} wrapperCol={16}>
+      <Form form={form} labelCol={5} wrapperCol={16} layout="vertical">
         <SchemaField schema={schema} scope={{ useAsyncDataSource, loadData }} />
       </Form>
     </Modal>

+ 25 - 0
src/pages/device/Firmware/Task/Detail/index.less

@@ -0,0 +1,25 @@
+.firmwareDetailCard {
+  position: relative;
+  height: 100px;
+  padding-top: 15px;
+  padding-left: 15px;
+  background: linear-gradient(135.62deg, #f6f7fd 22.27%, rgba(255, 255, 255, 0.86) 91.82%);
+  border-radius: 2px;
+  box-shadow: 0 4px 18px #efefef;
+
+  .firmwareDetailCardTitle {
+    color: rgba(0, 0, 0, 0.64);
+    font-size: 14px;
+  }
+
+  .firmwareDetailCardNum {
+    font-size: 36px;
+  }
+
+  .firmwareDetailCardImg {
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    width: 120px;
+  }
+}

+ 170 - 0
src/pages/device/Firmware/Task/Detail/index.tsx

@@ -0,0 +1,170 @@
+import { PageContainer } from '@ant-design/pro-layout';
+import { observer } from '@formily/react';
+import { Badge, Card, Col, Row } from 'antd';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+import { Tooltip } from 'antd';
+import { useRef, useState } from 'react';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import { EyeOutlined } from '@ant-design/icons';
+// import { useHistory } from 'umi';
+import type { FirmwareItem } from '@/pages/device/Firmware/typings';
+import useDomFullHeight from '@/hooks/document/useDomFullHeight';
+// import usePermissions from '@/hooks/permission';
+import SearchComponent from '@/components/SearchComponent';
+import { service } from '@/pages/device/Firmware';
+import styles from './index.less';
+import { model } from '@formily/reactive';
+
+const colorMap = new Map();
+colorMap.set('waiting', '#FF9000');
+colorMap.set('loading', '#4293FF');
+colorMap.set('finish', '#24B276');
+colorMap.set('error', '#F76F5D');
+
+const state = model<{
+  waiting: number;
+  loading: number;
+  finish: number;
+  error: number;
+}>({
+  waiting: 0,
+  loading: 2,
+  finish: 4,
+  error: 0,
+});
+
+const Detail = observer(() => {
+  const actionRef = useRef<ActionType>();
+  const intl = useIntl();
+  const { minHeight } = useDomFullHeight(`.firmware-task-detail`, 24);
+  // const { permission } = usePermissions('device/Firmware');
+  const [param, setParam] = useState({});
+
+  const arr = [
+    {
+      key: 'waiting',
+      name: '等待升级',
+      img: require('/public/images/firmware/waiting.png'),
+    },
+    {
+      key: 'loading',
+      name: '升级中',
+      img: require('/public/images/firmware/loading.png'),
+    },
+    {
+      key: 'finish',
+      name: '升级完成',
+      img: require('/public/images/firmware/finish.png'),
+    },
+    {
+      key: 'error',
+      name: '升级失败',
+      img: require('/public/images/firmware/error.png'),
+    },
+  ];
+
+  const columns: ProColumns<FirmwareItem>[] = [
+    {
+      title: '设备名称',
+      ellipsis: true,
+      dataIndex: 'name',
+    },
+    {
+      title: '所属产品',
+      ellipsis: true,
+      dataIndex: 'version',
+    },
+    {
+      title: '创建时间',
+      ellipsis: true,
+      dataIndex: 'signMethod',
+    },
+    {
+      title: '完成时间',
+      ellipsis: true,
+      dataIndex: 'signMethod',
+    },
+    {
+      title: '进度',
+      ellipsis: true,
+      dataIndex: 'signMethod',
+    },
+    {
+      title: '状态',
+      ellipsis: true,
+      dataIndex: 'signMethod',
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.data.option',
+        defaultMessage: '操作',
+      }),
+      valueType: 'option',
+      align: 'center',
+      width: 200,
+      render: () => [
+        <a onClick={() => {}} key="link">
+          <Tooltip
+            title={intl.formatMessage({
+              id: 'pages.data.option.detail',
+              defaultMessage: '查看',
+            })}
+            key={'detail'}
+          >
+            <EyeOutlined />
+          </Tooltip>
+        </a>,
+      ],
+    },
+  ];
+  return (
+    <PageContainer>
+      <Card style={{ marginBottom: 20 }}>
+        <Row gutter={24}>
+          {arr.map((item) => (
+            <Col span={6} key={item.key}>
+              <div className={styles.firmwareDetailCard}>
+                <div className={styles.firmwareDetailCardTitle}>
+                  <Badge color={colorMap.get(item.key)} />
+                  {item.name}
+                </div>
+                <div
+                  className={styles.firmwareDetailCardNum}
+                  style={{ color: colorMap.get(item.key) }}
+                >
+                  {state[item.key]}
+                </div>
+                <div className={styles.firmwareDetailCardImg}>
+                  <img style={{ width: '100%' }} src={item.img} />
+                </div>
+              </div>
+            </Col>
+          ))}
+        </Row>
+      </Card>
+      <SearchComponent<FirmwareItem>
+        field={columns}
+        target="firmware-task-detail"
+        onSearch={(data) => {
+          // 重置分页数据
+          actionRef.current?.reset?.();
+          setParam(data);
+        }}
+      />
+      <ProTable<FirmwareItem>
+        scroll={{ x: 1366 }}
+        tableClassName={'firmware-task-detail'}
+        tableStyle={{ minHeight }}
+        search={false}
+        params={param}
+        request={async (params) =>
+          service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
+        }
+        columns={columns}
+        actionRef={actionRef}
+      />
+    </PageContainer>
+  );
+});
+export default Detail;

+ 279 - 0
src/pages/device/Firmware/Task/Save/index.tsx

@@ -0,0 +1,279 @@
+import { Modal } from 'antd';
+import type { FirmwareItem } from '@/pages/device/Firmware/typings';
+import { createSchemaField } from '@formily/react';
+import {
+  Form,
+  FormGrid,
+  FormItem,
+  Input,
+  Select,
+  ArrayTable,
+  NumberPicker,
+  Radio,
+} from '@formily/antd';
+import { createForm, onFieldValueChange } from '@formily/core';
+import type { ISchema } from '@formily/json-schema';
+import FUpload from '@/components/Upload';
+import { service } from '@/pages/device/Firmware';
+import type { Response } from '@/utils/typings';
+import { useRef } from 'react';
+import type { ProductItem } from '@/pages/device/Product/typings';
+import { onlyMessage } from '@/utils/util';
+import FSelectDevices from '@/components/FSelectDevices';
+
+interface Props {
+  data?: FirmwareItem;
+  close: () => void;
+  visible: boolean;
+}
+
+const Save = (props: Props) => {
+  const { data, close, visible } = props;
+
+  const form = createForm({
+    validateFirst: true,
+    initialValues: data,
+    effects() {
+      onFieldValueChange('mode', async (field) => {
+        field
+          .query('timeoutSeconds1')
+          .take()
+          .setDecoratorProps({
+            gridSpan: field.value === 'push' ? 1 : 2,
+          });
+        field
+          .query('timeoutSeconds')
+          .take()
+          .setDecoratorProps({
+            gridSpan: field.value === 'push' ? 1 : 2,
+          });
+      });
+      onFieldValueChange('releaseType', async (field) => {
+        field.setDecoratorProps({
+          gridSpan: field.value === 'all' ? 2 : 1,
+        });
+      });
+    },
+  });
+
+  const products = useRef<ProductItem[]>([]);
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      FormGrid,
+      Input,
+      FUpload,
+      Select,
+      ArrayTable,
+      NumberPicker,
+      Radio,
+      FSelectDevices,
+    },
+  });
+
+  const save = async () => {
+    const values: FirmwareItem = await form.submit();
+    const product = products.current?.find((item) => item.id === values.productId);
+    values.productName = product?.name || '';
+    const resp = (await service.save(values)) as Response<FirmwareItem>;
+    if (resp.status === 200) {
+      onlyMessage('保存成功!');
+    } else {
+      onlyMessage('保存失败!', 'error');
+    }
+  };
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      grid: {
+        type: 'void',
+        'x-component': 'FormGrid',
+        'x-component-props': {
+          minColumns: 2,
+          maxColumns: 2,
+        },
+        properties: {
+          name: {
+            title: '任务名称',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-component-props': {
+              placeholder: '请输入任务名称',
+            },
+            required: true,
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入任务名称',
+              },
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+            ],
+          },
+          mode: {
+            title: '推送方式',
+            'x-component': 'Select',
+            'x-decorator': 'FormItem',
+            enum: [
+              { label: '平台推送', value: 'push' },
+              { label: '设备拉取', value: 'pull' },
+            ],
+            'x-component-props': {
+              placeholder: '请选择推送方式',
+            },
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择推送方式',
+              },
+            ],
+          },
+          timeoutSeconds: {
+            title: '响应超时时间',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-component-props': {
+              placeholder: '请输入响应超时时间(秒)',
+            },
+            'x-visible': false,
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入响应超时时间',
+              },
+              {
+                maximum: 99999,
+                minimum: 1,
+              },
+            ],
+            'x-reactions': {
+              dependencies: ['.mode'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="push"}}',
+                },
+              },
+            },
+          },
+          timeoutSeconds1: {
+            title: '升级超时时间',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-component-props': {
+              placeholder: '请输入升级超时时间(秒)',
+            },
+            'x-visible': false,
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入升级超时时间',
+              },
+              {
+                maximum: 99999,
+                minimum: 1,
+              },
+            ],
+            'x-reactions': {
+              dependencies: ['.mode'],
+              fulfill: {
+                state: {
+                  visible: '{{!!$deps[0]}}',
+                },
+              },
+            },
+          },
+          releaseType: {
+            type: 'number',
+            title: '升级设备',
+            default: 'all',
+            'x-visible': false,
+            enum: [
+              { label: '所有设备', value: 'all' },
+              { label: '选择设备', value: 'part' },
+            ],
+            'x-decorator': 'FormItem',
+            'x-component': 'Radio.Group',
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择升级设备',
+              },
+            ],
+            'x-reactions': {
+              dependencies: ['.mode'],
+              fulfill: {
+                state: {
+                  visible: '{{!!$deps[0]}}',
+                },
+              },
+            },
+          },
+          part: {
+            title: '选择设备',
+            'x-decorator': 'FormItem',
+            'x-component': 'FSelectDevices',
+            'x-visible': false,
+            required: true,
+            'x-reactions': {
+              dependencies: ['.releaseType'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="part"}}',
+                },
+              },
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择设备',
+              },
+            ],
+          },
+          description: {
+            title: '说明',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input.TextArea',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              rows: 3,
+              showCount: true,
+              maxLength: 200,
+              placeholder: '请输入说明',
+            },
+          },
+        },
+      },
+    },
+  };
+
+  return (
+    <Modal
+      maskClosable={false}
+      width="50vw"
+      title="新增任务"
+      onCancel={() => close()}
+      onOk={() => save()}
+      visible={visible}
+    >
+      <Form form={form} labelCol={5} wrapperCol={16} layout="vertical">
+        <SchemaField schema={schema} />
+      </Form>
+    </Modal>
+  );
+};
+export default Save;

+ 192 - 0
src/pages/device/Firmware/Task/index.tsx

@@ -0,0 +1,192 @@
+import { PageContainer } from '@ant-design/pro-layout';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+import { Button, Popconfirm, Tooltip } from 'antd';
+import { useRef, useState } from 'react';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import { EditOutlined, EyeOutlined, MinusOutlined, PlusOutlined } from '@ant-design/icons';
+import { Link, useHistory } from 'umi';
+import { model } from '@formily/reactive';
+import { observer } from '@formily/react';
+import type { FirmwareItem } from '@/pages/device/Firmware/typings';
+import Save from './Save';
+import { onlyMessage } from '@/utils/util';
+import { PermissionButton } from '@/components';
+import useDomFullHeight from '@/hooks/document/useDomFullHeight';
+import usePermissions from '@/hooks/permission';
+import SearchComponent from '@/components/SearchComponent';
+import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
+import { service } from '@/pages/device/Firmware';
+
+export const state = model<{
+  current?: FirmwareItem;
+  visible: boolean;
+}>({
+  visible: false,
+});
+const Task = observer(() => {
+  const actionRef = useRef<ActionType>();
+  const intl = useIntl();
+  const { minHeight } = useDomFullHeight(`.firmware-task`, 24);
+  const { permission } = usePermissions('device/Firmware');
+  const [param, setParam] = useState({});
+  const history = useHistory<Record<string, string>>();
+
+  const columns: ProColumns<FirmwareItem>[] = [
+    {
+      title: '任务名称',
+      ellipsis: true,
+      dataIndex: 'name',
+    },
+    {
+      title: '推送方式',
+      ellipsis: true,
+      dataIndex: 'version',
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.table.description',
+        defaultMessage: '说明',
+      }),
+      ellipsis: true,
+      align: 'center',
+      dataIndex: 'description',
+    },
+    {
+      title: '完成比例',
+      ellipsis: true,
+      dataIndex: 'signMethod',
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.data.option',
+        defaultMessage: '操作',
+      }),
+      valueType: 'option',
+      align: 'center',
+      width: 200,
+
+      render: (text, record) => [
+        <Link
+          onClick={() => {
+            state.current = record;
+          }}
+          to={`/device/firmware/detail/${record.id}`}
+          key="link"
+        >
+          <Tooltip
+            title={intl.formatMessage({
+              id: 'pages.data.option.detail',
+              defaultMessage: '查看',
+            })}
+            key={'detail'}
+          >
+            <EyeOutlined />
+          </Tooltip>
+        </Link>,
+        <a
+          key="editable"
+          onClick={() => {
+            state.visible = true;
+          }}
+        >
+          <Tooltip
+            title={intl.formatMessage({
+              id: 'pages.data.option.edit',
+              defaultMessage: '编辑',
+            })}
+          >
+            <EditOutlined />
+          </Tooltip>
+        </a>,
+        <a key="delete">
+          <Popconfirm
+            title={intl.formatMessage({
+              id: 'pages.data.option.remove.tips',
+              defaultMessage: '确认删除?',
+            })}
+            onConfirm={async () => {
+              await service.remove(record.id);
+              onlyMessage(
+                intl.formatMessage({
+                  id: 'pages.data.option.success',
+                  defaultMessage: '操作成功!',
+                }),
+              );
+              actionRef.current?.reload();
+            }}
+          >
+            <Tooltip
+              title={intl.formatMessage({
+                id: 'pages.data.option.remove',
+                defaultMessage: '删除',
+              })}
+            >
+              <MinusOutlined />
+            </Tooltip>
+          </Popconfirm>
+        </a>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <SearchComponent<FirmwareItem>
+        field={columns}
+        target="firmware-task"
+        onSearch={(data) => {
+          // 重置分页数据
+          actionRef.current?.reset?.();
+          setParam(data);
+        }}
+      />
+      <ProTable<FirmwareItem>
+        scroll={{ x: 1366 }}
+        tableClassName={'firmware-task'}
+        tableStyle={{ minHeight }}
+        search={false}
+        params={param}
+        headerTitle={
+          <div>
+            <PermissionButton
+              onClick={() => {
+                state.visible = true;
+              }}
+              isPermission={permission.add}
+              key="button"
+              icon={<PlusOutlined />}
+              type="primary"
+            >
+              {intl.formatMessage({
+                id: 'pages.data.option.add',
+                defaultMessage: '新增',
+              })}
+            </PermissionButton>
+            <Button
+              onClick={() => {
+                const url = getMenuPathByParams(MENUS_CODE['device/Firmware/Task/Detail'], '123');
+                history.push(url);
+              }}
+            >
+              详情
+            </Button>
+          </div>
+        }
+        request={async (params) =>
+          service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
+        }
+        columns={columns}
+        actionRef={actionRef}
+      />
+      <Save
+        data={state.current}
+        visible={state.visible}
+        close={() => {
+          state.visible = false;
+        }}
+      />
+    </PageContainer>
+  );
+});
+export default Task;

+ 71 - 22
src/pages/device/Firmware/index.tsx

@@ -3,16 +3,21 @@ import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
 import { Button, Popconfirm, Tooltip } from 'antd';
 import moment from 'moment';
-import { useRef } from 'react';
+import { useRef, useState } from 'react';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { EditOutlined, EyeOutlined, MinusOutlined, PlusOutlined } from '@ant-design/icons';
-import { Link } from 'umi';
+import { Link, useHistory } from 'umi';
 import { model } from '@formily/reactive';
 import { observer } from '@formily/react';
 import type { FirmwareItem, TaskItem } from '@/pages/device/Firmware/typings';
 import Service from '@/pages/device/Firmware/service';
 import Save from '@/pages/device/Firmware/Save';
 import { onlyMessage } from '@/utils/util';
+import { PermissionButton } from '@/components';
+import useDomFullHeight from '@/hooks/document/useDomFullHeight';
+import usePermissions from '@/hooks/permission';
+import SearchComponent from '@/components/SearchComponent';
+import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 
 export const service = new Service('firmware');
 
@@ -35,18 +40,18 @@ export const state = model<{
 const Firmware = observer(() => {
   const actionRef = useRef<ActionType>();
   const intl = useIntl();
+  const { minHeight } = useDomFullHeight(`.firmware`, 24);
+  const { permission } = usePermissions('device/Firmware');
+  const [param, setParam] = useState({});
+  const history = useHistory<Record<string, string>>();
 
   const columns: ProColumns<FirmwareItem>[] = [
     {
-      dataIndex: 'index',
-      valueType: 'indexBorder',
-      width: 48,
-    },
-    {
       title: intl.formatMessage({
         id: 'pages.device.firmware.name',
         defaultMessage: '固件名称',
       }),
+      ellipsis: true,
       dataIndex: 'name',
     },
     {
@@ -54,6 +59,7 @@ const Firmware = observer(() => {
         id: 'pages.device.firmware.version',
         defaultMessage: '固件版本',
       }),
+      ellipsis: true,
       dataIndex: 'version',
     },
     {
@@ -61,6 +67,7 @@ const Firmware = observer(() => {
         id: 'pages.device.firmware.productName',
         defaultMessage: '所属产品',
       }),
+      ellipsis: true,
       dataIndex: 'productName',
     },
     {
@@ -68,6 +75,7 @@ const Firmware = observer(() => {
         id: 'pages.device.firmware.signMethod',
         defaultMessage: '签名方式',
       }),
+      ellipsis: true,
       dataIndex: 'signMethod',
     },
     {
@@ -78,9 +86,20 @@ const Firmware = observer(() => {
       dataIndex: 'createTime',
       width: '200px',
       align: 'center',
+      ellipsis: true,
+      valueType: 'dateTime',
       render: (text: any) => moment(text).format('YYYY-MM-DD HH:mm:ss'),
-      sorter: true,
-      defaultSortOrder: 'descend',
+      // sorter: true,
+      // defaultSortOrder: 'descend',
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.table.description',
+        defaultMessage: '说明',
+      }),
+      ellipsis: true,
+      align: 'center',
+      dataIndex: 'description',
     },
     {
       title: intl.formatMessage({
@@ -157,20 +176,50 @@ const Firmware = observer(() => {
 
   return (
     <PageContainer>
+      <SearchComponent<FirmwareItem>
+        field={columns}
+        target="firmware"
+        onSearch={(data) => {
+          // 重置分页数据
+          actionRef.current?.reset?.();
+          setParam(data);
+        }}
+      />
       <ProTable<FirmwareItem>
-        toolBarRender={() => [
-          <Button
-            onClick={() => {
-              state.visible = true;
-            }}
-            key="button"
-            icon={<PlusOutlined />}
-            type="primary"
-          >
-            新增
-          </Button>,
-        ]}
-        request={async (params) => service.query(params)}
+        scroll={{ x: 1366 }}
+        tableClassName={'firmware'}
+        tableStyle={{ minHeight }}
+        search={false}
+        params={param}
+        headerTitle={
+          <div>
+            <PermissionButton
+              onClick={() => {
+                state.visible = true;
+              }}
+              isPermission={permission.add}
+              key="button"
+              icon={<PlusOutlined />}
+              type="primary"
+            >
+              {intl.formatMessage({
+                id: 'pages.data.option.add',
+                defaultMessage: '新增',
+              })}
+            </PermissionButton>
+            <Button
+              onClick={() => {
+                const url = getMenuPathByParams(MENUS_CODE['device/Firmware/Task'], '123');
+                history.push(url);
+              }}
+            >
+              升级任务
+            </Button>
+          </div>
+        }
+        request={async (params) =>
+          service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
+        }
         columns={columns}
         actionRef={actionRef}
       />

+ 15 - 0
src/pages/init-home/index.less

@@ -0,0 +1,15 @@
+.init {
+  width: 100%;
+  height: 100vh;
+  padding: 64px 128px;
+  overflow: hidden;
+  background-image: url('/images/init-home/background.png');
+  background-repeat: no-repeat;
+  background-size: 100% 100%;
+
+  .box {
+    width: 100%;
+    height: 100%;
+    background: white;
+  }
+}

+ 13 - 0
src/pages/init-home/index.tsx

@@ -0,0 +1,13 @@
+import { TitleComponent } from '@/components';
+import styles from './index.less';
+
+const InitHome = () => {
+  return (
+    <div className={styles.init}>
+      <TitleComponent data={'系统初始化'} />
+      <div className={styles.box}>123</div>
+    </div>
+  );
+};
+
+export default InitHome;

+ 118 - 0
src/pages/rule-engine/Alarm/Config/Save/input.tsx

@@ -0,0 +1,118 @@
+import { Modal } from 'antd';
+import type { FirmwareItem } from '@/pages/device/Firmware/typings';
+import { createSchemaField } from '@formily/react';
+import { Form, FormGrid, FormItem, Input, Switch } from '@formily/antd';
+import { createForm, onFormInit } from '@formily/core';
+import type { ISchema } from '@formily/json-schema';
+import { service } from '@/pages/rule-engine/Alarm/Config';
+import { onlyMessage } from '@/utils/util';
+import type { IOConfigItem } from '../typing';
+
+interface Props {
+  data?: FirmwareItem;
+  close: () => void;
+}
+
+const InputSave = (props: Props) => {
+  const { data, close } = props;
+
+  const form = createForm({
+    validateFirst: true,
+    initialValues: data,
+    effects() {
+      onFormInit(async (f) => {
+        const resp = await service.getDataExchange('consume');
+        if (resp.status === 200) {
+          f.setInitialValues(resp.result?.config.config);
+          f.setValuesIn('id', resp.result?.id);
+          f.setValuesIn('state', resp.result?.state?.value === 'enabled' ? true : false);
+        }
+      });
+    },
+  });
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      FormGrid,
+      Input,
+      Switch,
+    },
+  });
+
+  const save = async () => {
+    form.validate();
+    const inputConfig: IOConfigItem = await form.submit();
+    const res = await service.saveOutputData({
+      config: {
+        sourceType: 'kafka',
+        config: inputConfig,
+      },
+      id: inputConfig.id,
+      sourceType: 'kafka',
+      exchangeType: 'consume',
+    });
+
+    if (res.status === 200) {
+      onlyMessage('操作成功');
+    }
+  };
+
+  const inputSchema: ISchema = {
+    type: 'object',
+    properties: {
+      id: {
+        'x-component': 'Input',
+        'x-hidden': true,
+      },
+      address: {
+        title: 'kafka地址',
+        type: 'string',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        'x-component-props': {
+          placeholder: '请输入kafka地址',
+        },
+      },
+      topic: {
+        title: 'topic',
+        type: 'string',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        'x-component-props': {
+          placeholder: '请输入topic',
+        },
+      },
+      state: {
+        title: '状态',
+        type: 'string',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Switch',
+        default: false,
+        'x-component-props': {
+          checkedChildren: '禁用',
+          unCheckedChildren: '启用',
+        },
+      },
+    },
+  };
+
+  return (
+    <Modal
+      maskClosable={false}
+      width="45vw"
+      title="编辑"
+      onCancel={() => close()}
+      onOk={() => save()}
+      visible
+    >
+      <Form form={form} labelCol={5} wrapperCol={16} layout="vertical">
+        <SchemaField schema={inputSchema} />
+      </Form>
+    </Modal>
+  );
+};
+export default InputSave;

+ 155 - 0
src/pages/rule-engine/Alarm/Config/Save/output.tsx

@@ -0,0 +1,155 @@
+import { Modal } from 'antd';
+import type { FirmwareItem } from '@/pages/device/Firmware/typings';
+import { createSchemaField } from '@formily/react';
+import { Form, FormGrid, FormItem, Input, Switch } from '@formily/antd';
+import { createForm, onFormInit } from '@formily/core';
+import type { ISchema } from '@formily/json-schema';
+import { service } from '@/pages/rule-engine/Alarm/Config';
+import { onlyMessage } from '@/utils/util';
+import type { IOConfigItem } from '../typing';
+
+interface Props {
+  data?: FirmwareItem;
+  close: () => void;
+}
+
+const OutputSave = (props: Props) => {
+  const { data, close } = props;
+
+  const form = createForm({
+    validateFirst: true,
+    initialValues: data,
+    effects() {
+      onFormInit(async (f) => {
+        const resp = await service.getDataExchange('producer');
+        if (resp.status === 200) {
+          f.setInitialValues(resp.result?.config.config);
+          f.setValuesIn('id', resp.result?.id);
+          f.setValuesIn('state', resp.result?.state?.value === 'enabled' ? true : false);
+        }
+      });
+    },
+  });
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      FormGrid,
+      Input,
+      Switch,
+    },
+  });
+
+  const save = async () => {
+    form.validate();
+    const inputConfig: IOConfigItem = await form.submit();
+    const res = await service.saveOutputData({
+      config: {
+        sourceType: 'kafka',
+        config: inputConfig,
+      },
+      id: inputConfig.id,
+      sourceType: 'kafka',
+      exchangeType: 'consume',
+    });
+
+    if (res.status === 200) {
+      onlyMessage('操作成功');
+    }
+  };
+
+  const outputSchema: ISchema = {
+    type: 'object',
+    properties: {
+      id: {
+        'x-component': 'Input',
+        'x-hidden': true,
+      },
+      address: {
+        title: 'kafka地址',
+        type: 'string',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        'x-component-props': {
+          placeholder: '请输入kafka地址',
+        },
+      },
+      topic: {
+        title: 'topic',
+        type: 'string',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        'x-component-props': {
+          placeholder: '请输入topic',
+        },
+      },
+      state: {
+        title: '状态',
+        type: 'string',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Switch',
+        default: false,
+        'x-component-props': {
+          checkedChildren: '禁用',
+          unCheckedChildren: '启用',
+        },
+      },
+      // layout2: {
+      //   type: 'void',
+      //   'x-decorator': 'FormGrid',
+      //   'x-decorator-props': {
+      //     maxColumns: 2,
+      //     minColumns: 2,
+      //     columnGap: 24,
+      //   },
+      //   properties: {
+      //     username: {
+      //       title: '用户名',
+      //       type: 'string',
+      //       // required: true,
+      //       'x-decorator': 'FormItem',
+      //       'x-component': 'Input',
+      //       'x-component-props': {
+      //         placeholder: '请输入用户名',
+      //       },
+      //       'x-decorator-props': {
+      //         gridSpan: 1,
+      //       },
+      //     },
+      //     password: {
+      //       title: '密码',
+      //       type: 'string',
+      //       // required: true,
+      //       'x-decorator': 'FormItem',
+      //       'x-component': 'Input',
+      //       'x-decorator-props': {
+      //         gridSpan: 1,
+      //       },
+      //       'x-component-props': {
+      //         placeholder: '请输入密码',
+      //       },
+      //     },
+      //   },
+      // },
+    },
+  };
+
+  return (
+    <Modal
+      maskClosable={false}
+      width="50vw"
+      title="编辑"
+      onCancel={() => close()}
+      onOk={() => save()}
+      visible
+    >
+      <Form form={form} labelCol={5} wrapperCol={16} layout="vertical">
+        <SchemaField schema={outputSchema} />
+      </Form>
+    </Modal>
+  );
+};
+export default OutputSave;

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

@@ -276,6 +276,7 @@ const Configuration = () => {
         scroll={{ x: 1366 }}
         params={param}
         columns={columns}
+        columnEmptyText={''}
         request={(params) =>
           service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
         }

+ 3 - 0
src/utils/menu/index.ts

@@ -37,6 +37,9 @@ const extraRouteObj = {
       { code: 'Save2', name: '测试详情' },
     ],
   },
+  'device/Firmware': {
+    children: [{ code: 'Task', name: '升级任务' }],
+  },
   'link/Channel': {
     children: [
       {

+ 4 - 9
src/utils/menu/router.ts

@@ -16,8 +16,6 @@ export enum MENUS_CODE {
   'device/Category' = 'device/Category',
   'device/Command' = 'device/Command',
   'device/DataSource' = 'device/DataSource',
-  'device/Firmware/Save' = 'device/Firmware/Save',
-  'device/Firmware' = 'device/Firmware',
   'device/Instance' = 'device/Instance',
   'device/Location' = 'device/Location',
   'device/Product/Save' = 'device/Product/Save',
@@ -101,12 +99,9 @@ export enum MENUS_CODE {
   'visualization/Category' = 'visualization/Category',
   'visualization/Configuration' = 'visualization/Configuration',
   'visualization/Screen' = 'visualization/Screen',
-  'device/Firmware/Detail/History' = 'device/Firmware/Detail/History',
-  'device/Firmware/Detail/Task/Detail' = 'device/Firmware/Detail/Task/Detail',
-  'device/Firmware/Detail/Task/Release' = 'device/Firmware/Detail/Task/Release',
-  'device/Firmware/Detail/Task/Save' = 'device/Firmware/Detail/Task/Save',
-  'device/Firmware/Detail/Task' = 'device/Firmware/Detail/Task',
-  'device/Firmware/Detail' = 'device/Firmware/Detail',
+  'device/Firmware' = 'device/Firmware',
+  'device/Firmware/Task' = 'device/Firmware/Task',
+  'device/Firmware/Task/Detail' = 'device/Firmware/Task/Detail',
   'device/Instance/Detail/Config/Tags' = 'device/Instance/Detail/Config/Tags',
   'device/Instance/Detail/Config' = 'device/Instance/Detail/Config',
   'device/Instance/Detail/Functions' = 'device/Instance/Detail/Functions',
@@ -168,7 +163,7 @@ export const getDetailNameByCode = {
   'system/Menu/Detail': '菜单详情',
   'device/Product/Detail': '产品详情',
   'device/Instance/Detail': '设备详情',
-  'device/Firmware/Detail': '固件详情',
+  'device/Firmware/Task/Detail': '详情',
   'system/Department/Detail': '部门详情',
   'system/Role/Detail': '权限配置',
   'link/Type/Detail': '网络组件详情',