一、背景介绍

做过电商项目的同学都知道,店铺装修是电商系统必备的一个功能,在某些场景下,可能是广告页制作、活动页制作、微页面制作,但基本功能都是类似的。所谓店铺装修,就是用户可以在 PC 端进行移动页面的制作,只需要通过简单的拖拽就可以实现页面的编辑,属于用户高度自定义的功能。最终编辑的结果,可以在 H5、小程序进行展示推广。

有赞美业是一套美业行业的 SaaS 系统,为美业行业提供信息化和互联网化解决方案。有赞美业本身提供了店铺装修的功能,方便用户自定义网店展示内容,下面是有赞美业店铺装修功能的截图:

上面的图片是 PC 端的界面,下面两张图分别是 H5 和小程序的最终展示效果。可以简单地看到,PC 端主要做页面的编辑和预览功能,包括了丰富的业务组件和详细的自定义选项;H5 和小程序则承载了最终的展示功能。

再看看有赞美业当前的技术基本面:目前我们的 PC 端是基于 React 的技术栈,H5 端是基于 Vue 的技术栈,小程序是微信原生开发模式。

在这个基础上,如果要做技术设计,我们可以从以下几个角度考虑:

三端的视图层都是数据驱动类型,如何管理各端的数据流程?

三个端三种不同技术栈,业务中却存在相同的内容,是否存在代码复用的可能?

PC 最终生成的数据,需要与 H5、小程序共享,三端共用一套数据,应该通过什么形式来做三端数据的规范管理?

在扩展性上,怎么低成本地支持后续更多组件的业务加入?

二、方案设计

所以我们针对有赞美业的技术基本面,设计了一个方案来解决以上几个问题。

首先摆出一张架构图:

2.1 数据驱动

首先关注 CustomPage 组件,这是整个店铺装修的总控制台,内部维护三个主要组件 PageLeft、 PageView 和 PageRight,分别对应上面提到的 PC 端3个模块。

为了使数据共享,CustomPage 通过 React context 维护了一个”作用域“,提供了内部三个组件共享的“数据源”。 PageLeft 、 PageRight 分别是左侧组件和右侧编辑组件,共享 context.page 数据,数据变更则通过 context.pageChange 传递。整个过程大致用代码表示如下:

// CustomerPage

class CustomerPage extends React.Component {

static childContextTypes = {

page: PropTypes.object.isRequired,

pageChange: PropTypes.func.isRequired,

activeIndex: PropTypes.number.isRequired,

};

getChildContext() {

const { pageInfo, pageLayout } = this.state;

return {

page: { pageInfo, pageLayout },

pageChange: this.pageChange || (() => void 0),

activeIndex: pageLayout.findIndex(block => block.active),

};

}

render() {

return (

);

}

}

// PageLeft

class PageLeft extends Component {

static contextTypes = {

page: PropTypes.object.isRequired,

pageChange: PropTypes.func.isRequired,

activeIndex: PropTypes.number.isRequired,

};

render() {...}

}

// PageRight

class PageRight extends Component {

static contextTypes = {

page: PropTypes.object.isRequired,

pageChange: PropTypes.func.isRequired,

activeIndex: PropTypes.number.isRequired,

};

render() {...}

}

至于 H5 端,可以利用 Vue 的动态组件完成业务组件的动态化,这种异步组件的方式提供了极大的灵活性,非常适合店铺装修的场景。

小程序因为没有动态组件的概念,所以只能通过 if else 的面条代码来实现这个功能。更深入的考虑复用的话,目前社区有开源的工具实现 Vue 和小程序之间的转换,可能可以帮助我们做的更多,但这里就不展开讨论了。

PC 编辑生成数据,最终会与 H5、小程序共享,所以协商好数据格式和字段含义很重要。为了解决这个问题,我们抽取了一个npm包,专门管理3端数据统一的问题。这个包描述了每个组件的字段格式和含义,各端在实现中,只需要根据字段描述进行对应的样式开发就可以了,这样也就解决了我们说的扩展性的问题。后续如果需要增加新的业务组件,只需要协商好并升级新的npm包,就能做到3端的数据统一。

/**

* 显示位置

*/

export const position = {

LEFT: 0,

CENTER: 1,

RIGHT: 2,

};

export const positionMap = [{

value: position.LEFT,

name: '居左',

}, {

value: position.CENTER,

name: '居中',

}, {

value: position.RIGHT,

name: '居右',

}];

2.2 跨端复用

PageView 是预览组件,是这个设计的核心。按照最直接的思路,我们可能会用 React 把所有业务组件都实现一遍,然后把数据排列展示的逻辑实现一遍;再在 H5 和小程序把所有组件实现一遍,数据排列展示的逻辑也实现一遍。但是考虑到代码复用性,我们是不是可以做一些“偷懒”?

如果不考虑小程序的话,我们知道 PC 和 H5 都是基于 dom 的样式实现,逻辑也都是 js 代码,两端都实现一遍的话肯定做了很多重复的工作。所以为了达到样式和逻辑复用的能力,我们想了一个方法,就是通过 iframe 嵌套 H5 的页面,通过 postmessage 来做数据交互,这样就实现了用 H5 来充当预览组件,那么 PC 和 H5 的代码就只有一套了。按照这个实现思路,PageView 组件可以实现成下面这样:

class PageView extends Component {

render() {

const { page = {} } = this.props;

const { pageInfo = {}, pageLayout = [] } = page;

const { loading } = this.state;

return (

title={pageInfo.title}

src={this.previewUrl}

frameBorder="0"

allowFullScreen="true"

width="100%"

height={601}

ref={(elem) => { this.iframeElem = elem; }}

/>

);

}

}

PageView 代码很简单,就是内嵌 iframe,其余的工作都交给 H5。H5 将拿到的数据,按照规范转换成对应的组件数组展示:

v-for="(item, index) in components"

:is="item.component"

:options="item.options"

:isEdit="false">

computed: {

components() {

return mapToComponents(this.list);

},

},

因为有了 iframe ,还需要利用 postmessage 进行跨源通信,为了方便使用,我们做了一层封装(代码参考自有赞餐饮):

export default class Messager {

constructor(win, targetOrigin) {

this.win = win;

this.targetOrigin = targetOrigin;

this.actions = {};

window.addEventListener('message', this.handleMessageListener, false);

}

handleMessageListener = (event) => {

// 我们能相信信息的发送者吗? (也许这个发送者和我们最初打开的不是同一个页面).

if (event.origin !== this.targetOrigin) {

console.warn(`${event.origin}不对应源${this.targetOrigin}`);

return;

}

if (!event.data || !event.data.type) {

return;

}

const { type } = event.data;

if (!this.actions[type]) {

console.warn(`${type}: missing listener`);

return;

}

this.actions[type](event.data.value);

};

on = (type, cb) => {

this.actions[type] = cb;

return this;

};

emit = (type, value) => {

this.win.postMessage({

type, value,

}, this.targetOrigin);

return this;

};

destroy() {

window.removeEventListener('message', this.handleMessageListener);

}

}

在此基础上,业务方就只需要关注消息的处理,例如 H5 组件接收来自 PC 的数据更新可以这样用:

this.messager = new Messager(window.parent, `${window.location.protocol}//mei.youzan.com`);

this.messager.on('pageChangeFromReact', (data) => {

...

});

这样通过两端协商的事件,各自进行业务逻辑处理就可以了。

这里有个细节需要处理,因为预览视图高度会动态变化,PC 需要控制外部视图高度,所以也需要有动态获取预览视图高度的机制。

// vue script

updated() {

this.$nextTick(() => {

const list = document.querySelectorAll('.preview .drag-box');

let total = 0;

list.forEach((item) => {

total += item.clientHeight;

});

this.messager.emit('vueStyleChange', { height: total });

}

}

// react script

this.messsager.on('vueStyleChange', (value) => {

const { height } = value;

height && (this.iframeElem.style.height = `${height}px`);

});

2.3 拖拽实现

拖拽功能是通过 HTML5 drag & drop api 实现的,在这次需求中,主要是为了实现拖动过程中组件能够动态排序的效果。这里有几个关键点,实现起来可能会花费一些功夫:

向上向下拖动过程中视图自动滚动

拖拽结果同步数据变更

适当的动画效果

目前社区有很多成熟的拖拽相关的库,我们选用了vuedraggable。原因也很简单,一方面是避免重复造轮子,另一方面就是它很好的解决了我们上面提到的几个问题。

vuedraggable 封装的很好,使用起来就很简单了,把我们前面提到的动态组件再封装一层 draggable 组件:

v-model="list"

:options="sortOptions"

@start="onDragStart"

@end="onDragEnd"

class="preview"

:class="{dragging: dragging}">

v-for="(item, index) in components"

:is="item.component"

:options="item.options"

:isEdit="false">

const sortOptions = {

animation: 150,

ghostClass: 'sortable-ghost',

chosenClass: 'sortable-chosen',

dragClass: 'sortable-drag',

};

// vue script

computed: {

list: {

get() {

return get(this.designData, 'pageLayout') || [];

},

set(value) {

this.designData.pageLayout = value;

this.notifyReact();

},

},

components() {

return mapToComponents(this.list);

},

},

三、总结

到这里,所有设计都完成了。总结一下就是:PC 端组件间主要通过 React context 来做数据的共享;H5 和 小程序则是通过数据映射对应的组件数组来实现展示;核心要点则是通过 iframe 来达到样式逻辑的复用;另外可以通过第三方npm包来做数据规范的统一。

当然除了基本架构以外,还会有很多技术细节需要处理,比如需要保证预览组件不可点击等,这些则需要在实际开发中具体处理。

java店铺装修功能_有赞美业店铺装修前端解决方案相关推荐

  1. 有赞美业微前端的落地总结

    2020年4月,有赞美业的前端团队历经7个月时间,完成了美业PC架构从单体SPA到微前端架构的设计.迁移工作.PPT在去年6月份就有了,现在再整理一下形成文章分享给大家. 头图 目录 Part 01 ...

  2. 【微前端】1174- 有赞美业微前端的落地总结

    2020年4月,有赞美业的前端团队历经7个月时间,完成了美业PC架构从单体SPA到微前端架构的设计.迁移工作.PPT在去年6月份就有了,现在再整理一下形成文章分享给大家. 头图 目录 Part 01 ...

  3. Java堆栈功能_【ThinkingInJava】35、用java实现堆栈功能

    /** * 书本:<Thinking In Java> * 功能:用java实现堆栈功能 * 文件:LinkedStack.java * 时间:2015年4月17日14:23:34 * 作 ...

  4. java 新功能_最新的Java版本101有什么新功能?

    java 新功能 Looking at the constant additions to Java Software Development platform, it won't be wrong ...

  5. java image功能_使用JAI扩展Java Image的功能 | 学步园

    Java Image功能一直都在增强,但是向磁盘写一个图像文件或者返回一个PNG或JPEG依然比较难实现.但是我们可以使用Java Advanced Imaging(JAI)API来解决这个问题.JA ...

  6. 没有体现JAVA接口功能_深入浅出分析Java抽象类和接口【功能,定义,用法,区别】...

    本文实例讲述了Java抽象类和接口.分享给大家供大家参考,具体如下: 对于OOP编程来说,抽象是它一大特征之一.在Java中,可以通过两种形式来体现OOP的抽象:抽象类和接口. 这两者有相似之处也有很 ...

  7. java抢单功能_基于消息队列的高并发抢单功能实现方法与流程

    本发明涉及嵌入式软件中间件,具体涉及一种基于消息队列的高并发抢单功能实现方法. 背景技术: 中间件是一种独立的系统软件或服务程序,分布式应用系统借助这种软件在不同的技术之间共享资源,管理计算资源和网络 ...

  8. java全文检索功能_基于Java的全文检索引擎简介

    Lucene是一个基于Java的全文索引工具包. 基于Java的全文索引引擎Lucene简介:关于作者和Lucene的历史 全文检索的实现:Luene全文索引和数据库索引的比较 中文切分词机制简介:基 ...

  9. java 时间提醒功能_提醒软件【送给她】——如果工作时间长了,提醒一下,该休息一下了,休息时长可设置,单位是秒...

    这是一个工作时长提醒软件,仅仅是实现了提醒的部分,单该程序启动时,就会出现一个很大的背景,覆盖整个桌面,并进行计时操作,提醒您,该休息了,休息的时长可进行设置,在config.properties中设 ...

最新文章

  1. Hadmard product(哈达玛积)
  2. Java中数组的length属性
  3. 利用Python制作王者荣耀出装小助手,引来了老板的注意!
  4. Shuffle CodeForces - 1366B(思维)
  5. 使用Java迭代器修改数据时要小心
  6. 使用Transformers离线模型(以bert模型为例)
  7. 端到端文本检测与识别——FOTS
  8. nodejs gulp less编辑
  9. python用pandas读取数据时出现错误_Python Pandas错误标记数据
  10. (C++)统计字符_给定一个英文字符串,请写一段代码找出这个字符串中首先出现三次的那个英文字符
  11. 你用过Foxpro吗?
  12. mysql relay log.info_技术分享 | slave_relay_log_info 表认知的一些展开
  13. 网页制作 - 问卷网的首页
  14. 小程序EXcel文件下载并打开
  15. html5 斜边,css斜切角 斜边 倒角
  16. Android开发 TextView
  17. 硬盘变为RAW格式,数据还能恢复吗?
  18. 【二维线段树】20150209测试 千石抚子的三维积木
  19. 宽度优先搜索的复杂度分析
  20. 美洽客服端下载地址是什么?

热门文章

  1. python语言属于-python语言属于()
  2. 安卓nba2k13数据包该放那个目录下
  3. 考研还是工作?两战失败老道有话说
  4. Hololens 简体中文版的安装步骤(2018.11)
  5. 90年代小学计算机课程,九十年代小学课-体育与劳动
  6. STM32系列(HAL库)——F103C8T6通过SPI方式读写W25Q64—(Flash存储模块)
  7. 王者荣耀壁纸上面的英雄太酷了,为什么不把它们下载下来呢?
  8. acrobat进行OCR文字识别失败
  9. 【Stable Diffusion】windows 1050显卡,17年笔记本还能再战
  10. 视频编码中vbr和cbr 、avbr、 qvbr 、cvbr、fixqp优缺点