我们组维护的管理后台会接到很多开发需求,每次新开页面,就会到处复制黏贴相关代码。

  并且还会经常性的翻阅文档,先在书签或地址栏输入WIKI地址,然后找到那一份说明文档,再定位到要看的组件位置。

  虽然单人损耗的时间并不是非常多,但还是会打断思路,影响开发的流畅性,当把所有人的时间累加起来,那损耗的时间也很可观。

  为了能提升团队成员的开发效率,就开始构思一套可视化搭建系统。理想状态下,拖动组件,配置交互和样式,页面生成,直接可用。

  但是要完成这套功能,开发成本比较大,现在我想先解决当前的痛点,减少代码复制的频率和快速读取组件文档。

  为此,在构思了好多天后,打算搞一个半吊子的可视化搭建系统。

  所谓半吊子是指搭建完后,点击生成,会在后台创建视图和数据两个脚本文件、自动添加权限、新增菜单栏,不过后续我们还得继续做开发,完善页面功能。

一、界面

  界面分成左右两部分,左边是配置区域,右边空白处是组件的预览区域。

  

1)组件区域

  组件区域的第一个下拉框可以选择Ant Design和部分模板组件,选中后,会替换组件地址的链接,点击就能跳转到组件的说明文档。

  第二个下拉框能选择页面中需要的组件,例如图中的提示组件,点击添加后会在右边显示,并且还会提供一个删除图标,目前暂不支持拖动效果。

  

2)配置区域

  在配置区域中,可以输入菜单名称、路由、文件目录和权限等信息。

  原先的话,还得手动的在路由和权限两个文件中新增配置项,现在都能自动化了。

  原理就是先用Node分别读取这两份文件,得到一个数组,然后将配置内容塞到此数组中,再将数组序列化写入文件内。

  注意,需求在引入模块(调用require()函数)前删除模块缓存,否则读到的将是之前的文件内容。

//权限文件的绝对路径
const absAuthorityPath = pathObj.resolve(__dirname, 'src/utils/authority.ts');
delete require.cache[absAuthorityPath]; //删除模块缓存
const authorities = require(absAuthorityPath);
const obj = {
id: authority,
pid: parent,
name: menu,
desc: '',
routers: currentPath,
};
authorities.push(obj); //添加权限
//写入文件
fs.writeFileSync(absAuthorityPath, `module.exports = ${JSON.stringify(authorities, null, 2)}`);

  fs.writeFileSync()用于同步写入文件。module.exports是Node的模块语法,而export default是ES6语法,Node原生并不支持,好在webpack对于这些模块化语法都支持。

  一旦点击生成文件按钮,在项目重新构建后,左边菜单列表就能出现刚刚配置的菜单名称(例如名称叫菜单测试),并且能够跳转,权限也加好了。

  

  视图和数据文件也是用Node创建的,在Node项目中写好一份模板字符串(下面是生成视图模板的函数),将可变部分作为参数传入。

export function setPageTemplate({name, antd, namespace, code='', props, component}) {
return `import { connect, Dispatch, ${namespace}ModelState } from "umi";
import { setColumn } from '@/utils/tools';
import { TEMPLATE_MODEL } from '@/utils/constants';
${antd}
// 页面参数类型
interface ${name}Props {
dispatch: Dispatch;
state: ${namespace}ModelState;
}
// 全局声明
${code}
const ${name} = ({ dispatch, state }: ${name}Props) => {
// dispatch({ type: "xx/xx", payload: {} });
// 状态
// const { } = state;
// 通用组件配置
${props}
return <>
${component}
</>;
};
export default connect((data: {${namespace}: ${namespace}ModelState}) => ({ state: data.${namespace} }))(${name});`;
}

二、配置

  配置是本系统的核心,构思了很久,原先考虑了系统的灵活性,就想直接提供脚本编辑框,自定义逻辑。

  不过出现个问题,那就是我这边目前是用TypeScript语言开发的,那么我在自定义脚本逻辑时,也需要使用TypeScript语法。

  浏览器提供的 eval() 函数并不支持TypeScript语法,需要先做转译,网上搜索后,得到了解决方案,下载了TypeScript库后。

  但是却一直报错,在网上也查到了些解决方案(方案一方案二),不过并不适用于我目前的项目环境。

./node_modules/typescript/lib/typescript.js
Critical dependency: the request of a dependency is an expression ./node_modules/typescript/lib/typescript.js
Critical dependency: the request of a dependency is an expression ./node_modules/typescript/lib/typescript.js
Module not found: Can't resolve 'perf_hooks' in 'C:\Users\User\node_modules\typescript\lib'

  最终决定暂时放弃自定义脚本逻辑,先解决当前痛点,尽快将系统上线。

  期间还遇到个比较隐蔽的bug,如下所示,数组会先调用 toString() 转换成字符串,最终变为 eval("(1, 2)"),所以得到的值是 2。

eval(`(${[1,2]})`);  //2

  还遇到个问题,那就是在用 JSON.stringify() 序列化对象时,若参数是函数,那么就会被过滤掉。

JSON.stringify({func:() => {}});  //"{}"

1)物料库

  物料库中的组件分为两种,一种是自定义的后台模板组件,另一种是第三方的Ant Design 3.X组件。

  为了快速搭建页面,选择的组件是前者。这次顺便用TypeScript,再次完善了组件代码的类型声明。

  

  后者只是用来文档查询和在模板字符串中拼接引入语句,如下所示。

`import { ${antds.join(',')} } from 'antd';`

2)自定义组件

  自定义组件的声明采用JSON格式,TypeScript声明的类型如下所示。

interface OptionsType {
value: string;
label: string;
children: Array<{
value: string;
label: string;
link: string; //链接地址
readonlyProps?: ObjectType; //会影响组件的呈现,并且不能配置的属性
readonlyStrProps?: string; //待拼接的字符串属性
handleProps?: (values:ObjectType) => ObjectType; //在格式化表单数据后,再处理特定的组件属性
handleStrProps?: (values:ObjectType) => string; //拼接无法转换成字符串的属性
props: Array<{
label: string;
name: string;
params?: ObjectType;
control: JSX.Element | ((index: number) => JSX.Element);
type?: string;
initControl?: (props:any) => JSX.Element;
}>
}>;
}

  链接地址就是说明文档的地址,在组件的属性中,有一部分是回调函数,而目前已经舍弃了自定义的回调逻辑。

  所以这部分属性要特殊处理(声明在 readonlyProps),不能在界面中输入。

        readonlyProps: {
initPanes: (record: ObjectType): TabPaneType[] => [
{
name: "示例",
key: "demo",
controls: [
{ label: '测试组件', control: <>内容</> }
]
},
],
},

  readonlyStrProps 就是 readonlyProps 对应的字符串格式,该属性还会增加一些其它属性,配上注释,也相当于是份组件文档了。

        readonlyStrProps: `,
// 标签栏内容回调函数,参数为 record,当标签栏只有一项时,将不显示菜单
"initPanes": (record: ObjectType): TabPaneType[] => [
{
name: "示例",
key: "demo",
controls: [
{ label: '测试组件', control: <>内容</> }
]
},
],
// useEffect钩子中的回调函数,参数是 record
"effectCallback": (record: ObjectType) => {}`,

  handleProps() 是一个回调函数,在表单接收到数据后,有些组件需要再做一次特殊的处理。

  例如加些特定属性、数组元素合并成字符串等,从而才能顺利的在预览界面呈现。

        handleProps: (values:ObjectType) => { //对表单中的值做处理
// 对接口数组做特殊处理,从['api', 'get']转换成api.get
values.url && (values.url = values.url.join('.'));// 初始化表单需要的组件
if(values.controls.length === 0) {
values.controls = [
{
label: "示例",
name: "demo",
control: <>测试组件</>
},
];
}else {
values.originControls = values.controls; //备份组件名称数组
values.controls = values.controls.map((item:string) => getControls(item));
}
delete values.controlskeys; //删除冗余属性
return values;
},

  handleStrProps() 是在输出模板时使用,将那些特殊属性写成字符串形式。

        handleStrProps: (values:ObjectType):string => {
if(values.controls.length === 0) {
delete values.originControls; //删除备份数组
delete values.controls; //删除原始属性
return `,"controls": [
{
label: "示例",
name: "demo",
control: <>测试组件</>
},
]`;
}
// 组件名称数组处理
const newControls = values.originControls.map((item:string) => getStrControls(item));
delete values.originControls;
delete values.controls;
return `,"controls": [${newControls.join(',')}]`;
},

  在经过一系列的处理后,将一些字符串代码传递给接口,接口最后拼接成两个文件,输出到指定目录中。

  不过生成的代码,排版有点混乱,每次都还需要手动格式化一下。

Node.js躬行记(18)——半吊子的可视化搭建系统的更多相关文章

  1. Node.js躬行记(6)——自制短链系统

    短链顾名思义是一种很短的地址,应用广泛,例如页面中有一张二维码图片,包含的是一个原始地址(如下所示),如果二维码中的链接需要修改,那么就得发代码替换掉. 原始地址:https://github.com ...

  2. Node.js躬行记(24)——低代码

    低代码开发平台(LCDP)是无需编码(0代码)或通过少量代码就可以快速生成应用程序的开发平台.让具有不同经验水平的开发人员可以通过图形化的用户界面,通过拖拽组件和模型驱动的逻辑来创建网页和移动应用程序 ...

  3. Node.js躬行记(1)——Buffer、流和EventEmitter

    一.Buffer Buffer是一种Node的内置类型,不需要通过require()函数额外引入.它能读取和写入二进制数据,常用于解析网络数据流.文件等. 1)创建 通过new关键字初始化Buffer ...

  4. Node.js躬行记(2)——文件系统和网络

    一.文件系统 fs模块可与文件系统进行交互,封装了常规的POSIX函数.POSIX(Portable Operating System Interface,可移植操作系统接口)是UNIX系统的一个设计 ...

  5. Node.js躬行记(4)——自建前端监控系统

    这套前端监控系统用到的技术栈是:React+MongoDB+Node.js+Koa2.将性能和错误量化.因为自己平时喜欢吃菠萝,所以就取名叫菠萝系统.其实在很早以前就有这个想法,当时已经实现了前端的参 ...

  6. Node.js躬行记(15)——活动规则引擎

    在日常的业务开发中,会包含许多的业务规则,一般就是用if-else硬编码的方式实现,这样就会增加逻辑的维护成本,若无注释,可能都无法理解规则意图. 因为一旦规则有所改变,那么就需要修改代码再发布代码, ...

  7. Node.js躬行记(19)——KOA源码分析(上)

    本次分析的KOA版本是2.13.1,它非常轻量,诸如路由.模板等功能默认都不提供,需要自己引入相关的中间件. 源码的目录结构比较简单,主要分为3部分,__tests__,lib和docs,从名称中就可 ...

  8. Node.js躬行记(21)——花10分钟入门Node.js

    Node.js 不是一门语言,而是一个基于 V8 引擎的运行时环境,下图是一张架构图. 由图可知,Node.js 底层除了 JavaScript 代码之外,还有大量的 C/C++ 代码. 常说 Nod ...

  9. Node.js躬行记(23)——Worker threads

    Node.js 官方提供了 Cluster 和 Child process 创建子进程,通过 Worker threads 模块创建子线程.但前者无法共享内存,通信必须使用 JSON 格式,有一定的局 ...

随机推荐

  1. 假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以 某个固定的已知的前缀开头的,如果将它们全部找出来?

    使用 keys 指令可以扫出指定模式的 key 列表. 对方接着追问:如果这个 redis 正在给线上的业务提供服务,那使用 keys 指令会 有什么问题? 这个时候你要回答 redis 关键的一个特 ...

  2. elasticsearch 索引数据多了怎么办,如何调优,部署 ?

    面试官:想了解大数据量的运维能力. 解答:索引数据的规划,应在前期做好规划,正所谓"设计先行,编码在后", 这样才能有效的避免突如其来的数据激增导致集群处理能力不足引发的线上客户 ...

  3. Kafka 与传统 MQ 消息系统之间有三个关键区别?

    (1).Kafka 持久化日志,这些日志可以被重复读取和无限期保留 (2).Kafka 是一个分布式系统:它以集群的方式运行,可以灵活伸缩,在内部通过 复制数据提升容错能力和高可用性 (3).Kafk ...

  4. cornerstone 忽略不必要文件

    转:https://www.jianshu.com/p/f48207baa0cd

  5. Java如何声明一个数组?JS如何声明一个数组?如何获取数组长度

    1 Long[] numbers; //一般使用的定义方式,可分为静态和动态两种定义方式,下有说明. 2 Long numbers[]; //跟上面用法一致. 3 Long... numbers; / ...

  6. 字节码增强-learnning

    jvm加载java的过程主要是: 编写java文件->进行java文件的编译->生成.class字节码文件->jvm通过类加载器去加载生成的二进制文件 java编译器将源码文件编译称 ...

  7. 转:C++初始化成员列表

    转载至:https://blog.csdn.net/zlintokyo/article/details/6524185 C++初始化成员列表和新机制初始化表达式列表有几种用法: 1.如果类存在继承关系 ...

  8. Zookeeper Watcher 机制 -- 数据变更通知 ?

    Zookeeper 允许客户端向服务端的某个 Znode 注册一个 Watcher 监听,当服务 端的一些指定事件触发了这个 Watcher,服务端会向指定客户端发送一个事件通 知来实现分布式的通知功 ...

  9. 如何给 Spring 容器提供配置元数据?

    这里有三种重要的方法给 Spring 容器提供配置元数据. XML 配置文件. 基于注解的配置. 基于 java 的配置.

  10. 移动端比1px还小的border

    巧用border 在移动端 经常出现border,细边框但有的时候 产品大大1px甚至乎会觉得不够细那么要如何写出比1px还要小的border下面是代码 希望对大家有所帮助 .thinner-bord ...