自定义前端拦截器

某种情况下,我们需要通过自定义请求拦截器来做自己的逻辑处理,平台内置了一些拦截器

登录拦截器LoginRedirectInterceptor

重定向到登录拦截器LoginRedirectInterceptor

import { UrlHelper, IResponseErrorResult, LoginRedirectInterceptor } from '@kunlun/dependencies';

export class BizLoginRedirectInterceptor extends LoginRedirectInterceptor {
  /**
   * 重定向到登录页
   * @param response 错误响应结果
   * @return 是否重定向成功
   */
  public redirectToLogin(response: IResponseErrorResult): boolean {
    if (window.parent === window) {
      const redirect_url = location.pathname;
      // 可自定义跳转路径
      location.href = `${UrlHelper.appendBasePath('login')}?redirect_url=${redirect_url}`;
    } else {
      // iframe页面的跳转
      window.open(`${window.parent.location.origin}/#/login`, '_top');
    }
    return true;
  }
}

请求成功拦截器RequestSuccessInterceptor

请求失败拦截器 RequestErrorInterceptor

网络请求异常拦截器 NetworkErrorInterceptor

当我们需要重写某个拦截器的时候,只需要继承对应的拦截器,然后重写里面的方法即可

// 自定义登录拦截器
export class CustomLoginRedirectInterceptor extends LoginRedirectInterceptor{
    public error(response: IResponseErrorResult) {
        // 自己的逻辑处理

        return true // 必写
    }
}

// 自定义请求成功拦截器
export class CustomRequestSuccessInterceptor extends RequestSuccessInterceptor{
    public success(response: IResponseErrorResult) {
        // 自己的逻辑处理
        return true // 必写
    }
}

// 自定义请求失败拦截器
export class CustomRequestErrorInterceptor extends RequestErrorInterceptor{
    public error(response: IResponseErrorResult) {
        const { errors } = response;

    if (errors && errors.length) {
      const notPermissionCodes = [
        SystemErrorCode.NO_PERMISSION_ON_MODULE,
        SystemErrorCode.NO_PERMISSION_ON_VIEW,
        SystemErrorCode.NO_PERMISSION_ON_MODULE_ENTRY,
        SystemErrorCode.NO_PERMISSION_ON_HOMEPAGE
      ];

      /**
       * 用来处理重复的错误提示
       */
      const executedMessages: string[] = [];

      for (const errorItem of errors) {
        const errorCode = errorItem.extensions?.errorCode;
        if (!notPermissionCodes.includes(errorCode as any)) {
          const errorMessage = errorItem.extensions?.messages?.[0]?.message || errorItem.message;

          if (!executedMessages.includes(errorMessage)) {
            // 自己的逻辑处理
          }

          executedMessages.push(errorMessage);
        }
      }
    }

    return true;
  }
}

// 自定义网络请求异常拦截器
export class CustomNetworkErrorInterceptor extends NetworkErrorInterceptor{
      public error(response: IResponseErrorResult) {
            const { networkError } = response;
            if (networkError) {
            const { name, message } = networkError;
            if (name && message) {
                // 自己的逻辑处理
            }
            return false;
            }
            return true;
  }
}

当拦截器的代码写完后,需要在启动工程里面的main.ts导入它

// main.ts
import MyInterceptor from './MyInterceptor.ts'
import BizLoginRedirectInterceptor from './BizLoginRedirectInterceptor.ts'

// 使用拦截器

VueOioProvider({
    ...
    http: {
        url: '',
        interceptor: {
            loginRedirect: new BizLoginRedirectInterceptor(),
            requestError: new MyInterceptor() // 这里的key是requestError,是因为我自定义的是错误拦截器,大家根据自己自定的拦截器选择对应的key
        }
    }
})

Oinone社区 作者:oinone原创文章,如若转载,请注明出处:https://doc.oinone.top/frontend/80.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

(0)
oinone的头像oinone
上一篇 2023年6月20日 pm4:07
下一篇 2023年11月2日 pm1:58

相关推荐

  • 「前端」关闭源码依赖

    问题背景 在 5.x 版本的 oinone 前端架构中,我们开放了部分源码,但是导致以下性能问题: 1.构建耗时增加​​:Webpack 需要编译源码文件 2.​​加载性能下降​​:页面需加载全部编译产物 3.​​冷启动缓慢​​:开发服务器启动时间延长 以下方案通过关闭源码依赖。 操作步骤 1. 添加路径修正脚本 1: 在当前项目工程根目录中的scripts目录添加patch-package-entry.js脚本,内容如下: const fs = require('fs'); const path = require('path'); const targetPackages = [ '@kunlun+vue-admin-base', '@kunlun+vue-admin-layout', '@kunlun+vue-router', '@kunlun+vue-ui', '@kunlun+vue-ui-antd', '@kunlun+vue-ui-common', '@kunlun+vue-ui-el', '@kunlun+vue-widget' ]; // 递归查找目标包的 package.json function findPackageJson(rootDir, pkgName) { const entries = fs.readdirSync(rootDir); for (const entry of entries) { const entryPath = path.join(rootDir, entry); const stat = fs.statSync(entryPath); if (stat.isDirectory()) { if (entry.startsWith(pkgName)) { const [pkGroupName, name] = pkgName.split('+'); const pkgDir = path.join(entryPath, 'node_modules', pkGroupName, name); const pkgJsonPath = path.join(pkgDir, 'package.json'); if (fs.existsSync(pkgJsonPath)) { return pkgJsonPath; } } // 递归查找子目录 const found = findPackageJson(entryPath, pkgName); if (found) return found; } } return null; } // 从 node_modules/.pnpm 开始查找 const pnpmDir = path.join(__dirname, '../', 'node_modules', '.pnpm'); for (const pkgName of targetPackages) { const packageJsonPath = findPackageJson(pnpmDir, pkgName); if (packageJsonPath) { try { const packageJSON = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); if (packageJSON.main === 'index.ts') { const libName = packageJSON.name.replace('@', '').replace('/', '-'); packageJSON.main = `dist/${libName}.esm.js`; packageJSON.module = `dist/${libName}.esm.js`; const typings = 'dist/types/index.d.ts'; packageJSON.typings = typings; const [pwd] = packageJsonPath.split('package.json'); const typingsUrl = path.resolve(pwd, typings); const dir = fs.existsSync(typingsUrl); if (!dir)…

    2025年4月17日
    53100
  • 从一个方法开始:浅析页面渲染流程

    渲染前的准备 渲染前的准备,在 Vue 渲染框架下,会先安装所有所支持的默认组件,比如 Mask,Header 等,这些组件支持 XML 默认模版的 Vue 框架下的渲染,详情可见 main.ts 中,maskInstall 与 install,这两个函数将平台内部支持的组件进行了注册,随后将整个 Vue 挂载为运行时 App,随后进行初始化。 渲染的起步 OioProvider 方法是整个应用的入口,我们忽略掉一些配置方法,将注意力集中到 initializeServices 从名称中我们可以看出来内部保存的都是初始化服务,其中提供了渲染服务等,我们当前使用的是 Vue 框架,所以当前其渲染的 Root 节点为 Vue, 以此,我们视野可以暂时转移到 admin-base中的 Root.vue以及 RootWidget上, 其实现了整个 Vue 框架下的 Root 节点如何渲染,其中定义了多个 widget,比如登陆页,首页,忘记密码已经重置密码等页面, 在本文中我们着重关注渲染首页的能力,RootWidget将 DefaultMetadataMainViewWidget作为渲染 Props中的 page即首页提供给下层组件使用, 渐入佳境 DefaultMetadataMainViewWidget从名称中可以看到,其为元数据主视图,主要做两件事,1:提供 Mask 的渲染2:提供元数据上下文初始化 该组件主要通过观察路由变化触发上面两个动作,当路由发生变化,该组件 reloadPage将被调用,reloadPage方法通过组装路由参数构成一个唯一 key 向后段查询当前路由所对应的渲染信息, 随后将获取到的信息进行处理,初始化,即 元数据上下文初始化,在初始化后,会将获取到的数据注入到 MetadataViewWidget中, Mask 的渲染 关于 Mask 的渲染,在获取到数据后,将生成 maskTemplate,并将其赋值, DefaultMetadataMainView.vue文件将渲染该模板,并渲染到页面中 数据的变更 当上面两件任务完成后,将开始主视图的渲染,上文提到,DefaultMetadataMainViewWidget只负责 mask 的渲染和上下文的初始化,所以 DefaultMetadataMainViewWidget通过触发事件的方式来实现主视图的渲染, DefaultMetadataMainViewWidget将必要信息作为事件参数触发,MultiTabsContainerWidget接收到 reloadMainViewCallChaining事件后,开启主视图渲染, MultiTabsContainerWidget会刷新运行时上下文,即 refreshRuntimeContext,该方法将尝试查询并通过 createOrUpdateEnterTab方法创建 Tab 页,createOrUpdateEnterTab最终生成一个 MultiTabItem格式的对象,该对象描述了 Tab 的相关信息,随后调用 createTabContainerWidget创建 tab 的容器即新建了一个 MultiTabContainerWidget组件即单个 tab 的容器,随后调用 setActiveTabItem, 并获取其绑定的 Vue 组件,并将其组件放置在 KeepAlive内部,触发更新, 主视图的渲染 MultiTabContainerWidget继承自MetadataViewWidget,当 MetadataViewWidget数据发生变更, 其绑定的 Vue 组件将解析 viewTemplate, 获取到与该模板 dslNodeType想匹配的 Vue 组件,当前例子中为 View.vue,随后 View.vue开始渲染,View.vue文件只是一个纯粹的容器,当 View.vue被挂载时,其内部的 template属性包含了整个页面的描述信息,View.vue需要做的就是将这个 template翻译并渲染成 DOM 展现在浏览器上, 渲染整个页面 当 View.vue被挂载时,其内部的 template属性包含了整个页面的描述信息,View.vue主要做了两个事情,一:将 template 中的 widget转换为组件,二:根据当前的 template信息生成 slot信息, const currentSlots = computed<Slots | undefined>( () => DslRender.fetchVNodeSlots(props.dslDefinition) || (Object.keys(context.slots).length ? context.slots : undefined) ); const renderWidget = createCustomWidget(InternalWidget.View, { …context.attrs, type: props.type || viewType.value, template: props.dslDefinition, metadataHandle: props.metadataHandle || metadataHandle.value, rootHandle: props.rootHandle || rootHandle.value, parentHandle: props.parentHandle || parentHandle.value, slotName: props.slotName, inline: inlineProp } as ViewWidgetProps); 生成这两部分信息后,View.vue会将这两部分挂载到页面上,这两部分从代码中可以看出,主要靠 fetchVNodeSlots,createCustomWidget两个函数, export function createCustomWidget( widget: string, props: CustomWidgetProps ): RenderWidget | undefined public static fetchVNodeSlots(dsl: DslDefinition | undefined, supportedSlotNames?: string[]):…

    2025年3月19日
    54900
  • 母版-布局-DSL 渲染基础(v4)

    概述 不论是母版、布局还是DSL,我们统一使用XML进行定义,可以更好的提供结构化表述。 参考文档: XML百度百科 XML语法参考 下面文档中未介绍到的Mask母版和Layout布局,可以去数据库中base库的表base_layout_definition和base_mask_definition的template字段查看 母版 确定了主题、非主内容分发区域所使用组件和主内容分发区域联动方式的页面配置。 母版内容分为主内容分发区域与非主内容分发区域。非主内容分发区域一般包含顶部栏、底部栏和侧边栏。侧边栏可以放置菜单,菜单与主内容分发区域内容进行联动。 默认母板 <mask> <multi-tabs /> <header> <widget widget="app-switcher" /> <block> <widget widget="notification" /> <widget widget="divider" /> <widget widget="language" /> <widget widget="divider" /> <widget widget="user" /> </block> </header> <container> <sidebar> <widget widget="nav-menu" height="100%" /> </sidebar> <content> <breadcrumb /> <block width="100%"> <widget width="100%" widget="main-view" /> </block> </content> </container> </mask> 该模板中包含了如下几个组件: mask:母版根标签 multi-tabs:多选项卡 header:顶部栏 container:容器 sldebar:侧边栏 nav-menu:导航菜单 content:主内容 breadcrumb:面包屑 block:div块 main-view:主视图;用于渲染布局和DSL等相关内容; 母版将整个页面的大体框架进行了描述,接下来将主要介绍布局和DSL是如何在main-view中进行渲染的。关于自定义母版组件的相关内容 点击查看 布局 布局是将页面拆分成一个一个的小单元,按照从上到下、从左到右进行顺序排列 布局主要用于控制页面中元素的展示的相对位置,原则上不建议将元数据相关内容在布局中进行使用,可最大化布局的利用率。 默认表格视图(TABLE) <view type="TABLE"> <pack widget="group"> <view type="SEARCH"> <element widget="search" slot="search" /> </view> </pack> <pack widget="group" slot="tableGroup"> <element widget="actionBar" slot="actionBar"> <xslot name="actions" /> </element> <element widget="table" slot="table"> <element widget="expandColumn" slot="expandRow" /> <xslot name="fields" /> <element widget="rowActions" slot="rowActions" /> </element> </pack> </view> 该模板中包含了如下几个组件: view:视图;用于定义当前视图类型,不同的视图类型会有不同的数据交互,以及渲染不同的组件。 pack:容器类型相关组件。 element:元素组件;包含各种各样的组件,根据组件实现有不同的作用。 xslot:DSL插槽;用于将DSL中定义的模板分别插入到对应的槽中; 特别的,任何XML标签上的slot属性都具备DSL插槽的全部能力。当学习完DSL相关内容后,我们将会对DSL插槽有比较清晰的理解。 PS:在下面的内容中,将使用该布局进行描述。 DSL 准备工作 为了方便描述DSL和元数据之间的关系,我们需要先定义一个简单模型,这个模型里面包含字段和动作。这些通常是服务端定义的。(对服务端不感兴趣的同学可以跳过代码部分) DemoModel.java @Model.model(DemoModel.MODEL_MODEL) @Model(displayName = "演示模型", labelFields = {"name"}) public class DemoModel extends IdModel { private static final long serialVersionUID = -7211802945795866154L; public static final String MODEL_MODEL = "demo.DemoModel"; @Field(displayName = "名称") private String name; @Field(displayName = "是否启用") private Boolean isEnabled; } DemoModelAction.java @Model.model(DemoModel.MODEL_MODEL) @UxRouteButton( action = @UxAction(name = "redirectCreatePage", displayName = "创建", contextType = ActionContextTypeEnum.CONTEXT_FREE), value = @UxRoute(model =…

    2023年11月1日
    2.5K10
  • oio-select 选择器

    API Select props 参数 说明 类型 默认值 版本 allowClear 支持清除 boolean false autofocus 默认获取焦点 boolean false clearIcon 自定义的多选框清空图标 VNode | slot – disabled 是否禁用 boolean false dropdownClassName 下拉菜单的 className 属性 string – dropdownRender 自定义下拉框内容 ({menuNode: VNode, props}) => VNode | v-slot – filterOption 是否根据输入项进行筛选。当其为一个函数时,会接收 inputValue option 两个参数,当 option 符合筛选条件时,应返回 true,反之则返回 false。 boolean | function(inputValue, option) true getTriggerContainer 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。 function(triggerNode) () => document.body menuItemSelectedIcon 自定义当前选中的条目图标 VNode | slot – options options 数据,如果设置则不需要手动构造 selectOption 节点 array<{value, label, [disabled, key, title]}> [] placeholder 选择框默认文字 string|slot – removeIcon 自定义的多选框清除图标 VNode | slot – suffixIcon 自定义的选择框后缀图标 VNode | slot – value(v-model:value) 指定当前选中的条目 string|string[]|number|number[] – 注意,如果发现下拉菜单跟随页面滚动,或者需要在其他弹层中触发 Select,请尝试使用 getPopupContainer={triggerNode => triggerNode.parentNode} 将下拉弹层渲染节点固定在触发器的父元素中。 事件 事件名称 说明 回调参数 blur 失去焦点的时回调 function change 选中 option,或 input 的 value 变化(combobox 模式下)时,调用此函数 function(value, option:Option/Array<Option>) deselect 取消选中时调用,参数为选中项的 value (或 key) 值,仅在 multiple 或 tags 模式下生效 function(value,option:Option) dropdownVisibleChange 展开下拉菜单的回调 function(open) focus 获得焦点时回调 function inputKeyDown 键盘按下时回调 function mouseenter 鼠标移入时回调 function mouseleave 鼠标移出时回调 function popupScroll 下拉列表滚动时的回调 function search 文本框值变化时回调 function(value: string) select 被选中时调用,参数为选中项的 value (或 key) 值 function(value, option:Option)

    2023年12月18日
    77500
  • Class Component(ts)(v4)

    Class Component 一种使用typescript的class声明组件的方式。 IWidget类型声明 IWidget是平台内所有组件的统一接口定义,也是一个组件定义的最小集。 /** * 组件构造器 */ export type WidgetConstructor<Props extends WidgetProps, Widget extends IWidget<Props>> = Constructor<Widget>; /** * 组件属性 */ export interface WidgetProps { [key: string]: unknown; } /** * 组件 */ export interface IWidget<Props extends WidgetProps = WidgetProps> extends DisposableSupported { /** * 获取当前组件响应式对象 * @return this */ getOperator(); /** * 组件初始化 * @param props 属性 */ initialize(props: Props); /** * 创建子组件 * @param constructor 子组件构造器 * @param slotName 插槽名称 * @param props 属性 * @param specifiedIndex 插入/替换指定索引的子组件 */ createWidget<ChildProps extends WidgetProps = WidgetProps>( constructor: WidgetConstructor<ChildProps, IWidget<ChildProps>>, slotName?: string, props?: ChildProps, specifiedIndex?: number ); } Widget Widget是平台实现的类似于Class Component组件抽象基类,定义了包括渲染、生命周期、provider/inject、watch等相关功能。 export abstract class Widget<Props extends WidgetProps = WidgetProps, R = unknown> implements IWidget<Props> { /** * 添加事件监听 * * @param {string} path 监听的路径 * @param {deep?:boolean;immediate?:boolean} options? * * @example * * @Widget.Watch('formData.name', {deep: true, immediate: true}) * private watchName(name: string) { * … todo * } * */ protected static Watch(path: string, options?: { deep?: boolean; immediate?: boolean }); /** * 可以用来处理不同widget之间的通讯,当被订阅的时候,会将默认值发送出去 * * @param {Symbol} name 唯一标示 * @param {unknown} value? 默认值 * * @example *…

    2023年11月1日
    1.3K00

Leave a Reply

登录后才能评论