amis作为工具库来使用 - 封装api调用

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;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李庆政370

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值