可视化搭建移动端店铺解决方案
原文地址:https://juejin.cn/post/6979410699453726727? ? 文章已經過作者許可轉載
關注并將「趣談前端」設為星標
每早08:30按時推送技術干貨/優秀開源/技術思維
前言
經過許久的深思熟慮與探索,同時也借鑒了行業內不錯的產品(如:有贊,H5-Dooring等),但跟列舉的產品還是有區別的(先賣個關子,后面再講有哪些區別)。其實這種功能在零售系統(目前我所在公司是零售行業的領頭羊)和電商系統應該很常見,很多應用場景都會用到,像產品營銷頁面、企業/個人微官網、H5活動頁面等移動端頁面,通過可視化配置快速搭建H5頁面,且提供豐富的頁面組件,更方便的為使用者搭建更強大的H5頁面。
PC端界面如下:
PC端界面移動端(H5和小程序)界面如下:
技術方案
PC端?React?技術棧,移動端?UniApp?跨平臺框架,功能的設計結構圖如下:
裝修頁面前端設計模式.png /**?@description:?DecoratePage?Context交互*?@version:?分支號?20210629*?@author:?xuchao*/ import?React,?{?PureComponent?}?from?'react'; import?{?withRouter,?router?}?from?'umi'; import?{?Layout,?Modal,?Button?}?from?'antd'; import?{?isEmpty,?findIndex,?isArray,?find,?every,?cloneDeep?}?from?'lodash'; import?{?DndProvider?}?from?'react-dnd'; import?{?HTML5Backend?}?from?'react-dnd-html5-backend'; import?{?showMsg?}?from?'@/global'; import?Component?from?'./components/Component'; import?Preview?from?'./components/Preview'; import?Compiler?from?'./components/Compiler'; import?{?DecorateContext,?components?}?from?'./utilities'; import?'./style.less';const?{?Header?}?=?Layout;export?default?class?Decorate?extends?PureComponent?{state?=?{compiler:?'PageSetting',pagename:?'頁面標題',selectIndex:?0,previewData:?[],};getChildContext()?{return?{...this.state,...this.props,setState:?state?=>?this.setState(state),};}render()?{return?(<DecorateContext.Provider?value={this.getChildContext()}><Layout?className="decorate"><Header?className="header"><span className="hand">返回首頁裝修</span><Button?type="primary"?className="fr">發布</Button><Button?type="primary"?className="fr?mr10">保存</Button><Button?className="fr?mr10">預覽</Button></Header><DndProvider?backend={HTML5Backend}><Layout?className="container"><Component?/><Preview?/><Compiler?/></Layout></DndProvider></Layout></DecorateContext.Provider>);} }?數據
前面說到與列舉的產品有哪些區別,區別在于PC端與移動端的數據交互,它們都是通過 iframe 嵌套 H5 的頁面,通過 postmessage API 來做數據交互,而是我沒有這樣做,原因是項目特別緊,加上人員分配問題,所以采用數據定義模式。
通過上面的設計結構圖可以看出PC端最后會生成一份?schema?數據存儲服務端,移動端從服務端獲取到?schema?數據進行解析。數據格式如下:
//?圖片廣告 {component:?'ImageTextAd',options:?{template:?'image', // image:一行一個 carousel:輪播海報 slide:大圖橫向滑動 zone:繪制熱區image:?[{id:?'',url:?'',title:?'',linkCode:?'',linkName:?'',//?熱區zones:?[{x:?178,y:?91,width:?158,height:?132,code:?'123',text:?'測試鏈接2',}],},{id:?'',url:?'',title:?'',linkCode:?'',linkName:?'',//?熱區zones:?[{x:?436,y:?97,width:?170,height:?168,code:?'',text:?'',}],},],indicator:?'dotted',?//?指示器style:?{boxShadow:?'none',borderRadius:?'none',padding:?'0',},}, }, //?公告 {component:?'Notice',options:?{content:?'公告內容',style:?{background:?'rgb(255,?248,?233)',color:?'rgb(100,?101,?102)',},}, }, //?圖文導航 {component:?'ImageTextNav',options:?{template:?'image-nav', // image-nav:圖片導航 text-nav:文字導航images:?[{url:?'',title:?'',link:?'',}],style:?{background:?'rgb(255,?248,?233)',color:?'rgb(100,?101,?102)',},}, }, //?標題欄 {component:?'Title',options:?{style:?{textAlign:?'left',background:?'#FFFFFF',},title:?{text:?'',style:?{fontSize:?'16px',fontWeight:?'bold',color:?'#323233',},},content:?{text:?'',style:?{fontSize:?'12px',fontWeight:?'400',color:?'#969799',},},}, }, //?文本模塊 {component:?'RichText',options:?{content:?'<html></html>',style:?{backgroundColor:?'#F9F9F9',padding:?'10px?10px?0',},}, }, //?輔助分割 {component:?'DivideLine',options:?{template:?'block', // block:輔助空白 line:輔助線style:?{height:?30,//?borderTopWidth:?'1px',//?borderTopStyle:?'dashed',//?borderTopColor:?'#EBEDF0',//?margin:?'10px?0?0',},}, }, //?商品搜索 {component:?'GoodSearch',options:?{style:?{backgroundColor:?'#FFFFFF',},box:?{style:?{borderRadius:?'none',textAlign:?'left',height:?28,backgroundColor:?'#F7F8FA',color:?'#c8c9cc',},},}, }, //?左右圖文 {component:?'LRImageText',options:?{template:?'lr', // lr:左圖右文 rl:左文右圖content:?'',?//?內容image:?{url:?'',?//?圖片地址linkCode:?'',?//?跳轉頁面codelinkName:?'',?//?跳轉頁面namestyle:?{boxShadow:?'none',borderRadius:?'none',},},}, }, //?圖文導航 {component:?'ImageTextNav',options:?{template:?'image', // image:圖片導航 text:文字導航image:?[{url:?'',title:?'導航一',linkCode:?'',linkName:?'',},{url:?'',title:?'導航二',linkCode:?'',linkName:?'',},{url:?'',title:?'導航三',linkCode:?'',linkName:?'',},{url:?'',title:?'導航四',linkCode:?'',linkName:?'',},{id:?uuid(),url:?'',title:?'導航五',linkCode:?'',linkName:?'',},],style:?{backgroundColor:?'#FFFFFF',color:?'#333333',},}, }, //?魔方 {component:?'Cube',options:?{template:?'row-one', // row-one:一行一個 row-two:一行兩個 row-four:一行四個 row-col:一大兩小image:?[{url:?'',linkType:?'',linkName:?'',},],imageMargin:?0,layoutMargin:?0,}, }, //?定位菜單 {component:?'PositionMenu',data:?[],?//?分組信息options:?{template:?'tab-style-one', // tab-style-one:樣式1 tab-style-two:樣式2 tab-style-three:樣式3data:?[{id:?'9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d',code:?'',name:?'',menuName:?'',comsize:?6,},{id:?'9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6c',code:?'',name:?'',menuName:?'',comsize:?6,},],style:?{borderRadius:?'none',fontWeight:?'400',paddingLeft:?'5px',paddingRight:?'5px',},listStyle:?'row-one', // row-one:大圖模式 row-two:一行兩個 row-three:一行三個 row-col:詳細列表commodityStyle:?'no-border', // no-border:無邊白底 shadow:卡片投影 stroke:描邊白底 transparent:無邊透明底commodityName:?true,?//?商品名稱commodityDesc:?true,?//?商品描述commodityPrice:?true,?//?商品價格originalPrice:?true,?//?劃線價格buyButton:?true,?//?購買按鈕buyButtonStyle:?'style-1',?//?購買按鈕樣式buyButtonText:?'馬上搶',?//?購買按鈕文字commoditySubscript:?true,?//?商品角標commoditySubscriptStyle:?'new',?//?商品角標樣式}, }, //?普通商品 {component:?'Goods',data:?[],?//?商品信息options:?{template:?'large', // large:大圖模式 small:一行兩個 three:一行三個 list:詳細列表data:?[],?//?商品信息style:?{borderRadius:?'none',fontWeight:?'400',paddingLeft:?'5px',paddingRight:?'5px',},listStyle:?'row-one', // row-one:大圖模式 row-two:一行兩個 row-three:一行三個 row-col:詳細列表commodityStyle:?'no-border', // no-border:無邊白底 shadow:卡片投影 stroke:描邊白底 transparent:無邊透明底commodityName:?true,?//?商品名稱commodityDesc:?true,?//?商品描述commodityPrice:?true,?//?商品價格originalPrice:?true,?//?劃線價格buyButton:?true,?//?購買按鈕buyButtonStyle:?'style-1',?//?購買按鈕樣式buyButtonText:?'馬上搶',?//?購買按鈕文字commoditySubscript:?true,?//?商品角標commoditySubscriptStyle:?'new',?//?商品角標樣式}, }, //?限時折扣 {template:?'row-one',data:?[],style:?{borderRadius:?'none',fontWeight:?'400',padding:?'0',margin:?'0',},comsize:?10,tag:?'限時折扣',commodityStyle:?'no-border',commodityName:?true,commodityDesc:?false,commodityPrice:?true,originalPrice:?true,lastStock:?true,countdown:?true,progressBar:?true,buyButton:?true,buyButtonStyle:?'style-1',buyButtonText:?'即將開搶', }?拖拽
拖拽依賴第三方庫react-dnd,提供的Hooks Api特別方便,上面的設計結構圖?Component組件(DragSource) 和?Preview組件(DropTarget) 用到了拖拽,Preview組件不僅要支持上下拖拽,而且需要配合Compiler組件聯動。
/**?@description:?DragSource?拖動組件*?@version:?分支號?20210629*?@author:?xuchao*/ import?React,?{?useContext?}?from?'react'; import?{?useDrag?}?from?'react-dnd'; import?{?findIndex,?some,?isUndefined,?filter?}?from?'lodash'; import?{?v1?as?uuid?}?from?'uuid'; import?{?DecorateContext?}?from?'../../utilities'; import?schema?from?'../Materials/schema';export?default?({?component,?name,?icon,?max,?componentType,?fixedIndex?})?=>?{const?{?previewData?=?[],?setState?}?=?useContext(DecorateContext);const?number?=?filter(previewData,?{?component?}).length;const?[,?drag]?=?useDrag(()?=>?({type:?'component',options:?{dropEffect:?'copy',},item:?{type:?'add',component,name,max,componentType,fixedIndex,},end:?(item,?monitor)?=>?{const?hasPh?=?some(previewData,?{?component:?'placeholder'?});const?phIndex?=?findIndex(previewData,?{?component:?'placeholder'?});if?(!hasPh)?return;//?組件放置已達上限if?(number?===?max)?{previewData.splice(phIndex,?1);setState({?previewData:?[...previewData]?});return;}if?(monitor.didDrop())?{//?判斷拖拽放入Preview組件中,占位元素替換成組件元素previewData.splice(phIndex,?1,?{id:?uuid(),component:?item.component,options:?schema[component].defaultOptions,});}?else?{//?判斷拖拽沒有放入Preview組件中,刪除占位元素previewData.splice(phIndex,?1);}setState({previewData:?[...previewData],selectIndex:?phIndex,compiler:?item.component,});},}),[previewData],);/***?@description:?新增組件*?@author:?xuchao*/const?handleClick?=?()?=>?{if?(number?===?max)?return;previewData.splice(!isUndefined(fixedIndex)???fixedIndex?:?previewData.length,?0,?{id:?uuid(),component,options:?schema[component].defaultOptions,});setState({previewData:?[...previewData],selectIndex:?!isUndefined(fixedIndex)???fixedIndex?:?previewData.length?-?1,compiler:?component,});};return?(<div?ref={drag}?className="item"?onClick={handleClick}><i?className={icon}></i><div?className="name">{name}</div><div?className="number">{number}/{max}</div></div>); };?/**?@description:?DropTarget?放置組件*?@version:?分支號?20210629*?@author:?xuchao*/ import?React,?{?useContext,?useCallback?}?from?'react'; import?{?useDrop?}?from?'react-dnd'; import?{?findIndex,?some,?isUndefined,?filter?}?from?'lodash'; import?update?from?'immutability-helper'; import?{?DecorateContext?}?from?'../../utilities'; import?Item?from?'./Item';export?default?()?=>?{const?{?previewData?=?[],?selectIndex,?setState?}?=?useContext(DecorateContext);const?[,?drop]?=?useDrop(()?=>?({accept:?'component',hover:?item?=>?{const?limit?=?filter(previewData,?{?component:?item.component?}).length;const?hasPh?=?some(previewData,?{?component:?'placeholder'?});const?spliceIndex?=?!isUndefined(item.fixedIndex)??item.fixedIndex:?previewData.length;if?(item.type?===?'add'?&&?!hasPh)?{//?判斷占位符是否已經存在,若懸停空白處,插入占位符previewData.splice(spliceIndex,?0,?{component:?'placeholder',limit:?item.max?===?limit???true?:?false,});setState({?previewData:?[...previewData]?});}},}),[previewData],);/***?@description:?move?callback*?@param?{number}?dragIndex*?@param?{number}?hoverIndex*?@param?{object}?item*?@author:?xuchao*/const?handleMove?=?useCallback((dragIndex,?hoverIndex,?item)?=>?{if?(item.type?===?'add'?&&?!dragIndex)?{//?判斷拖拽是?Component?的組件,則?dragIndex?為?undefined,修改占位符的位置即可const?limit?=?filter(previewData,?{?component:?item.component?}).length;const?hasPh?=?some(previewData,?{?component:?'placeholder'?});const?spliceIndex?=?!isUndefined(item.fixedIndex)???item.fixedIndex?:?hoverIndex;//?判斷占位符是否已經存在,不再重復插入if?(hasPh)?{const?phIndex?=?findIndex(previewData,?{component:?'placeholder',});setState({previewData:?update(previewData,?{$splice:?[[phIndex,?1],[spliceIndex,0,{component:?'placeholder',limit:?item.max?===?limit???true?:?false,},],],}),});return;}setState({previewData:?update(previewData,?{$splice:?[[spliceIndex,0,{component:?'placeholder',limit:?item.max?===?limit???true?:?false,},],],}),});}?else?{//?判斷拖拽是?Preview?的組件,則?dragIndex?不為?undefined,替換?dragIndex?和?hoverIndex?位置的元素即可setState({previewData:?update(previewData,?{$splice:?[[dragIndex,?1],[hoverIndex,?0,?previewData[dragIndex]],],}),selectIndex:?dragIndex?===?selectIndex???hoverIndex?:?dragIndex,});}},//?eslint-disable-next-line?react-hooks/exhaustive-deps[previewData],);/***?description:?delete?callback*?param?{object}?event*?param?{number}?index*?author:?xuchao*/const?handleDelete?=?(event,?index)?=>?{event.stopPropagation();previewData.splice(index,?1);setState({previewData:?[...previewData],compiler:?selectIndex?===?previewData.length???undefined?:?previewData[index].compiler,});};return?(<div?ref={drop}?className="content">{previewData.map((item,?index)?=>?{return?(<Itemkey={item.id}index={index}selectIndex={selectIndex}{...item}onClick={()?=>?setState({?selectIndex:?index,?compiler:?item.component?})}onMove={handleMove}onDelete={handleDelete}/>);})}</div>); };?總結
開發耗費時間比較長的地方是怎么設計與移動端同步數據和拖拽功能,最后還是迎刃而解。如果大家有什么疑問可以交流一下????
?? 看完三件事
如果你覺得這篇內容對你挺有啟發,我想邀請你幫我三個小忙:
點個【在看】,或者分享轉發,讓更多的人也能看到這篇內容
關注公眾號【趣談前端】,定期分享?工程化?/?可視化?/?低代碼?/?優秀開源。
Dooring可視化搭建平臺數據源設計剖析
可視化搭建的一些思考和實踐
基于Koa + React + TS從零開發全棧文檔編輯器(進階實戰)
從零使用electron搭建桌面端Dooring
點個在看你最好看
總結
以上是生活随笔為你收集整理的可视化搭建移动端店铺解决方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 字节跳动社招全岗位研发面经(已拿offe
- 下一篇: 如何为谷歌浏览器启用暗模式