模仿低代码平台的可视化形式,解决频繁修改Mock数据的问题

背景

最近公司经常有一些大屏驾驶舱项目,因为大屏项目的某些“特殊性”,通常很长一段时间内,我们没有完整的数据,很多数据需要进行Mock。而因为数据的真实性、业主的需求变化、汇报支撑等原因,「经常」要修改Mock的数据。

image.png

image.png

image.png

这个过程中发现了下面一些痛点:

  • mock数据写死在前端代码里时,需要修改代码重新发版
  • 使用api工具mock接口数据,需要定位要修改的界面、数据对应的api接口
  • 修改完数据需要刷新页面,无法实时预览效果
  • 需要前后端开发人员来操作数据变动,非技术人员无法直接修改,增加了一层沟通时间

在经历了两次类似场景的折磨后,我想能不能用低代码平台的数据操作方式来解决这些问题:在编辑模式下选中页面模块,直接就能修改数据,修改后模块热更新,所见即所得,并且对非开发人员相对友好,不再依赖开发人员修改数据。

但是对于我司的人力情况、业务需求与投入产出比来说,搞一套低代码平台并不实际也无法满足定制化较多的业务需求,实际上解决上面的痛点我需要的功能是:

  • 直接生成mock接口(底层mock逻辑可以自己实现,也可调用第三方mock工具)
  • 可将页面进入开发模式,点击模块直接定位到对应的mock接口
  • 定位mock接口后可直接修改响应数据,修改后模块热更新,所见即所得
  • 对于某些常见模块,如列表、柱状图等,有定制的数据修改界面,对非开发人员更友好
  • 接入简单,任意项目可快速接入,即可支持在平台内定位mock使用的mock接口

2023-07-21 16.43.52.gif
实时定位与修改mock数据

解决方案

最终我开发一个「低成本轻量级」的平台,可对公司所有驾驶舱进行统一管理,不需要进行频繁版本发布及接口开发,即可便捷、快速修改驾驶舱Mock数据的工具。

1. 大屏管理

添加已有大屏的信息,创建mock接口

image.png
展示和添加大屏项目

2. mock接口

进入项目的接口管理,即可查看和管理该项目下的mock接口

image.png
接口定义-查看与管理该项目下的mock接口

image.png
新增mock接口-支持静态JSON与动态脚本

此时,创建出的mock接口与yapi等第三方类似,使用固定的http://xxx.xx.com/mock/<path>发请求即可

3. 项目预览与实时编辑

点击项目进入预览页面,该页面会嵌入iframe加载项目信息的url,进行预览,如果项目接入了mock工具(下文介绍接入流程),可进行实时编辑。

2023-07-21 16.43.52.gif
实时定位与修改mock数据

项目接入

要让驾驶舱项目支持快速定位实时更新,需要在驾驶舱项目中对需要使用Mock接口的模块进行标记,这个标记是非常简单的。在初始化插件之后,只需要在对应模块上增加一句v-mock="{ id: 'xxx', cb: xxx }"即可。以下为具体接入流程:

  • Step1: 安装依赖
yarn add brand-mock-tool
  • Step2: 在项目入口处初始化插件
    该依赖是一个vue插件,在入口进行注册即可
import { createApp } from 'vue'
import mockTool from 'brand-mock-tool'


// 同时支持Vue2,按照Vue2的方式初始化即可
const app = createApp(App)
app.use(mockTool)
  • Step3:标记使用了Mock接口的模块
    插件初始化后,会注册全局指令v-mock ,通过指令标记调用的「接口id」以及数据请求后的「回调函数」即可。 当在可视化平台中修改mock数据时,会触发cb的执行,由于vue自己的响应式原理,页面会自动更新,实现热更新的效果。
<script setup>

  import { ref } from 'vue'
  const data = ref([])
  const fetchData = async () => {

    // 请求数据,自动更新视图

    data = await fetch('http://data.com')


  }


</script>
<template>
  <div v-mock="{ id: '1234', cb: fetchData }">{{ data.name }}</div>
</template>

只需要安装并注册一个vue插件,在使用了mock接口的模块处使用指令进行标记即可,非常简单,对项目代码基本没有入侵。

实现过程

在实现过程中,主要难点在于如何实现平台与项目的交互,其他都是比较常规的逻辑,就不再赘述了。

1. 实现mock模块的选中效果

借鉴vue-devtool的实现方案

image.png

当鼠标悬浮到使用了mock接口的模块上时,该模块需要被高亮显示。这与vue-devtool的选择组件逻辑是一致的,
于是这部分我直接借鉴了vue-devtool的实现。

image.png
在vue-devtool中,当我们想调试选中某个组件时,进入选择模式,被选中的组件会被高亮。

鼠标悬浮的target元素可能是需要高亮的组件本身,也可能是组件的一个子节点,因此当鼠标悬浮到dom时,需要判断其是否为一个组件的根节点,如果不是,则从其父级元素开始一直往上查找出组件的根节点。

vue2中在对组件进行渲染时,会给根节点添加__vue__属性,记录该组件信息,即如果存在该属性,则可以认为其是一个组件的根节点。

// for vue2 向上查找当前组件根节点
export function findRelatedComponent (el) {
  while (!el.__vue__ && el.parentElement) {
    el = el.parentElement
  }
  return el.__vue__
}

注意,vue3vue2略有不同,vue3在渲染时会给组件的每个子节点添加__vueParentComponent属性,可以直接获取到当前节点所在组件。这里我们采用vue2的实现方式,只对根节点进行标记。

使用插件对mock模块的根节点进行标记

标记的目的有三个:

  1. 标记当前节点为mock模块的根节点,以此进行高亮
  2. 记录当前模块使用的mock接口的id
  3. 记录当前模块更新mock数据后的回调,以便热更新

在进行标记操作时,为简化用户使用,将标记功能封装为vue指令v-mock

import { App } from "vue";
import { DATA_MOCK_ID, ATTR_MOCK_CB } from '../constants'


const mock = (el: any, binding: any) => {
  if (el && binding.value) {
    const {
      value: { id, cb },
    } = binding
    el.setAttribute(DATA_MOCK_ID, id) // 使用了mock模块的根节点添加`DATA_MOCK_ID`属性,记录接口的id
    el[ATTR_MOCK_CB] = cb // 回调函数
  }
}
<script setup>

  import { ref, onMounted } from 'vue'
  const data = ref({})
  const fetchData = async () => {

    // 请求数据,自动更新视图

    data = await fetch('http://data.com')


  }


  
  onMounted(() => {
    fetchData()
  })
</script>
<template>
  <div v-mock="{ id: '1234', cb: fetchData }">{{ data.name }}</div>
</template>

因此鼠标悬浮元素时,可向上查找具有DATA_MOCK_ID属性的节点,对其进行高亮,并获取回调函数。

高亮节点

获取到需要高亮的节点后,使用getBoundingClientRect获取其位置及大小信息,创建蒙版createOverlay,定位到节点上面即可。

2. mock平台与iframe中的模块通信

我们的驾驶舱项目是通过iframe嵌入在mock平台中的,因为是不同域名,所以无法直接操作iframe中的dom,所以高亮和选中mock模块的实际操作需要驾驶舱页面内自己完成,无法由mock平台直接对其进行操作。这就需要建立一套通信机制,来完成以下操作:

  • mock平台通知驾驶舱开始和停止选择模式
  • 驾驶舱内选择模块后,通知mock平台选中模块使用的mock接口id及回调函数
  • mock平台中修改接口数据后,通知驾驶舱执行回调函数,实现热更新
export default class Bridge {
  listenParent() {
    // if current page is in iframe, add listener to parent window
    if (this.inIframe) {
      window.addEventListener("message", (e) => {
        const data = e.data;
        if (!data) return;
        switch (data.type) {
          case listenEvents.START_SELECTING:
            // 开始选择模式
            this.modulePicker.startSelecting({
              onSelected: (id, el) => {
                this.selectedEl = el
                this.sendToParent(sendEvents.SELECTED, id);
              }
            });
            break;

          case listenEvents.STOP_SELECTING:
            // 停止选择模式
            this.modulePicker.stopSelecting();
            break;

          case listenEvents.UPDATE_MODULE:
            // 数据更新,执行回调
            this.updateModule();
            break;

          default:
            break;
        }
      });
    }
  }

  // 通知mock平台选中的模块信息(接口id)
  sendToParent(event: string, data: any = {}) {
    if (this.inIframe) {
      window.parent.postMessage(
        {
          type: event,
          data,
        },
        "*"
      );
    }
  }
  
  updateModule() {
    this.selectedEl?.__mock_cb__ && this.selectedEl.__mock_cb__()
  }
}

然后在mock平台中进行相应指令的发送和接收即可:

const updateIframe = () => {
  iframe?.contentWindow?.postMessage({ type: listenEvents.UPDATE_MODULE }, '*')
}

3. 热更新

因为vue的响应式特性,热更新就非常简单了。一般我们都会将数据的获取和赋值封装为函数放到onMounted等时机执行,只需将该函数作为回调函数放到上文的标记中,在更新数据后调用,就会重新获取数据并赋值,更新界面。

<script>
  const data = ref({})
  
  // 在mock平台更新数据后,会自动重新执行getData函数,从而实现界面的热更新
  async function getData() {
    data = await fetch('http://data.com')


  }


  onMounted(() => {
    fetchData()
  })
</script>
<template>
  <div v-mock="{ id: '1234', cb: getData }">{{ data.name }}</div>
</template>

成果

经过几个项目的实际使用,不论是开发还是其他项目人员都能感到很大的便利性。
作为开发人员,终于不用再频繁的给产品修改数据,直接把平台丢给他自己修改,并且可以实时查看效果。
作为产品,能自己修改数据,所见即所得,也很满意。

后续规划

定制某些常见场景的数据编辑界面

如常规的列表、柱状图等,数据结构复杂,对非开发人员不太友好,同时结构又相对固定,因此考虑将这些模块的操作界面进行定制,如列表的数据结构,直接展示为对对应结构的表格,进行填写即可,最终输出对应的数据。

扩展vue插件,脱离mock平台,在页面内可直接呼出操作面板

当前需要将页面嵌入mock平台中使用,考虑增加类似vconsole的模式,在开发模式下按快捷键可在底部直接呼出操作面板,进行mock数据的操作。基本实现并不复杂,将mock平台的部分功能及界面进行移植即可。主要考虑将相关逻辑进行封装,避免维护两套相似逻辑。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYNzIg32' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片