前端低代碼之路(三)-- 營銷頁面代碼架構分享

前言:以前寫了2篇基本沒有乾貨,怕讀者罵我狗,既然分享了就完全點,都是幹代碼的,有啥不能寫的。具體的代碼我會發到github進行開源。整個項目就不開源了,只開源編輯部分。代碼是react代碼,沒辦法一直就用這個框架,意思到了不要太看中語法框架。沒有時間對龐大的代碼進行一一講解,各位且本身看吧,嫌煩就別看了。看看這裏的思路就行了。css

咱們仍是從架構講吧,不少人問前端架構幹啥的,其實我也不知道,我通過winter(臥槽,winter是誰,不知道的請自行搜索)指點,告訴我前端架構就是解決複用性的問題,豁然開朗,請不要再給前端架構搞的神祕而遙不可及。以React爲例,就是合理的分拆組件,到達最高的複用和擴展。隨着項目不一樣也不盡相同,固然不少人喜歡用*-cli等工具生產項目,這個不糾結,反正我以爲靈活度不夠,一直都是本身作。html

image.png

通常個人項目裏面README.md開頭都會有這麼一個圖,說明當前項目的文檔目錄結構,以便參與項目的同事看得懂代碼。前言廢話說完,進入正題,項目目錄不是重點。本人由於是react系開發者,因此代碼以react爲主,可是我分享的基本原則通用,未必非react不可。前端

根據上一篇的產品形態,我們針對於2類產品分析,一類就是H5頁面生成器,一類就是流程定義+form表單生成。node

先說H5頁面編輯器,來重複一下基本的產品需求react

  • 展現效果需求,穩定性,不能有報錯,不能白屏。速度還得快,不能讓客戶等過久。
  • 業務需求,組件的豐富,就是市面上有的咱要有,沒有的也但願有。例如,爲了有條理的展現更多商品,相似於分類的組件,爲了更好的控制頁面結構,相似於layout的需求
  • 路由需求,隨着營銷類的運營能力提高,不少活動是以主題活動的形式開展,就不是一個一個單頁的宣傳了,每每是一個會場的概念,就會集合不少個單頁組成一些列的頁面搞活動,那麼就須要控制路由的能力
  • 投放需求,活動頁面會有定點投放的須要,相似於某些活動只在杭州搞,杭州之外的地區不參加,頁面就要有根據地區展現的能力,還有有些活動是定點在某個渠道搞,其餘渠道不搞。
  • 數據收集的須要,搞活動目的仍是要留存客戶,固然但願還有入口可讓用戶留下點信息。以便後續的變現。
  • 數據分析的須要,通常來說,頁面訪問數據將來仍是要能提高之後的活動效果,用戶行爲數據分析,以便爲後續活動提供策略支持,簡單的uv,pv已經不能知足當下運營的須要了,而是要跟立體的數據,例如商品的點擊次數,用戶在頁面的留存時間,用戶在整個活動中的訪問鏈路。

基於上述的問題,咱們須要拆解需求,造成一個基礎的前端框框,把這些需求點框進去,無論UI,UI是很簡單的,沒有任何須要架構的地方,根據prd去畫就行了。基本編輯的UI也就是左中右結構,左側組件列表,中間效果展現,右側屬性配置。基本如是jquery

image.png

咱們用react代碼實現以下, 入口 editor.html, editor.jsgit

<div className="page-container">
    <Suspense fallback="">
          <ThemeProvider theme={theme}>
                <Header savePage={this.savePage} publishPage={this.publishPage} />
                <Operation compCount={compCount} />
                <Preview pageConf={pageConf} compList={compList} />
                <Config
                  pageConf={pageConf}
                  compList={compList}
                  currentUuid={currentUuid}
                  onSort={this.handleSort}
                />
          </ThemeProvider>
    </Suspense>
</div>
複製代碼

這是UI的設計,外層的 <div/> <Suspense/> <ThemeProvider/>忽略github

<Header/> - 頭部包含返回按鈕,預覽,保存,發佈等按鈕功能ajax

<Operation/> - 左側的組件列表markdown

<Preview/> - 中間的預覽展現,爲了很好的隔絕dom結構和event事件,中間使用iframe加載一個preview.html,利用postmate組件進行iframe通訊工做。其中暴露不少問題,後面會詳細講解。

<Config/> - 就是根據用戶選中的組件,展現相應的配置項

這裏講下整個項目的通訊閉環,我想你們都很容易明白技術實現的過程當中是怎麼作的了。整個頁面UI其實都是一份數據結構在驅動。利用react的內部數據雙向綁定,每一次的新建修改其實都是在改同一份數據結構。舉個列子:

env: "publish",
pageId: "1632599993707810817",
pageData: {
    pageConf:{
        background: {a: 1, r: 255, b: 255, g: 255}
        pageDec: ""
        pageName: "杭州印象"
        pageURL: "https://cdn.izelas.run/publish/2194972e.html"
    }, 
    compList: [
        {
            uuid: "c3f1ecdc71c1df833a4498fddd4040db",
            compLabel: "輪播",
            compName: "Carousel",
            chosen: false,
            setting: {
                carouselProps: {effect: "scrollx", autoplay: true},
                imageList: (5) [{…}, {…}, {…}, {…}, {…}],
                outLine: false,
                place: "inline",
            }
       }
    ]
},
複製代碼

秀到這你們就明白了,其實技術總體沒什麼神祕的,也符合大多數研發者的實現。上述配置就是一個頁面裏面包含了一個輪播組件,後的會在用戶點擊發布的時候,把數據和渲染作一次加載,打包成靜態文件,推送到cdn上,返回一個靜態頁面地址,這就是整個邏輯。由於後臺是nodejs那塊代碼相對簡單,回頭再放,主要是前端的渲染。下面你們要作幾件事

  • 定義清楚全部組件的Schema,也就是compList中每一個組件對應的數據結構。這個跟你具體組件的業務需求密切結合。
  • 解決數據通訊問題,也就是新建組件,修改組件屬性,刪除組件事件發生時候,預覽界面要實時改變
  • 數據加載的問題,組件並不是徹底靜態,有些組件須要獲取後臺數據用以填充頁面,例如商品組件,展現用戶選擇的商品。這些商品信息須要從後臺ajax獲取,加載這些數據的策略影響渲染

看UI代碼,你們會發現,入口頁面持有全部的數據,任何子組件不持有數據,經過組件props屬性進行傳遞。而事件經過廣播或者監聽模式完成。對於這個模式不清楚的,我稍微解釋一下,由於react在父子組件的事件上能夠經過props注入完成,若是是兄弟組件,或者更遠的平級組件,就須要藉助共同的父組件來傳遞事件。效率不高,還容易搞混。如今的模式是,當我點擊一個按鈕,這個按鈕其實沒有實際邏輯操做,只是大喊一聲我被點了,就完了,把本身被點了這個事廣播出去,那麼誰處理?誰監聽誰處理。就好比老師上課點名,張三,這個時候全班同窗都聽到了,可是隻有張三答了「到」。就是這個意思。

// 持有 pageData -> compList,pageConf 的頁面監聽事件
// 支持用戶點擊新建,這裏點擊觸發是在operation組件,向此處廣播事件
emitter.addListener('_micro_page_createComp_', this.createComp)
// 監聽數據變化,而後通知底層組件 包括組件建立,刪除,複製,位置調整
emitter.addListener('_micro_page_onSetCompList_', this.onSetCompList)
// 監聽組件屬性值改變的 設置某個組件屬性
emitter.addListener('_micro_page_setCompProps_', this.setCompData)
// 監聽頁面設置
emitter.addListener('_micro_page_setPageConfig_', this.setPageConfig)
// 監聽歷史記錄回滾消息
emitter.addListener('_micro_page_posiCompList_', this.goBackAction)
// 組件列表操做消息監聽
emitter.addListener('_micro_page_compListeAction_', this.buildCompListData)
複製代碼
// 按鈕的實現就很是簡單了,只要把UI畫上,點擊一下就把被點擊的事件廣播出去,附帶一個默認配置參數
    // 剩下的就交給監聽了此時間的方法,這個頂層數據發生變化,那麼react會自動diff,全部接收到props
    // 的組件都會更新props,而後更新UI。config的原理是同樣的。這樣就把數據和處理的方法所有集中在
    // 入口文件中,邏輯比較清晰,容易排查問題。
    <div
      className="module-item"
      onClick={() => {
        emitter.emit('_micro_page_createComp_', _cloneDeep(defaultConfig))
      }}
    >
      <div className="module-item-inner">
        <SvgIcon component={renderComponent(compName)} viewBox="0 0 22 22"/>
        <div className="module-item-name">{compLabel}</div>
      </div>
    </div>
複製代碼

這個邏輯講明白了,其實就不復雜了,無非就是組件多一點,UI界面複雜一些而已。根據prd的組件設計去開發就好,沒什麼難度。還剩一些問題,就是數據埋點和ajax數據通訊。

數據埋點通常你們都會寫一個比較通用的上報函數,也比較簡單。很是成熟,不會github上搜搜太多。不行就看看GA(google analysis)的代碼。主要是一些按鈕數據的上報,好比點擊某個商品,雖然商品頁面會統計商品的pv,可是不知道是從營銷頁面來的。相似的問題不少,固然GA也提供了方法能夠在點擊事件發生時候主動上報數據,問題來了,自己低代碼的平臺,頁面尚未發佈,須要根據用戶製做頁面的結果生成相應的頁面,可能咱們都不知道用戶須要選什麼商品。還有一個問題就是代碼侵入,若是GA的方法改了呢,那麼我就要改。得不償失,個人解法就是便籤隱藏屬性。類型jquery的data-,而後根據數據埋點的須要把須要的數據放到一個自定義屬性中,數據收集組件本身監聽pop上來的事件,到dom中自行取用。

還有ajax請求的問題,這個問題用前端的辦法確實沒啥好解的,不是不能作,只是用nodejs的解法更方便,反正我也是要根據配置,把頁面打成靜態的文件,上傳到cdn的,那麼幹脆就在這一步作了就行了。其實沒啥難度,就是掃描一下全部組件,發現有ajax請求的,nodejs發起請求,把請求回來的數據靜態化到配置項裏面,而後調用cdn方法把html文件上傳就行了。

const _putHTMLToOss = async (pageConf, compList, pageId) => {
  try {
    // 獲取全部動態數據
    const _compList = await getAllData(compList)
    // 設置靜態頁面url
    const publishHtmlPath = pageConf.pageURL
      ? pageConf.pageURL.replace("https://cdn.izelas.run/", "")
      : `hd/publish/${getShortStr()}.html`;
    if (!pageConf.pageURL)
      pageConf.pageURL = `https://m.myweimai.com/${publishHtmlPath}`;
      // 裝配模板須要的數據
      const injector = {
          jses: [
            `${cdnPath}/micropage/${timestamp.micropage}/common.js`,
            `${cdnPath}/micropage/${timestamp.micropage}/micro-view.js`,
          ],
          css: [
            venders.materialUI,
            `${cdnPath}/micropage/${timestamp.micropage}/common.css`,
            `${cdnPath}/micropage/${timestamp.micropage}/micro-view.css`,
          ],
          data: JSON.stringify({
            env,
            pageId,
            pageData: {
              pageConf,
              compList: _compList,
            },
            weimaiHosts,
          }),
          title: (pageConf && pageConf.pageName) || "微脈-模塊化編輯器",
     };
    // 讀取模板文件
    const renderString = fs.readFileSync(path.join(view_path, "view.html"), {
      encoding: "utf-8",
    });
    // 編譯模板
    const compiled = _.template(renderString);
    // 數據和模板結合成已編譯好的字符串
    const compiledStr = compiled(injector);
    // 調用oss受權客戶端
    const ossClient = new OSS(ossKey);
    // 上傳文件到oss
    await ossClient.put(publishHtmlPath, Buffer.from(compiledStr));
    return true;
  } catch (error) {
    console.error(error);
    return false;
  }
};
複製代碼

此文代碼基本是截取,會意不要生搬,畢竟每一個系統都有特殊性,尤爲是行業對於產品的要求,這套代碼的源碼會開源到github,等我整理完,由於nodejs代碼有驗證和一些技術點不方便開源,只放前端代碼。仍是那個意思,明白思路,根據本身的業務本身思考,能幫到各位最好,有好的解決方案歡迎提供。系統也在不斷的升級中。還須要加入一些js和圖片資源加載的優化,還有就是模板和換膚的一些思考。這些有待各位同仁的共同努力。

相關文章
相關標籤/搜索