xieyonghong 3 лет назад
Родитель
Сommit
eb87ed66be

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

@@ -1,6 +1,31 @@
 import { connect, mapProps } from '@formily/react';
-import BraftEditor from 'braft-editor';
+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;

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

@@ -2,7 +2,7 @@ import React from 'react';
 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;
@@ -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>

+ 9 - 2
src/components/Upload/index.tsx

@@ -11,9 +11,10 @@ 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,6 +36,7 @@ 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);
@@ -63,12 +65,17 @@ const FUpload = connect((props: Props) => {
     ),
     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) => {
+            props.onChange({ [props?.display || '_a']: value.target.value, url: null, size: null });
+          }}
           onClick={(e) => {
             e.preventDefault();
             e.stopPropagation();

+ 1 - 1
src/pages/device/Instance/Save/index.tsx

@@ -3,7 +3,7 @@ import { service } from '@/pages/device/Instance';
 import type { DeviceInstance } from '../typings';
 import { useEffect, useState } from 'react';
 import { useIntl } from '@@/plugin-locale/localeExports';
-import { UploadImage, Modal } from '@/components';
+import { Modal, UploadImage } from '@/components';
 import { debounce } from 'lodash';
 
 interface Props {

+ 141 - 23
src/pages/notice/Config/Debug/index.tsx

@@ -1,12 +1,23 @@
-import { Button, Modal } from 'antd';
+import { message, Modal } from 'antd';
 import { useMemo } from 'react';
-import { createForm } from '@formily/core';
+import { createForm, Field, onFieldReact, onFieldValueChange } from '@formily/core';
 import { createSchemaField, observer } from '@formily/react';
-import { Form, FormItem, Input, Select } from '@formily/antd';
+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 +27,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,49 +69,124 @@ 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 }} />

+ 7 - 1
src/pages/notice/Config/Detail/index.tsx

@@ -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();

+ 12 - 11
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 ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
 import SearchComponent from '@/components/SearchComponent';
 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,12 +33,9 @@ const Log = observer(() => {
           onClick={() => {
             Modal.info({
               title: '详情信息',
+              width: '30vw',
               content: (
-                <div>
-                  <p>some messages...some messages...</p>
-                  <p>some messages...some messages...</p>
-                  {JSON.stringify(record)}
-                </div>
+                <div style={{ height: '300px', overflowY: 'auto' }}>{JSON.stringify(record)}</div>
               ),
               onOk() {},
             });
@@ -47,26 +46,28 @@ const Log = observer(() => {
       ],
     },
   ];
+  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 }]}
         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,
         }}
         columns={columns}
-        request={async (params) => service.query(params)}
-      ></ProTable>
+        request={async (params) => service.getHistoryLog(state.current?.id || '', params)}
+      />
     </Modal>
   );
 });

+ 77 - 42
src/pages/notice/Config/index.tsx

@@ -9,7 +9,7 @@ import {
   PlusOutlined,
   UnorderedListOutlined,
 } from '@ant-design/icons';
-import { Button, message, Popconfirm, Tooltip } from 'antd';
+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';
@@ -24,6 +24,7 @@ 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({
@@ -117,6 +108,7 @@ const Config = observer(() => {
           key="debug"
           onClick={() => {
             state.debug = true;
+            state.current = record;
           }}
         >
           <Tooltip
@@ -171,17 +163,9 @@ 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 }]}
         field={columns}
@@ -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) => (
@@ -233,6 +266,7 @@ const Config = observer(() => {
                 key="debug"
                 onClick={() => {
                   state.debug = true;
+                  state.current = record;
                 }}
               >
                 <BugOutlined />
@@ -254,6 +288,7 @@ const Config = observer(() => {
                 key="log"
                 onClick={() => {
                   state.log = true;
+                  state.current = record;
                 }}
               >
                 <UnorderedListOutlined />

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

@@ -13,11 +13,26 @@ 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 = {

+ 24 - 8
src/pages/notice/Template/Debug/index.tsx

@@ -1,4 +1,4 @@
-import { Button, Modal } from 'antd';
+import { message, Modal } from 'antd';
 import { useEffect, useMemo } from 'react';
 import { createForm, Field, onFieldReact, onFieldValueChange } from '@formily/core';
 import { createSchemaField, observer } from '@formily/react';
@@ -13,7 +13,7 @@ import {
   Select,
 } from '@formily/antd';
 import { ISchema } from '@formily/json-schema';
-import { configService, state } from '@/pages/notice/Template';
+import { configService, service, state } from '@/pages/notice/Template';
 import { useLocation } from 'umi';
 import { useAsyncDataSource } from '@/utils/util';
 import { Store } from 'jetlinks-store';
@@ -32,7 +32,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);
@@ -163,17 +162,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 }} />

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

@@ -87,39 +87,31 @@ 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
+            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);
-              }
-            } catch (e) {
-              message.error('邮件数据反显开发中...');
+            if (form1.modified) {
+              form1.setValuesIn('variableDefinitions', idList);
             }
           });
           onFieldValueChange('variableDefinitions.*.type', (field) => {
@@ -219,7 +211,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;
@@ -640,14 +632,6 @@ const Detail = observer(() => {
                   tip: '请输入收件人邮箱,多个收件人用换行分隔',
                 },
               },
-              // message: {
-              //   "x-component": 'FBraftEditor',
-              //   "x-decorator": 'FormItem',
-              //   title: '模版内容',
-              //   "x-decorator-props": {
-              //     tip: '请输入收件人邮箱,多个收件人用换行分隔'
-              //   },
-              // },
               attachments: {
                 type: 'array',
                 title: '附件信息',
@@ -660,15 +644,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 +661,7 @@ const Detail = observer(() => {
                       },
                       'x-component-props': {
                         type: 'file',
+                        display: 'name',
                         placeholder: '请上传文件',
                       },
                     },

+ 12 - 12
src/pages/notice/Template/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 ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
 import SearchComponent from '@/components/SearchComponent';
 import { useLocation } from 'umi';
 import { InfoCircleOutlined } from '@ant-design/icons';
+import { useRef, useState } from 'react';
 
 const Log = observer(() => {
   const location = useLocation<{ id: string }>();
@@ -28,15 +29,13 @@ 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>
+                <div style={{ height: '300px', overflowY: 'auto' }}>{record.errorStack}</div>
               ),
               onOk() {},
             });
@@ -47,15 +46,16 @@ const Log = observer(() => {
       ],
     },
   ];
+  const [param, setParam] = useState<any>();
+  const actionRef = useRef<ActionType>();
   return (
     <Modal onCancel={() => (state.log = false)} title="通知记录" width={'70vw'} visible={state.log}>
       <SearchComponent
         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}
       />
@@ -64,11 +64,11 @@ const Log = observer(() => {
         pagination={{
           pageSize: 5,
         }}
+        params={param}
         columns={columns}
-        request={async (params) => service.query(params)}
-      ></ProTable>
+        request={async (params) => service.getHistoryLog(state.current?.id || '', params)}
+      />
     </Modal>
   );
 });
-
 export default Log;

+ 71 - 40
src/pages/notice/Template/index.tsx

@@ -9,7 +9,7 @@ import {
   PlusOutlined,
   UnorderedListOutlined,
 } from '@ant-design/icons';
-import { Button, Popconfirm, Tooltip } from 'antd';
+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';
@@ -22,7 +22,7 @@ import Log from '@/pages/notice/Template/Log';
 import { downloadObject } from '@/utils/util';
 import moment from 'moment';
 import { ProTableCard } from '@/components';
-import NoticeCard from '@/components/ProTableCard/CardItems/noticeTemplate';
+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({
@@ -113,12 +109,7 @@ const Template = () => {
             );
           }}
         >
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.download',
-              defaultMessage: '下载配置',
-            })}
-          >
+          <Tooltip title="导出">
             <ArrowDownOutlined />
           </Tooltip>
         </a>,
@@ -154,7 +145,7 @@ const Template = () => {
 
   const [param, setParam] = useState({});
   return (
-    <PageContainer className={'page-title-show'}>
+    <PageContainer>
       <SearchComponent
         defaultParam={[{ column: 'type$IN', value: id }]}
         field={columns}
@@ -169,26 +160,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

+ 14 - 1
src/pages/notice/Template/service.ts

@@ -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;
 };

+ 4 - 0
src/utils/BaseService.ts

@@ -39,6 +39,10 @@ class BaseService<T> implements IBaseService<T> {
     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);