开发可视化页面编辑管理系统实践分享
发布于 4 年前 作者 zhaowei 468 次浏览 来自 分享

背景

之前参与开发一个小程序商城,涉及非常多的落地页面需要前端开发维护,又由于小程序包大小限制,不能完成用小程序代码一一实现,所以最后决定把落地页的需求拆成一个单独的产品-落地页管理系统,通过可视化的界面编辑维护小程序和H5的落地页面,落地页面通过获取后台数据动态生成页面内容。
对于运营同事来说,在没有前端同事配合的情况下,也可通过可视化的操作来搭建所需的落地页面。解决页面生成的效率问题,同时释放开发人员。

落地页管理系统的需求

  • 页面的可维护(新建、编辑、删除、状态管理等)。
  • 页面的常见功能或属性编辑(标题,背景、有效期、微信自定义分享、返回顶部功能等)。
  • 组成页面的元素组件化,可自由拖拽组装,编辑修改组件属性。
  • 页面结构数据化,客户端根据数据动态渲染落地页面。

功能需求的技术实现

技术栈

后端技术栈 node+Koa+mongodb,提供落地页的更删改查API服务、素材的上传更新及有效期定时检测等。
前端技术栈 Vue+Vuex+Vue-route+EmenetUI+Axios,开发实现组成页面所需的基础组件及业务营销组件。
后端只是简单存储记录,本篇主要分享可视化页面编辑和渲染的前端技术实现。

项目目录说明

src							
|-- App.vue								
|-- main.js                       // 项目入口
|-- components                    // 公共组件目录
|   |-- BreadCrumb                	// 面包屑导航组件
|   |   |-- breadcrumb.scss
|   |   |-- index.vue
|   |-- Carousel                  	// 轮播图组件
|   |   |-- carousel.scss
|   |   |-- index.vue
|   |-- CarouselSet               	// 轮播图属性编辑组件
|   |   |-- index.vue
|   |-- ContextMenu               	// 右键菜单组件
|   |   |-- contextmenu.scss
|   |   |-- index.vue
|   |-- Event                     	// 事件组件
|   |   |-- event.scss
|   |   |-- index.vue
|   |-- EventSet                  	// 事件属性编辑组件
|   |   |-- index.vue
|   |-- Goods                     	// 商品组件
|   |   |-- goods.scss
|   |   |-- index.vue
|   |-- GoodsSet                  	// 商品属性编辑组件
|   |   |-- index.vue
|   |-- HeadInfo                  	// 头部显示组件
|   |   |-- headinfo.scss
|   |   |-- index.vue
|   |-- Picture                   	// 图片组件
|   |   |-- index.vue
|   |   |-- picture.scss
|   |-- PictureSet                	// 图片属性编辑组件
|   |   |-- index.vue
|   |-- Video                     	// 视频组件
|   |   |-- index.vue
|   |   |-- video.scss
|   |-- VideoSet                  	// 视频属性编辑组件
|       |-- index.vue
|-- pages                         // 页面目录
|   |-- LandPage                  	// 落地页渲染页面
|   |   |-- index.vue
|   |   |-- landpage.scss
|   |-- Login                     	// 登陆页面
|   |   |-- index.vue
|   |   |-- login.scss
|   |-- PageCreate                	// 落地页创建页面
|   |   |-- index.vue
|   |   |-- pagecreate.scss
|   |   |-- components            	// 创建页面分成左侧组件列表、中间窗口渲染组件、右侧属性编辑组件
|   |       |-- pageCenter        	// 中间窗口渲染组件
|   |       |   |-- index.vue
|   |       |   |-- pagecenter.scss
|   |       |-- pageRight         	// 右侧属性编辑组件
|   |           |-- index.vue
|   |           |-- pageright.scss
|   |-- PageEditor                	// 落地页编辑页面,共享创建页面,根据路由判断实现不同业务需求
|   |   |-- index.vue
|   |   |-- pageeditor.scss
|   |-- PageList                  	// 落地页列表页面(新建、编辑、删除、状态管理等)
|   |   |-- index.vue
|   |   |-- pagelist.scss
|   |-- PagePreview               	// 落地页预览页面
|       |-- index.vue
|       |-- pagepreview.scss
|-- assets                        // 资源目录(样式文件)
|   |-- style
|       |-- animate.css
|       |-- common.scss
|       |-- normalize.css
|       |-- var.scss
|-- http                          // http请求的接口与方法
|   |-- index.js
|   |-- request.js
|-- router                        // 页面路由目录
|   |-- index.js
|   |-- permission.js
|-- store                         // vuex状态管理目录
|   |-- actions.js
|   |-- index.js
|   |-- mutations.js
|   |-- state.js
|-- utility                       // 常用工具类函数与静态配置文件目录
	|-- const.js
	|-- filter.js
	|-- index.js
	|-- landpage.js

页面元素组件化和数据化

对落地页中的布局和功能需求进行分析总结并抽象出大概元素组件,分为基础组件(图片、视频、轮播图)和营销组件(事件、商品)。
落地页面的数据结构如下:

page:{
	//页面名称
	name:'',
	//页面标题
	title:'',
	//页面唯一的key
	urlKey:'',
	//是否需要有效期
	needExpire:false,
	expireDate:'',  
	//额外功能字段
	extra:{
		//页面背景色
		backgroundColor:'',
		//是否需要返回顶部按钮
		needBackTop:false,
		backTopImage:'',
		//是否需要自定义分享
		needShare:false,
		shareText:'',
		shareImage:'',
	},
	//页面中的组件
	components:[
		{ type:'Picture', name:'图片' }
	]
}

落地页的创建页面大致如下,分为左侧元素组件列表、中间实时渲染窗口和右侧组件属性编辑区域。

页面中可以有多个元素组件,然后每个元素组件都有自己的特有属性,每个元素组件对应一个属性组件用来编辑该组件的特有属性,在公共组件目录中可以看到对应的Set组件。

从组件的拖拽到属性编辑与实时渲染

从左侧拖拽组件到中间窗口即为页面中添加一个元素组件,新组件为当前激活组件,右侧区域会根据当前激活组件动态加载对应的属性编辑组件。

当拖拽左侧元素组件时,使用@dragstart事件记录当前拖拽的元素,中间窗口使用@dragover计算正在拖拽元素的位置坐标实现拖拽排序和层级元素的判断,@drop事件中根据之前的记录和判断生成元素组件数据到page.components或元素组件的children层级数组中。

<!-- 中间窗口的渲染模版代码 -->
<div class="page-wraper" @dragover="dragover" @drop="drop" :style="style">
    <div
      v-for="(component) in components"
      :key="component.id"
      :class="`component-wraper`">
      <!-- 动态组件加载 -->
      <component :class="`${component.id==activeComponent.id?'active':''} ${relativeComponent&&relativeComponent.id==component.id?('hover-'+relativeType):''}`"
        v-bind:is="component.type"
        v-bind="component"
        @contextmenu.native.prevent="contextmenu($event)"
        @mousedown.native="setActiveComponent(component)"
        @dragover.native.stop="checkDragOver(component,$event)"></component>
        <!-- 动态层级子组件加载 -->
        <component  v-for="child in component.children"
          :key="child.id" 
          :class="`component-child ${child.id==activeComponent.id?'active':''}`"
          v-bind:is="child.type"
          v-bind="child"         
          @contextmenu.native.prevent="contextmenu($event)"
          @mousedown.native="setActiveComponent(child)"
          @dragover.native.stop="checkDragOver(component, $event, child)"></component>
    </div>
</div>

左侧组件到中间窗口组件及右侧属性编辑组件的通信使用vuex的commit触发响应。当编辑右侧属性组件时会触发修改vuex的state,中间窗口组件通过mapState映射当前编辑组件的实时数据,vue会自动响应中间窗口中元素组件的渲染更新。

动态渲染落地页面

请求接口获取当前落地页面数据,判断不同属性值添加不同功能,渲染页面的代码类似之前中间窗口的渲染模版代码,没有对应的事件处理。

<div class="page-wraper" :style="style">
    <div class="component-wraper" v-for="(component) in components" :key="component.id">
      <!-- 动态组件加载 -->
      <component v-bind:is="component.type"  v-bind="component"> </component> 
        <!-- 动态层级子组件加载 -->
        <component  v-for="child in component.children" class="component-child"
          :key="child.id"         
          v-bind:is="child.type"
          v-bind="child"></component>
    </div>

    <transition name="el-fade-in">
    <div class="backTop" v-if="scrollTop&&page&&page.extra.needBackTop" @click="backTop">
      <img :src="staticURL+page.extra.backTopImage" />
    </div>   
    </transition>
</div>

vue内置组件component通过v-bind:is动态加载所需的元素组件,并传递组件数据。
元素组件内部获取组件属性并渲染组件,组件会根据环境的不同响应事件,例如:在中间渲染窗口中假如给图片组件添加链接属性,点击并没有响应,但是在落地页面中加载图片组件点击则会响应该事件。

待完善的功能

  • 逐步实现事件组件、商品组件等。
  • 小程序端的渲染实现(数据结构相同,需使用小程序语法实现),目前只实现H5端。

最后

篇幅有限,细节性问题就不在此描述啦,代码已开源,感兴趣的朋友欢迎 猛戳围观地址
此次分享属个人拙见,如有不足之处欢迎指正,谢谢。

讨论是绝佳的反思,被别人指出问题正是改进的空间。

回到顶部