厌倦了写活动页?快来撸一个页面生成器吧!

栏目: JavaScript · 发布时间: 6年前

内容简介:如果你经常接触一些公司的活动页,可能会经常头疼以下问题:这些项目周期短,需求频繁,迭代快,技术要求不高,成长空间也小。但是我们还是马不停蹄的赶着产品提来的一个个需求,随着公司规模的增加,我们不可能无限制的增加人手不断地重复着这些活动。这里我就不具体介绍一些有的没的的一些概念了,因为要介绍的概念实在太多了,作为一个前端的我们,直接上代码撸就好了!!!! 想要了解更多,也欢迎访问:

如果你经常接触一些公司的活动页,可能会经常头疼以下问题:这些项目周期短,需求频繁,迭代快,技术要求不高,成长空间也小。但是我们还是马不停蹄的赶着产品提来的一个个需求,随着公司规模的增加,我们不可能无限制的增加人手不断地重复着这些活动。这里我就不具体介绍一些有的没的的一些概念了,因为要介绍的概念实在太多了,作为一个前端的我们,直接上代码撸就好了!!!! 想要了解更多,也欢迎访问:

源地址

blogs

目标

我们的目标是实现一个页面制作后台,在后台中我们可以对页面进行 组件选择 --> 布局样式调整 --> 发布上线 --> 编辑修改 这样的流程操作。

架构设计

首先是要能提供组件给用户进行选择,那么我们需要一个 组件库 ,然后需要对选择的组件进行布局样式调整,所以我们需要一个 页面编辑后台 接着我们需要将编辑产出的数据渲染成真实的页面,所以我们需要一个 node服务 和用于填充的 template 模板 。发布上线,这个直接对接各个公司内部的发布系统就好了,这里我们不做过多阐述。最后的编辑修改功能也就是针对配置的修改,所以我们需要一个数据库,这里我选择的是用了 mysql 。当然你也可以顺便做做权限管理,页面管理....等等之类的活。 啰嗦了这么长,我们来画个图,了解下大概的流程:

厌倦了写活动页?快来撸一个页面生成器吧!

开撸

组件的实现

首先我们来实现组件这一部分,因为组件关联着后台编辑的预览和最后发布的使用。组件设计我们应该尽量保持组件的对外一致性,这样在进行渲染的时候,我们可以提供一个统一的对外数据接口。这里我们的技术选型是基于 Vue 的,所以下面的代码部分也主要是基于 Vue 的,但是万变不离其宗,其他语言也类似。

根据上图,我们的组件是会被一个个拆分单独发布到 npm 仓库的,为什么这么设计呢?其实之前也考虑过设计成一个组件库,所有组件都包含在一个组件库内,这样只需要发布一个组件库包,用的时候按需加载就好了。后来在实践的过程中发现这样并不合适协同开发,其他前端如果想贡献组件,接入的改造成本也很大。举个:chestnut::小明在业务中写了个 Button 组件,这个组件经常会被其他项目复用,他想把这个组件贡献到我们的系统中,被模板使用,如果是一个组件库的话,他首先得拉取我们组件库的代码,然后按照组件库的规范格式进行提交。这样一来,偷懒的小明可能就不太愿意这么干,最爽的方法当然是在本地构建一个npm库,开发选用的是用 TypeScript 还是其他的我们不关心,选用的 Css 预处理器我们也不关心,甚至编码规范的 ESLint 我们也不关心。最后只需通过编译后的文件即可。这样就避免了一个组件库的约束。依托于NPM完善的发布/拉取,以及版本控制机制,可以让我们少做一些额外的工作,也可以快速的把平台搭建起来。

说了这么多,代码呢?,我们以一个 Button 为例,我们对外提供这样的形式组件:

<template>
  <div :style="data.style.container" class="w_button_container">
    <button :style="data.style.btn"> {{data.context}}</button>
  </div>
</template>
<script>

export default {
  name: 'WButton',
  props: {
    data: {
      type: Object,
      default: () => {}
    }
  }
}
</script>
复制代码

可以看到我们只对外暴露了一个 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 脚本进行。这里要实现的代码比较多,我就大致说一下过程,因为也不是核心逻辑,可有可无,只能说有了体验上会好一点:

puppeteer

这样,就可以在加载组件选择区的时候,为组件附上缩略图。

组件预览区

当用户在选择区选择了组件,我们需要展示在预览区域,那么我们怎么知道用户选择了哪些组件呢?总不能提前全部把组件写入渲染区域,通过 v-if 来判断选择吧?当然没有这么蠢,Vue 已经提供了动态组件的功能了:

<div 
   :class="[index===currentEditor ? 'active' : '']" 
   :is="select.name" 
   :data="select.data">
</div>
复制代码

为什么我们不用缩略图代替真实组件?一方面生成的缩略图尺寸存在问题,另一方面,我们需要编辑的联动性,就是编辑区的编辑需要及时的反馈给用户。

额外的问题

说了这么多,貌似一切都很顺利,但是这样在实践的时候,发现了存在一个明显的问题就是:我们中间的预览区域其实就是为了尽可能模拟移动端页面效果。但是如果我们加入了一些包含类似 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
      helper.postMsgToChild({type: 'syncState', value: state})
    },
  // ...
}
复制代码

Template 模板的实现

模板的设计实现,我参考了 Vue-cli 2.x 版本的思想,把这里的模板,存在了对应的 git 仓库中。当用户需要进行页面构建的时候,直接从 git 仓库中拉取对应的模板即可。当然拉取完,也会缓存一份在本地,以后渲染,直接从本地缓存中读取即可。我们现在把中心放在模板的格式和规范上。模板我们采用什么样的语法无所谓,这里我才用了和 Vue-cli 一样的 Handlerbars 引擎。这里直接上我们模板的设计:

<template>
  <div class="pg-index" :style="{backgroundColor: '{{bgColor}}'}">
      <div class="main-container" :style="{
        backgroundColor: '{{bgColor}}',
        backgroundImage: '{{bgImage}}' ? 'url({{bgImage}})' : null,
        backgroundSize: '{{bgSize}}',
        backgroundRepeat: 'no-repeat'
      }">
        {{#components}}
          <div class="cp-module-editor {{className}} {{data.className}}">
            <{{name}} class="temp-component" :data="{{tostring data}}" data-type="{{upcasefirst name}}"></{{name}}>
          </div>
        {{/components}}
      </div>
  </div>
</template>

<script>
    {{#noRepeatCpsName}}
  import {{upcasefirst this}} from '{{this}}'
    {{/noRepeatCpsName}}
export default {
  name: '{{upcasefirst repoName}}',
  components: {
    {{#noRepeatCpsName}}
      {{upcasefirst this}},
    {{/noRepeatCpsName}}
  }
}
</script>

复制代码

为了简化逻辑,我们把模板都设计成流式布局,所有组件一个个堆叠往下顺序排列。这个文件便是我们 vue-webpack-simple 的模板中的 App.vue 。我们对其进行了改写。这样在数据填充万,便可以渲染出一个 Vue 单文件。这里我只举着一个例子,我们还可以实现多页模板等等复杂的模板,根据需求拉取不同的模板即可。

Node 渲染服务

当后台提交渲染请求的时候,我们的 node 服务所做的工作主要是:

  1. 拉取对应模板
  2. 渲染数据
  3. 编译

拉取也就是去指定仓库中通过 download-git-repo 插件进行拉取模板。编译其实也就是通过 metalsmith 静态模板生成器把模板作为输入,数据作为填充,按照 handlebars 的语法进行规则渲染。最后产出 build 构建好的目录。在这一步,我们之前所需的组件,会被渲染进 package.json 文件。我们来看一下核心代码:

// 这里就像一个管道,以数据入口为生成源,通过renderTemplateFiles编译产出到目标目录
function build(data, temp_dest, source, dest, cb) {
  let metalsmith = Metalsmith(temp_dest)
    .use(renderTemplateFiles(data))
    .source(source)
    .destination(dest)
    .clean(false)

  return metalsmith.build((error, files) => {
    if (error) console.log(error);
    let f = Object.keys(files)
      .filter(o => fs.existsSync(path.join(dest, o)))
      .map(o => path.join(dest, o))
    cb(error, f)
  })
}


function renderTemplateFiles(data) {
  return function (files) {
    Object.keys(files).forEach((fileName) => {
      let file = files[fileName]
      // 渲染方法
      file.contents = Handlebars.compile(file.contents.toString())(data)
    })
  }
}
复制代码

最后我们得到的是一个 Vue 项目,此时还不能直接跑在浏览器端,这里就涉及到当前发布系统所支持的形式了。怎么说?如果你的公司发布系统需要在线编译,那么你可以把源文件直接上传到git仓库,触发仓库的 WebHook 让发布系统替你发掉这个项目即可。如果你们的发布系统是需要你编译后提交编译文件进行发布的,那么你可以通过 node 命令,进行本地构建,产出 HTML,CSS,JS。直接提交给发布系统即可。 到这里,我们的任务就差不多了~具体的核心实心大多已经阐述清楚,如果实现当中有什么问题和不妥,也欢迎一起探讨交流!!


以上所述就是小编给大家介绍的《厌倦了写活动页?快来撸一个页面生成器吧!》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

大话存储

大话存储

张冬 / 清华大学出版社 / 2008-11 / 58.00元

网络存储,是近二十年来的新兴行业。从纸带到硬盘再到大型磁盘阵列,存储系统经历了从简单到复杂,从单块硬盘到存储区域网络(SAN)。网络存储行业目前已经是一个步入正轨的IT行业了。. 网络存储是一个涉及计算机硬件以及网络协议/技术、操作系统以及专业软件等各方面综合知识的领域。目前国内阐述网络存储的书籍少之又少,大部分是国外作品,对存储系统底层细节的描述不够深入,加之术语太多,初学者很难真正理解网......一起来看看 《大话存储》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具