let amis = amisRequire('amis/embed');
let amisLib = amisRequire('amis');
常用的有amisLib.clearStoresCache amisLib.evalExpression amisLib.updateEnv amisLib.wrapFetcher(封装fetcher接口调用) , amisLib.ServiceStore(mobx model定义)等
amisLib.setVaraible(data, key, value); //支持传入path 设置数据 key 可以是name or node.name
amisLib.getVariable(self.data, name);
这些utils等工具函数可以直接拿来用。
amisEnv:
const amisEnv =
{
// api接口调用fetcher实现
fetcher: ({
url, // 接口地址
method, // 请求方法 get、post、put、delete
data, // 请求数据
responseType,
config, // 其他配置
headers // 请求头
}) => {
config.withCredentials = true;
responseType && (config.responseType = responseType);
if (config.cancelExecutor) {
config.cancelToken = new (axios).CancelToken(
config.cancelExecutor
);
}
config.headers = headers || {};
config.headers["Authorization"]= `Bearer ${token}`;
if (method !== 'post' && method !== 'put' && method !== 'patch') {
if (data) {
config.params = data;
}
return (axios)[method](url, config);
} else if (data && data instanceof FormData) {
// config.headers['Content-Type'] = 'multipart/form-data';
} else if (
data &&
typeof data !== 'string' &&
!(data instanceof Blob) &&
!(data instanceof ArrayBuffer)
) {
data = JSON.stringify(data);
config.headers['Content-Type'] = 'application/json';
}
return (axios)[method](url, data, config);
},
isCancel: (value) => { (axios).isCancel(value) },
copy: content => {
copy(content);
toast.success('内容已复制到粘贴板');
},
// 用来实现通知,不传则使用amis内置
notify: (type, msg) => {
if (msg != 'Response is empty!') {
let mtype = {
success: '成功',
error: '错误',
info: '信息',
warning: '警告',
warn: '警惕'
}
Notice[type](
{
title: mtype[type],
desc: msg.toString()
}
);
}
},
// 用来实现提示,不传则使用amis内置
alert: content => {
Message.info({
content: content,
duration: 3,
closable: true
});
},
// 用来实现确认框,不传则使用amis内置。
// confirm: content => {}
// 主题,默认是 default,还可以设置成 cxd 或 dark,但记得引用它们的 css,比如 sdk 目录下的 cxd.css
theme: "ang"
}
export default amisEnv;
使用amis的fetch进行接口调用(好处是可以复用amis的api配置,和data映射等逻辑):
1.使用store的fetchInit(底层为getEnv(self).fetcher()),需env(使用store是一个组件一个store,同一时刻同一组件/store只允许调用一次接口)。
amisLib.updateEnv(amisEnv);// 注意: updateEnv只是更新env参数(fetcher配置等)。不会生成rootStore,也不会有组件store(要使用store必须提前amis.embed()渲染,创建出rootStore或者自行用mobx创建RendererStore)
在前端环境中可以无缝使用,如下所示用于大屏中vue组件接口调用:
1-1.由于amisLib中并未暴露出来RendererStore和defaultOptions,所以从amis源码中提取了
amis-core/src/utils 到 src/amis/utils中
amis-core/src/store 到 src/amis/store中
创建src/amis/factory.js文件:
import { promisify, wrapFetcher, normalizeLink, qsparse, parseQuery} from '../amis/utils/index';
//此处只放相对独立的defaultOptions,并export出去。
export function loadRenderer(schema, path) {
return (
<div className="RuntimeError">
<p>Error: 找不到对应的渲染器</p>
<p>Path: {path}</p>
<pre>
<code>{JSON.stringify(schema, null, 2)}</code>
</pre>
</div>
);
}
export const defaultOptions = {
session: 'global',
affixOffsetTop: 0,
affixOffsetBottom: 0,
richTextToken: '',
useMobileUI: true, // 是否启用移动端原生 UI
enableAMISDebug:
(window.enableAMISDebug === null || window.enableAMISDebug === undefined) ?
(location.search.indexOf('amisDebug=1') !== -1 ? true :
false) : window.enableAMISDebug,
loadRenderer,
fetcher() {
return Promise.reject('fetcher is required');
},
// 使用 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: () => {}
};
}
},
isCancel() {
console.error(
'Please implement isCancel. see https://aisuda.bce.baidu.com/amis/zh-CN/start/getting-started#%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97'
);
return false;
},
updateLocation() {
console.error(
'Please implement updateLocation. see https://aisuda.bce.baidu.com/amis/zh-CN/start/getting-started#%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97'
);
},
jumpTo: (to, action) => {
if (to === 'goBack') {
return window.history.back();
}
to = normalizeLink(to);
if (action && action.actionType === 'url') {
action.blank === false ? (window.location.href = to) : window.open(to);
return;
}
if (/^https?:\/\//.test(to)) {
window.location.replace(to);
} else {
location.href = to;
}
},
isCurrentUrl: (to) => {
if (!to) {
return false;
}
const link = normalizeLink(to);
const location = window.location;
let pathname = link;
let search = '';
const idx = link.indexOf('?');
if (~idx) {
pathname = link.substring(0, idx);
search = link.substring(idx);
}
if (search) {
if (pathname !== location.pathname || !location.search) {
return false;
}
const query = qsparse(search.substring(1));
const currentQuery = parseQuery(location);
return Object.keys(query).every(key => query[key] === currentQuery[key]);
} else if (pathname === location.pathname) {
return true;
}
return false;
},
copy(contents) {
console.error('copy contents', contents);
},
// 用于跟踪用户在界面中的各种操作
tracker(eventTrack, props) {},
//不传递,让SchemaRenderer使用默认的resolveRenderer方法即可 (const rendererResolver = props.env.rendererResolver || resolveRenderer;)
// rendererResolver: resolveRenderer,
replaceTextIgnoreKeys: [
'type',
'name',
'mode',
'target',
'reload',
'persistData'
],
/**
* 过滤 html 标签,可用来添加 xss 保护逻辑
*/
filterHtml: (input) => input
};
npm install amis-formula lodash moment //安装依赖的一些第三方库
npm install typescript -g
根目录下创建tsconfig.json:
{
"compilerOptions": {
"target": "es6",
"forceConsistentCasingInFileNames": true,
"allowSyntheticDefaultImports": true,
"module": "es6",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"typeRoots": [
"./node_modules/@types"
],
"types": [
"node",
"lodash"
],
"lib": [
"es6",
"dom",
"es2015.collection"
]
}
}
tsc // 编译src/amis/utils和store为esm格式(es6)。
1-2.在渲染入口,创建RendererStore并传入env环境配置对象。
import { RendererStore } from '../../amis/store';
import { promisify, wrapFetcher } from '../../amis/utils/index';
import { defaultOptions } from "../../amis/factory";
mounted(){
this.initAmisEnv(amisEnv);
}
methods:{}
initAmisEnv(options) {
options = {
...defaultOptions,
...options,
fetcher: options.fetcher
? wrapFetcher(options.fetcher, options.tracker)
: defaultOptions.fetcher,
confirm: promisify(
options.confirm || window.confirm
),
// locale,
// translate
}
const store = RendererStore.create({}, options); //创建RendererStore,并传入传入初始状态值({})和env环境配置对象(options)
window.amisStore = store;
},
}
1-3.mixins/compBase中(每个组件创建一个组件store,并在created阶段进行api调用):
const amisLib = amisRequire('amis');
const { isEffectiveApi, evalExpression, createObject, ServiceStore, isEmpty, guid, resolveMapping } = amisLib;
created() {
this.initStore(); // 初始化store 组件单独的
this.initDataSource();
},
//销毁
beforeDestroy() {
this.eventBus.$off('comp.method');//避免重复挂载
clearTimeout(this.timer);
let rootStore = window.amisStore;
// console.log(rootStore.stores,"stores");
rootStore.removeStore(this.store);
},
methods:{
initStore() {
let rootStore = window.amisStore;
const store = rootStore.addStore({
id: guid(),
path: "path",
storeType: "ServiceStore",
parentId: ''
});
this.store = store;
},
initDataSource() {
if (this.node && this.node.dataSource && this.node.dataSource.type && this.node.props.indexOf('visible') >= 0) { // initFetch初始调用判断 visible才调用
let initFetch = true;
let dsType = this.node.dataSource.type;
if (dsType === 'commonApi' || dsType === 'influxdbApi') {
let dsApi = this.node.dataSource[dsType];
dsApi && (initFetch = dsApi.initFetch);
}
if (initFetch) this.resolveDataSource();
}
},
resolveDataSource() { // 4选2(deviceTree and api/influxdbApi)一起处理 or 4选1处理
if (!this.node || !this.node.dataSource) {
return
}
let apiCall = () => {//api 调用方法
if (!this.api) return;
typeof this.api !== 'string' && this.convertMapping(['data', 'responseData']);
let variableMap = { ...this.globalVariableMap, ...this.variableMap };
this.data = { ...this.data, componentTitle: this.node.title, m: { options: this.m.options }, variableMap: (variableMap && JSON.stringify(variableMap) !== "{}") ? variableMap : undefined, params: (this.params && JSON.stringify(this.params) !== "{}") ? this.params : undefined };// get请求 url &:$$ data所有参数 url?param &:$$ data+param所有参数 url/url?param type:${type} 只过滤data,param不过滤 url/url?param false/undefined data不传递,param传递
const isEffective = isEffectiveApi(this.api, this.data);
debugDevApi('api是否有效:', isEffective , 'api:', this.api, 'data:', this.data, `${this.node.name}(${this.node.title})`);
if (isEffective) { //, initFetch, initFetchOn sendOn数据域
this.store.fetchInitData(this.api, this.data) // 数据映射 data 数据域
.then(this.afterDataFetch);
}
}
let dsType = this.node.dataSource.type;
if (dsType === 'commonApi' || dsType === 'influxdbApi') {
let dsApi = this.node.dataSource[dsType];
dsApi && (this.api = dsApi.api);
apiCall();
} else if (dsType === 'constant') {// constant与接口返回值.data结构一致 {status:0,data:{rows: []}}.data, {status:0,data:{}}.data
let constant = this.node.dataSource.constant ? this.node.dataSource.constant : this.node.constant; //兼容旧版逻辑
try {
this.resultData = JSON.parse(constant); //json串
} catch (e) {
this.resultData = constant; //{} []
}
return;
}
},
afterDataFetch(result) { //
// const data = (result && result.hasOwnProperty('ok') ) ? result.data : result;
debugDevApi(`api接口调用返回的结果:`, result, `${this.node.name}(${this.node.title})`);
let data = {};
let dsType = this.node.dataSource.type;
if (dsType === 'influxdbApi' && this.api.dataCompute) {
data = new Function("rows", "yieldDic", this.api.dataCompute).bind(this)(result.data.rows || [], result.data.yieldDic || {});
} else {
data = result.data;
}
// dispatchEvent?.('fetchInited', data); 分发fetchInited事件
if (!isEmpty(data)) {
//更新传入数据值。
this.resultData = data;
// onBulkChange(data); // 将接口返回值更新到组件数据域(store) data 不是rootStore
}
this.initInterval(data);
},
initInterval(value) { //若开启定时刷新,执行轮询逻辑
let dsType = this.node.dataSource.type;
const { interval, silentPolling, stopAutoRefreshWhen } = this.node.dataSource[dsType];
// silentPolling //interval动画 (目前未实现,后续需要再添加)
clearTimeout(this.timer);
interval &&
(!stopAutoRefreshWhen ||
/** 接口返回值需要同步到数据域中再判断,否则会多请求一轮 */
!evalExpression(stopAutoRefreshWhen, createObject(this.data, value))) &&
(this.timer = setTimeout(
this.reload,
Math.max(interval, 1000)
));
return value;
},
}
2.自行实现 fetchInit方法处理(封装fetch进行接口调用),无需env。如下所示:
import axios from "@/libs/api.request"; //自定义的fetcher调用接口(axios调用) { fetcher: ()=>{ ……; return (axios)[method](url, data, config) } }
let fetcher = amisLib.wrapFetcher(axios.fetcher, axios.tracker); //传入用户的axios配置封装一个接口调用promise。tracker存在 则调用 tracker?.({eventType: 'api', eventData: omit(api, ['config', 'data', 'body'])},api.data);
let pl = await fetcher(api, data); //调用接口获取返回值
//...... 对pl的后续处理
可在nodejs中使用(需借助jsdom 并移除部分不兼容代码),如下所示在xstate状态机中作为接口调用动作来使用:
2-1.入口文件中进行全局变量声明:
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
global.window = dom.window;
global.document = window.document;
global.FormData = require('form-data');
const amisUtils = require('../common/amisUtils');
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
const fetchInitData = require("../common/serviceApi");
amisUtils.fetchInitData = fetchInitData;
global.amisUtils = amisUtils;
global.AsyncFunction = AsyncFunction;
2-2.从amis源码中提取了
amis-core/src/utils 到 common/amisUtils中。并移除了部分不兼容的浏览器端代码,如dom.tsx等(或者用js-dom兼容下也行)。
创建common/serviceApi文件:
const amisUtils = require('../common/amisUtils');
const amisEnv = require('../common/amisEnv')
let fetcher = amisUtils.wrapFetcher(amisEnv.fetcher, amisEnv.tracker);
let fetchCancel = null;
const fetchInitData = async(//amis api调用动作 不支持轮训。服务器端 Buffer代替Blob File ArrayBuffer
api,
data,
options
) => {
try {
console.log(fetchCancel, "fetchCancel");
if (fetchCancel) {
fetchCancel();
fetchCancel = null;
}
const json = await fetcher(api, data, {//
...options,
cancelExecutor: (executor) => (fetchCancel = executor)
});
fetchCancel = null;
if (!json.ok) {
// updateMessage(json.msg ?? (options && options.errorMessage), true);
console.error( //getEnv(self).notify
'error',
json.msg,
json.msgTimeout !== undefined
? {
closeButton: true,
timeout: json.msgTimeout
}
: undefined
);
} else {
// let replace = !!api.replaceData; //接口返回值设置到数据域
// let data = {
// ...(replace ? {} : data),
// ...normalizeApiResponseData(json.data)
// };
// reInitData(data, replace);
// self.hasRemoteData = true;
if (options && options.onSuccess) {
const ret = options.onSuccess(json);
if (ret && ret.then) {
await ret;
}
}
// 配置了获取成功提示后提示,默认是空不会提示。
options &&
options.successMessage &&
console.log('success', self.msg);
}
return json;
} catch (e) {
console.error(e)
let message = e.message || e;
console.error('error', message);
return;
}
};
module.exports = fetchInitData; const amisUtils = require('../common/amisUtils');
const amisEnv = require('../common/amisEnv')
let fetcher = amisUtils.wrapFetcher(amisEnv.fetcher, amisEnv.tracker);
let fetchCancel = null;
const fetchInitData = async(//amis api调用动作 不支持轮训。服务器端 Buffer代替Blob File ArrayBuffer
api,
data,
options
) => {
try {
console.log(fetchCancel, "fetchCancel");
if (fetchCancel) {
fetchCancel();
fetchCancel = null;
}
const json = await fetcher(api, data, {//
...options,
cancelExecutor: (executor) => (fetchCancel = executor)
});
fetchCancel = null;
if (!json.ok) {
// updateMessage(json.msg ?? (options && options.errorMessage), true);
console.error( //getEnv(self).notify
'error',
json.msg,
json.msgTimeout !== undefined
? {
closeButton: true,
timeout: json.msgTimeout
}
: undefined
);
} else {
// let replace = !!api.replaceData; //接口返回值设置到数据域
// let data = {
// ...(replace ? {} : data),
// ...normalizeApiResponseData(json.data)
// };
// reInitData(data, replace);
// self.hasRemoteData = true;
if (options && options.onSuccess) {
const ret = options.onSuccess(json);
if (ret && ret.then) {
await ret;
}
}
// 配置了获取成功提示后提示,默认是空不会提示。
options &&
options.successMessage &&
console.log('success', self.msg);
}
return json;
} catch (e) {
console.error(e)
let message = e.message || e;
console.error('error', message);
return;
}
};
module.exports = fetchInitData;
npm install amis-formula lodash moment //安装依赖的一些第三方库
npm install typescript -g
根目录下创建tsconfig.json:
{
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,// 转换为cjs语法时,fix (0 , assign_1.default) is not a function 问题。
//会加入一个__importDefault来解决此问题。如下:var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; };
"module": "commonJS",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"typeRoots": [
"./node_modules/@types"
],
"types": [
"node",
"lodash"
],
"lib": [
"es5",
"dom",
"es2015.collection"
]
}
}
tsc // 编译common/amisUtils为cjs格式。
2-3.接口调用动作的相关逻辑实现:
let dataSource = node.dataSource;
return async (input, context, event) => {
let api;
let resultData = {};
if (!dataSource) {
return
}
function convertMapping(fieldArray) {
_.forEach(fieldArray, field => {
let param = {};
if (!api[field] || !Array.isArray(api[field])) return;
_.forEach(api[field], item => {
item.key && (param[item.key] = item.value);
});
api[field] = param;
});
}
let apiCall = async () => {//api 调用方法
if (!api) return;
typeof api !== 'string' && convertMapping(['data', 'responseData']);
let dataScoped = { ...context, ...node.options }; //context + influxdb参数配置
if (amisUtils.isEffectiveApi(api, dataScoped)) { //, initFetch, initFetchOn sendOn数据域
// console.log(api, dataScoped)
let pl = await amisUtils.fetchInitData(api, dataScoped); // 数据映射 data 数据域
const data = (pl ? pl.hasOwnProperty('ok') : undefined) ? pl.data : pl;
if (!amisUtils.isEmpty(data)) {
resultData = data;
}
}
}
let dsType = dataSource.type;
if (dsType === 'commonApi' || dsType === 'influxdbApi') {
let dsApi = dataSource[dsType];
dsApi && (api = dsApi.api);
await apiCall();
} else if (dsType === 'constant') { // constant与接口返回值.data结构一致 {status:0,data:{rows: []}}.data, {status:0,data:{}}.data
let constant = dataSource.constant ? dataSource.constant : node.constant; //兼容旧版逻辑
try {
resultData = JSON.parse(constant); //json串
} catch (e) {
resultData = constant; //{} []
}
}
return resultData;
}
额外:service和crud组件中调用接口并更新数据域相关功能amis源码如下所示:
export function normalizeApiResponseData(data: any) {
if (typeof data === 'undefined') {
data = {};
} else if (!isPlainObject(data)) {
data = {
[Array.isArray(data) ? 'items' : 'result']: data
};
}
return data;
}
调用接口获取返回值{status:0, data:{ }},取出payload中的data(json.data)然后设置到数据域中。
1.service的fetchInitData方法的核心代码如下:
Amis-core/src/store/service.ts :
const fetchInitData: ( api: Api, data?: object, options?: fetchOptions) => Promise<any> = flow(function* getInitData(
api: Api,
data: object,
options?: fetchOptions
) {
//....省略
const json: Payload = yield getEnv(self).fetcher(api, data, {//真正调用接口,env.fetcher是wrapFetcher(options.fetcher, options.tracker)封装后的。
...options,
cancelExecutor: (executor: Function) => (fetchCancel = executor)
});
if (!json.ok) { ... } else {
let replace = !!(api as ApiObject).replaceData;
let data = {
...(replace ? {} : self.data),
...normalizeApiResponseData(json.data)
};
reInitData(data, replace); //接口返回值设置到数据域
self.hasRemoteData = true;
if (options && options.onSuccess) {
const ret = options.onSuccess(json); //分发submitSucc事件 dispatchEvent
if (ret && ret.then) {
await ret;
}
}
// 配置了获取成功提示后提示,默认是空不会提示。
options &&
options.successMessage &&
console.log('success', self.msg);
}
return json;
});
crud接口调用:返回值{status:0, data:{rows:[], count:10}} 会将result.rows | result.items 统一赋值给items作为数据域数据
crud的fetchInitData方法核心代码如下:
Amis-core/src/store/crud.ts :
//处理和service.ts类似:
//....省略
const json: Payload = yield getEnv(self).fetcher(api, ctx, {//真正调用接口,env.fetcher是wrapFetcher(options.fetcher, options.tracker)封装后的。
...options,
cancelExecutor: (executor: Function) => (fetchCancel = executor)
});
let result = normalizeApiResponseData(json.data);
const { total, count, page, hasNext, items: oItems, rows: oRows, columns, ...rest } = result;
let items: Array<any>;
items = result.items || result.rows;
rowsData = items;
const data = {
...((api as ApiObject).replaceData ? {} : self.pristine),
items: rowsData,
count: count,
total: total,
...rest
};
self.items.replace(rowsData);
self.reInitData( //接口返回值设置到数据域
data,
!!(api as ApiObject).replaceData,
(api as ApiObject).concatDataFields
);
//....省略
return json;