为大家附上 Vue 官方文档:cn.vuejs.org/v2/api/#is
画板元素渲染编辑画板只需要循环遍历pages[i].elements数组,将里面的元素组件JSON数据取出,通过动态组件渲染出各个组件,支持拖拽改变位置尺寸.
元素组件管理在client目录新建plugins来管理组件库。也可以将该组件库发到npm上工程中通过npm管理
组件库编写组件,考虑的是组件库,所以我们竟可能让我们的组件支持全局引入和按需引入,如果全局引入,那么所有的组件需要要注册到Vue component 上,并导出:
client/plugins下新建index.js入口文件
```
/**
* 组件库入口
* */
import Text from './text'
// 所有组件列表
const components = [
Text
]
// 定义 install 方法,接收 Vue 作为参数
const install = function (Vue) {
// 判断是否安装,安装过就不继续往下执行
if (install.installed) return
install.installed = true
// 遍历注册所有组件
components.map(component => Vue.component(component.name, component))
}
// 检测到 Vue 才执行,毕竟我们是基于 Vue 的
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
export default {
install,
// 所有组件,必须具有 install,才能使用 Vue.use()
Text
}
```
复制代码
组件开发
示例: text文本组件
client/plugins下新建text组件目录
|-- text --------text组件
|--src --------资源
|--index.vue --------组件
|--index.js --------入口
复制代码
text/index.js
// 为组件提供 install 方法,供组件对外按需引入
import Component from './src/index'
Component.install = Vue => {
Vue.component(Component.name, Component)
}
export default Component
复制代码
text/src/index.vue
<!--text.vue-->
<template>
<div class="qk-text">
{{text}}
</div>
</template>
<script>
export default {
name: 'QkText', // 这个名字很重要,它就是未来的标签名<qk-text></qk-text>
props: {
text: {
type: String,
default: '这是一段文字'
}
}
}
</script>
<style lang="scss" scoped>
</style>
复制代码
编辑器里使用组件库:
// 引入组件库
import QKUI from 'client/plugins/index'
// 注册组件库
Vue.use(QKUI)
// 使用:
<qk-text text="这是一段文字"></qk-text>
复制代码
按照这个组件开发方式我们可以扩展任意多的组件,来丰富组件库
需要注意的是这里的组件最外层宽高都要求是100%
配置文件Quark-h5编辑器左侧选择组件区域可以通过一个配置文件定义可选组件 新建一个ele-config.js配置文件:
export default [
{
title: '基础组件',
components: [
{
elName: 'qk-text', // 组件名,与组件库名称一致
title: '文字',
icon: 'iconfont iconwenben',
// 给每个组件配置默认显示样式
defaultStyle: {
height: 40
}
}
]
},
{
title: '表单组件',
components: []
},
{
title: '功能组件',
components: []
},
{
title: '业务组件',
components: []
}
]
复制代码
公共方法中提供一个function 通过组件名和默认样式获取元素组件JSON,getElementConfigJson(elName, defaultStyle)方法
元素属性编辑公共属性样式编辑公共样式属性编辑比较简单就是对元素JSON对象commonStyles字段进行编辑操作
props属性编辑1.为组件的每一个prop属性开发一个属性编辑组件. 例如:QkText组件需要text属性,新增一个attr-qk-text组件来操作该属性 2.获取组件prop对象 3.遍历prop对象key, 通过key判断显示哪些属性编辑组件
元素添加动画实现动画效果引入Animate.css动画库。元素组件动画,可以支持多个动画。数据存在元素JSON对象animations数组里。
选择面板hover预览动画监听mouseover和mouseleave,当鼠标移入时将动画className添加入到元素上,鼠标移出时去掉动画lassName。这样就实现了hover预览动画
编辑预览动画组件编辑时支持动画预览和单个动画预览。
封装一个动画执行方法
/**
* 动画方法, 将动画css加入到元素上,返回promise提供执行后续操作(将动画重置)
* @param $el 当前被执行动画的元素
* @param animationList 动画列表
* @param isDebugger 动画列表
* @returns {Promise<void>}
*/
export default async function runAnimation($el, animationList = [], isDebug , callback){
let playFn = function (animation) {
return new Promise(resolve => {
$el.style.animationName = animation.type
$el.style.animationDuration = `${animation.duration}s`
// 如果是循环播放就将循环次数置为1,这样有效避免编辑时因为预览循环播放组件播放动画无法触发animationend来暂停组件动画
$el.style.animationIterationCount = animation.infinite ? (isDebug ? 1 : 'infinite') : animation.interationCount
$el.style.animationDelay = `${animation.delay}s`
$el.style.animationFillMode = 'both'
let resolveFn = function(){
$el.removeEventListener('animationend', resolveFn, false);
$el.addEventListener('animationcancel', resolveFn, false);
resolve()
}
$el.addEventListener('animationend', resolveFn, false)
$el.addEventListener('animationcancel', resolveFn, false);
})
}
for(let i = 0, len = animationList.length; i < len; i ){
await playFn(animationList[i])
}
if(callback){
callback()
}
}
复制代码
animationIterationCount 如果是编辑模式的化动画只执行一次,不然无法监听到动画结束animationend事件
执行动画前先将元素样式style缓存起来,当动画执行完再将原样式赋值给元素
let cssText = this.$el.style.cssText;
runAnimations(this.$el, animations, true, () => {
this.$el.style.cssText = cssText
})
复制代码
元素添加事件
提供事件mixins混入到组件,每个事件方法返回promise,元素被点击时按顺序执行事件方法
页面插入js脚本参考百度H5,将脚本以script标签形式嵌入。页面加载后执行。 这里也可以考虑mixins方式混入到页面或者组件,可根据业务需求自行扩展,都是可以实现的。
redo/undo历史操作纪录- 历史操作纪录存在状态机store.state.editor.historyCache数组中。
- 每次修改编辑操作都把整个pageDataJson字段push到historyCache
- 点击redo/undo时根据index获取到pageDataJson重新渲染页面
将psd每个设计图中的每个图层导出成图片保存到静态资源服务器中,
服务端安装psd依赖
cnpm install psd --save
复制代码
加入psd.js依赖,并且提供接口来处理数据
var PSD = require('psd');
router.post('/psdPpload',async ctx=>{
const file = ctx.request.files.file; // 获取上传文件
let psd = await PSD.open(file.path)
var timeStr = new Date();
let descendantsList = psd.tree().descendants();
descendantsList.reverse();
let psdSourceList = []
let currentPathDir = `public/upload_static/psd_image/${timeStr}`
for (var i = 0; i < descendantsList.length; i ){
if (descendantsList[i].isGroup()) continue;
if (!descendantsList[i].visible) continue;
try{
await descendantsList[i].saveAsPng(path.join(ctx.state.SERVER_PATH, currentPathDir `/${i}.png`))
psdSourceList.push({
...descendantsList[i].export(),
type: 'picture',
imageSrc: ctx.state.BASE_URL `/upload_static/psd_image/${timeStr}/${i}.png`,
})
}catch (e) {
// 转换不出来的图层先忽略
continue;
}
}
ctx.body = {
elements: psdSourceList,
document: psd.tree().export().document
};
})
复制代码
最后把获取的数据转义并返回给前端,前端获取到数据后使用系统统一方法,遍历添加统一图片组件
- psd源文件大小最好不要超过30M,过大会导致浏览器卡顿甚至卡死
- 尽可能合并图层,并栅格化所有图层
- 较复杂的图层样式,如滤镜、图层样式等无法读取
这里只需要注意下图片跨域问题,官方提供html2canvas: proxy解决方案。它将图片转化为base64格式,结合使用设置(proxy: theProxyURL), 绘制到跨域图片时,会去访问theProxyURL下转化好格式的图片,由此解决了画布污染问题。 提供一个跨域接口
/**
* html2canvas 跨域接口设置
*/
router.get('/html2canvas/corsproxy', async ctx => {
ctx.body = await request(ctx.query.url)
})
复制代码
渲染模板实现逻辑
在engine-template目录下新建swiper-h5-engine页面组件,这个组件接收到页面JSON数据就可以把页面渲染出来。跟编辑预览画板实现逻辑差不多。
然后使用vue-cli库打包命令将组件打包成engine.js库文件。ejs模板引入该页面组件配合json数据渲染出页面
适配方案提供两种方案解决屏幕适配 1、等比例缩放 在将json元素转换为dom元素的时候,对所有的px单位做比例转换,转换公式为 new = old * windows.x / pageJson.width,这里的pageJson.width是页面的一个初始值,也是编辑时候的默认宽度,同时viewport使用device-width。 2.全屏背景, 页面垂直居中 因为会存在上下或者左右有间隙的情况,这时候我们把背景颜色做全屏处理
页面垂直居中只适用于全屏h5, 以后扩展长页和PC页就不需要垂直居中处理。
模板打包package.json中新增打包命令
"lib:h5-swiper": "vue-cli-service build --target lib --name h5-swiper --dest server/public/engine_libs/h5-swiper engine-template/engine-h5-swiper/index.js"
执行npm run lib:h5-swiper 生成引擎模板js如图