{"id":15013016,"url":"https://github.com/ahyiru/web-design","last_synced_at":"2025-04-05T00:10:15.987Z","repository":{"id":40720882,"uuid":"387819787","full_name":"ahyiru/web-design","owner":"ahyiru","description":"Low-Code development platform.","archived":false,"fork":false,"pushed_at":"2025-01-15T11:44:23.000Z","size":46378,"stargazers_count":184,"open_issues_count":0,"forks_count":7,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-28T23:09:24.943Z","etag":null,"topics":["dashboard","express","full-stack","hooks","jsonschema","low-code","mongoose","nodejs","react","responsive","webgl","webpack"],"latest_commit_sha":null,"homepage":"https://ihuxy.com","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ahyiru.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-07-20T14:34:06.000Z","updated_at":"2025-02-13T14:39:12.000Z","dependencies_parsed_at":"2023-02-15T21:46:06.417Z","dependency_job_id":"e6602361-17fb-4b09-90d7-a88fa71f6abd","html_url":"https://github.com/ahyiru/web-design","commit_stats":{"total_commits":150,"total_committers":1,"mean_commits":150.0,"dds":0.0,"last_synced_commit":"e10c1e26ecc22c8341eb9897c5cb14310a57ce67"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahyiru%2Fweb-design","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahyiru%2Fweb-design/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahyiru%2Fweb-design/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahyiru%2Fweb-design/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ahyiru","download_url":"https://codeload.github.com/ahyiru/web-design/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247266565,"owners_count":20910836,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dashboard","express","full-stack","hooks","jsonschema","low-code","mongoose","nodejs","react","responsive","webgl","webpack"],"created_at":"2024-09-24T19:43:36.365Z","updated_at":"2025-04-05T00:10:15.971Z","avatar_url":"https://github.com/ahyiru.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## 低代码（low-code）简单实践\n\n[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/ahyiru/ihuxy/blob/develop/LICENSE)\n[![npm version](https://img.shields.io/npm/v/@huxy/router.svg)](https://www.npmjs.com/package/@huxy/router)\n[![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/ahyiru/web-design/lint-ci.yml?branch=master)](https://github.com/ahyiru/web-design/actions/workflows/lint-ci.yml)\n[![](https://img.shields.io/badge/blog-ihuxy-blue.svg)](http://ihuxy.com/)\n\n\nWeb Design 是一个简单的低代码开发平台，用于快速搭建项目、开发页面、配置接口等。\n\nWeb Design 使用可视化操作生成页面描述 `json schema`，然后通过自定义规则将 `json schema` 渲染成目标页面来实现页面可视化开发。\n\nWeb Design 基于 [huxy-admin](https://github.com/ahyiru/huxy-admin) 模板创建。除了提供页面可视化搭建，还包含了工程化配置、主题设计、Layout 设计、API 配置、权限和 i18n 配置等。\n\n### 详细功能介绍：[系统功能](https://github.com/ahyiru/huxy-admin/blob/develop/doc/functions.md)\n\n\n### 理解\n\n低代码就是尽量**少写代码**，定义好业务组件，通过可视化操作实现开发工作。它主要受众是开发者。\n\n无代码（no-code）即不需要写代码就能完成开发，更加偏向业务层的定制。\n\n### 解决了什么？\n\n\u003e 提效降本、质量保障、降低开发门槛。\n\n低代码可以提升开发效率，保障系统稳定性，也降低了开发门槛，可以直接可视化开发。\n\n### 可能存在的问题\n\n- 不灵活。适用于通用业务领域，对定制化需求不友好。\n- 不可控。业务拓展性、可维护性较低。\n- 不好用。开发不想用，业务不会用。\n\n低代码或许可以降低开发门槛，但复杂度并不会降低。可视化开发的自由度越高，组件粒度就越细，配置复杂度就越高。\n\n## 简单实践\n\n前端低代码开发不仅是界面开发，应该还包含工程化、项目管理、api 接口、权限控制等一些列的开发提效。\n\n### 设计\n\n一个页面其实就是一棵树，所以不管是拖拽还是配置，最终完成的就是一棵数据树。所以我们可以通过`json schema`来进行页面设计。\n\n### 实现\n\n#### 基础搭建\n\n[工程化](https://mp.weixin.qq.com/s?__biz=MzAwOTI3MTk3Nw==\u0026mid=2455985991\u0026idx=1\u0026sn=f1ee35b789a052518a9d2ade54134497\u0026chksm=8cf5d081bb825997f7570355d36a7f6e0b24f56d8613e1a2174c9800c8709b41bf4e8c31ae58\u0026=300862977\u0026=zh_CN#rd)、[layout 设计](https://mp.weixin.qq.com/s?__biz=MzAwOTI3MTk3Nw==\u0026mid=2455985961\u0026idx=1\u0026sn=bc656fc27eea4fc204903d87e70215ff\u0026chksm=8cf5d0efbb8259f9780c0ca630bc668843c6fed262395e8a8fc76c4559de40e5d212b73e6806\u0026=300862977\u0026=zh_CN#rd)、[权限和 i18n](https://mp.weixin.qq.com/s?__biz=MzAwOTI3MTk3Nw==\u0026mid=2455986033\u0026idx=1\u0026sn=1f5bc749f32df0e0cc84b08125a4bc32\u0026chksm=8cf5d0b7bb8259a18b1843e3c2693e72cfb40ff35c40707e32bf653e6e11ca1d6ae812c6cbcc\u0026=300862977\u0026=zh_CN#rd)、[API 管理](https://mp.weixin.qq.com/s?__biz=MzAwOTI3MTk3Nw==\u0026mid=2455986140\u0026idx=1\u0026sn=50b43b26a16abd10adf466a735a6da41\u0026chksm=8cf5d01abb82590c557cd3a80f6c5adb41194a7a501ae238bd39b28dcfe1319b9ad0fbc9ce4c\u0026=300862977\u0026=zh_CN#rd) 这些都是一些管理平台的基础设施，前面也讲过，大家可以去看看。\n\n### 页面设计\n\n#### schema 设计\n\n```javascript\nconst schema = {\n  type: 'a',\n  props: {},\n  children: [],\n};\n```\n\n- type：标签名或组件名。组件可以是 UI 组件或业务组件，先注册再使用。\n- props：属性配置。组件的属性可根据组件库或自定义组件使用文档去配置。如果属性里面含有组件，可依照 schema 渲染原则执行。\n- children：子节点。可以是文本节点，组件，或子元素列表。\n\n#### custom render\n\n```javascript\nconst render = (schema, params) =\u003e {\n  schema = Array.isArray(schema) ? schema : [schema];\n  const dom = schema.map((item, i) =\u003e {\n    let {type, props, children} = item;\n    type = (type || 'div').trim();\n    const first = type.charAt(0);\n    type = first.toUpperCase() === first ? components[type] || 'div' : type;\n    props = {\n      key: i,\n      ...formatProps(props, params),\n    };\n    children = Array.isArray(children) ? render(children, params) : [formatChildren(children || props.children, params) ?? null];\n    return createElement(type, props, ...children);\n  });\n  return dom;\n};\n```\n\n- components：我们注册的组件。\n- formatProps、formatChildren：将 props 或 children 转换为我们需要的运行时的值。主要用于我们自定义的组件。props 或 children 可以是函数，可以传递我们需要的参数 params，最终返回我们需要的数据。\n- render：通过 react 的 `createElement(type,props,...children)` 渲染。\n\n**_属性解析_**\n\n```javascript\nconst render = (schema, params) =\u003e {\n  schema = Array.isArray(schema) ? schema : [schema];\n  const dom = schema.map((item, i) =\u003e {\n    let {type, props, children} = item;\n    type = (type || 'div').trim();\n    const first = type.charAt(0);\n    type = first.toUpperCase() === first ? components[type] || 'div' : type;\n    props = {\n      key: i,\n      ...formatProps(props, params),\n    };\n    children = Array.isArray(children) ? render(children, params) : [formatChildren(children || props.children, params) ?? null];\n    return createElement(type, props, ...children);\n  });\n  return dom;\n};\n```\n\n可使用自定义函数，组件内部数据作为参数，来获取属性值。或直接使用全局 configs。\n\n例如：\n\n```javascript\n{\n  prop:'test',\n  isPending:`{true}`,\n  style:`{{width:'800px'}}`,\n  handle:`{self=\u003eself.rules}`,\n  onClick:`{()=\u003ee=\u003ealert('hello')}`,\n}\n\n```\n\n通过 `{code}` 将 code 字符串转换为运行时代码。\n\n**_判断并提取字符串代码_**\n\n```javascript\nconst matchedStr = (str, c = ['{', '}']) =\u003e str?.trim?.().match(new RegExp(`^${c[0]}([\\\\s\\\\S]*)${c[1]}$`))?.[1];\n```\n\n**_执行字符串代码_**\n\n```javascript\nconst str2code = (str, hasReturn = false) =\u003e {\n  str = hasReturn ? str : `return ${str};`;\n  const exec = Function(str);\n  return exec();\n};\n```\n\n\u003e str2code 会直接执行并返回结果，如果返回的是函数会执行函数并返回结果。如果我们需要返回函数，就要包裹一层函数。例如：`onClick`，`{()=\u003ee=\u003ealert('hello')}`。\n\n**_路由设置_**\n\n路由配置可直接在页面配置，存入后台，使用时获取路由配置即可。\n\n```javascript\n{\n  path:'/low-code',\n  name:'低代码',\n  icon:'CoffeeOutlined',\n  denied:false,\n  children:[\n    {\n      path:'/dom',\n      name:'原生dom',\n      icon:'CodeOutlined',\n      componentPath:'/lowCode',\n      loadData:{\n        pageSchema,\n      },\n    },\n    {\n      path:'/ui',\n      name:'UI组件',\n      icon:'CodeOutlined',\n      componentPath:'/lowCode',\n      loadData:{\n        pageSchema,\n      },\n    },\n    {\n      path:'/users',\n      name:'业务组件',\n      icon:'CodeOutlined',\n      componentPath:'/lowCode',\n      loadData:{\n        pageSchema,\n      },\n    },\n    {\n      path:'/users/add',\n      name:'新增用户',\n      componentPath:'/lowCode',\n      loadData:{\n        pageSchema,\n      },\n    },\n    {\n      path:'/users/edit/:id',\n      name:'编辑用户',\n      componentPath:'/lowCode',\n      loadData:{\n        pageSchema,\n      },\n    },\n  ],\n}\n\n```\n\n如果整个系统都是通过 `schema` 数据配置生成的，那么我们只需一个渲染器，通过路由 id 获取到 `shcema` 数据，然后渲染成当前路由页面。所以只需一个渲染文件即可。\n\n**_根据 `projectId`、`routerId` 获取路由页面数据。_**\n\n```javascript\nconst pageSchema = async ({id}) =\u003e {\n  const {result} = await apiList.listSchemaFn({routerId: id, projectId: defProject._id});\n  return {result};\n};\n```\n\n通过设置路由 `loadData` 来提前请求数据，页面直接获取即可。详细使用见 [useRouter](https://mp.weixin.qq.com/s?__biz=MzAwOTI3MTk3Nw==\u0026mid=2455986102\u0026idx=1\u0026sn=4328f6e2d4d3077d7aac4962dbbaa736\u0026chksm=8cf5d070bb8259661c6782d0235e12afce48fe6ce6d63139066a2d264feba61ee3769b32a66b\u0026=300862977\u0026=zh_CN#rd)\n\n```javascript\nconst pageSchema = async ({id}) =\u003e {\n  const {result} = await apiList.listSchemaFn({routerId: id, projectId: defProject._id});\n  return {result};\n};\n```\n\n**_页面渲染_**\n\n```javascript\nconst Index = props =\u003e {\n  const {pageSchema} = props;\n  if (pageSchema == null || pageSchema.pending) {\n    return \u003cSpinner global /\u003e;\n  }\n  return customRender(pageSchema.result || [], {}, props);\n};\n```\n\n### 可视化开发示例\n\n#### 创建项目\n\n![1](./doc/img/1.png)\n\n首先我们创建一个项目，如图所示。本示例使用 `控制台` 项目演示。\n\n![2](./doc/img/2.png)\n\n#### 创建用户并分配项目\n\n![3](./doc/img/3.png)\n\n#### 创建 API\n\n![4](./doc/img/4.png)\n\n![5](./doc/img/5.png)\n\n#### 新建项目路由\n\n![6](./doc/img/6.png)\n\n#### 为用户设置路由权限\n\n![7](./doc/img/7.png)\n\n### 页面设计\n\n#### 原生 html 标签\n\n![10](./doc/img/10.png)\n\n![23](./doc/img/23.png)\n\n根据 dom 元素属性自行配置。\n\n#### UI 组件\n\n![12](./doc/img/12.png)\n\n![14](./doc/img/14.png)\n\n当我们设计好页面时，可以随时回到项目路由查看改页面，也可点击预览查看，符合预期效果后保存即可。\n\n![22](./doc/img/22.png)\n\n![25](./doc/img/25.png)\n\n原生标签和基础组件只能设计出一些静态展示效果，我们可以自定义一些业务组件，给页面加入交互性。\n\n#### 业务组件\n\n以 `table` 和 `form` 为例，简单设计一个用户管理页面。\n\n![15](./doc/img/15.png)\n\n为 `table` 设置了自定义属性 `actions `、`columns `、`searchSchema `、`modalSchema `\n\n```javascript\n{\n  actions,\n  columns,\n  searchSchema,\n  modalSchema,\n}\n\n```\n\n- actions：定义事件\n- columns：表头设计\n- searchSchema：搜索表单\n- modalSchema：弹窗表单\n\n![16](./doc/img/16.png)\n\n事件定义可自行定义 `action name` ，共页面使用，`apiName` 从我们 API 系统里面选。\n\n**_预览_**\n\n![17](./doc/img/17.png)\n\n可实时进行页面预览，也提供了撤销重做操作。\n\n#### 编辑功能\n\n**_props 编辑_**\n\n```javascript\nconst editProps = values =\u003e {\n  const tree = editNodes(schemaTree, selectedKey, {props: values}, 'key');\n  setSchema(tree);\n  record(clone(tree));\n  setDisableUndo(false);\n};\n```\n\n每次编辑都会触发 `schema` 更新，并会记录每次操作的数据，使用 `record` 函数记录，便于我们完成撤销重做功能。\n\n**_cacheData 函数_**\n\n```javascript\nconst {record, undo, redo, clean} = cacheData();\n```\n\n**_撤销重做_**\n\n```javascript\nconst undoDesign = () =\u003e {\n  const {index, data} = undo();\n  setSchema(data);\n  if (index === 0) {\n    setDisableUndo(true);\n  }\n  setDisableRedo(false);\n};\n\nconst redoDesign = () =\u003e {\n  const {index, length, data} = redo();\n  setSchema(data);\n  if (index === length - 1) {\n    setDisableRedo(true);\n  }\n  setDisableUndo(false);\n};\n```\n\n**_组件移动_**\n\n提供了组件移动功能，可根据需要自行拖动。\n\n![27](./doc/img/27.png)\n\n```javascript\nconst onDrop = info =\u003e {\n  const fromId = info.dragNode.key;\n  const toId = info.node.key;\n  const dropPosition = info.dropPosition;\n  const tree = moveNodes(schemaTree, fromId, toId, dropPosition, 'key');\n  setSchema(tree);\n};\n```\n\n#### 效果\n\n可点击按钮或链接查看效果。\n\n![28](./doc/img/28.png)\n\n页面 `schema` ：\n\n![29](./doc/img/29.png)\n\n**_用户管理页面_**\n\n![18](./doc/img/18.png)\n\n页面 `schema`：\n\n![30](./doc/img/30.png)\n\n编辑页面\n\n![19](./doc/img/19.png)\n\n页面 `schema`：\n\n![31](./doc/img/31.png)\n\n### 总结\n\n低代码更多的是用来当作提升开发效率的一个工具，在我们当前业务范围内，写少量代码封装好业务组件，即可进行可视化开发。\n\n平台的通用性和灵活性，需要我们在实际业务中去权衡。\n\n我们需要认清，没有一劳永逸的方法，只有在不断探索中提升。\n\n项目地址：[https://github.com/ahyiru/web-design](https://github.com/ahyiru/web-design)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fahyiru%2Fweb-design","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fahyiru%2Fweb-design","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fahyiru%2Fweb-design/lists"}