Node.js躬行记(18)——半吊子的可视化搭建系统

简介:   我们组维护的管理后台会接到很多开发需求,每次新开页面,就会到处复制黏贴相关代码。  并且还会经常性的翻阅文档,先在书签或地址栏输入WIKI地址,然后找到那一份说明文档,再定位到要看的组件位置。  虽然单人损耗的时间并不是非常多,但还是会打断思路,影响开发的流畅性,当把所有人的时间累加起来,那损耗的时间也很可观。

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

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

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

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

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

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

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


一、界面


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


5.png




1)组件区域

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

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


6.png


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对于这些模块化语法都支持。

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


7.png


  视图和数据文件也是用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,再次完善了组件代码的类型声明。


7.png


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

`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(',')}]`;
        },


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

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

 

相关文章
|
16天前
|
开发框架 前端开发 JavaScript
C# 6.0+JavaScript云LIS系统源码  云LIS实验室信息管理新型解决方案
云LIS是为区域医疗提供临床实验室信息服务的计算机应用程序,可协助区域内所有临床实验室相互协调并完成日常检验工作,对区域内的检验数据进行集中管理和共享,通过对质量控制的管理,最终实现区域内检验结果互认。其目标是以医疗服务机构为主体,以医疗资源和检验信息共享为目标,集成共性技术及医疗服务关键技术,建立区域协同检验,最大化利用有限的医疗卫生资源。
26 1
|
12天前
|
安全 数据管理 中间件
云LIS系统源码JavaScript+B/S架构MVC+SQLSugar医院版检验科云LIS系统源码 可提供演示
检验科云LIS系统源码是医疗机构信息化发展的重要趋势。通过云计算技术实现数据的集中管理和共享可以提高数据利用效率和安全性;通过高效灵活的系统设计和可扩展性可以满足不同医疗机构的需求;通过移动性和智能化可以提高医疗服务的精准度和效率;通过集成性可以实现医疗服务的协同性和效率。因此,多医院版检验科云LIS系统源码将成为未来医疗机构信息化发展的重要方向之一。
24 2
|
10天前
|
缓存 JavaScript 前端开发
Node.js的模块系统:CommonJS模块系统的使用
【4月更文挑战第29天】Node.js采用CommonJS作为模块系统,每个文件视为独立模块,通过`module.exports`导出和`require`引入实现依赖。模块有独立作用域,保证封装性,防止命名冲突。引入的模块会被缓存,提高加载效率并确保一致性。利用CommonJS,开发者能编写更模块化、可维护的代码。
|
14天前
|
开发框架 前端开发 JavaScript
JavaScript云LIS系统源码ASP.NET CORE 3.1 MVC + SQLserver + Redis医院实验室信息系统源码 医院云LIS系统源码
实验室信息系统(Laboratory Information System,缩写LIS)是一类用来处理实验室过程信息的软件,云LIS系统围绕临床,云LIS系统将与云HIS系统建立起高度的业务整合,以体现“以病人为中心”的设计理念,优化就诊流程,方便患者就医。
21 0
|
15天前
|
存储 开发框架 前端开发
C#开发的全套成熟的LIS系统源码JavaScript+SQLserver 2012区域云LIS系统源码
医院云LIS系统是一套成熟的实验室信息管理系统,目前已在多家三级级医院应用,并不断更新。云LIS系统是为病人为中心、以业务处理为基础、以提高检验科室管理水平和工作效率为目标,将医学检验、科室管理和财务统计等检验科室/实验室所有工作进行整合,全面改善检验科室/实验室的工作现状。
18 0
|
24天前
|
开发框架 前端开发 JavaScript
采用C#.Net +JavaScript 开发的云LIS系统源码 二级医院应用案例有演示
技术架构:Asp.NET CORE 3.1 MVC + SQLserver + Redis等 开发语言:C# 6.0、JavaScript 前端框架:JQuery、EasyUI、Bootstrap 后端框架:MVC、SQLSugar等 数 据 库:SQLserver 2012
22 0
|
24天前
|
JavaScript API
node.js之模块系统
node.js之模块系统
|
27天前
|
缓存 并行计算 JavaScript
【Node系列】模块系统
Node.js 的模块系统是其核心特性之一,允许开发者编写可复用的代码,并通过简单的导入和导出机制来共享和使用这些模块。
20 3
|
2月前
|
算法 JavaScript 前端开发
游戏物理系统 - 如何在JavaScript中实现基本的碰撞检测算法?
在JavaScript中实现2D矩形碰撞检测,常用AABB方法,适合简单游戏。创建Rectangle类,包含位置和尺寸属性,并定义`collidesWith`方法检查两矩形是否相交。通过比较边界位置判断碰撞,当四条边界条件均满足时,认定发生碰撞。基础算法适用于初级需求,复杂场景可采用更高级的碰撞检测库。
13 1
|
3月前
|
JavaScript 前端开发 关系型数据库
分享66个NodeJs系统源码总有一个是你想要的
分享66个NodeJs系统源码总有一个是你想要的
35 1