瀏覽代碼

feat(notice): notice template

Lind 4 年之前
父節點
當前提交
32a0146aa4

+ 6 - 2
src/components/BaseCrud/index.tsx

@@ -13,6 +13,7 @@ import type { ISchemaFieldProps } from '@formily/react/lib/types';
 import type { ModalProps } from 'antd/lib/modal/Modal';
 import type { TablePaginationConfig } from 'antd/lib/table/interface';
 import type { SearchConfig } from '@jetlinks/pro-table/lib/components/Form/FormRender';
+import type { Form } from '@formily/core';
 
 export type Option = {
   model: 'edit' | 'preview' | 'add';
@@ -33,12 +34,13 @@ export type Props<T> = {
   schemaConfig?: ISchemaFieldProps;
   defaultParams?: Record<string, any>;
   actionRef: React.MutableRefObject<ActionType | undefined>;
-  modelConfig?: ModalProps;
+  modelConfig?: ModalProps & { loading?: boolean };
   request?: (params: any) => Promise<Partial<RequestData<T>>>;
   toolBar?: React.ReactNode[];
   pagination?: false | TablePaginationConfig;
   search?: false | SearchConfig;
-  formEffect?: () => void;
+  formEffect?: () => void; // 与form参数 只有一个生效
+  form?: Form;
 };
 
 const BaseCrud = <T extends Record<string, any>>(props: Props<T>) => {
@@ -59,6 +61,7 @@ const BaseCrud = <T extends Record<string, any>>(props: Props<T>) => {
     pagination,
     search,
     formEffect,
+    form,
   } = props;
 
   return (
@@ -117,6 +120,7 @@ const BaseCrud = <T extends Record<string, any>>(props: Props<T>) => {
         schemaConfig={schemaConfig}
         modelConfig={modelConfig}
         formEffect={formEffect}
+        customForm={form}
       />
     </>
   );

+ 13 - 9
src/components/BaseCrud/save/index.tsx

@@ -1,5 +1,5 @@
 import React, { useEffect, useState } from 'react';
-import { message, Modal } from 'antd';
+import { message, Modal, Spin } from 'antd';
 import {
   NumberPicker,
   Editable,
@@ -31,19 +31,21 @@ import type { ISchemaFieldProps } from '@formily/react/lib/types';
 import type { ModalProps } from 'antd/lib/modal/Modal';
 import FUpload from '@/components/Upload';
 import FMonacoEditor from '@/components/FMonacoEditor';
+import type { Form as Form1 } from '@formily/core';
 
 interface Props<T> {
   schema: ISchema;
   service: BaseService<T>;
   reload: () => void;
   schemaConfig?: ISchemaFieldProps;
-  modelConfig?: ModalProps;
+  modelConfig?: ModalProps & { loading?: boolean };
   formEffect?: () => void;
+  customForm?: Form1;
 }
 
 const Save = <T extends Record<string, any>>(props: Props<T>) => {
   const intl = useIntl();
-  const { service, schema, reload, schemaConfig, modelConfig, formEffect } = props;
+  const { service, schema, reload, schemaConfig, modelConfig, formEffect, customForm } = props;
 
   const [visible, setVisible] = useState<boolean>(false);
   const [current, setCurrent] = useState<T>();
@@ -93,7 +95,7 @@ const Save = <T extends Record<string, any>>(props: Props<T>) => {
   });
 
   const save = async () => {
-    const values: T = await form.submit();
+    const values: T = await (customForm || form).submit();
     await service.update(values);
     message.success(
       intl.formatMessage({
@@ -116,11 +118,13 @@ const Save = <T extends Record<string, any>>(props: Props<T>) => {
       onOk={save}
       {...modelConfig}
     >
-      <PreviewText.Placeholder value="-">
-        <Form form={form} labelCol={5} wrapperCol={16}>
-          <SchemaField schema={schema} {...schemaConfig} />
-        </Form>
-      </PreviewText.Placeholder>
+      <Spin spinning={modelConfig?.loading || false}>
+        <PreviewText.Placeholder value="-">
+          <Form form={customForm || form} labelCol={5} wrapperCol={16}>
+            <SchemaField schema={schema} {...schemaConfig} />
+          </Form>
+        </PreviewText.Placeholder>
+      </Spin>
     </Modal>
   );
 };

+ 1 - 1
src/pages/device/Category/index.tsx

@@ -10,7 +10,7 @@ import ProTable from '@jetlinks/pro-table';
 import Save from '@/pages/device/Category/Save';
 import { model } from '@formily/reactive';
 import { observer } from '@formily/react';
-import { Response } from '@/utils/typings';
+import type { Response } from '@/utils/typings';
 
 export const service = new Service('device/category');
 

+ 2 - 12
src/pages/link/Gateway/index.tsx

@@ -16,9 +16,10 @@ import { useIntl } from '@@/plugin-locale/localeExports';
 import type { ISchema } from '@formily/json-schema';
 import Service from '@/pages/link/Gateway/service';
 import { CurdModel } from '@/components/BaseCrud/model';
-import type { Field, FieldDataSource, FormPathPattern } from '@formily/core';
+import type { Field, FormPathPattern } from '@formily/core';
 import { action } from '@formily/reactive';
 import { onFieldReact, onFieldValueChange } from '@formily/core';
+import { useAsyncDataSource } from '@/utils/util';
 
 export const service = new Service('gateway/device');
 
@@ -203,17 +204,6 @@ const Gateway = () => {
     });
   };
 
-  const useAsyncDataSource =
-    (services: (arg0: Field) => Promise<FieldDataSource>) => (field: Field) => {
-      field.loading = true;
-      services(field).then(
-        action.bound!((resp: any) => {
-          field.dataSource = resp;
-          field.loading = false;
-        }),
-      );
-    };
-
   const schema: ISchema = {
     type: 'object',
     properties: {

+ 205 - 21
src/pages/notice/Config/index.tsx

@@ -1,5 +1,4 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import BaseService from '@/utils/BaseService';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import {
   ArrowDownOutlined,
@@ -8,16 +7,165 @@ import {
   EditOutlined,
   MinusOutlined,
 } from '@ant-design/icons';
-import { Tooltip } from 'antd';
-import { useRef } from 'react';
+import { message, Popconfirm, Tooltip } from 'antd';
+import { useMemo, useRef, useState } from 'react';
 import BaseCrud from '@/components/BaseCrud';
 import { useIntl } from '@@/plugin-locale/localeExports';
+import type { ISchema } from '@formily/json-schema';
+import { downObject, useAsyncDataSource } from '@/utils/util';
+import { CurdModel } from '@/components/BaseCrud/model';
+import Service from '@/pages/notice/Config/service';
+import { createForm, onFieldValueChange } from '@formily/core';
+import { observer } from '@formily/react';
 
-export const service = new BaseService<ConfigItem>('notifier/config');
+export const service = new Service('notifier/config');
 
-const Config = () => {
+const Config = observer(() => {
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
+  const providerRef = useRef<NetworkType[]>([]);
+  const oldTypeRef = useRef();
+
+  const [configSchema, setConfigSchema] = useState<ISchema>({});
+  const [loading, setLoading] = useState<boolean>(false);
+  const createSchema = async () => {
+    // eslint-disable-next-line @typescript-eslint/no-use-before-define
+    const DForm = form;
+    if (!DForm?.values) return;
+    DForm.setValuesIn('provider', DForm.values.provider);
+    const resp = await service.getMetadata(
+      DForm?.values?.type,
+      // eslint-disable-next-line @typescript-eslint/no-use-before-define
+      currentType || DForm.values?.provider,
+    );
+    const properties = resp.result?.properties as ConfigMetadata[];
+    setConfigSchema({
+      type: 'object',
+      properties: properties?.reduce((previousValue, currentValue) => {
+        if (currentValue.type?.type === 'array') {
+          // 单独处理邮件的其他配置功能
+          previousValue[currentValue.property] = {
+            type: 'array',
+            title: '其他配置',
+            'x-component': 'ArrayItems',
+            'x-decorator': 'FormItem',
+            items: {
+              type: 'object',
+              properties: {
+                space: {
+                  type: 'void',
+                  'x-component': 'Space',
+                  properties: {
+                    sort: {
+                      type: 'void',
+                      'x-decorator': 'FormItem',
+                      'x-component': 'ArrayItems.SortHandle',
+                    },
+                    name: {
+                      type: 'string',
+                      title: 'key',
+                      'x-decorator': 'FormItem',
+                      'x-component': 'Input',
+                    },
+                    value: {
+                      type: 'string',
+                      title: 'value',
+                      'x-decorator': 'FormItem',
+                      'x-component': 'Input',
+                    },
+                    description: {
+                      type: 'string',
+                      title: '备注',
+                      'x-decorator': 'FormItem',
+                      'x-component': 'Input',
+                    },
+                    remove: {
+                      type: 'void',
+                      'x-decorator': 'FormItem',
+                      'x-component': 'ArrayItems.Remove',
+                    },
+                  },
+                },
+              },
+            },
+            properties: {
+              add: {
+                type: 'void',
+                title: '添加条目',
+                'x-component': 'ArrayItems.Addition',
+              },
+            },
+          };
+        } else {
+          previousValue[currentValue.property] = {
+            title: currentValue.name,
+            type: 'string',
+            'x-component': 'Input',
+            'x-decorator': 'FormItem',
+          };
+        }
+        return previousValue;
+      }, {}),
+    });
+    DForm.setValues(CurdModel.current);
+    setLoading(false);
+  };
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      name: {
+        title: '名称',
+        'x-component': 'Input',
+        'x-decorator': 'FormItem',
+      },
+      type: {
+        title: '类型',
+        'x-component': 'Select',
+        'x-decorator': 'FormItem',
+        'x-reactions': ['{{useAsyncDataSource(getTypes)}}'],
+      },
+      provider: {
+        title: '服务商',
+        'x-component': 'Select',
+        'x-decorator': 'FormItem',
+      },
+      configuration: configSchema,
+    },
+  };
+
+  const formEvent = () => {
+    onFieldValueChange('type', async (field, f) => {
+      const type = field.value;
+      if (!type) return;
+      f.setFieldState('provider', (state) => {
+        state.value = undefined;
+        state.dataSource = providerRef.current
+          .find((item) => type === item.id)
+          ?.providerInfos.map((i) => ({ label: i.name, value: i.id }));
+      });
+    });
+    onFieldValueChange('provider', async (field) => {
+      // eslint-disable-next-line @typescript-eslint/no-use-before-define
+      currentType = field.value;
+      await createSchema();
+    });
+  };
+
+  const form = useMemo(
+    () =>
+      createForm({
+        effects: formEvent,
+        initialValues: CurdModel.current,
+      }),
+    [],
+  );
+
+  let currentType = form.values.provider;
+
+  if (oldTypeRef.current !== currentType) {
+    form.clearFormGraph('configuration.*'); // 回收字段模型
+    form.deleteValuesIn('configuration.*');
+  }
 
   const columns: ProColumns<ConfigItem>[] = [
     {
@@ -55,7 +203,16 @@ const Config = () => {
       align: 'center',
       width: 200,
       render: (text, record) => [
-        <a onClick={() => console.log(record)}>
+        <a
+          key="edit"
+          onClick={async () => {
+            setLoading(true);
+            CurdModel.update(record);
+            form.setValues(record);
+            await createSchema();
+            CurdModel.model = 'edit';
+          }}
+        >
           <Tooltip
             title={intl.formatMessage({
               id: 'pages.data.option.edit',
@@ -65,17 +222,7 @@ const Config = () => {
             <EditOutlined />
           </Tooltip>
         </a>,
-        <a>
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.remove',
-              defaultMessage: '删除',
-            })}
-          >
-            <MinusOutlined />
-          </Tooltip>
-        </a>,
-        <a>
+        <a onClick={() => downObject(record, '通知配置')} key="download">
           <Tooltip
             title={intl.formatMessage({
               id: 'pages.data.option.download',
@@ -85,7 +232,7 @@ const Config = () => {
             <ArrowDownOutlined />
           </Tooltip>
         </a>,
-        <a>
+        <a key="debug">
           <Tooltip
             title={intl.formatMessage({
               id: 'pages.notice.option.debug',
@@ -95,7 +242,7 @@ const Config = () => {
             <BugOutlined />
           </Tooltip>
         </a>,
-        <a>
+        <a key="record">
           <Tooltip
             title={intl.formatMessage({
               id: 'pages.data.option.record',
@@ -105,11 +252,42 @@ const Config = () => {
             <BarsOutlined />
           </Tooltip>
         </a>,
+        <a key="remove">
+          <Popconfirm
+            onConfirm={async () => {
+              await service.remove(record.id);
+              message.success(
+                intl.formatMessage({
+                  id: 'pages.data.option.success',
+                  defaultMessage: '操作成功!',
+                }),
+              );
+              actionRef.current?.reload();
+            }}
+            title="确认删除?"
+          >
+            <Tooltip
+              title={intl.formatMessage({
+                id: 'pages.data.option.remove',
+                defaultMessage: '删除',
+              })}
+            >
+              <MinusOutlined />
+            </Tooltip>
+          </Popconfirm>
+        </a>,
       ],
     },
   ];
 
-  const schema = {};
+  const getTypes = async () =>
+    service.getTypes().then((resp) => {
+      providerRef.current = resp.result;
+      return resp.result.map((item: NetworkType) => ({
+        label: item.name,
+        value: item.id,
+      }));
+    });
 
   return (
     <PageContainer>
@@ -120,10 +298,16 @@ const Config = () => {
           id: 'pages.notice.config',
           defaultMessage: '通知配置',
         })}
+        modelConfig={{
+          width: '50vw',
+          loading: loading,
+        }}
         schema={schema}
+        form={form}
+        schemaConfig={{ scope: { useAsyncDataSource, getTypes } }}
         actionRef={actionRef}
       />
     </PageContainer>
   );
-};
+});
 export default Config;

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

@@ -0,0 +1,16 @@
+import BaseService from '@/utils/BaseService';
+import { request } from 'umi';
+
+class Service extends BaseService<ConfigItem> {
+  public getTypes = () =>
+    request(`${this.uri}/types`, {
+      method: 'GET',
+    });
+
+  public getMetadata = (type: string, provider: string) =>
+    request(`${this.uri}/${type}/${provider}/metadata`, {
+      method: 'GET',
+    });
+}
+
+export default Service;

+ 25 - 0
src/pages/notice/Config/typings.d.ts

@@ -8,3 +8,28 @@ type ConfigItem = {
   createTime: number;
   configuration: Record<string, unknown>;
 };
+
+type Provider = {
+  type: string;
+  id: string;
+  name: string;
+};
+
+type NetworkType = {
+  id: string;
+  name: string;
+  providerInfos: Provider[];
+};
+
+type ConfigMetadata = {
+  property: string;
+  name: string;
+  description: string;
+  type: {
+    name: string;
+    id: string;
+    type: string;
+    expands?: Record<string, any>;
+  };
+  scopes: any[];
+};

+ 31 - 0
src/utils/util.ts

@@ -0,0 +1,31 @@
+import moment from 'moment';
+import type { Field, FieldDataSource } from '@formily/core';
+import { action } from '@formily/reactive';
+
+export const downObject = (record: Record<string, unknown>, fileName: string) => {
+  // 创建隐藏的可下载链接
+  const ghostLink = document.createElement('a');
+  ghostLink.download = `${fileName}-${
+    record?.name || moment(new Date()).format('YYYY/MM/DD HH:mm:ss')
+  }.json`;
+  ghostLink.style.display = 'none';
+  //字符串内容转成Blob地址
+  const blob = new Blob([JSON.stringify(record)]);
+  ghostLink.href = URL.createObjectURL(blob);
+  //触发点击
+  document.body.appendChild(ghostLink);
+  ghostLink.click();
+  //移除
+  document.body.removeChild(ghostLink);
+};
+
+export const useAsyncDataSource =
+  (services: (arg0: Field) => Promise<FieldDataSource>) => (field: Field) => {
+    field.loading = true;
+    services(field).then(
+      action.bound!((resp: any) => {
+        field.dataSource = resp;
+        field.loading = false;
+      }),
+    );
+  };