被动客制化服务转为主动搭建服务
前言
从前端角度看,大量客户定制化需求带来的是非常繁重的交付任务,需要前端开发者消耗大量精力处理重复性高、技术难度低的工作内容,同时也会给企业带来更高的开发成本、更低的人效。
前端开发者如何从定制业务中脱身,可以参考市场上已有的行业化解决方案,即低代码平台(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
}
}
]
}