# 指令集

# 简介

# 背景

在主应用与插件开发中,时常会存在操作特性的功能数据,开发人员根据当前插件的业务场景实现与主应用之间的操作特性的功能数据。而这种开发模式很容易造成以下几点弊端:首先,插件可以随意操作特性的功能和数据,主应用无法对操作方法进行限制;其次,实现方式难以形成规范,实现代码分散在各个主应用及各个插件中;再次,操作方法难以形成文档化,后续迭代升级困难;最后,插件与主应用未完全解耦,后续迭代升级中若其中一方传递数据格式发生改变时,势必会修改另一方同步升级。

# 价值与意义

  • 在插件和主应用之间提供一个指令集,用于收集插件与主应用之间的操作特性的功能和数据时,以此达到操作方法的规范化文档化,并在后续的迭代/升级中提供依据
  • 在主应用或插件迭代/升级后,操作特性的功能和数据时传参发生改变,只需在指令集中做容错处理并升级响应调用方,而不是同时升级主应用和插件
  • 在主应用程序中,提供受控的接口,允许插件访问和操作特定的功能和数据,而不是直接操作主应用程序的全局数据,以此限制插件的操作功能权限。。
  • 通过消息传递机制,主应用程序可以在必要时将特定的数据传递给插件,而不是插件直接访问主应用程序的全局环境。

数据SDK通信架构图

# 开发模式

  • 指令集的开发由主应用开发人员进行开发,插件开发人员通过 window.esignbridge.$directive.具体方法名 调用指令集方法。
  • 指令集的可配合 数据SDK事件中心 进行开发

# 事件中心

注意事项

事件中心(EventEmitter) 用于事件的监听、触发。与数据SDK初始化时一并注册到 window 下。

# 安装

yarn add @esign/bridge-event-emitter -S

# 使用

import Vue from 'vue'
import eventEmitter from '@esign/bridge-event-emitter'

/**
 * @params name: string 事件中心名称,可传可不传
 */
Vue.use(eventEmitter)
// Vue.use(eventEmitter, '事件中心名称')

// 或以函数的形式直接调用
eventEmitter()
// eventEmitter('事件中心名称')

# API (window.esignbridge.$eventEmitter)

  • on(type: string, cb: Function) 监听订阅
    • @param type: 必选,事件名称
    • @param cb: 必选,回调函数
  • emit(type: string, ...args: any[]) 触发订阅
    • @param type: 必选,事件名称
    • @returns args: 可选,传递参数
  • off(type: string, cb?: Function) 删除订阅
    • @param type: 必选,事件名称
    • @param cb: 可选,回调函数
  • once(type: string, cb: Function) 单次触发订阅
    • @param type: 必选,事件名称
    • @param cb: 必选,回调函数

# 指令集注册工具

用于将主应用指令集注册到 window 下,以供插件通过 window.esignbridge.$directive 调用

# 安装

yarn add @esign/bridge-directive -S

# 使用

import bridgeDirective from '@esign/bridge-directive'

/**
 * 注册完指令后,会在 window.esignbridge.$directive 下挂载所有的指令
 * @param directive 指令集,类型为 object
 */
bridgeDirective.use(directive)

/** 若要在 window.esignbridge.$directive 下增加新的指令,可调用 registerDirective 方法 */
window.esignbridge.$directive.registerDirective(directive)

# 规范

# 指令集命名规范:

  • @esign/站点(或功能块)-event-api: 主应用指令集包名命名规范, 以签署(sign)功能为例,其指令集名称为: @esign/sign-event-api
  • window.esignbridge.$directive.具体方法名: 插件调用指令集工具包方式, 以签署提交功能为例,其调用方式为 window.esignbridge.$directive.emitSignSubmit(xxx: 参数)

# 指令集事件名称命名规范

  • 通用指令集命名规范: 事件模式-指令类型-业务特性-事件类型(或功能点), 扩展点类指令集命名规范: 事件模式-扩展点名称-指令类型-业务特性-事件类型(或功能点)
    • 事件模式分为: emit(触发)on(监听)once(单次监听)remove(清除)
    • 事件类型或功能点类型
      • 事件类型: click(鼠标点击事件)change(表单元素改变事件)submit(表单提交事件)load(页面加载事件)unload(页面卸载事件)resize(窗口大小改变事件)scroll(滚动条位置改变事件)keypress(键盘按下事件)mouseout(鼠标移入事件)mouseover(鼠标移出事件)
      • 功能点类型: beforeSubmit(在表单提交之前执行)afterRender(在页面渲染完成之后执行)beforeClose(在窗口关闭之前执行)afterLogin(在登陆成功之后执行)beforeJump(在页面跳转之前执行)afterSearch(在搜索之后执行)......
    • 以提交(submit)签署事件功能为例: emitSignComponentEpuEventSubmitClick: 触发提交事件、onSignComponentEpuEventSubmitClick: 监听提交事件、removeSignComponentEpuEventSubmitClick: 清除提交事件

# 指令集文件目录规范

  • 指令集进行分层处理,每个扩展点(插件)都拥有自己的子指令集文件/文件夹的名称以扩展点名称命名;通用指令集则放在指令集根目录下。以签署(sign)功能为例
    +-- sign-event-api -------------------------------------------------------- 签署功能指令集
    |   +-- sign-submit-button-epu -------------------------------------------- 签署提交按钮扩展点指令集目录
    |   |   +-- index.ts ------------------------------------------------------ 签署提交按钮扩展点指令集文件
    |   +-- sign-cancel-button-epu -------------------------------------------- 签署取消按钮扩展点指令集目录
    |   |   +-- index.ts ------------------------------------------------------ 签署取消按钮扩展点指令集文件
    |   +-- sign-seal-dialog-epu ---------------------------------------------- 签署领章弹窗指令集目录
    |   |   +-- index.ts ------------------------------------------------------ 签署领章弹窗指令集文件
    |   +-- index.ts ---------------------------------------------------------- 通用指令集
    |   +-- main.ts ----------------------------------------------------------- 入口文件
    |   +-- package.json ------------------------------------------------------ package.json
    
  • 每个子指令集/通用指令集所包含的功能点,分为五类: 上行数据类下行数据类流程类工具类以及自定义事件类
  • 案例传送门

# 案例

# 指令集(@esign/xxx-event-api)

指令集逻辑文件 index.ts

/**
 * 指令集命名规范:事件模式-扩展点名称-指令类型-业务特性-事件类型(或功能点) (例如: 触发-登陆组件扩展点-流程类-登陆-事件类型)
 */

const EVENT_EMITTER_KEY = {
  GoResult: 'sign-scan-seal-epu-event-go-result-click',
}

type State = Record<string, any>

interface Mutations {
  SET_PAGE_SIGN_SCAN_SEAL_QUERY(state: State, data: Record<string, any>): void
  SET_PAGE_RESULT(state: State, data: Record<string, any>): void
}

/**
 * 上行数据类
 */
/**
* 业务自定义 mutations,用于修改主应用的 state
* 备注: 之所以从主应用 vuex 中移动至此,目的有二:
* 1. 便于指令集开发人员开发与插件交互的mutation方法
* 2. 后续维护升级,仅需升级指令集包
*/
export const mutations: Mutations = {
  SET_PAGE_SIGN_SCAN_SEAL_QUERY(state: State, data: Record<string, any>) {
    state.page_SignScanSeal.query = data
  },
  SET_PAGE_RESULT(state: State, data: Record<string, any>) {
    state.page_Result = data
  }
}
const uplinkApi = {
  // 设置结果页数据
  emitUplinkSetPageResultClick: (value: Record<string, any>) => {
    window.esignbridge.$store.setStoreData('pluginData/SET_PAGE_RESULT', value)
  },
}

/**
 * 下行数据
 */
const downlinkApi = {
  // 获取所有全局数据源
  onDownlinkGetGlobalStateClick() {
    return window.esignbridge.$store.getData()
  },
  // 获取当前页所有数据源
  onDownliknGetCurrentPageStateClick() {
    return window.esignbridge.$store.getPageData()
  },
}

/**
* 流程类
*/
const processApi = {}

/**
 * 工具类
 */
  const utilsApi = {}

/**
 * 自定义事件类
 */
const eventApi = {
  /**
   * 前往结果页
   */
  emitSignScanSealEpuEventGoResultClick: (params: any) => {
    window.esignbridge.$eventEmitter.emit(EVENT_EMITTER_KEY.GoResult, params)
  },
  onSignScanSealEpuEventGoResultClick: (cb: Function) => {
    window.esignbridge.$eventEmitter.on(EVENT_EMITTER_KEY.GoResult, cb)
  },
  removeSignScanSealEpuEventGoResultClick: (cb: Function) => {
    window.esignbridge.$eventEmitter.off(EVENT_EMITTER_KEY.GoResult, cb)
  },
}

export default {
  ...uplinkApi,
  ...downlinkApi,
  ...processApi,
  ...utilsApi,
  ...eventApi
}

指令集入口文件 main.ts

import directive from './index'
import bridgeDirective from '@esign/bridge-directive'

bridgeDirective.use(directive)

export { mutations } from './index'
export default directive

# 主应用

插件入口文件(loadPlugin.ts)

import '@esign/xxx-event-api'

与插件交互的数据源(/src/store/modules/pluginData.ts)

import { mutations as xxxMutations } from '@esign/xxx-event-api'

type State = Record<string, any>

const state: State = {
  global: {
    // 全局数据
    userInfo: { // 用户信息
      id: '1'
      name: '张三',
      code: 'zhangsan'
    },
    token: 'MDEyMzQ1Njc4OQ==', // 用户token
  },
  /**
   * Result页数据源
   * 页面数据的 key 的命名规范: page_ + 页面路由的 name 值
   */
  page_Result:{

  },
  // 人面识别页数据源
  page_SignScanSeal: {
    query: {}, // 路由参数
  },
}

const mutations = {
  ...xxxMutations,
}

export default {
  namespaced: true,
  state,
  mutations,
}

扫脸认证页面(SignScanSeal.vue)

<template>
  <!-- 扩展点: sign-scan-seal-epu -->
  <es-extension-point name="sign-scan-seal-epu" />
</template>
<script>
import { onSignScanSealEpuEventGoResultClick, removeSignScanSealEpuEventGoResultClick } from '@esign/xxx-event-api'
export default {
  mounted() {
    // 监听前往结果页的指令事件,并执行相应逻辑
    this.goResult = () => this.$router.push({ name: 'Result' })
    onSignScanSealEpuEventGoResultClick(this.goResult)
  },
  beforeDestroy() {
    // 页面卸载时,销毁已定义事件,避免重复调用
    removeSignScanSealEpuEventGoResultClick(this.goResult)
  }
}
</script>

扫脸认证扩展点(sign-scan-seal-epu)

<template>
  <div class="sign-scan-seal" />
</template>
<script>
export default {
  name: 'SignScanSeal',
  data() {
    return {}
  },
  computed: {
    // 获取主应用全局数据
    globalState() {
      return window.esignbridge.$directive.onDownlinkGetGlobalStateClick()
    },
    // 获取主应用当前页数据
    pageState() {
      return window.esignbridge.$directive.onDownliknGetCurrentPageStateClick()
    }
  },
  mounted() {
    /**
     * TODO
     * 1. 根据获取的数据 globalState 和 pageState 进行页面操作,并调用相应逻辑
     * 2. 触发扫脸回调后,获取扫脸结果
     * 3. 触发修改结果页的数据值的指令事件
     */
    // 
    window.esignbridge.$directive.emitUplinkSetPageResultClick({ code: 200, success: true, msg: '扫脸成功' })
    // 触发前往结果页的指令事件
    window.esignbridge.$directive.emitSignScanSealEpuEventGoResultClick()
  }
}
</script>
上次更新: 8/28/2023, 9:52:35 AM