乐趣区

关于模板:基于vue的页面配置化的实现下编辑器及生产

在上一篇基于 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()
     })
    }) */
    
    // 模板路由组件 js
    const interactjs = {'home': `./home.js?t=${Date.now()}`
    }
    // 从模板的 config.json 读取路由信息
    const routes = require('./config.json')
    
    // 获取 datasource.json
    const project = require('./datasource.json')
    
    // 获取 ejs
    const 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 异步加载路由组件

退出移动版