amis2/docs/zh-CN/components/service.md
2023-07-14 17:38:42 +08:00

30 KiB
Executable File
Raw Blame History

title description type group menuName icon order
Service 功能型容器 0 ⚙ 组件 Service 63

amis 中部分组件,作为展示组件,自身没有使用接口初始化数据域的能力,例如:TableCardsList等,他们需要使用某些配置项,例如source,通过数据映射功能,在当前的 数据链 中获取数据,并进行数据展示。

Service组件就是专门为该类组件而生,它的功能是:配置初始化接口,进行数据域的初始化,然后在Service内容器中配置子组件,这些子组件通过数据链的方法,获取Service所拉取到的数据

基本使用

最基本的使用,是配置初始化接口api,将接口返回的数据添加到自身的数据域中,以供子组件通过数据链进行获取使用。

{
    "type": "service",
    "api": "/api/mock2/page/initData",
    "body": {
        "type": "panel",
        "title": "$title",
        "body": "现在是:${date}"
    }
}

你可以通过查看网络面板看到,service接口返回的数据结构为:

{
  "status": 0,
  "msg": "",
  "data": {
    "title": "Test Page Component",
    "date": "2017-10-13"
  }
}

service的子组件中,就可以使用${title}${date}展示数据

展示列表

另外一种使用较为频繁的场景是serivce + table 进行列表渲染

{
    "type": "service",
    "api": "/api/mock2/crud/table?perPage=5",
    "body": [
        {
            "type": "table",
            "title": "表格1",
            "source": "$rows",
            "columns": [
                {
                    "name": "engine",
                    "label": "Engine"
                },
                {
                    "name": "version",
                    "label": "Version"
                }
            ]
        },
        {
            "type": "table",
            "source": "$rows",
            "columns": [
                {
                    "name": "engine",
                    "label": "Engine"
                },
                {
                    "name": "version",
                    "label": "Version"
                }
            ]
        }
    ]
}

上例中 service 接口返回数据结构如下:

{
  "status": 0,
  "msg": "ok",
  "data": {
    "count": 57,
    "rows": [
      {
        "engine": "Trident",
        "browser": "Internet Explorer 4.0",
        "platform": "Win 95+",
        "version": "4",
        "grade": "X",
        "id": 1
      },
      {
        "engine": "Trident",
        "browser": "Internet Explorer 5.0",
        "platform": "Win 95+",
        "version": "5",
        "grade": "C",
        "id": 2
      },
      {
        "engine": "Trident",
        "browser": "Internet Explorer 5.5",
        "platform": "Win 95+",
        "version": "5.5",
        "grade": "A",
        "id": 3
      },
      {
        "engine": "Trident",
        "browser": "Internet Explorer 6",
        "platform": "Win 98+",
        "version": "6",
        "grade": "A",
        "id": 4
      },
      {
        "engine": "Trident",
        "browser": "Internet Explorer 7",
        "platform": "Win XP SP2+",
        "version": "7",
        "grade": "A",
        "id": 5
      },
      {
        "engine": "Trident",
        "browser": "AOL browser (AOL desktop)",
        "platform": "Win XP",
        "version": "6",
        "grade": "A",
        "id": 6
      },
      {
        "engine": "Gecko",
        "browser": "Firefox 1.0",
        "platform": "Win 98+ / OSX.2+",
        "version": "1.7",
        "grade": "A",
        "id": 7
      },
      {
        "engine": "Gecko",
        "browser": "Firefox 1.5",
        "platform": "Win 98+ / OSX.2+",
        "version": "1.8",
        "grade": "A",
        "id": 8
      },
      {
        "engine": "Gecko",
        "browser": "Firefox 2.0",
        "platform": "Win 98+ / OSX.2+",
        "version": "1.8",
        "grade": "A",
        "id": 9
      },
      {
        "engine": "Gecko",
        "browser": "Firefox 3.0",
        "platform": "Win 2k+ / OSX.3+",
        "version": "1.9",
        "grade": "A",
        "id": 10
      }
    ]
  }
}

table中配置source属性为${rows}就可以获取到rows变量的数据,并用于展示。

动态渲染页面

Service 还有个重要的功能就是支持配置 schemaApi,通过它可以实现动态渲染页面内容。

{
  "type": "service",
  "schemaApi": "/api/mock2/service/schema?type=tabs"
}

同样观察schemaApi接口返回的数据结构:

{
  "status": 0,
  "msg": "",
  "data": {
    "type": "tabs",
    "tabs": [
      {
        "title": "TabA",
        "body": "卡片A内容"
      },
      {
        "title": "TabB",
        "body": "卡片B内容"
      }
    ]
  }
}

它将data返回的对象作为 amis 页面配置,进行了解析渲染,实现动态渲染页面的功能。

jsonp 请求

schemaApi 同样支持 jsonp 请求,完整用法请参考 amis-admin 项目。

{
  "type": "service",
  "schemaApi": "jsonp:/api/mock2/service/jsonp"
}

schemaApi接口 返回的内容其实是一段立即执行的 js 代码。我们可以通过 callback 参数执行函数名,或者通过 request._callback 获取

(function () {
  window.axiosJsonpCallbackxxxx &&
    window.axiosJsonpCallbackxxxx({
      status: 0,
      msg: '',
      data: {
        type: 'page',
        title: 'jsonp 示例',
        body: 'this is tpl from jsonp'
      }
    });
})();

js 请求

2.1.0 及以上版本

schemaApi 支持 js 请求,它会发起一个 xhr 请求去下载 js 文件后执行

{
  "type": "service",
  "schemaApi": "js:/api/mock2/service/jsschema"
}

这个接口的返回结果期望是一段 JavaScript 代码,和普通 json 返回结果最大的不同是这里可以执行 JavaScript 代码,比如支持 onClick 函数

return {
  type: 'button',
  label: '按钮修改',
  onClick: (e, props) => {
    alert('消息通知');
  }
};

这段代码里可以通过 api 变量拿到当前请求的 api 参数,比如 url 地址,可以通过判断进行二次处理

console.log(api);
return {
  type: 'button',
  label: '按钮修改',
  onClick: (e, props) => {
    alert(api.url);
  }
};

动态渲染表单项

默认 Service 可以通过配置schemaApi 动态渲染页面内容,但是如果想渲染表单项,请返回下面这种格式:

{
  "status": 0,
  "msg": "",
  "data": {
    "type": "container",
    "body": [
      {
        "type": "input-text",
        "name": "text",
        "label": "文本输入"
      }
    ]
  }
}

例如下例:

{
  "type": "service",
  "schemaApi": "/api/mock2/service/schema?type=controls"
}

schemaApi 除了能返回表单项之外,还能同时返回表单数据,如果你这样返回接口

{
  "status": 0,
  "msg": "",
  "data": {
    "data": {
      "a": "b" // 这样返回的选项会选中第二个选项B
    },
    "body": [
      {
        "type": "select",
        "name": "a",
        "label": "选项",
        "options": [
          {"label": "A", "value": "a"},
          {"label": "B", "value": "b"}
        ]
      }
    ]
  }
}

接口联动

apischemaApi都支持接口联动

{
    "title": "",
    "type": "form",
    "api": "/api/mock/saveForm?waitSeconds=1",
    "mode": "horizontal",
    "body": [
        {
        "label": "数据模板",
        "type": "select",
        "name": "tpl",
        "value": "tpl1",
        "size": "sm",
        "options": [
            {
            "label": "模板1",
            "value": "tpl1"
            },
            {
            "label": "模板2",
            "value": "tpl2"
            },
            {
            "label": "模板3",
            "value": "tpl3"
            }
        ],
        "description": "<span class=\"text-danger\">请修改该下拉选择器查看效果</span>"
        },
        {
        "type": "service",
        "api": "/api/mock2/form/initData?tpl=${tpl}",
        "body": [
            {
            "label": "名称",
            "type": "input-text",
            "name": "name"
            },
            {
            "label": "作者",
            "type": "input-text",
            "name": "author"
            },
            {
            "label": "请求时间",
            "type": "input-datetime",
            "name": "date"
            }
        ]
        }
    ],
    "actions": []
}

上例可看到,变更数据模板的值,会触发 service 重新请求,并更新当前数据域中的数据

更多相关见接口联动

定时轮询刷新

设置 interval 可以定时刷新 apischemaApi 接口,单位是毫秒,如"interval": 2000 则设置轮询间隔为 2s ,注意最小间隔时间是 1 秒。支持通过stopAutoRefreshWhen表达式定义轮询停止条件。

{
    "type": "service",
    "api": "/api/mock2/number/random?waitSeconds=1",
    "interval": 2000,
    "stopAutoRefreshWhen": "this.random === 6",
    "body": {
        "type": "panel",
        "title": "随机数字",
        "body": "现在是:${random}"
    }
}

静默轮询

设置silentPolling: true可以关闭等待接口加载时的 loading 动画,该配置仅在配置interval时生效。

{
    "type": "service",
    "api": "/api/mock2/number/random?waitSeconds=1",
    "interval": 2000,
    "silentPolling": true,
    "stopAutoRefreshWhen": "this.random === 6",
    "body": {
        "type": "panel",
        "title": "随机数字",
        "body": "现在是:${random}"
    }
}

通过 WebSocket 实时获取数据

Service 支持通过 WebSocket 获取数据,只需要设置 ws由于无示例服务所以无法在线演示

{
  "type": "service",
  "ws": "ws://localhost:8777",
  "body": {
    "type": "panel",
    "title": "$title",
    "body": "随机数:${random}"
  }
}

1.4.0 及以上版本

或者是对象的方式支持配置初始 data,这个 data 会在建立连接时发送初始数据

{
  "type": "service",
  "ws": {
    "url": "ws://localhost:8777?name=${name}",
    "data": {
      "name": "${name}"
    }
  },
  "body": {
    "label": "名称",
    "type": "static",
    "name": "name"
  }
}

可以只设置 ws通过 ws 来获取所有数据,也可以同时设置 api 和 ws让 api 用于获取全部数据,而 ws 用于获取实时更新的数据。

后端实现示例,基于 ws

const WebSocket = require('ws');

const ws = new WebSocket.Server({port: 8777});

ws.on('connection', function connection(ws) {
  setInterval(() => {
    const random = Math.floor(Math.random() * Math.floor(100));
    // 返回给 amis 的数据
    const data = {
      random
    };
    // 发送前需要转成字符串
    ws.send(JSON.stringify(data));
  }, 500);
});

WebSocket 客户端的默认实现是使用标准 WebSocket如果后端使用定制的 WebSocket比如 socket.io可以通过覆盖 env.wsFetcher 来自己实现数据获取方法,默认实现是:

1.4.0 及以上版本修改了 ws 类型,将之前的字符串改成了对象的方式,会有两个参数 url 和 body

下面是目前 amis 中 WebSocket 支持的默认实现:

wsFetcher(ws, onMessage, onError) {
    if (ws) {
      const socket = new WebSocket(ws.url);
      socket.onopen = event => {
        if (ws.body) {
          socket.send(JSON.stringify(ws.body));
        }
      };
      socket.onmessage = event => {
        if (event.data) {
          let data;
          try {
            data = JSON.parse(event.data);
          } catch (error) {}
          if (typeof data !== 'object') {
            let key = ws.responseKey || 'data';
            data = {
              [key]: event.data
            };
          }
          onMessage(data);
        }
      };
      socket.onerror = onError;
      return {
        close: socket.close
      };
    } else {
      return {
        close: () => {}
      };
    }
  }

通过 onMessage 来通知 amis 数据修改了,并返回 close 函数来关闭连接。

1.8.0 及以上版本

如果 WebSocket 返回的结果不是 JSON 而只是某个字符串,需要配置 responseKey 属性来将这个结果放在这个 key 上,比如下面的例子

{
  "type": "service",
  "ws": {
    "url": "ws://localhost:8777?name=${name}",
    "data": {
      "name": "${name}"
    },
    "responseKey": "name"
  },
  "body": {
    "label": "名称",
    "type": "static",
    "name": "name"
  }
}

对应的后端就只需要返回字符串

const WebSocket = require('ws');

const ws = new WebSocket.Server({port: 8777});

ws.on('connection', function connection(ws) {
  setInterval(() => {
    const random = Math.floor(Math.random() * Math.floor(100));
    ws.send(random);
  }, 500);
});

调用外部函数获取数据

1.4.0 及以上版本

对于更复杂的数据获取情况,可以使用 dataProvider 属性来实现外部函数获取数据,它支持字符串和函数两种形式

{
    "type": "service",
    "dataProvider": "setData({ now: new Date().toString() })",
    "body": {
        "type": "tpl",
        "tpl": "现在是:${now}"
    }
}

函数将会传递两个参数:datasetData,其中 data 可以拿到上下文数据,而 setData 函数可以用来更新数据,比如下面的例子

{
    "type": "service",
    "dataProvider": "const timer = setInterval(() => { setData({date: new Date().toString()}) }, 1000); return () => { clearInterval(timer) }",
    "body": {
        "type": "tpl",
        "tpl": "现在是:${date}"
    }
}

上面这个例子还返回了一个函数,这个函数会在组件销毁的时候执行,可以用来清理资源。

下面是使用函数类型的示例,注意这个示例不能放在 JSON 中,只能在 jssdk 或 react 项目里使用。

{
    "type": "service",
    "dataProvider": (data, setData) => {
      const timer = setInterval(() => {
        setData({date: new Date().toString()})
      }, 1000);
      return () => { clearInterval(timer) }
    },
    "body": {
        "type": "tpl",
        "tpl": "现在是:${now}"
    }
}

1.8.0 及以上版本

新增了一个 env 属性,可以调用系统环境中的方法,比如 env.fetcher、tracker 等,比如下面的例子会调用 env.notify 来弹出提示

{
    "type": "service",
    "dataProvider": "env.notify('info', 'msg')"
}

函数触发事件

2.3.0 及以上版本

{
    "type": "service",
    "api": "/api/mock2/page/initData",
    "dataProvider": {
        "inited": "setData({ addedNumber: data.number + 1  })",
        "onApiFetched": "setData({ year: new Date(data.date).getFullYear(),  })"
    },
    "data": {
        "number": 8887
    },
    "body": {
        "type": "panel",
        "title": "$title",
        "body": [
            {
                "type": "tpl",
                "wrapperComponent": "p",
                "tpl": "静态数字为:<strong>${addedNumber}</strong>"
            },
            {
                "type": "tpl",
                "wrapperComponent": "p",
                "tpl": "接口返回值的日期为:<strong>${date}</strong>"
            },
            {
                "type": "tpl",
                "wrapperComponent": "p",
                "tpl": "接口返回值的年份为:<strong>${year}</strong>"
            },
        ]
    }
}

隐藏错误信息

2.8.1 及以上版本

默认会将接口返回的错误信息展示在 Service 的顶部区域,可以通过设置"showErrorMsg": false隐藏错误提示。

{
  "type": "service",
  "api": "/api/mock2/page/initDataError",
  "body": [
    {
      "type": "tpl",
      "tpl": "展示错误信息"
    },
    {
      "type": "icon",
      "icon": "fa-solid fa-arrow-up"
    }
  ]
}

设置"showErrorMsg": false隐藏错误提示,仅保留 toast 提示

{
  "type": "service",
  "api": "/api/mock2/page/initDataError",
  "showErrorMsg": false,
  "body": [
    {
      "type": "tpl",
      "tpl": "不展示错误信息"
    }
  ]
}

属性表

属性名 类型 默认值 说明 版本
type string "service" 指定为 service 渲染器
className string 外层 Dom 的类名
body SchemaNode 内容容器
api API 初始化数据域接口地址
ws string WebScocket 地址
dataProvider string | Record<"inited" | "onApiFetched" | "onSchemaApiFetched" | "onWsFetched", string> 数据获取函数
  • 1.4.0
  • 1.8.0支持env参数
  • 2.3.0 支持基于事件触发
initFetch boolean 是否默认拉取
schemaApi API 用来获取远程 Schema 接口地址
initFetchSchema boolean 是否默认拉取 Schema
messages Object 消息提示覆写,默认消息读取的是接口返回的 toast 提示文字,但是在此可以覆写它。
messages.fetchSuccess string 接口请求成功时的 toast 提示文字
messages.fetchFailed string "初始化失败" 接口请求失败时 toast 提示文字
interval number 轮询时间间隔,单位 ms(最低 1000)
silentPolling boolean false 配置轮询时是否显示加载动画
stopAutoRefreshWhen 表达式 配置停止轮询的条件
showErrorMsg boolean true 是否以 Alert 的形式显示 api 接口响应的错误信息,默认展示 2.8.1

事件表

当前组件会对外派发以下事件,可以通过onEvent来监听这些事件,并通过actions来配置执行的动作,在actions中可以通过${事件参数名}${event.data.[事件参数名]}来获取事件产生的数据,详细请查看事件动作

[name]为当前数据域中的字段名,例如:当前数据域为 {username: 'amis'},则可以通过${username}获取对应的值。

事件名称 事件参数 说明
init - 组件实例被创建并插入 DOM 中时触发。2.4.1 及以上版本
fetchInited responseData: any 请求的响应数据
responseStatus: number 响应状态0 表示成功
responseMsg: string响应消息, error表示接口是否成功
[name]: any 当前数据域中指定字段的值
api 接口请求完成时触发
fetchSchemaInited responseData: any 请求的响应数据
responseStatus: number 响应状态0 表示成功
responseMsg: string响应消息, error表示接口是否成功
[name]: any 当前数据域中指定字段的值
schemaApi 接口请求完成时触发

init

开始初始化。

{
  "type": "service",
  "api": "/api/mock2/page/initData",
  "body": {
    "type": "panel",
    "title": "$title",
    "body": "现在是:${date}"
  },
  "onEvent": {
    "init": {
      "actions": [
        {
          "actionType": "toast",
          "args": {
            "msg": "init"
          }
        }
      ]
    }
  }
}

fetchInited

api 接口请求完成。

{
  "type": "service",
  "api": "/api/mock2/page/initData",
  "body": [
    {
      "type": "panel",
      "title": "$title",
      "body": "现在是:${date}"
    }
  ],
  "onEvent": {
    "fetchInited": {
      "actions": [
        {
          "actionType": "toast",
          "args": {
            "msg": "title:${event.data.responseData.title}date:${date}status:${event.data.responseStatus}"
          }
        }
      ]
    }
  }
}

fetchSchemaInited

schemaApi 接口请求完成。

[
  {
    "type": "service",
    "schemaApi": "/api/mock2/service/schema?type=tabs",
    "onEvent": {
      "fetchSchemaInited": {
        "actions": [
          {
            "actionType": "toast",
            "args": {
              "msg": "type:${event.data.responseData.type}status:${event.data.responseStatus}"
            }
          }
        ]
      }
    }
  }
]

动作表

当前组件对外暴露以下特性动作,其他组件可以通过指定actionType: 动作名称componentId: 该组件id来触发这些动作,详细请查看事件动作

动作名称 动作配置 说明
reload - 重新加载,调用 api,刷新数据域数据
rebuild - 重新构建,调用 schemaApi,重新构建容器内 Schema
setValue - 更新数据域数据

reload

只做刷新

重新发送api请求,刷新 Page 时,只配置componentId目标组件 ID 即可。

[
  {
    "type": "button",
    "label": "刷新请求",
    "onEvent": {
      "click": {
        "actions": [
          {
            "componentId": "service-reload",
            "actionType": "reload"
          }
        ]
      }
    }
  },
  {
    "type": "service",
    "id": "service-reload",
    "name": "service-reload",
    "api": "/api/mock2/number/random",
    "body": "现在是:${random}"
  }
]

发送数据并刷新

刷新 Service 组件时,如果配置了data,将发送data给目标组件,并将该数据合并到目标组件的数据域中(如果配置"dataMergeMode": "override"将覆盖目标组件的数据),然后重新请求数据。

[
  {
    "type": "button",
    "label": "刷新请求",
    "onEvent": {
      "click": {
        "actions": [
          {
            "componentId": "service-reload",
            "actionType": "reload",
            "data": {
              "date": "${NOW()}"
            }
          }
        ]
      }
    }
  },
  {
    "type": "service",
    "id": "service-reload",
    "name": "service-reload",
    "api": "/api/mock2/number/random",
    "body": "现在是:${random}, 当前时间:${date}"
  }
]

rebuild

重新构建,基于 args 传参和 schemaApi 绑定变量,让 service 获取不同的 schema。

[
  {
    "type": "alert",
    "body": "请选择一种构建方式生成组件",
    "level": "info",
    "showIcon": true,
    "className": "mb-3",
    "visibleOn": "this.schemaType == null"
  },
  {
    "type": "button-group",
    "tiled": true,
    "className": "mb-3",
    "buttons": [
      {
        "type": "action",
        "label": "构建form",
        "icon": "fa fa-hammer",
        "onEvent": {
          "click": {
            "actions": [
              {
                "actionType": "rebuild",
                "componentId": "service-rebuild",
                "args": {
                  "schemaType": "form"
                }
              }
            ]
          }
        }
      },
      {
        "type": "action",
        "label": "构建tabs",
        "icon": "fa fa-hammer",
        "onEvent": {
          "click": {
            "actions": [
              {
                "actionType": "rebuild",
                "componentId": "service-rebuild",
                "args": {
                  "schemaType": "tabs"
                }
              }
            ]
          }
        }
      },
      {
        "type": "action",
        "label": "构建crud",
        "icon": "fa fa-hammer",
        "onEvent": {
          "click": {
            "actions": [
              {
                "actionType": "rebuild",
                "componentId": "service-rebuild",
                "args": {
                  "schemaType": "crud"
                }
              }
            ]
          }
        }
      }
    ]
  },
  {
    "type": "service",
    "id": "service-rebuild",
    "name": "service-rebuild",
    "schemaApi": {
      "url": "/api/mock2/service/schema?type=${schemaType}",
      "method": "post",
      "sendOn": "this.schemaType != null"
    }
  }
]

setValue

通过setValue更新指定 Service 的数据。

合并数据

默认setValue会将新数据与目标组件数据进行合并。

[
    {
      "type": "button",
      "label": "更新数据",
      "onEvent": {
        "click": {
          "actions": [
            {
              "actionType": "setValue",
              "componentId": "service-setvalue",
              "args": {
                "value": {
                  "name": "aisuda",
                  "email": "aisuda@baidu.com"
                }
              }
            }
          ]
        }
      }
    },
    {
      "type": "service",
      "id": "service-setvalue",
      "name": "service-setvalue",
      "data": {
        "name": "amis",
        "email": "amis@baidu.com"
      },
      "body": [
        {
          "type": "tpl",
          "tpl": "名字:${name|default:'-'},邮箱:${email|default:'-'}"
        }
      ]
    }
]

覆盖数据

可以通过"dataMergeMode": "override"来覆盖目标组件数据。

[
    {
      "type": "button",
      "label": "更新数据",
      "onEvent": {
        "click": {
          "actions": [
            {
              "actionType": "setValue",
              "componentId": "service-setvalue",
              "args": {
                "value": {
                  "name": "aisuda"
                }
              },
              "dataMergeMode": "override"
            }
          ]
        }
      }
    },
    {
      "type": "service",
      "id": "service-setvalue",
      "name": "service-setvalue",
      "data": {
        "name": "amis",
        "email": "amis@baidu.com"
      },
      "body": [
        {
          "type": "tpl",
          "tpl": "名字:${name|default:'-'},邮箱:${email|default:'-'}"
        }
      ]
    }
]