关于DSL解析器

0 阅读3分钟

DSL

DSL解析器可以根据不同的config里面的内容渲染不同的内容,数据驱动视图 变化。如果有想要增加的内容,只需要在这份配置中配置相关的信息即可,减少大量的重复性工作,并且支持可定制化操作。

  1. menuType:值为group,表示是有组数据,module表示是一个对应的模块。

  2. moduleType:默认为四个枚举值,

    • sider:表示是侧边栏数据
    • iframe:第三方页面,可以用来集成同一公司下面的其他项目
    • custom:用户自定义模块
    • schema:描述页面内容的一份数据结构
  3. ...

{
  mode: "dashboard"; // 模版类型,不同模版类型对应不一样的模版数据结构
  name: ""; // 名称
  desc: ""; // 描述
  homePgae: ""; // 首页(项目配置)
  // 头部菜单 多个
  menu: [
    {
      key: "", //菜单唯一描述
      name: "", //菜单名称
      menuType: "", // 枚举值:group / module
      // 当 menuType === group 时,可填
      subMenu: [
        {
          //   可递归 menuItem
        },
      ],
      // 当 menuType === module 时,可填
      moduleType: "", // 枚举值:sider / iframe / custom / schema
      siderConfig: {
        menu: [
          {
            // 可递归 menuItem ( 除 moduleType === sider)
          },
        ],
      },
      iframeConfig: {
        path: "", // iframe 路径
      },
      customConfig: {
        path: "", // custom 路径
      },
      schemaConfig: {   }
     }
    ]
 }

如何使用

编写一个hook文件对数据进行处理成需要的格式数据。在需要的地方引入进行,根据不同的数据进行不同的展示,以及使用watch等函数进行监听路由的变化、父子组件传值,使用v-bind将原有的组件库属性透传给我们自己的组件接收、子孙组件provide/inject 依赖注入传值。

export const useSchema = () => {
  const route = useRoute();
  const menuStore = useMenuStore();

  const api = ref("");
  const tableSchema = ref({});
  const tableConfig = ref({});

  const searchSchema = ref({});
  const searchConfig = ref({});
  // 构造 schemaConfig 相关配置、输送给 schemaView 解析
  const buildData = () => {
    const { key, sider_key: siderKey } = route.query;
    const mItem = menuStore.findMenuItem({
      key: "key",
      value: siderKey ?? key,
    });

    if (mItem && mItem.schemaConfig) {
      const { schemaConfig: sConfig } = mItem;
      const configSchema = JSON.parse(JSON.stringify(sConfig.schema));
      api.value = sConfig.api ?? "";

      tableSchema.value = {};
      tableConfig.value = undefined;

      searchSchema.value = {};
      searchConfig.value = undefined;

      nextTick(() => {
        // 构造 tableSchema 和 tableConfig
        tableSchema.value = buildDtoSchema(configSchema, "table");
        tableConfig.value = sConfig.tableConfig;

        // 构造 searchSchema 和 searchConfig
        const dtoSearchSchema = buildDtoSchema(configSchema, "search");
        // 判断当前是否是携带参数跳转过来的搜索框
        for (const key in dtoSearchSchema.properties) {
          if (route.query[key] !== undefined) {
            dtoSearchSchema.properties[key].option.default = route.query[key];
          }
        }
        searchSchema.value = dtoSearchSchema;
        searchConfig.value = sConfig.searchConfig;
      });
    }
  };

  /**
   * 构建对应的 schema 方法(清除噪音)(比如searchSchema/tableSchema)
   * 将schema字段中带有option结尾的属性换为 option
   * @param {*} 配置名:schema/custom/iframe等 
   * @param {*} 所属组件:table/search
   * @returns 
   */
  const buildDtoSchema = (_schema, comName) => {
    if (!_schema?.properties) {
      return {};
    }
    const dtoSchema = {
      type: "object",
      properties: {},
    };

    // 提取有效 schema 字段信息
    for (const key in _schema.properties) {
      const props = _schema.properties[key];
      // 如果有对应的Option, 提取该 Option 里面的内容
      if (props[`${comName}Option`]) {
        let dtoProps = {};
        // 提取 props 中非 Option 的部分,存放到 dtoProps 中
        for (const pKey in props) {
          if (pKey.indexOf("Option") < 0) {
            dtoProps[pKey] = props[pKey];
          }
        }
        // 将 comNameOption 字段名修改为 option 然后结合在 dtoProps 中
        dtoProps = Object.assign({}, dtoProps, {
          option: props[`${comName}Option`],
        });

        dtoSchema.properties[key] = dtoProps;
      }
    }
    return dtoSchema;
  };

  // 监听路由与数据的变化,重新构建
  watch(
    [
      () => route.query.key,
      () => route.query.sider_key,
      () => menuStore.menuList,
    ],
    () => {
      buildData();
    },
    { deep: true },
  );

  onMounted(() => {
    buildData();
  });
  return { api, tableSchema, tableConfig, searchSchema, searchConfig };
};

思考与收获

这次的DSL解析器的编写,不只是教了什么是DSL解析器以及如何去使用,我认为更重要是在编写的过程中遇到的问题与思考,在多个页面时如何实现懒加载、不同项目之间的数据请求应该怎么进行区分并且是比较好的一种方法、遇到问题的解决与排查方式等,这些都是与解析器同等重要的知识点。