目标:在浏览器控制台输入js代码,将读取页面所展示的请求参数和响应参数,将他们转化为TS的类型声明,并在控制台中输出出来。
将Knife4j所展示请求参数和响应参数转化为TS类型声明
- 1 找到所需要的元素节点
- 2 转化元素节点
- 3 封装成函数
- 4 全部代码
- 5 代码压缩
- 6 使用说明及成果
1 找到所需要的元素节点
根据图1、图2所示,分析页面结构找到对应的参数行节点
图1 响应参数页面截图
图2 页面元素结构
let elements = null;
// 获取所有class为"api-title"的元素
document.getElementsByClassName("api-title").forEach((item) => {
// 如果找到与传入参数title相匹配的元素
if (item.innerText === title)
// 将elements设置为该元素的下一个兄弟元素,这些元素包含"ant-table-row"类
elements = item.nextSibling.getElementsByClassName("ant-table-row");
});
2 转化元素节点
现有的元素节点结构使用起来较为费劲,并且浪费性能,需要将其转化为我们需要的数据格式
先转化为列表
// 初始化一些变量,用于后续的数据结构构建
let minLevel = 0, // 初始化最小级别为100
maxLevel = 0, // 初始化最大级别为0
objectArray = [], // 创建一个空数组,用于存储对象
load = { datas: [] }, // 创建一个对象,用于临时存储数据
continuity = false, // 初始化连续性标志为false
textLoad = ""; // 初始化文本承载字符串为空
// 根据传入的title参数,创建一个基础对象,并将其添加到objectArray数组中
objectArray.push({
class: "-1",
key: title === "请求参数" ? "request" : "response",
title: title === "请求参数" ? "request" : "response",
type: title === "请求参数" ? "request" : "response",
});
// 遍历elements数组中的每个元素
elements.forEach((item) => {
// 获取当前元素的级别,并更新minLevel和maxLevel
if (item.className.split("").at(-1) > maxLevel)
maxLevel = item.className.split("").at(-1);
if (item.className.split("").at(-1) < minLevel)
minLevel = item.className.split("").at(-1);
// 创建一个对象,包含当前元素的相关信息,并添加到objectArray数组中
objectArray.push({
class: item.className.split("").at(-1),
key: item.children[0].innerText, // 参数名称
title: item.children[1].innerText, // 参数说明
type: item.children[title === "请求参数" ? 4 : 2].innerText, // 类型
require: title === "请求参数" ? item.children[3].innerText : "", // 是否必须
});
});
再定义两个函数便于将数据转换为字符串
// 定义generate函数,用于生成TypeScript接口代码
function generate(item, load) {
// 构建接口的开始部分
let temp = `export interface ${format(item.key)} { \n`;
// 遍历load.datas数组中的每个数据项
load.datas.forEach((value) => {
// 获取数据项的类型,并根据类型构建TypeScript类型字符串
let type = value.type;
let tempType = "";
if (type.indexOf("integer") >= 0) tempType = "number";
else if (type.indexOf("number") >= 0) tempType = "number";
else if (type.indexOf("string") >= 0) tempType = "string";
else if (type.indexOf("boolean") >= 0) tempType = "boolean";
else if (type.indexOf("分页返回") >= 0) tempType = `${format(value.key)}`;
else if (type.indexOf("array") >= 0) tempType = `${format(value.key)}[]`;
else tempType = "any";
// 构建接口的属性定义,并添加到temp字符串中
temp += `\t${value.key}${
value.require === "false" ? "?" : ""
}: ${tempType}; // ${value.title} \n`;
});
// 构建接口的结束部分,并将其添加到textLoad字符串中
return temp += "}\n";
}
// 定义format函数,用于将字符串的首字母大写
function format(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
最后将列表数据转化为树形结构,同时拼接字符串
// 从最大级别开始向下遍历,构建最终的数据结构
while (minLevel <= maxLevel) {
for (let i = objectArray.length - 1; i >= 0; i--) {
// 如果当前对象的级别不等于最大级别,设置连续性为false
if (objectArray[i].class != maxLevel) continuity = false;
// 如果当前对象的级别等于最大级别
if (objectArray[i].class == maxLevel) {
// 如果load.datas数组为空,设置load.end为当前索引
if (load.datas.length == 0) load.end = i;
// 设置连续性为true
continuity = true;
// 将当前对象添加到load.datas数组中
load.datas.unshift(objectArray[i]);
}
// 如果load.datas数组不为空且连续性为false,执行数据处理
if (load?.datas.length > 0 && !continuity) {
// 设置load.start为当前索引+1
load.start = i + 1;
// 将load.datas数组中的数据作为当前对象的children属性
objectArray[i].children = load.datas;
// 调用generate函数,生成TypeScript接口代码
textLoad = generate(objectArray[i], load) + textLoad;
// 从objectArray数组中移除已处理的数据
objectArray.splice(load.start, load.end - load.start + 1);
// 重置load对象
load = { datas: [] };
}
}
// 减小最大级别,继续下一轮循环
maxLevel--;
}
3 封装成函数
封装成函数便于调用
// 定义getText函数,接受一个参数title,表示要查找的标题
function getText(title) {
// 上方的所有代码
// 返回生成的TypeScript接口代码
return textLoad;
}
// 调用getText函数,打印生成的TypeScript接口代码
console.log(getText("请求参数") + "\n" + getText("响应参数"));
4 全部代码
// 这段代码的主要功能是将HTML中的表格数据转换为TypeScript接口定义,以便在TypeScript项目中使用。代码中使用了递归和循环来处理嵌套的数据结构,并且通过生成TypeScript代码的方式,使得数据结构更加清晰和易于维护。
// 定义getText函数,接受一个参数title,表示要查找的标题
function getText(title) {
let elements = null;
// 获取所有class为"api-title"的元素
document.getElementsByClassName("api-title").forEach((item) => {
// 如果找到与传入参数title相匹配的元素
if (item.innerText === title)
// 将elements设置为该元素的下一个兄弟元素,这些元素包含"ant-table-row"类
elements = item.nextSibling.getElementsByClassName("ant-table-row");
});
// 初始化一些变量,用于后续的数据结构构建
let minLevel = 0, // 初始化最小级别为100
maxLevel = 0, // 初始化最大级别为0
objectArray = [], // 创建一个空数组,用于存储对象
load = { datas: [] }, // 创建一个对象,用于临时存储数据
continuity = false, // 初始化连续性标志为false
textLoad = ""; // 初始化文本承载字符串为空
// 根据传入的title参数,创建一个基础对象,并将其添加到objectArray数组中
objectArray.push({
class: "-1",
key: title === "请求参数" ? "request" : "response",
title: title === "请求参数" ? "request" : "response",
type: title === "请求参数" ? "request" : "response",
});
// 遍历elements数组中的每个元素
elements.forEach((item) => {
// 获取当前元素的级别,并更新minLevel和maxLevel
if (item.className.split("").at(-1) > maxLevel)
maxLevel = item.className.split("").at(-1);
if (item.className.split("").at(-1) < minLevel)
minLevel = item.className.split("").at(-1);
// 创建一个对象,包含当前元素的相关信息,并添加到objectArray数组中
objectArray.push({
class: item.className.split("").at(-1),
key: item.children[0].innerText, // 参数名称
title: item.children[1].innerText, // 参数说明
type: item.children[title === "请求参数" ? 4 : 2].innerText, // 类型
require: title === "请求参数" ? item.children[3].innerText : "", // 是否必须
});
});
// 从最大级别开始向下遍历,构建最终的数据结构
while (minLevel <= maxLevel) {
for (let i = objectArray.length - 1; i >= 0; i--) {
// 如果当前对象的级别不等于最大级别,设置连续性为false
if (objectArray[i].class != maxLevel) continuity = false;
// 如果当前对象的级别等于最大级别
if (objectArray[i].class == maxLevel) {
// 如果load.datas数组为空,设置load.end为当前索引
if (load.datas.length == 0) load.end = i;
// 设置连续性为true
continuity = true;
// 将当前对象添加到load.datas数组中
load.datas.unshift(objectArray[i]);
}
// 如果load.datas数组不为空且连续性为false,执行数据处理
if (load?.datas.length > 0 && !continuity) {
// 设置load.start为当前索引+1
load.start = i + 1;
// 将load.datas数组中的数据作为当前对象的children属性
objectArray[i].children = load.datas;
// 调用generate函数,生成TypeScript接口代码
textLoad = generate(objectArray[i], load) + textLoad;
// 从objectArray数组中移除已处理的数据
objectArray.splice(load.start, load.end - load.start + 1);
// 重置load对象
load = { datas: [] };
}
}
// 减小最大级别,继续下一轮循环
maxLevel--;
}
// 定义generate函数,用于生成TypeScript接口代码
function generate(item, load) {
// 构建接口的开始部分
let temp = `export interface ${format(item.key)} { \n`;
// 遍历load.datas数组中的每个数据项
load.datas.forEach((value) => {
// 获取数据项的类型,并根据类型构建TypeScript类型字符串
let type = value.type;
let tempType = "";
if (type.indexOf("integer") >= 0) tempType = "number";
else if (type.indexOf("number") >= 0) tempType = "number";
else if (type.indexOf("string") >= 0) tempType = "string";
else if (type.indexOf("boolean") >= 0) tempType = "boolean";
else if (type.indexOf("分页返回") >= 0) tempType = `${format(value.key)}`;
else if (type.indexOf("array") >= 0) tempType = `${format(value.key)}[]`;
else tempType = "any";
// 构建接口的属性定义,并添加到temp字符串中
temp += `\t${value.key}${
value.require === "false" ? "?" : ""
}: ${tempType}; // ${value.title} \n`;
});
// 构建接口的结束部分,并将其添加到textLoad字符串中
return temp += "}\n";
}
// 定义format函数,用于将字符串的首字母大写
function format(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
// 返回生成的TypeScript接口代码
return textLoad;
}
// 调用getText函数,打印生成的TypeScript接口代码
console.log(getText("请求参数") + "\n" + getText("响应参数"));
5 代码压缩
内容太长了,复制和使用起来都很不便捷,为方便使用rollup将其压缩,压缩后代码如下方所示
function e(e){let t=null;document.getElementsByClassName("api-title").forEach((n=>{n.innerText===e&&(t=n.nextSibling.getElementsByClassName("ant-table-row"))}));let n=0,s=0,a=[],l={datas:[]},r=!1,i="";for(a.push({class:"-1",key:"请求参数"===e?"request":"response",title:"请求参数"===e?"request":"response",type:"请求参数"===e?"request":"response"}),t.forEach((t=>{t.className.split("").at(-1)>s&&(s=t.className.split("").at(-1)),t.className.split("").at(-1)<n&&(n=t.className.split("").at(-1)),a.push({class:t.className.split("").at(-1),key:t.children[0].innerText,title:t.children[1].innerText,type:t.children["请求参数"===e?4:2].innerText,require:"请求参数"===e?t.children[3].innerText:""})}));n<=s;){for(let e=a.length-1;e>=0;e--)a[e].class!=s&&(r=!1),a[e].class==s&&(0==l.datas.length&&(l.end=e),r=!0,l.datas.unshift(a[e])),l?.datas.length>0&&!r&&(l.start=e+1,a[e].children=l.datas,i=c(a[e],l)+i,a.splice(l.start,l.end-l.start+1),l={datas:[]});s--}function c(e,t){let n=`export interface ${o(e.key)} { \n`;return t.datas.forEach((e=>{let t=e.type,s="";s=t.indexOf("integer")>=0||t.indexOf("number")>=0?"number":t.indexOf("string")>=0?"string":t.indexOf("boolean")>=0?"boolean":t.indexOf("分页返回")>=0?`${o(e.key)}`:t.indexOf("array")>=0?`${o(e.key)}[]`:"any",n+=`\t${e.key}${"false"===e.require?"?":""}: ${s}; // ${e.title} \n`})),n+="}\n"}function o(e){return e.charAt(0).toUpperCase()+e.slice(1)}return i}console.log(e("请求参数")+"\n"+e("响应参数"));
6 使用说明及成果
将第五部分的代码复制到控制台,进行回车
最后将控制台钟打印出来的TS类型声明复制到代码中就可以直接使用了