{{data.context}}
可以看到我们只对外暴露了一个 props
,这样做法的好处是可以统一组件对外暴露的数据,组件内部爱怎么玩怎么玩。注意,这里我们也可以引入一些第三方组件库,比如mint-ui
之类的。
后台编辑的实现
在写代码前,我们先考虑一下需要实现哪些功能:
-
一个属性编辑区,提供给使用者编辑组件内部
props
的功能 -
一个组件选择区,提供使用者选择需要的组件
-
一个组件预览区,提供使用者拖拽排序页面预览的功能
编辑区的实现
按照顺序,我们先来实现组件的属性编辑功能。我们要考虑,一个组件暴露出哪些可配置的信息。这些可配置的信息如何同步到后台编辑区,让使用者进行编辑,一个按钮的可配置信息可能是这样:
如果把这些配置全部写在后台库里面,根据当前选择的组件加载不同的配置,维护起来会相当麻烦,而且随着组件数量的增加,也会变得臃肿,所以我们可以将这些配置存储在服务端,后台只需要根据存储的规则进行解析便可,举个例子,我们其实可以存储这样的编辑配置:
[
{
“blockName”: “按钮布局设置”,
“settings”: {
“src”: {
“type”: “input”,
“require”: true,
“label”: “按钮文案”
}
}
}
]
我们在编辑后台,通过接口请求到这些配置,便可以进行规则渲染:
/**
* 根据类型,选择创建对应的组件
* @param {VNode} vm
* @returns {any}
*/
createEditorElement (vm: VNode) {
let dom = null
switch (vm.config.type) {
case ‘align’:
dom = this.createAlignElement(vm)
break;
case ‘select’:
dom = this.createSelectElement(vm)
break;
case ‘actions’:
dom = this.createActionElement(vm)
break;
case ‘vue-editor’:
dom = this.createVueEditor(vm)
break;
default:
dom = this.createBasicElement(vm)
}
return dom
}
组件选择区
首先我们需要考虑的是,组件怎么进行注册?因为组件被用户选用的时候,我们是需要渲染该组件的,所以我们可以提供一段 node 脚本来遍历所需组件,进行组件的安装注册:
// 定义渲染模板和路径
var OUTPUT_PATH = path.join(__dirname, ‘…/packages/index.js’);
console.log(chalk.yellow(‘正在生成包引用文件…’))
var INSTALL_COMPONENT_TEMPLATE = ’ {{name}}';
var IMPORT_TEMPLATE = ‘import {{componentName}} from ‘{{name}}’’;
var MAIN_TEMPLATE = `/* Automatic generated by ‘./compiler/build-entry.js’ */
{{include}}
const components = [
{{install}}
]
const install = function(Vue) {
components.map((component) => {
Vue.component(component.name, component)
})
}
/* istanbul ignore if */
if (typeof window !== ‘undefined’ && window.Vue) {
install(window.Vue)
}
export {
install,
{{list}}
}
`;
// 渲染引用文件
var template = render(MAIN_TEMPLATE, {
include: includeComponentTemplate.join(endOfLine),
install: installTemplate.join(,${endOfLine}
),
version: process.env.VERSION || require(‘…/package.json’).version,
list: listTemplate.join(,${endOfLine}
)
});
// 写入引用
fs.writeFileSync(OUTPUT_PATH, template);
最后渲染出来的文件大概是这样:
import WButton from ‘w-button’
const components = [
WButton
]
const install = function(Vue) {
components.map((component) => {
Vue.component(component.name, component)
})
}
/* istanbul ignore if */
if (typeof window !== ‘undefined’ && window.Vue) {
install(window.Vue)
}
export {
install,
WButton
}
这个也是组件库的通用写法,所以这里的思想就是把发布到 npm
上的组件,进行聚合,聚合成一个组件包引用,我们在后台编辑的时候,是需要全量引入的:
import * as W_UI from ‘…/…/packages’
Vue.use(W_UI)
这样,我们组件便注册完了,组件选择区,主要是提供组件的可选项,我们可以遍历组件,提供一个个 List 让用户选择,当然如果我们每个组件如果只提供一个组件名,用户可能并不知道组件长什么样,所以我们最好可以提供一下组件长什么样的缩略图。这里我们可以在组件发布的时候,也通过 node
脚本进行。这里要实现的代码比较多,我就大致说一下过程,因为也不是核心逻辑,可有可无,只能说有了体验上会好一点:
-
用户启用 dev-server 进行代码编写测试
-
server 脚本使用 Chrome 工具 puppeteer,调整页面到手机端模式, 进行当前 dev-server 截图。
-
生成截图文件,上传到node服务,关联组件
这样,就可以在加载组件选择区的时候,为组件附上缩略图。
组件预览区
当用户在选择区选择了组件,我们需要展示在预览区域,那么我们怎么知道用户选择了哪些组件呢?总不能提前全部把组件写入渲染区域,通过v-if
来判断选择吧?当然没有这么蠢,Vue 已经提供了动态组件的功能了:
:class=“[index===currentEditor ? ‘active’ : ‘’]”
:is=“select.name”
:data=“select.data”>
为什么我们不用缩略图代替真实组件?一方面生成的缩略图尺寸存在问题,另一方面,我们需要编辑的联动性,就是编辑区的编辑需要及时的反馈给用户。
额外的问题
说了这么多,貌似一切都很顺利,但是这样在实践的时候,发现了存在一个明显的问题就是:我们中间的预览区域其实就是为了尽可能模拟移动端页面效果。但是如果我们加入了一些包含类似 position: fixed
样式的组件,会发现样式上就出现了明显的问题。典型的比如Dialog Loading
等。所以我们参考了 m-ui组件库的设计,将中间预览操作容器展示为一个iframe
。将iframe大小调整为375 * 667
,模拟 iPhone 6
的手机端。这样就不会存在样式问题了。可是这样又出现了另一个难点,那就是左侧的编辑数据如何及时的反应到iframe
中?没错,就是postMessgae
,大致思路如下:
利用 vuex
做数据存储池,所有的变化,通过 postMessgae
进行同步,这样我们只用确保数据池中的数据变化,便可以映射到渲染层的变化。比如,我们在预览区进行了组件选择和拖拽排序,那么我们只需通过 vuex
出发同步信息便可:
// action.ts
const action = {
setCurrentPage ({commit, state}, page: number) {
// 更新当前store
commit(‘setCurrentPage’,page)
// 对应postMessage