4.2.6 框架之网络请求-拦截器

在整个http的链路中,异常错误对前端来说尤为重要,他作用在很多不同的场景,通用的比如500, 502等; 一个好的软件通常需要在不同的错误场景中做不同的事情。当用户cookie失效时,希望能自动跳转到登录页;当用户权限发生变更时,希望能跳转到一个友好的提示页;那么如何满足这些个性化的诉求呢?接下来让我们一起了解oinone前端网络请求-拦截器。

一、入口

在src目录下main.ts中可以看到VueOioProvider,这是系统功能提供者的注册入口

image.png

图4-2-6-1 VueOioProvider

import interceptor from './middleware/network-interceptor';
VueOioProvider(
  {
    http: {
      callback: interceptor
    }
  },
  []
);

图4-2-6-2 拦截器的申明入口

二、middleware

在项目初始化时使用CLI构建初始化前端工程,在src/middleware有拦截器的默认实现:

image.png

图4-2-6-3 在src/middleware有拦截器的默认实现

三、interceptor

interceptor在请求返回后触发,interceptor有两个回调函数,error和next

error参数

  • graphQLErrors 处理业务异常

  • networkError 处理网络异常

next

  • extensions 后端返回扩展参数
const interceptor: RequestHandler = (operation, forward) => {
  return forward(operation).subscribe({
    error: ({ graphQLErrors, networkError }) => {
            console.log(graphQLErrors, networkError);
      // 默认实现 => interceptor error 
    },
    next: ({ extensions }) => {
            console.log(extensions);
      // 默认实现 => interceptor next 
    },
  });
};

图4-2-6-4 后端返回扩展参数

四、interceptor error

// 定义错误提示等级
const DEFAULT_MESSAGE_LEVEL = ILevel.ERROR;
// 错误提示等级 对应提示的报错
const MESSAGE_LEVEL_MAP = {
  [ILevel.ERROR]: [ILevel.ERROR],
  [ILevel.WARN]: [ILevel.ERROR, ILevel.WARN],
  [ILevel.INFO]: [ILevel.ERROR, ILevel.WARN, ILevel.INFO],
  [ILevel.SUCCESS]: [ILevel.ERROR, ILevel.WARN, ILevel.INFO, ILevel.SUCCESS],
  [ILevel.DEBUG]: [ILevel.ERROR, ILevel.WARN, ILevel.INFO, ILevel.SUCCESS, ILevel.DEBUG]
};
// 错误提示通用函数
const notificationMsg = (type: string = 'error', tip: string = '错误', desc: string = '') => {
  notification[type]({
    message: tip,
    description: desc
  });
};
// 根据错误等级 返回错误提示和类型
const getMsgInfoByLevel = (level: ILevel) => {
  let notificationType = 'info';
  let notificationText = translate('kunlun.common.info');
  switch (level) {
    case ILevel.DEBUG:
      notificationType = 'info';
      notificationText = translate('kunlun.common.debug');
      break;
    case ILevel.INFO:
      notificationType = 'info';
      notificationText = translate('kunlun.common.info');
      break;
    case ILevel.SUCCESS:
      notificationType = 'success';
      notificationText = translate('kunlun.common.success');
      break;
    case ILevel.WARN:
      notificationType = 'warning';
      notificationText = translate('kunlun.common.warning');
      break;
  }
  return {
    notificationType,
    notificationText
  };
};

error: ({ graphQLErrors, networkError }) => {
      if (graphQLErrors) {
        graphQLErrors.forEach(async ({ message, locations, path, extensions }) => {
          let { errorCode, errorMessage, messages } = extensions || {};
          // FIXME: extensions.errorCode
          if (errorCode == null) {
            const codeArr = /code: (\d+),/.exec(message);
            if (codeArr) {
              errorCode = Number(codeArr[1]);
            }
          }
          if (errorMessage == null) {
            const messageArr = /msg: (.*),/.exec(message);
            if (messageArr) {
              errorMessage = messageArr[1];
            }
          }
          // 错误通用提示
          if (messages && messages.length) {
            messages.forEach((m) => {
              notificationMsg('error', translate('kunlun.common.error'), m.message || '');
            });
          } else {
            notificationMsg('error', translate('kunlun.common.error'), errorMessage || message);
          }
            // 提示扩展信息 根据错误等级来提示对应级别的报错
          const extMessage = getValue(response, 'extensions.messages');
          if (extMessage && extMessage.length) {
            const messageLevelArr = MESSAGE_LEVEL_MAP[DEFAULT_MESSAGE_LEVEL];
            extMessage.forEach((m) => {
              if (messageLevelArr.includes(m.level)) {
                const { notificationType, notificationText } = getMsgInfoByLevel(m.level);
                notificationMsg(notificationType, notificationText, m.message || '');
              }
            });
          }

          // 消息模块的用户未登录错误码
          const MAIL_USER_NOT_LOGIN = 20080002;
          // 基础模块的用户未登录错误码
          const BASE_USER_NOT_LOGIN_ERROR = 11500001;
          if (
            [MAIL_USER_NOT_LOGIN, BASE_USER_NOT_LOGIN_ERROR].includes(Number(errorCode)) &&
            location.pathname !== '/auth/login'
          ) {
            const redirect_url = location.pathname;
            location.href = `/login?redirect_url=${redirect_url}`;
          }
          /**
           * 应用配置异常跳转至通用的教程页面
           */
          // 模块参数配置未完成
          const BASE_SYSTEM_CONFIG_IS_NOT_COMPLETED_ERROR = 11500004;
          if ([BASE_SYSTEM_CONFIG_IS_NOT_COMPLETED_ERROR].includes(Number(errorCode))) {
            const action = getValue(response, 'extensions.extra.action');
            if (action) {
              Action.registerAction(action.model, action);
              const searchParams: string[] = [];
              searchParams.push(`module=${action.module}`);
              searchParams.push(`model=${action.model}`);
              searchParams.push(`viewType=${action.viewType}`);
              searchParams.push(`actionId=${action.id}`);
              const href = `${origin}/page;${searchParams.join(';')}`;
              location.href = href;
            }
          }
        });
      }
      if (networkError) {
        const { name, result } = networkError;
        const errMsg = (result && result.message) || `${networkError}`;
        if (name && result && result.message) {
          notification.error({
            message: translate('kunlun.common.error'),
            description: `[${name}]: ${errMsg}`,
          });
        }
      }
    }

图4-2-6-5 interceptor error

四、interceptor next

next: ({ extensions }) => {
  if (extensions) {
    const messages = extensions.messages as {
      level: 'SUCCESS';
      message: string;
    }[];
    if (messages)
      messages.forEach((msg) => {
        notification.success({
          message: '操作成功',
          description: msg.message,
        });
      });
  }
}

图4-2-6-6 interceptor next

六、完整代码

import { NextLink, Operation } from 'apollo-link';
import { notification } from 'ant-design-vue';
import getValue from 'lodash/get';
import { Action, ILevel, translate } from '@kunlun/dependencies';

interface RequestHandler {
  (operation: Operation, forward: NextLink): Promise<any> | any;
}

const DEFAULT_MESSAGE_LEVEL = ILevel.ERROR;
const MESSAGE_LEVEL_MAP = {
  [ILevel.ERROR]: [ILevel.ERROR],
  [ILevel.WARN]: [ILevel.ERROR, ILevel.WARN],
  [ILevel.INFO]: [ILevel.ERROR, ILevel.WARN, ILevel.INFO],
  [ILevel.SUCCESS]: [ILevel.ERROR, ILevel.WARN, ILevel.INFO, ILevel.SUCCESS],
  [ILevel.DEBUG]: [ILevel.ERROR, ILevel.WARN, ILevel.INFO, ILevel.SUCCESS, ILevel.DEBUG]
};

const notificationMsg = (type: string = 'error', tip: string = '错误', desc: string = '') => {
  notification[type]({
    message: tip,
    description: desc
  });
};

const getMsgInfoByLevel = (level: ILevel) => {
  let notificationType = 'info';
  let notificationText = translate('kunlun.common.info');
  switch (level) {
    case ILevel.DEBUG:
      notificationType = 'info';
      notificationText = translate('kunlun.common.debug');
      break;
    case ILevel.INFO:
      notificationType = 'info';
      notificationText = translate('kunlun.common.info');
      break;
    case ILevel.SUCCESS:
      notificationType = 'success';
      notificationText = translate('kunlun.common.success');
      break;
    case ILevel.WARN:
      notificationType = 'warning';
      notificationText = translate('kunlun.common.warning');
      break;
  }
  return {
    notificationType,
    notificationText
  };
};

const interceptor: RequestHandler = (operation, forward) => {
  return forward(operation).subscribe({
    error: ({ graphQLErrors, networkError, response }) => {
      if (graphQLErrors) {
        graphQLErrors.forEach(async ({ message, locations, path, extensions }) => {
          let { errorCode, errorMessage, messages } = extensions || {};
          // FIXME: extensions.errorCode
          if (errorCode == null) {
            const codeArr = /code: (\d+),/.exec(message);
            if (codeArr) {
              errorCode = Number(codeArr[1]);
            }
          }
          if (errorMessage == null) {
            const messageArr = /msg: (.*),/.exec(message);
            if (messageArr) {
              errorMessage = messageArr[1];
            }
          }
          if (messages && messages.length) {
            messages.forEach((m) => {
              notificationMsg('error', translate('kunlun.common.error') || '', m.message || '');
            });
          } else {
            notificationMsg('error', translate('kunlun.common.error') || '', errorMessage || message);
          }

          const extMessage = getValue(response, 'extensions.messages');
          if (extMessage && extMessage.length) {
            const messageLevelArr = MESSAGE_LEVEL_MAP[DEFAULT_MESSAGE_LEVEL];
            extMessage.forEach((m) => {
              if (messageLevelArr.includes(m.level)) {
                const { notificationType, notificationText } = getMsgInfoByLevel(m.level);
                notificationMsg(notificationType, notificationText, m.message || '');
              }
            });
          }
          console.log(extMessage);

          // 消息模块的用户未登录错误码
          const MAIL_USER_NOT_LOGIN = 20080002;
          // 基础模块的用户未登录错误码
          const BASE_USER_NOT_LOGIN_ERROR = 11500001;
          if (
            [MAIL_USER_NOT_LOGIN, BASE_USER_NOT_LOGIN_ERROR].includes(Number(errorCode)) &&
            location.pathname !== '/auth/login'
          ) {
            const redirect_url = location.pathname;
            location.href = `/login?redirect_url=${redirect_url}`;
          }
          /**
           * 应用配置异常跳转至通用的教程页面
           */
          // 模块参数配置未完成
          const BASE_SYSTEM_CONFIG_IS_NOT_COMPLETED_ERROR = 11500004;
          if ([BASE_SYSTEM_CONFIG_IS_NOT_COMPLETED_ERROR].includes(Number(errorCode))) {
            const action = getValue(response, 'extensions.extra.action');
            if (action) {
              Action.registerAction(action.model, action);
              const searchParams: string[] = [];
              searchParams.push(`module=${action.module}`);
              searchParams.push(`model=${action.model}`);
              searchParams.push(`viewType=${action.viewType}`);
              searchParams.push(`actionId=${action.id}`);
              const href = `${origin}/page;${searchParams.join(';')}`;
              location.href = href;
            }
          }
        });
      }
      if (networkError) {
        const { name, result } = networkError;
        const errMsg = (result && result.message) || `${networkError}`;
        if (name && result && result.message) {
          notification.error({
            message: translate('kunlun.common.error') || '',
            description: `[${name}]: ${errMsg}`
          });
        }
      }
    }
  });
};

export default interceptor;

图4-2-6-7 完整代码

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

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

(0)
史, 昂的头像史, 昂数式管理员
上一篇 2024年5月23日 am8:31
下一篇 2024年5月23日 am8:33

相关推荐

  • 3.6 问题排查工具

    当前端发起对应用的访问时,如果出现错误,那么我们可以通过以下方式进行简易排查,如果排查不出来,则也可以把排查工具给出的信息发送给Oinone官方售后进行进一步分析。本文将通过模拟异常信息,来介绍排查工具,提供了哪些辅助信息帮我们来快速定位问题。 排查工具基础介绍 通过前端页面的 /debug 路由路径访问调试工具的页面,假设我们的前端页面访问地址为http://localhost:6800,那么我们的排查工具请求路径就是 http://localhost:6800/debug排查工具可以帮我们排查前端页面元数据异常和后端接口的异常 排查前端页面元数据 将问题页面浏览器地址栏内 page 后的部分复制到调试工具的 debug 路由后重新发起请求,如图可以看到调试工具展示的信息,可以根据这些信息排查问题。 排查后端接口 后端接口出现问题后,打开(在原页面)浏览器的调试工具,切换到“网络”的标签页,在左侧的历史请求列表中找到需要调试的请求,右键会弹出菜单,点击菜单中的 “复制”,再次展开该菜单,点击二级菜单中的“以 fetch 格式复制”,这样可以复制到调试所需要的信息 2.复制调试信息到“接口调试”标签页内的文本框内,点击“发起请求”按钮获取调试结果 我们可以看到页面展示了该接口的各种调试信息,我们可以据此排查问题。 场景化的排查思路 业务代码中存在代码bug 报错后发起调试请求,我们可以看到,调试工具直接给出了异常抛出的具体代码所在位置,此时再切换到“全部堆栈”下,可以看到是业务类的233行导致的空指针异常,查看代码后分析可得是data.getName().eqauls方法在调用前未做条件判断补全该判断后代码可以正常执行 业务代码中没有直接的错误,异常在平台代码中抛出 报错后发起调试请求可以看到异常不在业务代码内再切换到“全部堆栈”,可以看到具体异常信息,提示core_demo_item表出现了重复的主键,该表是DemoItem模型的我们还可以切换到“sql调试”的标签页,可以看到出错的具体sql语句经过分析可以得知是240行的data.create()�重复创建数据导致的。 三、排查工具无法定位怎么办 当我们通过排查工具还是没有定位到问题的时候,可以通过调试页面的“下载全部调试数据”和“下载调试数据”按钮将调试信息的数据发送给官方售后人员帮助我们定位排查问题。 点击页面最顶部的“下载全部调试数据”按钮,可以下载页面调试数据和接口调试数据点击“调试接口”标签页内的“下载调试数据”按钮,可以下载接口调试数据 四、排查工具细节

    2024年5月23日
    1.4K00
  • 自定义组件

    1. 定义组件介绍 平台提供了大多数的通用组件,面对企业个性话需求、复杂的业务场景,我们也提供了自定义组件的能力,帮助企业更快实施业务需求。 自定义组件包含“组件画廊”“组件排序”“元件画廊”“元件属性设计”四个页面。 1.1 组件与元件 在介绍如何自定义组件前,需要先了解以下概念: 组件:页面设计的组件库中看到的是组件。每个组件都有自己的属性面板,通过属性、字段决定组件逻辑,而自定义组件就是需要构建出组件自身的属性信息,再结合业务配置组件的属性、使用组件。 一个组件在不同的业务类型、视图类型、单值/多值,其属性面板是不同的,不同业务类型、视图类型、单值/多值的组合我们成为元件,多种组合即为多个元件,所以一个组件包括多个元件。 元件:一个组件可以对应多个元件。在创建时明确元件所适用的字段业务类型、单/多值、视图类型,在画布中切换元件时,会结合当前组件的字段业务类型、单/多值、所在视图类型确定可以使用哪一个元件。 此处切换的也是元件。 示例:创建一个“下拉选”的组件,其中可以包含“下拉单选”“下拉多选”两个元件。“下拉选”组件从组件库中拖入时,设置单值时使用“下拉单选”元件,设置多值时使用“下拉多选”元件。 2. 组件管理 2.1 组件创建 在组件画廊页面,点击添加组件,在弹窗中完善信息创建组件。 2.2 组件操作 自定义组件支持“搜索、删除、作废、查看引用关系、管理元件、编辑、低无一体、排序”的操作。 搜索:默认搜索可见组件,可切换“全部、可用、废弃”搜索组件,也可使用组件名称搜索。 删除:若组件未被引用,则可以直接删除。 作废:组件作废后,不可在画布中展示,不可在组件切换时使用,但已使用的数据不影响。 查看引用关系:可以查看存在引用关系的页面,支持点击跳转到对应页面的设计页面。仅当组件无引用关系时才支持删除。 管理元件:点击进入元件的管理页面。 编辑:可修改组件名称、组件图表、组件描述。 低无一体:比较复杂,在第5章中单独讲解。 排序:进入排序页,可拖动排序自定义组件。自定义组件会插在系统组件之后。可以点击“查看排序结果”选项页查看最终排序结果。排序同样会更新画布中的组件库顺序。 3. 元件管理 3.1 元件创建 在元件画廊页面,点击添加元件,在弹窗中完善信息创建一个元件。 3.2 元件操作 元件支持“删除、作废、查看引用关系、编辑、设计元件属性”的操作。 删除:若元件未被引用,则可以直接删除。 作废:元件作废后,不影响原来已使用的元件,无法新添加、使用该元件。 查看引用关系:可以查看存在引用关系的页面,支持点击跳转到对应页面的设计页面。仅当元件无引用关系时才支持删除。此处的引用关系数量会小于等于组件引用关系的数量。 编辑:可修改元件名称、支持视图类型、元件描述。 设计元件属性:比较复杂,将在第4章中单独讲解。 4. 设计元件属性 元件属性设计页面主要操作集中在这三部分,分别是①视图切换②属性面板设计区③复制功能 视图切换:元件创建时选择的支持视图类型,在①区域平铺可切换对应视图的属性面板进行设计。 属性面板设计区:可将组件拖入属性面板设计区进行设计,设计的是自定义组件的属性面板,左侧组件库和页面设计的组件库相同,仍然支持创建字段或使用模型字段,右侧进行元数据面板、属性面板设置。 复制功能:可将已设置好的属性面板复制到其他视图,提高设计效率。 5. 低无一体 低无一体简单讲就是组件代码上传,通过载入代码使组件在设计页面和实现页面可见和交互。 系统内置的属性不满足需求时,要用低无一体写代码,定制属性,比如从模型中拖拽设计就是内置的属性,从组件库中设置,就要配合低无一体,否则无效。 首次进入组件设计或组件中的元件变更时需要生成SDK。 生成结束后展示SDK生成时间,并且“下载模版工程”按钮可用。 点击下载模版工程,会自动下载模板工程。 在模版工程中编写前端代码。 根据实际需求上传JS、CSS文件后提交即可。

    2024年6月20日
    1.2K00
  • 3.5.7.2 自定义母版

    母版是什么 在业务系统中,母版是一种全局通用的组件,用于包裹每个页面的元素。这个概念类似于 PPT 中的母版,它定义了页面的整体结构、样式和布局,使得系统具有一致的外观和风格。 特点: 全局通用:母版是全局性的,适用于系统中的每个页面,确保一致性的用户体验。 包裹元素:母版包裹每个页面的元素,定义了整体的结构和布局。 外观一致性:通过统一的母版设计,系统达到外观和风格的一致性。 与 PPT 母版的类比: 在母版的概念上,与 PPT 中的母版类似,都是用于定义整体结构和样式,确保每个页面都具有一致的外观。 默认母版范围: 图3-5-7-20 默认母版范围 作用场景 在系统中,我们提供了多个纬度的母版,包括全局、应用和页面纬度。这样的设计允许根据不同的业务场景选择合适的母版纬度,以满足不同层次的定制需求。 母版纬度: 全局母版: 覆盖系统中所有页面,确保全局的一致性和统一的用户体验。 定义全局性的结构、样式和布局。 应用母版: 适用于特定应用或模块,定制化程度介于全局和页面之间。 允许在不同应用间实现一定的差异化。 页面母版: 面向具体页面,提供最大的定制化空间。 允许在不同页面中定义不同的结构和样式。 选择纬度: 业务场景决定:根据具体的业务场景和需求选择合适的母版纬度。 定制化需求:如果需要全局一致性,选择全局母版;如果在应用层面有特定需求,选择应用母版;如果需要最大的灵活性和定制化,选择页面母版。 参数说明: 在系统中,使用 registerMask 注册母版时,需要提供两个参数。第一个参数是母版的 XML,第二个参数用于控制母版的注册行为,包括 module、moduleName、model 和 actionName。 母版 XML(第一个参数): 提供母版的 XML 描述,定义了母版的结构和样式。 控制参数(第二个参数): module:指定模块的名称。 moduleName:指定模块的显示名称。 model:指定母版所属的模型。 actionName:指定母版的操作名称,可以是字符串或字符串数组。 全局母版 可以使用 registerMask 注册母版。当第二个参数为 {} 时,即代表注册全局母版。 示例工程目录 以下是需关注的工程目录示例,并确保导入导出依赖正确: 图3-5-7-21 全局纬度注册母版工程目录示例 示例代码 以下是一个示例代码: import { registerMask } from '@kunlun/dependencies'; /** * mask: 在做系统时,我们通常会把外层的布局(菜单、顶部导航)等抽出公共组件,公共组件会抽离全局通用。类似ppt母版的概念即为Mask * registerMask 第二个入参为{}即注册了全局得母版 * * 和默认母版相比移除右上角消息、语言切换、用户 * `<block> * <widget widget="notification" /> * <widget widget="divider" /> * <widget widget="language" /> * <widget widget="divider" /> * <widget widget="user" /> * </block>` */ const registerGlobalMask = () => { registerMask(` <mask> <multi-tabs /> <header> <widget widget="app-switcher" /> </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>`, {}); } registerGlobalMask(); 图3-5-7-22 全局纬度注册母版代码示例 复制这段代码执行下registerGlobalMask()试试看吧 效果 右上角消息、语言切换、用户被移除。 图3-5-7-23 全局纬度注册母版效果示例 应用母版 在系统中,可以使用 registerMask 注册母版。当第二个参数为 {moduleName: ‘resource’} 时,即注册了 resource(资源)应用的母版。 示例工程目录 以下是需关注的工程目录示例,更新导出./moduleMask: 图3-5-7-24 应用纬度注册母版工程目录示例 示例代码 以下是一个示例代码: /** * registerMask 第二个入参为{moduleName: 'resource'}即注册了resource(资源)应用的母版 * * resource来源: 通常是浏览器url上的module * * 和默认模版相比拿掉了菜单 * `<sidebar> * <widget widget="nav-menu" height="100%"…

    2024年5月23日
    1.3K00
  • 3.5.7.9 自定义多Tab

    在业务中,可能会遇到需要对多tab的交互或UI做全新设计的需求,这个时候可以自定义多tab组件支持。 首先继承平台的MultiTabsWidget组件,将自己vue文件设置到component处 import { MultiTabsWidget, SPI, ViewWidget } from '@kunlun/dependencies'; import Component from './CustomMultiTabs.vue'; @SPI.ClassFactory( ViewWidget.Token({ // 这里的tagName跟平台的组件一样,这样才能覆盖平台的组件 tagName: ['multi-tabs', 'MultiTabs'] }) ) export class CustomMultiTabsWidget extends MultiTabsWidget{ public initialize(props) { super.initialize(props); // 设置自定义的vue组件 this.setComponent(Component); return this; } } vue文件中继承平台的props,编写自定义页面代码 export const MultiTabsProps = { /** * 组件是否可见 */ invisible: { type: Boolean }, /** * tab列表数据 */ tabs: { type: Array as PropType<MultiTab[]> }, /** * 当前激活的tab */ activeTab: { type: Object as PropType<MultiTab> }, /** * 鼠标悬浮所在的tab */ hoverTab: { type: Object as PropType<MultiTab> }, /** * 鼠标经过tab事件回调 */ onMouseenterTab: { type: Function as PropType<(tab: MultiTab) => void> }, /** * 鼠标离开tab事件回调 */ onMouseleaveTab: { type: Function as PropType<(tab: MultiTab) => void> }, /** * 点击tab */ onClickTab: { type: Function as PropType<(tab: MultiTab) => void> }, /** * 刷新当前tab */ onRefreshCurrentTab: { type: Function as PropType<(tab: MultiTab) => void> }, /** * 关闭当前tab */ onCloseCurrentTab: { type: Function as PropType<(tab: MultiTab) => void> }, /** * 关闭除当前tab外的其他所有tab */ onCloseOtherTabs: { type: Function as PropType<(tab: MultiTab) => void> }, /** * 关闭当前tab左侧的所有tab */ onCloseLeftTabs: { type: Function as…

    2024年5月23日
    1.1K00
  • 3.5.2.2 构建View的Template

    我们在很多时候需要自定义模型的管理页面,而不是直接使用默认页面,比如字段的展示与隐藏,Action是否在这个页面上出现,搜索条件自定义等等,那么本章节带您一起学习如何自定义View的Template。 自定义View的Template 在使用默认layout的情况下,我们来做几个自定义视图Template,并把文件放到指定目录下。 图3-5-2-14 自定义View的Template 第一个Tabel Step1 自定义PetTalent的列表 我们先通过数据库查看默认页面定义,找到base_view表,过滤条件设置为model =\’demo.PetTalent\’,我们就看到该模型下对应的所有view,这些是系统根据该模型的ViewAction对应生成的默认视图,找到类型为【表格(type = TABLE)】的记录,查看template字段。 图3-5-2-15 base_view表查看template字段 <view name="tableView" cols="1" type="TABLE" enableSequence="false"> <template slot="actions" autoFill="true"/> <template slot="rowActions" autoFill="true"/> <template slot="fields"> <field invisible="true" data="id" label="ID" readonly="true"/> <field data="name" label="达人"/> <field data="dataStatus" label="数据状态"> <options> <option name="DRAFT" displayName="草稿" value="DRAFT" state="ACTIVE"/> <option name="NOT_ENABLED" displayName="未启用" value="NOT_ENABLED" state="ACTIVE"/> <option name="ENABLED" displayName="已启用" value="ENABLED" state="ACTIVE"/> <option name="DISABLED" displayName="已禁用" value="DISABLED" state="ACTIVE"/> </options> </field> <field data="createDate" label="创建时间" readonly="true"/> <field data="writeDate" label="更新时间" readonly="true"/> <field data="createUid" label="创建人id"/> <field data="writeUid" label="更新人id"/> </template> <template slot="search" autoFill="true" cols="4"/> </view> 图3-5-2-16 base_view表查看template字段 对比view的template定义与页面差异,从页面上看跟view的定义少了,创建人id和更新人id。因为这两个字段元数据定义里invisible属性。 a. 当XML里面没有配置,则用元数据覆盖了。 b. 当XML里面配置了,则不会用元数据覆盖了。 在下一步中我们只要view的DSL中给这两个字段加上invisible="false"就可以展示出来了 图3-5-2-17 查看列表展示 图3-5-2-18 invisible属性 新建pet_talent_table.xml文件放到对应的pamirs/views/demo_core/template目录下,内容如下 a. 对比默认视图,在自定义视图时需要额外增加属性model="demo.PetTalent" b. name设置为"tableView",系统重启后会替换掉base_view表中model为"demo.PetTalent",name为"tableView",type为"TABLE"的数据记录。 ⅰ. name不同的但type相同,且viewAction没有指定时,根据优先级priority进行选择。小伙伴可以尝试修改name="tableView1",并设置priority为1,默认生成的优先级为10,越小越优先。 ccreateUid和writeUid字段,增加invisible="false"的属性定义 <view name="tableView" model="demo.PetTalent" cols="1" type="TABLE" enableSequence="false"> <template slot="actions" autoFill="true"/> <template slot="rowActions" autoFill="true"/> <template slot="fields"> <field invisible="true" data="id" label="ID" readonly="true"/> <field data="name" label="达人"/> <field data="dataStatus" label="数据状态"> <options> <option name="DRAFT" displayName="草稿" value="DRAFT" state="ACTIVE"/> <option name="NOT_ENABLED" displayName="未启用" value="NOT_ENABLED" state="ACTIVE"/> <option name="ENABLED" displayName="已启用" value="ENABLED" state="ACTIVE"/> <option name="DISABLED" displayName="已禁用" value="DISABLED" state="ACTIVE"/> </options> </field> <field data="createDate" label="创建时间" readonly="true"/> <field data="writeDate" label="更新时间" readonly="true"/> <field data="createUid" label="创建人id" invisible="false"/> <field data="writeUid" label="更新人id" invisible="false"/> </template> <template slot="search" autoFill="true" cols="4"/> </view> 图3-5-2-19 增加invisible=”false”的属性定义 Step2 重启应用看效果 图3-5-2-20 示例效果 第一个Form Step1 自定义PetTalent的编辑页…

    2024年5月23日
    1.0K00

Leave a Reply

登录后才能评论