欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 美景 > amis作为工具库来使用 - 封装api调用

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

2024/10/23 23:27:59 来源:https://blog.csdn.net/qq_42152032/article/details/140064775  浏览:    关键词: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、deletedata, // 请求数据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.csstheme: "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, // 是否启用移动端原生 UIenableAMISDebug:(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

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:{}}.datalet 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等。

创建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 ArrayBufferapi,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 ArrayBufferapi,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 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:{}}.datalet 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事件 dispatchEventif (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;

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com