被动客制化服务转为主动搭建服务
前言
从前端角度看,大量客户定制化需求带来的是非常繁重的交付任务,需要前端开发者消耗大量精力处理重复性高、技术难度低的工作内容,同时也会给企业带来更高的开发成本、更低的人效。
前端开发者如何从定制业务中脱身,可以参考市场上已有的行业化解决方案,即低代码平台(Low Code
)和无代码平台(No Code
)。它的能力使前端开发者的角色由“页面的生产者”转换为“组件的生产者”。
阿里宜搭低代码平台
可视化组件拖拽搭建页面:
配置页面级数据源,数据驱动UI
更新:
编辑页面JS Code
,组件联动处理:
阿里已开源一套成熟的低代码框架:https://lowcode-engine.cn/
有赞移动端商城无代码搭建平台
可视化组件拖拽搭建页面:
Low Code
和No Code
的差异
低代码工具使用群体面向开发者角色,业务覆盖人群是初中级开发者,工作流程上极大降低重复性工作,因为基础单元是通用组件,所以最终产出页面风格统一,代码容易维护。
无代码平台使用群体面向运营角色,业务覆盖人群是所有普通用户,对比低代码工具,客制化能力弱,但是学习成本低。
搭建平台中的物料系统(Material Design
)
不管是低代码还是无代码,交付客户的需求拆分为基础单元,需要一个物料系统承载,所谓“物料”即聚合的通用组件+客制化组件。
- 通用组件常驻于组件区
- 客制组件托管于物料市场
1. 元组件
如“按钮、输入框、文本、标题、下拉框、价格、营销标签”等最细粒度的组件,特点是极少需要更新、无法继续拆分。
2. 基础组件
PC
端低代码搭建如“文本、图片、链接、布局、表单容器、表格、卡片、地图、富文本框”等。
移动端无代码搭建如“一行一列商品、一行两列商品、优惠券、视频、轮播图、搜索栏、信息明细”等。
特点是基于元组件开发,具备交互能力,提供可配置能力到开发者/运营人员。
3. 业务组件
即客户定制组件,针对每个客户的需求作开发,产出组件投放到物料市场。搭建平台手动添加对应的业务组件使用。特点是开发由一个组件、到一个仓库、到一个NPM包(CDN)的模式管理。
编辑态到运行态的中间层 - Schema
最终产出物(JSON-Schema
)数据,类似于一种DSL
(领域特定语言)。即仅适用于当前系统的语言描述。
// 无代码 Schema { version: '1', layout: { compfloat: ["component_id", ...], // 浮动排版的组件,霸屏或悬浮的组件 compflow: ["component_id", ...], // 流式排版的组件,即正常从上到下 components: { component_id: { type: "Carousel", // 对应组件名 props: { height: 100, ... }, pageid: "10001", // 页面id pagename: "页面名称", // 页面名称 theme: { // 动态主题 color-primary: "#333", border-radius: 30, ... } }, ... } } }
如果是低代码Schema
,因为涉及组件版本、平台解释器版本、页面数据源、自定义Javascript
代码,其Schema
模型比无代码模型复杂度更高,技术上更值得探索。
// 低代码 Schema { formUuid: 'FORM-NT766881QD7XWV5A3GDZB5RFHKOK2P3GD9SYKD1', title: '查询表格', version: 1, schemaVersion: 'V5', pages: [ { formType: 'display', flowData: { operatePermission: { OPERATE_BATCH_PRINT: 'y', OPERATE_RESTART: 'y', OPERATE_PRINT: 'y', OPERATE_BATCH_CREATE: 'y', OPERATE_HISTORY: 'y', OPERATE_CREATE: 'y', OPERATE_BATCH_EXPORT: 'y', OPERATE_DING_GROUP: 'n', OPERATE_TERMINATE: 'y', OPERATE_EDIT: 'y', OPERATE_BATCH_IMPORT: 'y', OPERATE_STASH: 'y', OPERATE_VIEW: 'y', OPERATE_DELETE: 'y', OPERATE_BATCH_DELETE: 'y', OPERATE_COMMENT: 'y' }, behaviors: [ { fieldBehavior: 'NORMAL', hidden: false, disabled: false, readOnly: true, componentName: 'TextField', }, ] }, id: 'FORM-NT766881QD7XWV5A3GDZB5RFHKOK2P3GD9SYKD1', componentsTree: [ { condition: true, css: 'body {\n background-color: #f2f3f5;\n}', children: [ { condition: true, children: [ { condition: true, children: [ { condition: true, loopArgs: ['item', 'index'], componentName: 'TextField', id: 'node_kahx5l5b', props: { labelTipsTypes: 'none', __useMediator: 'value', hasClear: false, labelTipsIcon: '', validationType: 'text', autoFocus: false, useI18nInput: false, tips: { type: 'JSExpression', value: '({"en_US":"","zh_CN":"","type":"JSExpression","extType":"i18n"})[this.utils.getLocale()]', }, trim: false, labelTextAlign: 'right', placeholder: { type: 'JSExpression', value: '"请输入"', }, state: '', behavior: 'NORMAL', value: { type: 'JSExpression', value: '""', }, addonBefore: { type: 'JSExpression', value: '""', }, validation: [{ type: 'required' }], hasLimitHint: false, cutString: false, htmlType: 'input', autoHeight: false, labelColOffset: 0, label: { type: 'JSExpression', value: '"活动名称"', }, __category__: 'form', labelColSpan: 4, wrapperColSpan: 0, rows: 4, addonAfter: { type: 'JSExpression', value: '""', }, scanCode: { editable: true, type: 'all', enabled: false }, wrapperColOffset: 0, size: 'medium', labelAlign: 'top', labelTipsText: { type: 'JSExpression', value: '""', }, maxLength: 200 } }, ], componentName: 'Form', } ], componentName: 'Dialog', props: { hasMask: true, visible: false, footer: true, footerActions: 'cancel,ok', confirmStyle: 'primary', confirmState: '确定', confirmText: { type: 'JSExpression', value: '"确定"', }, autoFocus: true, title: { type: 'JSExpression', value: '"创建活动"', }, closeable: 'esc', cancelText: { type: 'JSExpression', value: '"取消"', } } }, ], methods: { __initMethods__: { type: 'JSExpression', value: "function (exports, module) { 'use strict';\n\nexports.__esModule = true;\nexports.helloPage = helloPage;\nexports.onCreateActive = onCreateActive;\n/**\n * 私有的,可复用的函数\n * 动作面板帮助文档:\n * @see https://lark.alipay.com/legao/help/design-tool-logic\n */\nfunction printLog(obj) {\n console.info(obj);\n}\n\n/**\n * 页面内的函数,可以被页面内任何位置调用\n */\nfunction helloPage() {\n console.log('hello page');\n}\n\nfunction onCreateActive() {\n this.$('dialog_kahx5l5f').show();\n} }" } }, componentName: 'Page', dataSource: { online: [], list: [], sync: true }, lifeCycles: { constructor: { type: 'JSExpression', value: "function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}", extType: 'function' }, componentDidMount: { type: 'JSExpression', value: 'function main(){\n \n "use strict";\n\nvar __compiledFunc__ = function didMount() {\n // 页面节点加载渲染完毕\n if (this.siteSwitch) {\n this.siteSwitch();\n }\n};\n return __compiledFunc__.apply(this, arguments);\n }', extType: 'function' } }, props: { extensions: { 启用页头: true }, contentBgColor: 'white', pageStyle: ':root {\n background-color: #f2f3f5;\n}', className: 'page_km66qe5x', templateVersion: '1.0.0' } } ], dataSource: { online: [ { isReadonly: true, formUuid: 'FORM-NT766881QD7XWV5A3GDZB5RFHKOK2P3GD9SYKD1', name: 'urlParams', description: '当前页面地址的参数:如 aliwork.com/APP_xxxx/workbench?id=1&name=宜搭,可通过 this.state.urlParams.name 获取到宜搭', id: 'NT766881ZB7XUX0E07MU7B1DMJCF3L4GD9SYKCQ', protocal: 'URI' } ], list: [], sync: true } } ] }