Node.js躬行记(18)——半吊子的可视化搭建系统
我们组维护的管理后台会接到很多开发需求,每次新开页面,就会到处复制黏贴相关代码。
并且还会经常性的翻阅文档,先在书签或地址栏输入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)——半吊子的可视化搭建系统的更多相关文章
- Node.js躬行记(6)——自制短链系统
短链顾名思义是一种很短的地址,应用广泛,例如页面中有一张二维码图片,包含的是一个原始地址(如下所示),如果二维码中的链接需要修改,那么就得发代码替换掉. 原始地址:https://github.com ...
- Node.js躬行记(24)——低代码
低代码开发平台(LCDP)是无需编码(0代码)或通过少量代码就可以快速生成应用程序的开发平台.让具有不同经验水平的开发人员可以通过图形化的用户界面,通过拖拽组件和模型驱动的逻辑来创建网页和移动应用程序 ...
- Node.js躬行记(1)——Buffer、流和EventEmitter
一.Buffer Buffer是一种Node的内置类型,不需要通过require()函数额外引入.它能读取和写入二进制数据,常用于解析网络数据流.文件等. 1)创建 通过new关键字初始化Buffer ...
- Node.js躬行记(2)——文件系统和网络
一.文件系统 fs模块可与文件系统进行交互,封装了常规的POSIX函数.POSIX(Portable Operating System Interface,可移植操作系统接口)是UNIX系统的一个设计 ...
- Node.js躬行记(4)——自建前端监控系统
这套前端监控系统用到的技术栈是:React+MongoDB+Node.js+Koa2.将性能和错误量化.因为自己平时喜欢吃菠萝,所以就取名叫菠萝系统.其实在很早以前就有这个想法,当时已经实现了前端的参 ...
- Node.js躬行记(15)——活动规则引擎
在日常的业务开发中,会包含许多的业务规则,一般就是用if-else硬编码的方式实现,这样就会增加逻辑的维护成本,若无注释,可能都无法理解规则意图. 因为一旦规则有所改变,那么就需要修改代码再发布代码, ...
- Node.js躬行记(19)——KOA源码分析(上)
本次分析的KOA版本是2.13.1,它非常轻量,诸如路由.模板等功能默认都不提供,需要自己引入相关的中间件. 源码的目录结构比较简单,主要分为3部分,__tests__,lib和docs,从名称中就可 ...
- Node.js躬行记(21)——花10分钟入门Node.js
Node.js 不是一门语言,而是一个基于 V8 引擎的运行时环境,下图是一张架构图. 由图可知,Node.js 底层除了 JavaScript 代码之外,还有大量的 C/C++ 代码. 常说 Nod ...
- Node.js躬行记(23)——Worker threads
Node.js 官方提供了 Cluster 和 Child process 创建子进程,通过 Worker threads 模块创建子线程.但前者无法共享内存,通信必须使用 JSON 格式,有一定的局 ...
随机推荐
- const char * 组合理解
1 . const char *ptr 从char *ptr 可以理解为指向字符常量的指针,ptr是一个指向char *的常量,*ptr的值为const,不能修改. 2. char const *pt ...
- 什么是内部类? Static Nested Class 和 Inner Class的不同?
内部类就是在一个类的内部定义的类,内部类中不能定义静态成员. 内部类作为其外部类的一个成员,因此内部类可以直接访问外部类的成员.但有一点需要指出:静态成员不能访问非静态成员,因此静态内部类不能访问外部 ...
- Dubbo 可以对结果进行缓存吗?
为了提高数据访问的速度.Dubbo 提供了声明式缓存,以减少用户加缓存的工作 量 <dubbo:reference cache="true" /> 其实比普通的配置文件 ...
- 什么是 Mybatis?
1.Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时 只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动.创建连接.创建 statement 等繁杂的过程. ...
- ZAB 和 Paxos 算法的联系与区别?
相同点: 1.两者都存在一个类似于 Leader 进程的角色,由其负责协调多个 Follower 进程的运行 2.Leader 进程都会等待超过半数的 Follower 做出正确的反馈后,才会将一个提 ...
- java中的generate
流generate(Supplier s)返回无限顺序无序流,其中每个元素由提供的供应商生成.这适用于生成恒定流,随机元素流等. public class Flow { public static v ...
- window10使用putty传输文件到Linux服务器
由于Linux和Linux可以使用scp进行传输文件,而window系统无法向Linux传输文件,当然,有xshell等等类似的工具可以进行操作:putty工具就可以实现,毕竟zip压缩包也不大,启动 ...
- Linux运维最常用150个命令
线上查询及帮助命令(2个) man 查看命令帮助,命令的词典,更复杂的还有info,但不常用. help 查看Linux内置命令的帮助,比如cd命令. 文件和目录操作命令(18个) ls 全拼list ...
- TIME_WAIT 优化
·[场景描述] HTTP1.1之后,HTTP协议支持持久连接,也就是长连接,优点在于在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟. 如果我们使用了nginx去作为 ...
- Flex布局在小程序的使用
一篇旧文,上手小程序时做的一些探索 Flex布局是一种十分灵活方便的布局方式,目前主流的现代浏览器基本都实现了对Flex布局的完全支持.而在微信小程序中,IOS端使用的渲染引擎WKWebView和安卓 ...