手把手教你写一个yapi生成ts的插件

909 阅读3分钟

背景

在开发项目的时候,写ts类型定义总是一件繁琐的事情,特别是数据结构比较复杂时候,会消耗大量时间,这时候如果有个插件能帮忙解决这种繁琐工作就好了。

方案

  1. 先下载Tampermonkey油猴插件,如下

image.png

  1. 在浏览器扩展程序中打开油猴,并点击添加新脚本

image.png

  1. 以下脚本是我根据个人需求写的,可以参考复制,然后根据自己的情况修改下UserScript中的值
// ==UserScript==
// @name         脚本名称
// @namespace    命名空间,可写成网址
// @version      0.1
// @description  脚本描述
// @author       作者
// @match        需要匹配那个网址才执行这个脚本,例如  *://ok99ok99.com/*
// ==/UserScript==


(function () {
  "use strict";

  const httpRequest = new XMLHttpRequest();

  function request() {
    return new Promise(function (resolve, reject) {
      const interfaceId = window.location.pathname.replace(
        /\/project\/\d+\/interface\/api\//,
        ""
      );
      httpRequest.open(
        "GET",
        `https://${window.location.host}/api/interface/get?id=${interfaceId}`,
        true
      );
      httpRequest.send();
      httpRequest.onreadystatechange = function () {
        if (httpRequest.readyState == 4 && httpRequest.status == 200) {
          resolve(httpRequest.responseText);
        }
      };
    });
  }

  /**
   * yapi数据格式化
   * 自定义符号: #1代表""  #2代表;  #3代表[]
   */
  const dataFormat = (obj = {}) => {
    const { type, enum: enums = [], items = {}, description = "" } = obj;
    // 基本类型
    let value = "";
    const des = description ? ` // ${description}` : "";

    // 对象类型
    if (type === "object") {
      const { properties = {}, required = [] } = obj;
      return Object.keys(properties).reduce((pre, next) => {
        const isRequired = required.includes(next);
        const key = isRequired ? next : `${next}?`;
        return {
          ...pre,
          [`${key}`]: dataFormat(properties[next]),
        };
      }, {});
    }

    // 数组类型
    if (type === "array") {
      const { type: subType } = items;
      // 子元素是引用类型则递归
      if (["object", "array"].includes(subType)) {
        return [dataFormat(items)];
      }

      value = subType === "integer" ? "number" : subType;
      return `${value}#3#2${des}`;
    }

    if (enums.length > 0) {
      value = enums.map((str) => `#1${str}#1`).join("|");
      return `${value}#2${des}`;
    }

    value = type === "integer" ? "number" : type;
    return `${value}#2${des}`;
  };

  // yapi数据转ts类型
  const yapiToTs = (obj = {}) => {
    const data = dataFormat(obj);

    let str = JSON.stringify(data, null, "\t");

    // 去掉所有换行处的逗号,后面会分情况生成;
    str = str.replace(/,\n/g, "\n");

    // 去掉键值对的""
    str = str.replace(/"/g, "");

    // []处理,object和基本类型都可能作为数组元素
    str = str.replace(/\[\n(\t*)/g, "");
    str = str.replace(/(#2)?\n(\t*)\]/g, "[]");

    // 换行处的}和]后补加;
    str = str.replace(/\}\n/g, "};\n");
    str = str.replace(/\]\n/g, "];\n");

    // 自定义符号处理,注释见dataFormat方法
    str = str.replace(/#1/g, '"');
    str = str.replace(/#2/g, ";");
    str = str.replace(/#3/g, "[]");

    console.log(str);

    return str;
  };

  function createBtn(text) {
    const btn = document.createElement("button");
    btn.style.width = "110px";
    btn.style.height = "32px";
    btn.style.lineHetight = "32px";
    btn.style.textAlign = "center";
    btn.style.backgroundColor = "#fff";
    btn.style.color = "#000";
    btn.style.fontSize = "14px";
    btn.style.cursor = "pointer";
    btn.style.borderRadius = "4px";
    btn.style.margin = "4px";
    btn.style.border = "1px solid #000";
    btn.innerText = text;
    btn.addEventListener("mouseenter", () => {
      btn.style.color = "#2395f1";
      btn.style.borderColor = "#2395f1";
    });
    btn.addEventListener("mouseleave", () => {
      btn.style.color = "#000";
      btn.style.borderColor = "#000";
    });
    return btn;
  }

  function copy(text) {
    const copy = document.createElement("textarea");
    document.body.appendChild(copy);
    copy.value = text;
    copy.select();
    document.execCommand("copy");
    document.body.removeChild(copy);
  }

  function toast(msg) {
    const toastEle = document.createElement("p");
    const textEle = document.createTextNode(msg);

    toastEle.classList.add("weekly-create-toast");
    toastEle.style.zIndex = "999";
    toastEle.style.position = "fixed";
    toastEle.style.left = "50%";
    toastEle.style.top = "10%";
    toastEle.style.margin = "auto";
    toastEle.style.padding = "10px 20px";
    toastEle.style.textAlign = "center";
    toastEle.style.fontSize = "14px";
    toastEle.style.color = "#fff";
    toastEle.style.lineHeight = "1";
    toastEle.style.borderRadius = "8px";
    toastEle.style.background = "rgba(0, 0, 0, 0.8)";
    toastEle.style.transform = "translate(-50%, -50%)";
    toastEle.appendChild(textEle);
    document.body.appendChild(toastEle);
    setTimeout(() => {
      document.body.removeChild(toastEle);
    }, 3200);
  }

  function appendButton() {
    const container = document.createElement("div");
    container.style.position = "fixed";
    container.style.zIndex = "100";
    container.style.padding = "10px";
    container.style.bottom = "40px";
    container.style.border = "1px solid #a8b3cf33";
    container.style.backgroundColor = "#f8f8f8";
    container.style.right = "10px";
    container.style.boxShadow =
      "0 6px 16px 0 rgb(0 0 0 / 8%), 0 3px 6px -4px rgb(0 0 0 / 12%), 0 9px 28px 8px rgb(0 0 0 / 5%)";
    container.style.borderRadius = "4px";
    container.style.display = "flex";
    container.style.alignItems = "center";
    container.style.justifyContent = "center";
    container.style.flexDirection = "column";

    const bodyText = "复制body";
    const getBody = createBtn(bodyText);
    getBody.addEventListener("click", () => {
      getBody.innerText = "生成中...";
      request()
        .then(function (result) {
          return JSON.parse(result).data;
        })
        .then(function (result) {
          const resBody = eval("(" + result.req_body_other + ")");
          const yapiObj = resBody;
          const tsText = yapiToTs(yapiObj);
          return tsText;
        })
        .then(function (ts) {
          copy(ts);
          getBody.innerText = bodyText;
          toast("复制成功");
        })
        .catch(function (e) {
          console.error(e);
          getBody.innerText = bodyText;
          toast("生成失败");
        });
    });

    const resText = "复制result";
    const getRes = createBtn(resText);
    getRes.addEventListener("click", () => {
      getRes.innerText = "生成中...";
      request()
        .then(function (result) {
          return JSON.parse(result).data;
        })
        .then(function (result) {
          const resBody = eval("(" + result.res_body + ")");
          const yapiObj = resBody?.properties?.result;
          const tsText = yapiToTs(yapiObj);
          return tsText;
        })
        .then(function (ts) {
          copy(ts);
          getRes.innerText = resText;
          toast("复制成功");
        })
        .catch(function (e) {
          console.error(e);
          getRes.innerText = resText;
          toast("生成失败");
        });
    });

    const dataText = "复制返回数据";
    const getData = createBtn(dataText);
    getData.addEventListener("click", () => {
      getData.innerText = "生成中...";
      request()
        .then(function (result) {
          return JSON.parse(result).data;
        })
        .then(function (result) {
          const resBody = eval("(" + result.res_body + ")");
          const yapiObj = resBody;
          const tsText = yapiToTs(yapiObj);
          return tsText;
        })
        .then(function (ts) {
          copy(ts);
          getData.innerText = dataText;
          toast("复制成功");
        })
        .catch(function (e) {
          console.error(e);
          getData.innerText = dataText;
          toast("生成失败");
        });
    });

    document.body.appendChild(container);
    container.appendChild(getBody);
    container.appendChild(getRes);
    container.appendChild(getData);
  }

  appendButton();
})();

  1. 左上角点击保存脚本,刷新页面

效果

脚本运行成功的话,页面右下角会有一个小气泡

image.png

点击其中一个按钮就可以生成并复制对应的ts代码,比如:

{
	test1?: number; // 描述
	test2: boolean; // 描述
	test3?: "ENUM1"|"ENUM2";  // 描述
	test4?: string[];  // 描述
	test5: {};  // 描述
        test6: {
            xxx: string; // 描述
        };
	test7?: {
            xxx: string; // 描述
        }[];
}