在上一篇基于vue的页面配置化的实现(上)——模板开发中,咱们实现了模板开发。

本篇次要分两个局部:

  1. 如何实现让经营在编辑器里配置这些参数,以及在编辑器里实时预览模板的成果呢
  2. 依据模板js和配置参数在服务端生成页面

编辑器

先看下成果,最左侧是页面信息,如果有多个页面,则显示多栏。两头是实时预览成果,右侧则是配置datasource的中央。思考到页面可能依据链接参数有不同体现,所以正上方是反对自定义链接参数以便于实时预览。右上角则是进行保留公布操作。

设计

在本地模板开发阶段,咱们最初拿到了以下几个打包后的文件:

接下来就是怎么应用这些js和json

实现

首先咱们近程拉取模板信息,实现过程不赘述,假如咱们曾经拿到了上面几个要害信息

  • home.js:模板的页面组件
  • settings.js:配置面板组件
  • datasource.json:模板配置字段
  • config.json:模板根底信息

左侧页面列表

依据模板 config.json 咱们能够遍历出页面的路由信息

右侧配置面板

近程加载配置面板组件

这里的目标最终是要实现配置面板settings跟页面的datasource绑定在一起,实现通过配置面板来批改datasource。

  1. 封装一个近程tLoader的办法,拉取近程组件

    function loadJS(url = '') {  if (!url) return  if (!loadJS.cache) loadJS.cache = {}  if (loadJS.cache[url]) return Promise.resolve()  return new Promise((resolve, reject) => { _loadjs(   url,   () => {     loadJS.cache[url] = 'cached'     resolve()   },   () => {     console.error(`${url}加载失败`)     reject(new Error(`${url}加载失败`))   } )  })  function _loadjs(url, fn, fail) { var script = document.createElement('script') script.src = url script.async = true script.onload = fn script.onerror = fail ;(document.body || document.head).appendChild(script)  }}export default {  requestCache: {},  componentCache: {},  getComponent(name, cdn) { return this.componentCache[name]  },  async load(name, cdnPath) { let component = this.getComponent(name) if (component) return component let request = '' let url = '' if (!this.requestCache[name]) {   url = cdnPath   // url += '?t=' + new Date().getTime()   this.requestCache[name] = request = loadJS(cdnPath) } else request = this.requestCache[name] return await request.then((res) => {   component = window[name]   this.componentCache[name] = component   return component }).catch(err => {   console.log('加载失败', url)   console.error(err) })  }}
  2. 定义组件

    const template = await tLoader.load(name, `${url}?t=${new Date().getTime()}`)Vue.component('settings', template)
  3. 应用

    <component is="settings"></component>
  4. 将我的项目的datasource定义为全局变量,合并模板的data和我的项目的data

    const templateData = await axios.get('datasource.json')const projectData = await axios.get('projectDatasource.json')window['datasource'] = Object.merge(templateData, projectData)

正中间预览面板

原理:应用 iframe 加载页面,而后通过Vue.observable()绑定top.datasource

父页面

  1. 新建iframe

    <iframe  ref="page-iframe"  :key="iframeKey"  frameborder="0"  :src="pageRoute"  @load="iframeMounted"/><script>export default {  methods: {   // iframe加载实现后,告诉子页面能够开始加载模板的路由组件了   iframeMounted() {      this.post({       type: 'initPage',       data: {         js: `${js}.js`, // 加载模板路由js的url地址,让子页面可能加载并加载       }     })   }  }}</script>

    pageRoute 是用来加载iframe地址的,能够动静更新路由参数。

iframe 页面

  1. 监听父页面传递过去的音讯,并加载并定义模板路由组件

    window.addEventListener('message', function(d) {     const { type, data } = d.data     switch (type) {       case 'initPage':         // !!!,绑定父窗口的 datasource         window['datasource'] = Vue.observable(top.datasource)         // 挂载 page 的component,原理跟父页面的settings的加载形式统一         const template = await tLoader.load(data.name, `${data.js}?t=${new Date().getTime()}`)         Vue.component(data.name, template)         this.current.name = data.name // 以后页面         break       default: break     }   })
  2. 加载路由组件

    <!--current.name 用于切换模板的路由--><component     :is="current.name"     v-if="mounted"     class="page"   />

至此,编辑器的局部就实现了

服务端实现

本节不探讨如何存储模板和我的项目配置信息,存储的计划能够放在cdn上,也能够存储在数据库中;也不会探讨部署,只会探讨如何生成一个页面。

如果认真看下来,大略能晓得服务端要如何实现生产页面。还记得在本地模板开发的时候,本地预览的实现吗?没错,原理都是统一的。

实现

  1. 用 ejs 作为模板语言, 新建 index.ejs,用于渲染出html

    <!DOCTYPE html><html lang="zh-CN">  <head> <meta charset="utf-8"> <meta name="renderer" content="webkit"/> <meta name="force-rendering" content="webkit"/> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/> <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1.0"> <title></title> <style>   html{     width: 100%;     height: 100%;   }   <%= styles %> </style> <!-- import vue and ui js --> <% for(var j of js) { %>   <script src="<%=j%>"></script> <% } %>  </head>  <body> <div id="app"></div>  <!-- template router js --> <% for(var j in interactjs) { %>   <script src="<%=interactjs[j]%>"></script> <% } %> <script>   /* 路由 */   var routes = [     <% for (var n of routes) { %>       {         <% for (var i in n) { %>           <% if (i === 'component') { %>             <%- i %>: <%- "window['" + n[i] + "']"  %>,           <% } else { %>             <%- i %>: <%- JSON.stringify(n[i]) %>,           <% } %>         <% } %>       },     <% } %>   ]   var router = new VueRouter({     mode: 'hash',     routes: routes   });   var datasource = new Vue.observable(<%- JSON.stringify(project) %>)   var instance = new Vue({     router: router,     render: function (h, context) {       return h(         'div',         {},         [          h('router-view', {            on: {            }          }, null),         ]       )     },   }).$mount('#app'); </script>  </body></html>
  2. 渲染模板

    const fs = require('fs')const request = require('request')const prettier = require('prettier')// 下载模板的路由组件 js/* const dlJs = (url, stream) => new Promise(resolve => { request(url).pipe(stream).on('finish', () => {   resolve() })}) */// 模板路由组件 jsconst interactjs = {  'home': `./home.js?t=${Date.now()}`}// 从模板的 config.json 读取路由信息const routes = require('./config.json')// 获取datasource.jsonconst project = require('./datasource.json')// 获取ejsconst pageTemplate = fs.readFileSync(path.join(__dirname, 'template/index.ejs'), 'utf8')// render 渲染ret = ejs.render(pageTemplate, { styles: styles.join(''), js: [ './vue.runtime.min.js', './vue-router.js' ], interactjs, css, routes, project,})// 格式化文档const prettierHtml = prettier.format(ret, {  parser: 'html',  printWidth: 80,  singleQuote: true,})// 生成文件fs.writeFileSync(path.join(projectPath, 'index.html'), prettierHtml)// TODO// 将 ./*.js 的文件拷贝或下载到与index.html 同级目录下

    最初生成的工程跟咱们在模板开发的时候本地预览的dist简直统一的,只是生产不再须要settings

优化的思路: window 间接注入能够换成 system.js 异步加载路由组件