Преглед изворни кода

feat(notice): Config、Template

lind пре 3 година
родитељ
комит
3c983df55b

+ 25 - 3
src/components/FBraftEditor/index.tsx

@@ -1,6 +1,28 @@
-import { connect, mapProps } from '@formily/react';
-import BraftEditor from 'braft-editor';
+import {connect, mapProps} from '@formily/react';
+import BraftEditor, {BraftEditorProps, EditorState} from 'braft-editor';
 import 'braft-editor/dist/index.css';
+import {useState} from "react";
 
-const FBraftEditor = connect(BraftEditor, mapProps());
+interface Props extends BraftEditorProps {
+  value: any
+  onChange: (data: any) => void
+}
+
+const FBraftEditor = connect((props: Props) => {
+  const [editorState, setEditorState] = useState<EditorState>(BraftEditor.createEditorState(props.value));
+
+  return (
+    <>
+      {
+        // @ts-ignore
+        <BraftEditor
+          value={editorState}
+          onChange={(state) => {
+            setEditorState(state);
+            props.onChange(state.toHTML())
+          }}
+        />}
+    </>
+  )
+}, mapProps());
 export default FBraftEditor;

+ 5 - 5
src/components/ProTableCard/CardItems/noticeConfig.tsx

@@ -1,8 +1,8 @@
 import React from 'react';
-import { TableCard } from '@/components';
+import {TableCard} from '@/components';
 import '@/style/common.less';
 import '../index.less';
-import { imgMap } from './noticeTemplate';
+import {imgMap, typeList} from './noticeTemplate';
 
 export interface NoticeCardProps extends ConfigItem {
   detail?: React.ReactNode;
@@ -15,7 +15,7 @@ export default (props: NoticeCardProps) => {
     <TableCard actions={props.actions} showStatus={false} showMask={false}>
       <div className={'pro-table-card-item'}>
         <div className={'card-item-avatar'}>
-          <img width={88} height={88} src={imgMap[props.type]} alt={props.type} />
+          <img width={88} height={88} src={imgMap[props.type]} alt={props.type}/>
         </div>
         <div className={'card-item-body'}>
           <div className={'card-item-header'}>
@@ -24,11 +24,11 @@ export default (props: NoticeCardProps) => {
           <div className={'card-item-content'}>
             <div>
               <label>通知方式</label>
-              <div className={'ellipsis'}>{props.name}</div>
+              <div className={'ellipsis'}>{typeList[props.type][props.provider] || '暂无'}</div>
             </div>
             <div>
               <label>说明</label>
-              <div className={'ellipsis'}>{props.name}</div>
+              <div className={'ellipsis'}>{props.description}</div>
             </div>
           </div>
         </div>

+ 23 - 17
src/components/Upload/index.tsx

@@ -1,19 +1,20 @@
-import { LoadingOutlined, PlusOutlined, UploadOutlined } from '@ant-design/icons';
+import {LoadingOutlined, PlusOutlined, UploadOutlined} from '@ant-design/icons';
 import SystemConst from '@/utils/const';
 import Token from '@/utils/token';
-import type { ReactNode } from 'react';
-import { useState } from 'react';
-import { connect } from '@formily/react';
-import { Input, Upload } from 'antd';
-import type { UploadChangeParam } from 'antd/lib/upload/interface';
-import type { UploadListType } from 'antd/es/upload/interface';
+import type {ReactNode} from 'react';
+import {useState} from 'react';
+import {connect} from '@formily/react';
+import {Input, Upload} from 'antd';
+import type {UploadChangeParam} from 'antd/lib/upload/interface';
+import type {UploadListType} from 'antd/es/upload/interface';
 import './index.less';
 
 interface Props {
   value: string;
-  onChange: (value: string | FileProperty) => void;
+  onChange: (value: string | FileProperty | any) => void;
   type?: 'file' | 'image';
   placeholder: string;
+  display?: string
 }
 
 type FileProperty = {
@@ -35,45 +36,50 @@ const FUpload = connect((props: Props) => {
       const f = {
         size: info.file.size || 0,
         url: info.file.response?.result,
+        name: info.file.name
       };
       setUrl(f);
       props.onChange(f);
     }
   };
 
-  const map = new Map<
-    string,
+  const map = new Map<string,
     {
       node: ReactNode;
       type: UploadListType;
-    }
-  >();
+    }>();
   map.set('image', {
     node: (
       <>
         {url ? (
-          <img src={url as string} alt="avatar" style={{ width: '100%' }} />
+          <img src={url as string} alt="avatar" style={{width: '100%'}}/>
         ) : (
           <div>
-            {loading ? <LoadingOutlined /> : <PlusOutlined />}
-            <div style={{ marginTop: 8 }}>选择图片</div>
+            {loading ? <LoadingOutlined/> : <PlusOutlined/>}
+            <div style={{marginTop: 8}}>选择图片</div>
           </div>
         )}
       </>
     ),
     type: 'picture-card',
   });
+
   map.set('file', {
     node: (
       <>
         <Input
           placeholder={props.placeholder}
-          value={(url as FileProperty)?.url}
+          // 如果display 有值的话,显示display 的值
+          value={(url as FileProperty)[props?.display || 'url']}
+          onChange={value => {
+            // @ts-ignore
+            props.display && props.onChange({[props?.display]: value.target.value, url: null, size: null})
+          }}
           onClick={(e) => {
             e.preventDefault();
             e.stopPropagation();
           }}
-          addonAfter={<UploadOutlined />}
+          addonAfter={<UploadOutlined/>}
         />
       </>
     ),

+ 130 - 29
src/pages/notice/Config/Debug/index.tsx

@@ -1,12 +1,14 @@
-import { Button, Modal } from 'antd';
-import { useMemo } from 'react';
-import { createForm } from '@formily/core';
-import { createSchemaField, observer } from '@formily/react';
-import { Form, FormItem, Input, Select } from '@formily/antd';
-import { ISchema } from '@formily/json-schema';
-import { service, state } from '@/pages/notice/Config';
-import { useLocation } from 'umi';
-import { useAsyncDataSource } from '@/utils/util';
+import {message, Modal} from 'antd';
+import {useMemo} from 'react';
+import {createForm, Field, onFieldReact, onFieldValueChange} from '@formily/core';
+import {createSchemaField, observer} from '@formily/react';
+import {ArrayTable, DatePicker, Form, FormItem, Input, NumberPicker, PreviewText, Select} from '@formily/antd';
+import {ISchema} from '@formily/json-schema';
+import {service, state} from '@/pages/notice/Config';
+import {useLocation} from 'umi';
+import {useAsyncDataSource} from '@/utils/util';
+import {Store} from 'jetlinks-store';
+import FUpload from "@/components/Upload";
 
 const Debug = observer(() => {
   const location = useLocation<{ id: string }>();
@@ -16,9 +18,41 @@ const Debug = observer(() => {
     () =>
       createForm({
         validateFirst: true,
-        effects() {},
+        effects() {
+          onFieldValueChange('templateId', (field, form1) => {
+            const value = field.value;
+            // 找到模版详情;
+            const list = Store.get('notice-template-list');
+
+            const _template = list.find((item: any) => item.id === value);
+            form1.setValuesIn('variableDefinitions', _template.variableDefinitions);
+          });
+          onFieldReact('variableDefinitions.*.type', (field) => {
+            const value = (field as Field).value;
+            const format = field.query('.value').take() as any;
+            switch (value) {
+              case 'date':
+                format.setComponent(DatePicker);
+                break;
+              case 'string':
+                format.setComponent(Input);
+                break;
+              case 'number':
+                format.setComponent(NumberPicker);
+                break;
+              case 'file':
+                format.setComponent(FUpload, {
+                  type: 'file',
+                });
+                break;
+              case 'other':
+                format.setComponent(Input);
+                break;
+            }
+          });
+        },
       }),
-    [],
+    [state.debug],
   );
 
   const SchemaField = createSchemaField({
@@ -26,52 +60,119 @@ const Debug = observer(() => {
       FormItem,
       Input,
       Select,
+      ArrayTable,
+      PreviewText,
     },
   });
 
-  console.log(id, 'testt');
 
-  const getTemplate = () => {
-    return service.getTemplate(id).then((resp) => {
-      return resp.result?.map((item: any) => ({
-        label: item.name,
-        value: item.id,
-      }));
-    });
-  };
+  const getTemplate = () => service.getTemplate(state.current?.id!, {
+    terms: [{column: 'type', value: id}, {column: 'provider', value: state.current?.provider}]
+  }).then((resp) => {
+    Store.set('notice-template-list', resp.result);
+    return resp.result?.map((item: any) => ({
+      label: item.name,
+      value: item.id,
+    }));
+  })
 
   const schema: ISchema = {
     type: 'object',
     properties: {
-      configId: {
+      templateId: {
         title: '通知模版',
         type: 'string',
         'x-decorator': 'FormItem',
         'x-component': 'Select',
         'x-reactions': '{{useAsyncDataSource(getTemplate)}}',
       },
-      bianliang: {
+      variableDefinitions: {
         title: '变量',
         type: 'string',
         'x-decorator': 'FormItem',
-        'x-component': 'Select',
+        'x-component': 'ArrayTable',
+        'x-component-props': {
+          pagination: {pageSize: 9999},
+          scroll: {x: '100%'},
+        },
+        items: {
+          type: 'object',
+          properties: {
+            column1: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {title: '变量', width: '120px'},
+              properties: {
+                id: {
+                  type: 'string',
+                  'x-decorator': 'FormItem',
+                  'x-component': 'PreviewText.Input',
+                  'x-disabled': true,
+                },
+              },
+            },
+            column2: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {title: '名称', width: '120px'},
+              properties: {
+                name: {
+                  type: 'string',
+                  'x-decorator': 'FormItem',
+                  'x-component': 'PreviewText.Input',
+                  'x-disabled': true,
+                },
+              },
+            },
+            column3: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {title: '值', width: '120px'},
+              properties: {
+                value: {
+                  type: 'string',
+                  'x-decorator': 'FormItem',
+                  'x-component': 'Input',
+                },
+              },
+            },
+          },
+        },
       },
     },
   };
+  
+  const start = async () => {
+    const data: { templateId: string, variableDefinitions: any } = await form.submit();
+    // 应该取选择的配置信息
+    if (!state.current) return;
+    const templateId = data.templateId;
+    const list = Store.get('notice-template-list');
+    const _template = list.find((item: any) => item.id === templateId);
+
+    const resp = await service.debug(state?.current.id, {
+      template: _template,
+      context: data.variableDefinitions?.reduce((previousValue: any, currentValue: { id: any; value: any; }) => {
+        return {
+          ...previousValue,
+          [currentValue.id]: currentValue.value
+        }
+      }, {})
+    });
+    if (resp.status === 200) {
+      message.success('操作成功!');
+    }
+  }
   return (
     <Modal
       title="调试"
       width="40vw"
       visible={state.debug}
       onCancel={() => (state.debug = false)}
-      footer={
-        <Button type="primary" onClick={() => (state.debug = false)}>
-          关闭
-        </Button>
-      }
+      onOk={start}
     >
       <Form form={form} layout={'vertical'}>
-        <SchemaField schema={schema} scope={{ getTemplate, useAsyncDataSource }} />
+        <SchemaField schema={schema} scope={{getTemplate, useAsyncDataSource}}/>
       </Form>
     </Modal>
   );

+ 27 - 21
src/pages/notice/Config/Detail/index.tsx

@@ -1,9 +1,9 @@
-import { PageContainer } from '@ant-design/pro-layout';
-import { createForm, onFieldValueChange } from '@formily/core';
-import { Card, Col, Input, message, Row } from 'antd';
-import { ISchema } from '@formily/json-schema';
-import { useEffect, useMemo, useState } from 'react';
-import { createSchemaField, observer } from '@formily/react';
+import {PageContainer} from '@ant-design/pro-layout';
+import {createForm, onFieldValueChange} from '@formily/core';
+import {Card, Col, Input, message, Row} from 'antd';
+import {ISchema} from '@formily/json-schema';
+import {useEffect, useMemo, useState} from 'react';
+import {createSchemaField, observer} from '@formily/react';
 import {
   ArrayTable,
   Checkbox,
@@ -20,10 +20,10 @@ import {
   Switch,
 } from '@formily/antd';
 import styles from './index.less';
-import { service, state } from '@/pages/notice/Config';
-import { useAsyncDataSource } from '@/utils/util';
-import { useParams } from 'umi';
-import { typeList } from '@/pages/notice';
+import {service, state} from '@/pages/notice/Config';
+import {useAsyncDataSource} from '@/utils/util';
+import {useParams} from 'umi';
+import {typeList} from '@/pages/notice';
 import FUpload from '@/components/Upload';
 import WeixinCorp from '@/pages/notice/Config/Detail/doc/WeixinCorp';
 import WeixinApp from '@/pages/notice/Config/Detail/doc/WeixinApp';
@@ -35,26 +35,26 @@ import Email from '@/pages/notice/Config/Detail/doc/Email';
 
 export const docMap = {
   weixin: {
-    corpMessage: <WeixinCorp />,
-    officialMessage: <WeixinApp />,
+    corpMessage: <WeixinCorp/>,
+    officialMessage: <WeixinApp/>,
   },
   dingTalk: {
-    dingTalkMessage: <DingTalk />,
-    dingTalkRobotWebHook: <DingTalkRebot />,
+    dingTalkMessage: <DingTalk/>,
+    dingTalkRobotWebHook: <DingTalkRebot/>,
   },
   voice: {
-    aliyun: <AliyunVoice />,
+    aliyun: <AliyunVoice/>,
   },
   sms: {
-    aliyunSms: <AliyunSms />,
+    aliyunSms: <AliyunSms/>,
   },
   email: {
-    embedded: <Email />,
+    embedded: <Email/>,
   },
 };
 
 const Detail = observer(() => {
-  const { id } = useParams<{ id: string }>();
+  const {id} = useParams<{ id: string }>();
 
   const [provider, setProvider] = useState<string>();
   const form = useMemo(
@@ -299,7 +299,7 @@ const Detail = observer(() => {
                     type: 'boolean',
                     'x-component': 'Checkbox.Group',
                     'x-decorator': 'FormItem',
-                    enum: [{ label: '开启SSL', value: true }],
+                    enum: [{label: '开启SSL', value: true}],
                   },
                 },
               },
@@ -335,7 +335,13 @@ const Detail = observer(() => {
 
   const handleSave = async () => {
     const data: ConfigItem = await form.submit();
-    const response: any = await service.save(data);
+    let response;
+    if (data.id) {
+      response = await service.update(data);
+    } else {
+      response = await service.save(data);
+    }
+
     if (response?.status === 200) {
       message.success('保存成功');
       history.back();
@@ -348,7 +354,7 @@ const Detail = observer(() => {
         <Row>
           <Col span={10}>
             <Form className={styles.form} form={form} layout={'vertical'}>
-              <SchemaField scope={{ useAsyncDataSource, getTypes }} schema={schema} />
+              <SchemaField scope={{useAsyncDataSource, getTypes}} schema={schema}/>
               <FormButtonGroup.Sticky>
                 <FormButtonGroup.FormItem>
                   <Submit onSubmit={handleSave}>保存</Submit>

+ 22 - 18
src/pages/notice/Config/Log/index.tsx

@@ -1,10 +1,11 @@
-import { Modal } from 'antd';
-import { observer } from '@formily/react';
-import { service, state } from '..';
-import ProTable, { ProColumns } from '@jetlinks/pro-table';
+import {Modal} from 'antd';
+import {observer} from '@formily/react';
+import {service, state} from '..';
+import ProTable, {ActionType, ProColumns} from '@jetlinks/pro-table';
 import SearchComponent from '@/components/SearchComponent';
-import { useLocation } from 'umi';
-import { InfoCircleOutlined } from '@ant-design/icons';
+import {useLocation} from 'umi';
+import {InfoCircleOutlined} from '@ant-design/icons';
+import {useRef, useState} from "react";
 
 const Log = observer(() => {
   const location = useLocation<{ id: string }>();
@@ -22,6 +23,7 @@ const Log = observer(() => {
     {
       dataIndex: 'state',
       title: '状态',
+      renderText: (text) => text.text
     },
     {
       dataIndex: 'action',
@@ -31,42 +33,44 @@ const Log = observer(() => {
           onClick={() => {
             Modal.info({
               title: '详情信息',
+              width: '30vw',
               content: (
-                <div>
-                  <p>some messages...some messages...</p>
-                  <p>some messages...some messages...</p>
+                <div style={{height: '300px', overflowY: 'auto'}}>
                   {JSON.stringify(record)}
                 </div>
               ),
-              onOk() {},
+              onOk() {
+              },
             });
           }}
         >
-          <InfoCircleOutlined />
+          <InfoCircleOutlined/>
         </a>,
       ],
     },
   ];
+  const actionRef = useRef<ActionType>();
+  const [param, setParam] = useState<any>();
   return (
     <Modal onCancel={() => (state.log = false)} title="通知记录" width={'70vw'} visible={state.log}>
       <SearchComponent
-        defaultParam={[{ column: 'type$IN', value: id }]}
+        defaultParam={[{column: 'type$IN', value: id}]}
         field={columns}
         onSearch={(data) => {
-          // actionRef.current?.reset?.();
-          // setParam(data);
-          console.log(data);
+          actionRef.current?.reset?.();
+          setParam(data);
         }}
         enableSave={false}
       />
       <ProTable<LogItem>
+        params={param}
         search={false}
         pagination={{
-          pageSize: 5,
+          pageSize: 5
         }}
         columns={columns}
-        request={async (params) => service.query(params)}
-      ></ProTable>
+        request={async (params) => service.getHistoryLog(state.current?.id!, params)}
+      />
     </Modal>
   );
 });

+ 100 - 65
src/pages/notice/Config/index.tsx

@@ -1,5 +1,5 @@
-import { PageContainer } from '@ant-design/pro-layout';
-import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import {PageContainer} from '@ant-design/pro-layout';
+import type {ActionType, ProColumns} from '@jetlinks/pro-table';
 import {
   ArrowDownOutlined,
   BarsOutlined,
@@ -9,21 +9,22 @@ import {
   PlusOutlined,
   UnorderedListOutlined,
 } from '@ant-design/icons';
-import { Button, message, Popconfirm, Tooltip } from 'antd';
-import { useRef, useState } from 'react';
-import { useIntl } from '@@/plugin-locale/localeExports';
-import { downloadObject } from '@/utils/util';
+import {Button, message, Popconfirm, Space, Tooltip, Upload} from 'antd';
+import {useRef, useState} from 'react';
+import {useIntl} from '@@/plugin-locale/localeExports';
+import {downloadObject} from '@/utils/util';
 import Service from '@/pages/notice/Config/service';
-import { observer } from '@formily/react';
+import {observer} from '@formily/react';
 import SearchComponent from '@/components/SearchComponent';
-import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
-import { history, useLocation } from 'umi';
-import { model } from '@formily/reactive';
+import {getMenuPathByParams, MENUS_CODE} from '@/utils/menu';
+import {history, useLocation} from 'umi';
+import {model} from '@formily/reactive';
 import moment from 'moment';
-import { ProTableCard } from '@/components';
+import {ProTableCard} from '@/components';
 import NoticeConfig from '@/components/ProTableCard/CardItems/noticeConfig';
 import Debug from '@/pages/notice/Config/Debug';
 import Log from '@/pages/notice/Config/Log';
+import {typeList} from "@/components/ProTableCard/CardItems/noticeTemplate";
 
 export const service = new Service('notifier/config');
 
@@ -44,11 +45,6 @@ const Config = observer(() => {
 
   const columns: ProColumns<ConfigItem>[] = [
     {
-      dataIndex: 'index',
-      valueType: 'indexBorder',
-      width: 48,
-    },
-    {
       dataIndex: 'name',
       title: intl.formatMessage({
         id: 'pages.table.name',
@@ -56,18 +52,13 @@ const Config = observer(() => {
       }),
     },
     {
-      dataIndex: 'type',
-      title: intl.formatMessage({
-        id: 'pages.notice.config.type',
-        defaultMessage: '通知方式',
-      }),
+      dataIndex: 'provider',
+      title: '通知方式',
+      renderText: (text, record) => typeList[record.type][record.provider]
     },
     {
-      dataIndex: 'provider',
-      title: intl.formatMessage({
-        id: 'pages.table.provider',
-        defaultMessage: '服务商',
-      }),
+      dataIndex: 'description',
+      title: '说明',
     },
     {
       title: intl.formatMessage({
@@ -92,7 +83,7 @@ const Config = observer(() => {
               defaultMessage: '编辑',
             })}
           >
-            <EditOutlined />
+            <EditOutlined/>
           </Tooltip>
         </a>,
         <a
@@ -110,13 +101,14 @@ const Config = observer(() => {
               defaultMessage: '下载配置',
             })}
           >
-            <ArrowDownOutlined />
+            <ArrowDownOutlined/>
           </Tooltip>
         </a>,
         <a
           key="debug"
           onClick={() => {
             state.debug = true;
+            state.current = record;
           }}
         >
           <Tooltip
@@ -125,7 +117,7 @@ const Config = observer(() => {
               defaultMessage: '调试',
             })}
           >
-            <BugOutlined />
+            <BugOutlined/>
           </Tooltip>
         </a>,
         <a
@@ -140,7 +132,7 @@ const Config = observer(() => {
               defaultMessage: '通知记录',
             })}
           >
-            <BarsOutlined />
+            <BarsOutlined/>
           </Tooltip>
         </a>,
         <a key="remove">
@@ -163,7 +155,7 @@ const Config = observer(() => {
                 defaultMessage: '删除',
               })}
             >
-              <DeleteOutlined />
+              <DeleteOutlined/>
             </Tooltip>
           </Popconfirm>
         </a>,
@@ -171,19 +163,11 @@ const Config = observer(() => {
     },
   ];
 
-  // const getTypes = async () =>
-  //   service.getTypes().then((resp) => {
-  //     providerRef.current = resp.result;
-  //     return resp.result.map((item: NetworkType) => ({
-  //       label: item.name,
-  //       value: item.id,
-  //     }));
-  //   });
   const [param, setParam] = useState({});
   return (
-    <PageContainer className={'page-title-show'}>
+    <PageContainer>
       <SearchComponent
-        defaultParam={[{ column: 'type$IN', value: id }]}
+        defaultParam={[{column: 'type$IN', value: id}]}
         field={columns}
         onSearch={(data) => {
           actionRef.current?.reset?.();
@@ -191,26 +175,75 @@ const Config = observer(() => {
         }}
       />
       <ProTableCard<ConfigItem>
+        rowKey='id'
+        actionRef={actionRef}
         search={false}
         params={param}
         columns={columns}
-        headerTitle={'通知配置'}
-        toolBarRender={() => [
-          <Button
-            onClick={() => {
-              state.current = undefined;
-              history.push(getMenuPathByParams(MENUS_CODE['notice/Config/Detail'], id));
-            }}
-            key="button"
-            icon={<PlusOutlined />}
-            type="primary"
-          >
-            {intl.formatMessage({
-              id: 'pages.data.option.add',
-              defaultMessage: '新增',
-            })}
-          </Button>,
-        ]}
+        headerTitle={(
+          <Space>
+            <Button
+              onClick={() => {
+                state.current = undefined;
+                history.push(getMenuPathByParams(MENUS_CODE['notice/Config/Detail'], id));
+              }}
+              key="button"
+              icon={<PlusOutlined/>}
+              type="primary"
+            >
+              {intl.formatMessage({
+                id: 'pages.data.option.add',
+                defaultMessage: '新增',
+              })}
+            </Button>
+            <Upload
+              key={'import'}
+              showUploadList={false}
+              beforeUpload={(file) => {
+                const reader = new FileReader();
+                reader.readAsText(file);
+                reader.onload = async (result) => {
+                  const text = result.target?.result as string;
+                  if (!file.type.includes('json')) {
+                    message.warning('文件内容格式错误');
+                    return;
+                  }
+                  try {
+                    const data = JSON.parse(text || '{}');
+                    if (Array.isArray(data)) {
+                      message.warning('文件内容格式错误');
+                      return;
+                    }
+                    const res: any = await service.savePatch(data);
+                    if (res.status === 200) {
+                      message.success('操作成功');
+                      actionRef.current?.reload();
+                    }
+                  } catch {
+                    message.warning('文件内容格式错误');
+                  }
+                };
+                return false;
+              }}
+            >
+              <Button style={{marginLeft: 12}}>导入</Button>
+            </Upload>
+            <Popconfirm
+              title={'确认导出当前页数据?'}
+              onConfirm={async () => {
+                const resp: any = await service.queryNoPagingPost({...param, paging: false});
+                if (resp.status === 200) {
+                  downloadObject(resp.result, '通知配置数据');
+                  message.success('导出成功');
+                } else {
+                  message.error('导出错误');
+                }
+              }}
+            >
+              <Button>导出</Button>
+            </Popconfirm>
+          </Space>
+        )}
         gridColumn={3}
         request={async (params) => service.query(params)}
         cardRender={(record) => (
@@ -226,16 +259,17 @@ const Config = observer(() => {
                   history.push(getMenuPathByParams(MENUS_CODE['notice/Config/Detail'], id));
                 }}
               >
-                <EditOutlined />
+                <EditOutlined/>
                 编辑
               </Button>,
               <Button
                 key="debug"
                 onClick={() => {
                   state.debug = true;
+                  state.current = record;
                 }}
               >
-                <BugOutlined />
+                <BugOutlined/>
                 调试
               </Button>,
               <Button
@@ -247,16 +281,17 @@ const Config = observer(() => {
                   )
                 }
               >
-                <ArrowDownOutlined />
+                <ArrowDownOutlined/>
                 导出
               </Button>,
               <Button
                 key="log"
                 onClick={() => {
                   state.log = true;
+                  state.current = record;
                 }}
               >
-                <UnorderedListOutlined />
+                <UnorderedListOutlined/>
                 通知记录
               </Button>,
               <Popconfirm
@@ -268,15 +303,15 @@ const Config = observer(() => {
                 }}
               >
                 <Button key="delete">
-                  <DeleteOutlined />
+                  <DeleteOutlined/>
                 </Button>
               </Popconfirm>,
             ]}
           />
         )}
       />
-      <Debug />
-      <Log />
+      <Debug/>
+      <Log/>
     </PageContainer>
   );
 });

+ 19 - 3
src/pages/notice/Config/service.ts

@@ -1,5 +1,5 @@
 import BaseService from '@/utils/BaseService';
-import { request } from 'umi';
+import {request} from 'umi';
 import SystemConst from '@/utils/const';
 
 class Service extends BaseService<ConfigItem> {
@@ -13,11 +13,27 @@ class Service extends BaseService<ConfigItem> {
       method: 'GET',
     });
 
-  public getTemplate = (configId: string) =>
-    request(`${SystemConst.API_BASE}/notifier/template/${configId}/_query/no-paging`);
+  public getTemplate = (configId: string, data: Record<string, any>) =>
+    request(`${SystemConst.API_BASE}/notifier/template/${configId}/_query`, {
+      method: 'POST',
+      data
+    });
 
   public getTemplateVariable = (templateId: string) =>
     request(`${SystemConst.API_BASE}/notifier/template/${templateId}/detail`);
+
+  public getHistoryLog = (configId: string, data: Record<string, any>) =>
+    request(`${SystemConst.API_BASE}/notify/history/config/${configId}/_query`, {
+      method: 'POST',
+      data
+    });
+
+  public debug = (id: string, data: Record<string, any>) =>
+    request(`${SystemConst.API_BASE}/notifier/${id}/_send`, {
+      method: 'POST',
+      data
+    })
+
 }
 
 export default Service;

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

@@ -7,6 +7,7 @@ type ConfigItem = {
   creatorId: string;
   createTime: number;
   configuration: Record<string, unknown>;
+  description: string
 };
 
 type Provider = {

+ 36 - 32
src/pages/notice/Template/Debug/index.tsx

@@ -1,22 +1,13 @@
-import { Button, Modal } from 'antd';
-import { useEffect, useMemo } from 'react';
-import { createForm, Field, onFieldReact, onFieldValueChange } from '@formily/core';
-import { createSchemaField, observer } from '@formily/react';
-import {
-  ArrayTable,
-  DatePicker,
-  Form,
-  FormItem,
-  Input,
-  NumberPicker,
-  PreviewText,
-  Select,
-} from '@formily/antd';
-import { ISchema } from '@formily/json-schema';
-import { configService, state } from '@/pages/notice/Template';
-import { useLocation } from 'umi';
-import { useAsyncDataSource } from '@/utils/util';
-import { Store } from 'jetlinks-store';
+import {message, Modal} from 'antd';
+import {useEffect, useMemo} from 'react';
+import {createForm, Field, onFieldReact, onFieldValueChange} from '@formily/core';
+import {createSchemaField, observer} from '@formily/react';
+import {ArrayTable, DatePicker, Form, FormItem, Input, NumberPicker, PreviewText, Select,} from '@formily/antd';
+import {ISchema} from '@formily/json-schema';
+import {configService, service, state} from '@/pages/notice/Template';
+import {useLocation} from 'umi';
+import {useAsyncDataSource} from '@/utils/util';
+import {Store} from 'jetlinks-store';
 import FUpload from '@/components/Upload';
 
 const Debug = observer(() => {
@@ -32,7 +23,6 @@ const Debug = observer(() => {
             const value = (field as Field).value;
             const configs = Store.get('notice-config');
             const target = configs.find((item: { id: any }) => item.id === value);
-            console.log(target, 'target');
             // 从缓存中获取通知配置信息
             if (target && target.variableDefinitions) {
               form1.setValuesIn('variableDefinitions', target.variableDefinitions);
@@ -87,7 +77,7 @@ const Debug = observer(() => {
   const getConfig = () =>
     configService
       .queryNoPagingPost({
-        terms: [{ column: 'type$IN', value: id }],
+        terms: [{column: 'type$IN', value: id}],
       })
       .then((resp: any) => {
         // 缓存通知配置
@@ -114,8 +104,8 @@ const Debug = observer(() => {
         'x-decorator': 'FormItem',
         'x-component': 'ArrayTable',
         'x-component-props': {
-          pagination: { pageSize: 9999 },
-          scroll: { x: '100%' },
+          pagination: {pageSize: 9999},
+          scroll: {x: '100%'},
         },
         items: {
           type: 'object',
@@ -123,7 +113,7 @@ const Debug = observer(() => {
             column1: {
               type: 'void',
               'x-component': 'ArrayTable.Column',
-              'x-component-props': { title: '变量', width: '120px' },
+              'x-component-props': {title: '变量', width: '120px'},
               properties: {
                 id: {
                   type: 'string',
@@ -136,7 +126,7 @@ const Debug = observer(() => {
             column2: {
               type: 'void',
               'x-component': 'ArrayTable.Column',
-              'x-component-props': { title: '名称', width: '120px' },
+              'x-component-props': {title: '名称', width: '120px'},
               properties: {
                 name: {
                   type: 'string',
@@ -149,7 +139,7 @@ const Debug = observer(() => {
             column3: {
               type: 'void',
               'x-component': 'ArrayTable.Column',
-              'x-component-props': { title: '值', width: '120px' },
+              'x-component-props': {title: '值', width: '120px'},
               properties: {
                 value: {
                   type: 'string',
@@ -163,20 +153,34 @@ const Debug = observer(() => {
       },
     },
   };
+
+  const start = async () => {
+    const data: { templateId: string, variableDefinitions: any } = await form.submit();
+    // 应该取选择的配置信息
+    if (!state.current) return;
+    const resp = await service.debug(state?.current.id, {
+      template: state.current,
+      context: data.variableDefinitions?.reduce((previousValue: any, currentValue: { id: any; value: any; }) => {
+        return {
+          ...previousValue,
+          [currentValue.id]: currentValue.value
+        }
+      }, {})
+    });
+    if (resp.status === 200) {
+      message.success('操作成功!');
+    }
+  }
   return (
     <Modal
       title="调试"
       width="40vw"
       visible={state.debug}
       onCancel={() => (state.debug = false)}
-      footer={
-        <Button type="primary" onClick={() => (state.debug = false)}>
-          关闭
-        </Button>
-      }
+      onOk={start}
     >
       <Form form={form} layout={'vertical'}>
-        <SchemaField schema={schema} scope={{ getConfig, useAsyncDataSource }} />
+        <SchemaField schema={schema} scope={{getConfig, useAsyncDataSource}}/>
       </Form>
     </Modal>
   );

+ 58 - 77
src/pages/notice/Template/Detail/index.tsx

@@ -15,20 +15,20 @@ import {
   Submit,
   Switch,
 } from '@formily/antd';
-import type { Field } from '@formily/core';
-import { createForm, onFieldInit, onFieldValueChange } from '@formily/core';
-import { createSchemaField, observer } from '@formily/react';
-import type { ISchema } from '@formily/json-schema';
+import type {Field} from '@formily/core';
+import {createForm, onFieldInit, onFieldValueChange} from '@formily/core';
+import {createSchemaField, observer} from '@formily/react';
+import type {ISchema} from '@formily/json-schema';
 import styles from './index.less';
-import { useEffect, useMemo, useState } from 'react';
+import {useEffect, useMemo, useState} from 'react';
 import FUpload from '@/components/Upload';
-import { useParams } from 'umi';
-import { PageContainer } from '@ant-design/pro-layout';
-import { Card, Col, message, Row } from 'antd';
-import { typeList } from '@/pages/notice';
-import { configService, service, state } from '@/pages/notice/Template';
+import {useParams} from 'umi';
+import {PageContainer} from '@ant-design/pro-layout';
+import {Card, Col, message, Row} from 'antd';
+import {typeList} from '@/pages/notice';
+import {configService, service, state} from '@/pages/notice/Template';
 import FBraftEditor from '@/components/FBraftEditor';
-import { useAsyncDataSource } from '@/utils/util';
+import {useAsyncDataSource} from '@/utils/util';
 import WeixinCorp from '@/pages/notice/Template/Detail/doc/WeixinCorp';
 import WeixinApp from '@/pages/notice/Template/Detail/doc/WeixinApp';
 import DingTalk from '@/pages/notice/Template/Detail/doc/DingTalk';
@@ -39,31 +39,31 @@ import Email from '@/pages/notice/Template/Detail/doc/Email';
 
 export const docMap = {
   weixin: {
-    corpMessage: <WeixinCorp />,
-    officialMessage: <WeixinApp />,
+    corpMessage: <WeixinCorp/>,
+    officialMessage: <WeixinApp/>,
   },
   dingTalk: {
-    dingTalkMessage: <DingTalk />,
-    dingTalkRobotWebHook: <DingTalkRebot />,
+    dingTalkMessage: <DingTalk/>,
+    dingTalkRobotWebHook: <DingTalkRebot/>,
   },
   voice: {
-    aliyun: <AliyunVoice />,
+    aliyun: <AliyunVoice/>,
   },
   sms: {
-    aliyunSms: <AliyunSms />,
+    aliyunSms: <AliyunSms/>,
   },
   email: {
-    embedded: <Email />,
+    embedded: <Email/>,
   },
 };
 
 const Detail = observer(() => {
-  const { id } = useParams<{ id: string }>();
+  const {id} = useParams<{ id: string }>();
 
   const getConfig = () =>
     configService
       .queryNoPagingPost({
-        terms: [{ column: 'type$IN', value: id }],
+        terms: [{column: 'type$IN', value: id}],
       })
       .then((resp: any) => {
         return resp.result?.map((item: any) => ({
@@ -87,39 +87,29 @@ const Detail = observer(() => {
   const [provider, setProvider] = useState<string>();
   // 正则提取${}里面的值
   const pattern = /(?<=\$\{).*?(?=\})/g;
+
   const form = useMemo(
     () =>
       createForm({
         validateFirst: true,
         effects() {
-          onFieldInit('template.message', (field, form1) => {
+          onFieldInit('template.message', (field) => {
             if (id === 'email') {
               field.setComponent(FBraftEditor);
-              // form1.setValuesIn('template.message', 'testtt')
             }
-            console.log(form1);
-            ///给FBraftEditor 设置初始值
           });
           onFieldValueChange('provider', (field) => {
             const value = field.value;
             setProvider(value);
           });
           onFieldValueChange('template.message', (field, form1) => {
-            let value = (field as Field).value;
-            try {
-              if (id === 'email' && form1.modified) {
-                value = value?.toHTML();
-              }
-              console.log(value, 'value');
-              const idList = value
-                ?.match(pattern)
-                ?.filter((i: string) => i)
-                .map((item: string) => ({ id: item, type: 'string', format: '--' }));
-              if (form1.modified) {
-                form1.setValuesIn('variableDefinitions', idList);
-              }
-            } catch (e) {
-              message.error('邮件数据反显开发中...');
+            const value = (field as Field).value;
+            const idList = (typeof value === 'string') && value
+              ?.match(pattern)
+              ?.filter((i: string) => i)
+              .map((item: string) => ({id: item, type: 'string', format: '--'}));
+            if (form1.modified) {
+              form1.setValuesIn('variableDefinitions', idList);
             }
           });
           onFieldValueChange('variableDefinitions.*.type', (field) => {
@@ -129,11 +119,11 @@ const Detail = observer(() => {
               case 'date':
                 format.setComponent(Select);
                 format.setDataSource([
-                  { label: 'String类型的UTC时间戳 (毫秒)', value: 'string' },
-                  { label: 'yyyy-MM-dd', value: 'yyyy-MM-dd' },
-                  { label: 'yyyy-MM-dd HH:mm:ss', value: 'yyyy-MM-dd HH:mm:ss' },
-                  { label: 'yyyy-MM-dd HH:mm:ss EE', value: 'yyyy-MM-dd HH:mm:ss EE' },
-                  { label: 'yyyy-MM-dd HH:mm:ss zzz', value: 'yyyy-MM-dd HH:mm:ss zzz' },
+                  {label: 'String类型的UTC时间戳 (毫秒)', value: 'string'},
+                  {label: 'yyyy-MM-dd', value: 'yyyy-MM-dd'},
+                  {label: 'yyyy-MM-dd HH:mm:ss', value: 'yyyy-MM-dd HH:mm:ss'},
+                  {label: 'yyyy-MM-dd HH:mm:ss EE', value: 'yyyy-MM-dd HH:mm:ss EE'},
+                  {label: 'yyyy-MM-dd HH:mm:ss zzz', value: 'yyyy-MM-dd HH:mm:ss zzz'},
                 ]);
                 format.setValue('string');
                 break;
@@ -148,9 +138,9 @@ const Detail = observer(() => {
               case 'file':
                 format.setComponent(Select);
                 format.setDataSource([
-                  { label: '视频', value: 'video' },
-                  { label: '图片', value: 'img' },
-                  { label: '全部', value: 'any' },
+                  {label: '视频', value: 'video'},
+                  {label: '图片', value: 'img'},
+                  {label: '全部', value: 'any'},
                 ]);
                 format.setValue('any');
                 break;
@@ -219,7 +209,7 @@ const Detail = observer(() => {
     }
     if (id === 'email') {
       data.provider = 'embedded';
-      data.template.text = data.template.message.toHTML();
+      data.template.text = data.template.message;
     }
 
     let response;
@@ -384,8 +374,8 @@ const Detail = observer(() => {
                     'x-component': 'Radio.Group',
                     'x-decorator': 'FormItem',
                     enum: [
-                      { label: '是', value: true },
-                      { label: '否', value: false },
+                      {label: '是', value: true},
+                      {label: '否', value: false},
                     ],
                   },
                   userIdList: {
@@ -440,9 +430,9 @@ const Detail = observer(() => {
                     'x-component': 'Select',
                     'x-decorator': 'FormItem',
                     enum: [
-                      { label: 'markdown', value: 'markdown' },
-                      { label: 'text', value: 'text' },
-                      { label: 'link', value: 'link' },
+                      {label: 'markdown', value: 'markdown'},
+                      {label: 'text', value: 'text'},
+                      {label: 'link', value: 'link'},
                     ],
                   },
                   markdown: {
@@ -640,14 +630,6 @@ const Detail = observer(() => {
                   tip: '请输入收件人邮箱,多个收件人用换行分隔',
                 },
               },
-              // message: {
-              //   "x-component": 'FBraftEditor',
-              //   "x-decorator": 'FormItem',
-              //   title: '模版内容',
-              //   "x-decorator-props": {
-              //     tip: '请输入收件人邮箱,多个收件人用换行分隔'
-              //   },
-              // },
               attachments: {
                 type: 'array',
                 title: '附件信息',
@@ -660,15 +642,13 @@ const Detail = observer(() => {
                 },
                 items: {
                   type: 'object',
-
-                  'x-component': 'FormGrid',
-                  'x-component-props': {
+                  'x-decorator': 'FormGrid',
+                  'x-decorator-props': {
                     maxColumns: 24,
                     minColumns: 24,
                   },
-
                   properties: {
-                    file: {
+                    '{url:location,name:name}': {
                       'x-component': 'FUpload',
                       'x-decorator': 'FormItem',
                       'x-decorator-props': {
@@ -679,6 +659,7 @@ const Detail = observer(() => {
                       },
                       'x-component-props': {
                         type: 'file',
+                        display: 'name',
                         placeholder: '请上传文件',
                       },
                     },
@@ -722,8 +703,8 @@ const Detail = observer(() => {
         'x-decorator': 'FormItem',
         'x-component': 'ArrayTable',
         'x-component-props': {
-          pagination: { pageSize: 9999 },
-          scroll: { x: '100%' },
+          pagination: {pageSize: 9999},
+          scroll: {x: '100%'},
         },
         items: {
           type: 'object',
@@ -731,7 +712,7 @@ const Detail = observer(() => {
             column1: {
               type: 'void',
               'x-component': 'ArrayTable.Column',
-              'x-component-props': { title: '变量', width: '120px' },
+              'x-component-props': {title: '变量', width: '120px'},
               properties: {
                 id: {
                   type: 'string',
@@ -744,7 +725,7 @@ const Detail = observer(() => {
             column2: {
               type: 'void',
               'x-component': 'ArrayTable.Column',
-              'x-component-props': { title: '名称' },
+              'x-component-props': {title: '名称'},
               properties: {
                 name: {
                   type: 'string',
@@ -757,7 +738,7 @@ const Detail = observer(() => {
             column3: {
               type: 'void',
               'x-component': 'ArrayTable.Column',
-              'x-component-props': { title: '类型', width: '120px' },
+              'x-component-props': {title: '类型', width: '120px'},
               properties: {
                 type: {
                   type: 'string',
@@ -765,11 +746,11 @@ const Detail = observer(() => {
                   'x-component': 'Select',
                   required: true,
                   enum: [
-                    { label: '字符串', value: 'string' },
-                    { label: '时间', value: 'date' },
-                    { label: '数字', value: 'number' },
-                    { label: '文件', value: 'file' },
-                    { label: '其他', value: 'other' },
+                    {label: '字符串', value: 'string'},
+                    {label: '时间', value: 'date'},
+                    {label: '数字', value: 'number'},
+                    {label: '文件', value: 'file'},
+                    {label: '其他', value: 'other'},
                   ],
                 },
               },
@@ -777,7 +758,7 @@ const Detail = observer(() => {
             column4: {
               type: 'void',
               'x-component': 'ArrayTable.Column',
-              'x-component-props': { title: '格式', width: '150px' },
+              'x-component-props': {title: '格式', width: '150px'},
               required: true,
               properties: {
                 format: {

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

@@ -1,10 +1,10 @@
-import { Modal } from 'antd';
-import { observer } from '@formily/react';
-import { service, state } from '..';
-import ProTable, { ProColumns } from '@jetlinks/pro-table';
+import {Modal} from 'antd';
+import {observer} from '@formily/react';
+import {service, state} from '..';
+import ProTable, {ProColumns} from '@jetlinks/pro-table';
 import SearchComponent from '@/components/SearchComponent';
-import { useLocation } from 'umi';
-import { InfoCircleOutlined } from '@ant-design/icons';
+import {useLocation} from 'umi';
+import {InfoCircleOutlined} from '@ant-design/icons';
 
 const Log = observer(() => {
   const location = useLocation<{ id: string }>();
@@ -28,29 +28,30 @@ const Log = observer(() => {
       title: '操作',
       render: (text, record) => [
         <a
+          key='info'
           onClick={() => {
             Modal.info({
               title: '详情信息',
+              width: '30vw',
               content: (
-                <div>
-                  <p>这是通知记录的详细信息。。。。。</p>
-                  <p>这是通知记录的详细信息。。。。。</p>
-                  {JSON.stringify(record)}
+                <div style={{height: '300px', overflowY: 'auto'}}>
+                  {record.errorStack}
                 </div>
               ),
-              onOk() {},
+              onOk() {
+              },
             });
           }}
         >
-          <InfoCircleOutlined />
-        </a>,
+          <InfoCircleOutlined/>
+        </a>
       ],
     },
   ];
   return (
     <Modal onCancel={() => (state.log = false)} title="通知记录" width={'70vw'} visible={state.log}>
       <SearchComponent
-        defaultParam={[{ column: 'type$IN', value: id }]}
+        defaultParam={[{column: 'type$IN', value: id}]}
         field={columns}
         onSearch={(data) => {
           // actionRef.current?.reset?.();
@@ -65,10 +66,9 @@ const Log = observer(() => {
           pageSize: 5,
         }}
         columns={columns}
-        request={async (params) => service.query(params)}
-      ></ProTable>
+        request={async (params) => service.getHistoryLog(state.current?.id!, params)}
+      />
     </Modal>
   );
 });
-
 export default Log;

+ 93 - 60
src/pages/notice/Template/index.tsx

@@ -1,6 +1,6 @@
-import { PageContainer } from '@ant-design/pro-layout';
-import { useRef, useState } from 'react';
-import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import {PageContainer} from '@ant-design/pro-layout';
+import {useRef, useState} from 'react';
+import type {ActionType, ProColumns} from '@jetlinks/pro-table';
 import {
   ArrowDownOutlined,
   BugOutlined,
@@ -9,20 +9,20 @@ import {
   PlusOutlined,
   UnorderedListOutlined,
 } from '@ant-design/icons';
-import { Button, Popconfirm, Tooltip } from 'antd';
-import { useIntl } from '@@/plugin-locale/localeExports';
+import {Button, message, Popconfirm, Space, Tooltip, Upload} from 'antd';
+import {useIntl} from '@@/plugin-locale/localeExports';
 import Service from '@/pages/notice/Template/service';
 import ConfigService from '@/pages/notice/Config/service';
 import SearchComponent from '@/components/SearchComponent';
-import { history, useLocation } from 'umi';
-import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
-import { model } from '@formily/reactive';
+import {history, useLocation} from 'umi';
+import {getMenuPathByParams, MENUS_CODE} from '@/utils/menu';
+import {model} from '@formily/reactive';
 import Debug from './Debug';
 import Log from '@/pages/notice/Template/Log';
-import { downloadObject } from '@/utils/util';
+import {downloadObject} from '@/utils/util';
 import moment from 'moment';
-import { ProTableCard } from '@/components';
-import NoticeCard from '@/components/ProTableCard/CardItems/noticeTemplate';
+import {ProTableCard} from '@/components';
+import NoticeCard, {typeList} from '@/components/ProTableCard/CardItems/noticeTemplate';
 
 export const service = new Service('notifier/template');
 
@@ -44,21 +44,17 @@ const Template = () => {
   const columns: ProColumns<TemplateItem>[] = [
     {
       dataIndex: 'name',
-      title: intl.formatMessage({
-        id: 'pages.table.name',
-        defaultMessage: '名称',
-      }),
+      title: '名称',
     },
     {
-      dataIndex: 'type',
-      title: intl.formatMessage({
-        id: 'pages.notice.config.type',
-        defaultMessage: '通知方式',
-      }),
+      dataIndex: 'provider',
+      title: '通知方式',
+      renderText: (text, record) => typeList[record.type][record.provider]
     },
     {
-      dataIndex: 'description',
-      title: '说明',
+      dataIndex: 'createTime',
+      title: '时间',
+      valueType: 'dateTime'
     },
     {
       title: intl.formatMessage({
@@ -82,7 +78,7 @@ const Template = () => {
               defaultMessage: '编辑',
             })}
           >
-            <EditOutlined />
+            <EditOutlined/>
           </Tooltip>
         </a>,
         <Popconfirm
@@ -100,7 +96,7 @@ const Template = () => {
                 defaultMessage: '删除',
               })}
             >
-              <DeleteOutlined />
+              <DeleteOutlined/>
             </Tooltip>
           </a>
         </Popconfirm>,
@@ -114,12 +110,9 @@ const Template = () => {
           }}
         >
           <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.download',
-              defaultMessage: '下载配置',
-            })}
+            title='导出'
           >
-            <ArrowDownOutlined />
+            <ArrowDownOutlined/>
           </Tooltip>
         </a>,
         <a
@@ -135,7 +128,7 @@ const Template = () => {
               defaultMessage: '调试',
             })}
           >
-            <BugOutlined />
+            <BugOutlined/>
           </Tooltip>
         </a>,
         <a
@@ -145,7 +138,7 @@ const Template = () => {
           }}
         >
           <Tooltip title="通知记录">
-            <UnorderedListOutlined />
+            <UnorderedListOutlined/>
           </Tooltip>
         </a>,
       ],
@@ -154,9 +147,9 @@ const Template = () => {
 
   const [param, setParam] = useState({});
   return (
-    <PageContainer className={'page-title-show'}>
+    <PageContainer>
       <SearchComponent
-        defaultParam={[{ column: 'type$IN', value: id }]}
+        defaultParam={[{column: 'type$IN', value: id}]}
         field={columns}
         onSearch={(data) => {
           actionRef.current?.reset?.();
@@ -169,26 +162,66 @@ const Template = () => {
         search={false}
         params={param}
         columns={columns}
-        headerTitle={intl.formatMessage({
-          id: 'pages.notice.template',
-          defaultMessage: '通知模版',
-        })}
-        toolBarRender={() => [
-          <Button
-            onClick={() => {
-              state.current = undefined;
-              history.push(getMenuPathByParams(MENUS_CODE['notice/Template/Detail'], id));
-            }}
-            key="button"
-            icon={<PlusOutlined />}
-            type="primary"
-          >
-            {intl.formatMessage({
-              id: 'pages.data.option.add',
-              defaultMessage: '新增',
-            })}
-          </Button>,
-        ]}
+        headerTitle={(
+          <Space>
+            <Button
+              onClick={() => {
+                state.current = undefined;
+                history.push(getMenuPathByParams(MENUS_CODE['notice/Template/Detail'], id));
+              }}
+              key="button"
+              icon={<PlusOutlined/>}
+              type="primary"
+            >
+              {intl.formatMessage({
+                id: 'pages.data.option.add',
+                defaultMessage: '新增',
+              })}
+            </Button>
+            <Upload
+              key={'import'}
+              showUploadList={false}
+              beforeUpload={(file) => {
+                const reader = new FileReader();
+                reader.readAsText(file);
+                reader.onload = async (result) => {
+                  const text = result.target?.result as string;
+                  if (!file.type.includes('json')) {
+                    message.warning('文件内容格式错误');
+                    return;
+                  }
+                  try {
+                    const data = JSON.parse(text || '{}');
+                    const res: any = await service.savePatch(data);
+                    if (res.status === 200) {
+                      message.success('操作成功');
+                      actionRef.current?.reload();
+                    }
+                  } catch {
+                    message.warning('文件内容格式错误');
+                  }
+                };
+                return false;
+              }}
+            >
+              <Button style={{marginLeft: 12}}>导入</Button>
+            </Upload>
+            <Popconfirm
+              title={'确认导出当前页数据?'}
+              onConfirm={async () => {
+                const resp: any = await service.queryNoPagingPost({...param, paging: false});
+                if (resp.status === 200) {
+                  downloadObject(resp.result, '通知模版数据');
+                  message.success('导出成功');
+                } else {
+                  message.error('导出错误');
+                }
+              }}
+            >
+              <Button>导出</Button>
+            </Popconfirm>
+          </Space>
+        )}
         gridColumn={3}
         cardRender={(record) => (
           <NoticeCard
@@ -202,7 +235,7 @@ const Template = () => {
                   history.push(getMenuPathByParams(MENUS_CODE['notice/Template/Detail'], id));
                 }}
               >
-                <EditOutlined />
+                <EditOutlined/>
                 编辑
               </Button>,
               <Button
@@ -212,7 +245,7 @@ const Template = () => {
                   state.current = record;
                 }}
               >
-                <BugOutlined />
+                <BugOutlined/>
                 调试
               </Button>,
               <Button
@@ -224,7 +257,7 @@ const Template = () => {
                   );
                 }}
               >
-                <ArrowDownOutlined />
+                <ArrowDownOutlined/>
                 导出
               </Button>,
               <Button
@@ -233,7 +266,7 @@ const Template = () => {
                   state.log = true;
                 }}
               >
-                <UnorderedListOutlined />
+                <UnorderedListOutlined/>
                 通知记录
               </Button>,
               <Popconfirm
@@ -245,7 +278,7 @@ const Template = () => {
                 }}
               >
                 <Button key="delete">
-                  <DeleteOutlined />
+                  <DeleteOutlined/>
                 </Button>
               </Popconfirm>,
             ]}
@@ -253,8 +286,8 @@ const Template = () => {
         )}
         request={async (params) => service.query(params)}
       />
-      <Debug />
-      <Log />
+      <Debug/>
+      <Log/>
     </PageContainer>
   );
 };

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

@@ -1,5 +1,5 @@
 import BaseService from '@/utils/BaseService';
-import { request } from 'umi';
+import {request} from 'umi';
 import SystemConst from '@/utils/const';
 
 class Service extends BaseService<TemplateItem> {
@@ -13,9 +13,10 @@ class Service extends BaseService<TemplateItem> {
       method: 'GET',
     });
 
-  public batchInsert = () =>
+  public batchInsert = (data: Record<any, any>[]) =>
     request(`${this.uri}/_batch`, {
       method: 'POST',
+      data
     });
 
   public getConfigs = (data: any) =>
@@ -24,6 +25,18 @@ class Service extends BaseService<TemplateItem> {
       data,
     });
 
+  public getHistoryLog = (templateId: string, data: Record<string, any>) =>
+    request(`${SystemConst.API_BASE}/notify/history/template/${templateId}/_query`, {
+      method: 'POST',
+      data
+    });
+
+  public debug = (id: string, data: Record<string, any>) =>
+    request(`${SystemConst.API_BASE}/notifier/${id}/_send`, {
+      method: 'POST',
+      data
+    })
+
   public sendMessage = (notifierId: string) =>
     request(`${SystemConst.API_BASE}/notifier/${notifierId}/_send`, {
       method: 'POST',

+ 1 - 0
src/pages/notice/Template/typings.d.ts

@@ -15,4 +15,5 @@ type LogItem = {
   config: string;
   sendTime: number;
   state: string;
+  errorStack?: string
 };

+ 13 - 8
src/utils/BaseService.ts

@@ -1,6 +1,6 @@
 import Token from '@/utils/token';
 import SystemConst from '@/utils/const';
-import { request } from 'umi';
+import {request} from 'umi';
 
 interface IBaseService<T> {
   query: (params: any) => Promise<any>;
@@ -20,32 +20,37 @@ class BaseService<T> implements IBaseService<T> {
   }
 
   query(data: any): Promise<any> {
-    return request(`${this.uri}/_query/`, { data, method: 'POST' });
+    return request(`${this.uri}/_query/`, {data, method: 'POST'});
   }
 
   queryNoPagingPost(data: any): Promise<unknown> {
-    return request(`${this.uri}/_query/no-paging?paging=false`, { data, method: 'POST' });
+    return request(`${this.uri}/_query/no-paging?paging=false`, {data, method: 'POST'});
   }
 
   queryNoPaging(params: any): Promise<unknown> {
-    return request(`${this.uri}/_query/no-paging?paging=false`, { params, method: 'GET' });
+    return request(`${this.uri}/_query/no-paging?paging=false`, {params, method: 'GET'});
   }
 
   remove(id: string): Promise<unknown> {
-    return request(`${this.uri}/${id}`, { method: 'DELETE' });
+    return request(`${this.uri}/${id}`, {method: 'DELETE'});
   }
 
   save(data: Partial<T>): Promise<unknown> {
-    return request(this.uri, { data, method: 'POST' });
+    return request(this.uri, {data, method: 'POST'});
+  }
+
+
+  savePatch(data: Partial<T>): Promise<unknown> {
+    return request(this.uri, {data, method: 'PATCH'});
   }
 
   update(data: Partial<T>): Promise<any> {
     // @ts-ignore
-    return data.id ? request(this.uri, { data, method: 'PATCH' }) : this.save(data);
+    return data.id ? request(this.uri, {data, method: 'PATCH'}) : this.save(data);
   }
 
   detail(id: string): Promise<any> {
-    return request(`${this.uri}/${id}`, { method: 'GET' });
+    return request(`${this.uri}/${id}`, {method: 'GET'});
   }
 
   modify(id: string, data: Partial<T>): Promise<any> {