如何設計實現H5營銷頁面搭建系統

背景

近幾年,low codeno codepro code等愈來愈多的出如今咱們的視野中。抱着不被卷的心態 🐶,我決定來深刻探索一下。html

我所在的是營銷部門。天天/月都承載着大量的營銷活動,本文也是我在探索可視化搭建過程當中的一些心得體會前端

其實這些名詞都與搭建相關。其中一個應用最廣的場景就是營銷。咱們知道不管是淘寶、京東這些電商巨頭,亦或是攜程、去哪兒這些OTA,天天 APP 上都承接着無數的活動頁面。vue

大體梳理一下營銷活動的一些特色:react

  • 頁面相似: 頁面佈局和業務邏輯較固定
  • 需求高頻: 每週甚至天天有多個這種需求
  • 迭代快速: 開發時間短, 上線時間緊
  • 開發耗時: 開發任務重複, 消耗各方的溝通時間和人力

不一樣於常規的業務開發,營銷活動每每受影響的因素不少:節假日大促、政策規則等,因此每每多是今天上午說的活動,明天就要上這種。若是單靠前端同窗去維護,那怕不是要加無數的班(好比以前的我 😭)npm

每次來一個新活動,都靠前端同窗去畫頁面,顯然這種效率是極低的。若是排期寬裕點還行,若是遇到618雙11怕不是要逼瘋咱們。。api

樓層搭建

鑑於這種場景,內部也進行了不少的討論。得出的一致結論就是:開發同窗提供營銷搭建後臺,頁面作成可配置化,配置的工做交給產品/運營同窗。這樣,基於樓層搭建營銷頁面的方案就應運而生了。數組

其實樓層搭建在營銷頁面的搭建中是一種比較常見的方式。 如上圖是京東的一個活動頁面,頁面主要由三部分組成:頭圖樓層、優惠卷樓層、熱銷樓層。由於就像生活中的蓋樓同樣,因此在早期的營銷搭建中,就有了樓層的概念。每一個樓層其實就對應了一個具體的組件。 而後在具體樓層的編輯內容區域就能夠去上傳對應的數據了。緩存

但這種方式有一個很大的缺點就是:不夠直觀。隨着業務的快速迭代,也陸續獲得了一些反饋。最終發現運營同窗真正須要的是那種能夠直接拖拽生成頁面的,也就是可視化搭建markdown

可視化搭建

樓層搭建的基礎上進一步改造爲可視化搭建,複雜度提高了不少。單純的去看頁面的不一樣呈現,可能僅僅就是加了一個拖拽的操做。但真正準備去落地的時候,發現其中的細節特別多,也包含了不少的設計理念在裏面。數據結構

咱們先來看一下原型圖,而後仔細分析一下須要作的事情: 市面上大部分營銷可視化搭建系統基本都是相似上圖這樣的頁面呈現。左側對應組件區域,中間是畫布區域,右側是屬性區域。

大體操做流程就是拖動左側的組件到中間的畫布,選中組件,右側屬性面板就會展現與該組件關聯的屬性。編輯右側屬性,畫布中對應的組件樣式就會同步更新。頁面拼接完成,可經過相似預覽的操做進行頁面預覽。預覽無誤,便可經過發佈按鈕進行活動的發佈。

流程梳理完,咱們來看下項目的基礎架構:

這裏我基於原型對項目設計進行了功能的鋪平,其實仍是圍繞組件畫布屬性面板這三塊。

到這裏,咱們思考幾個問題:

  • 畫布區域如何渲染已添加到畫布中的組件(組件庫組件會不少,畫布中可能只需添加幾個組件,考慮如何作動態渲染)?
  • 組件從左側拖入畫布區域,選中組件,就可知道該組件關聯的屬性。組件 Schema 如何設計?
  • 畫布區域和預覽時組件的渲染是否可共用一套渲染邏輯?
  • 組件的數據如何去維護(考慮添加組件、刪除組件、組件渲染/預覽等場景)
  • 組件庫如何維護(考慮新增組件知足業務須要的場景)

首先來看第一條,簡單概括就是動態加載組件

動態加載組件

若是你常用vue,那我想你對vue中的動態組件確定不陌生:

<!-- 當 currentView 改變時組件就改變 -->
<component :is="currentView"></component>
複製代碼

市面上的大部分編輯器也都是利用了這個特性,大體實現思路就是:

  • 用一個數組componentData維護編輯器中的數據
  • 將組件拖動到畫布中時,將此組件的數據pushcomponentData
  • 編輯器遍歷(v-for)組件數據componentData,將組件依次渲染到畫布中

因爲我在的團隊包括我本身一直都在使用react,這裏着重來提下react組件動態加載的實現方式,框架使用的是umi

我在實現這部分功能時,在umiapi中找到了dynamic 封裝一個異步組件:

const DynamicComponent = (type, componentsType) => {
  return dynamic({
    loader: async function () {
      const { default: Component } = await import(
        `@/libs/${componentsType}/${type}`
      );
      return (props) => {
        return <Component {...props} />;
      };
    },
  });
};
複製代碼

而後在調用的時候,將組件數組傳入便可:

const Editor = memo((props) => {
  const {
    componentData,
  } = props;
  return (
    <div> {componentData.map((value) => ( <div key={value.id} > <DynamicComponent {...value} /> </div> ))} </div>
  );
});
複製代碼

解決了第一個問題,咱們來看第二個,也就是:組件 Schema該如何設計

組件 Schema 設計

這裏涉及到組件、畫布和屬性區域三塊的聯動。主要包含組件強相關的表單屬性以及初始值。

因爲涉及到組件屬性的字段限制及校驗,爲了規範和避免出錯,建議項目使用 ts

這裏以一個TabList組件爲例,展現一下它的Schema結構:

const TabList = {
  formData: [
    {
      key: 'tabs',
      name: 'Tab名稱',
      type: 'TitleList',
    },
    {
      key: 'layout',
      name: '佈局方式',
      type: 'Select',
      options: [
        {
          key: 'single',
          text: '單列',
        },
        {
          key: 'double',
          text: '雙列',
        },
      ],
    },
    {
      key: 'activeColor',
      name: '激活顏色',
      type: 'Color',
    },
    {
      key: 'color',
      name: '文字顏色',
      type: 'Color',
    },
    {
      key: 'fontSize',
      name: '文字大小',
      type: 'Number',
    },
  ],
  initialData: {
    tabs: [
      {
        id: uuid(6),
        title: '華北',
        list: [
          {
            icon:
              '',
            goCity: '煙臺',
            backCity: '北京',
            goDate: '08-18',
            goWeek: '週三',
            airline: '中國聯合航空',
            price: 357,
            disCount: '4',
          },
        ],
      },
    ],
    layout: 'single',
    color: 'rgba(153,153,153,1)',
    activeColor: 'rgba(0,102,204,1)',
    fontSize: 16,
  },
};
複製代碼

在組件初始化時就約定好其對應的結構,當將組件拖入畫布區域後,咱們能夠拿到當前選中的組件數據,而後右側的屬性面板就能夠渲染出對應的可編輯表單項。來看下右側表單區域的代碼:

const FormEditor = (props) => {
  const { formData, defaultValue } = props;
  console.log('FormEditor props', props);
  const [form] = Form.useForm();

  const handleFormChange = () => {
    console.log('表單更新',form.getFieldsValue());
  };

  return (
    <Form form={form} initialValues={defaultValue} onValuesChange={handleFormChange} > {formData.map((item, i) => { return ( <React.Fragment key={i}> {item.type === 'Number' && ( <Form.Item label={item.name} name={item.key}> <InputNumber max={item.range && item.range[1]} /> </Form.Item> )} {item.type === 'Text' && ( <Form.Item label={item.name} name={item.key}> <Input /> </Form.Item> )} {item.type === 'TitleList' && ( <Form.Item label={item.name} name={item.key}> <TitleList /> </Form.Item> )} {item.type === 'Select' && ( <Form.Item label={item.name} name={item.key}> <Select placeholder="請選擇"> {item.options.map((v: any, i: number) => { return ( <Option value={v.key} key={i}> {v.text} </Option> ); })} </Select> </Form.Item> )} </React.Fragment> ); })} </Form>
  );
};
複製代碼

表單區域具體表單項發生改變後就會觸發onValuesChange,也就是ant design表單的字段值更新時觸發回調事件。這時數據就會更新到store中。而畫布的數據源就是store中的componentData進而頁面會實時更新。來看下總體的數據流轉圖:

至此,第二個問題也就解決了。

接着看第三個問題:畫布區域和預覽時組件的渲染是否可共用一套渲染邏輯?

組件共享

咱們能夠把預覽組件理解爲畫布區的靜態版本或者快照版本。從頁面呈現上來看並無太大的差別,那麼從代碼設計上,這兩部分固然就能夠共享一個組件。咱們把這個共享組件叫作RenderComponent.tsx,數據源爲store中的componentData,而後結合DynamicComponent組件,就獲得了以下代碼:

const RenderComponent = memo((props) => {
  const {
    componentData,
  } = props;
  return (
    <> {componentData.map((value) => ( <div key={value.id} > <DynamicComponent {...value} /> </div> ))} </>
  );
});
複製代碼

數據存儲/分發

至於第四個問題:組件的數據如何去維護(考慮添加組件、刪除組件、組件渲染/預覽等場景),其實在上面回答第二個問題的時候,已經提到了。全局有維護一個store

state:{
  // 全部添加到畫布中的組件數據
  componentData:[],
  // 當前編輯的組件數據
  curComponent: {}
}

reducers:{
  // 添加組件到componentData
  addComponentData(){},
  // 編輯組件,更新componentData及curComponent
  editComponentData(){},
  // 刪除組件
  delComponentData(){}
}
複製代碼

對於可視化編輯器這種大型前端項目,須有一個全局狀態管理機制去作數據的存儲和分發。這樣對於數據狀態的共享和同步也是頗有幫助的。

組件開發/維護

來看上面提到的最後一個問題:組件庫如何維護(考慮新增組件知足業務須要的場景)

這種目前有兩種通用的作法:

  • 直接放在項目中
  • 抽成 npm 包,造成獨立的第三方組件庫

若是是項目初期,我感受第一種作法也不是不能夠,方便調試。但長遠來看,營銷場景下沉澱出來的組件絕對不會少,抽成第三方 npm 包纔是明智的選擇,同時要配合一個相似組件管理後臺的管理系統,對組件作統一的管理。

回到組件自己而言,必須有嚴格的開發規範。每一個組件原則上只是呈現上的不一樣,對於約定俗成地組件研發規範則必須遵照。至於如何去限制,能夠經過文檔(弱)或者 cli(強)去作。

模板

除了上面的幾個問題,還有一個點沒提到:模板。咱們知道營銷活動有一個很典型的特色:頁面相似。若是運營/產品同窗從零去生成一個頁面也是挺耗費時間的,並且大部分活動都是歸屬於某一個大類下面的,咱們能夠把這些類似的活動抽成模板。基於模板建立就會省時省力不少。鑑於這部份內容還在開發遷移中,暫時就不展開細說了。

到這裏,我感受已經把可視化編輯器實現上最爲複雜的幾部分以問題的形式一一解答了。其實不管是組件動態加載仍是組件schema的設計數據結構的設計組件庫的維護等,每一個團隊均可以制定一套適合本身的規範,沒有絕對的對錯之分。

其實在這個編輯器的實現過程當中,有不少不容咱們忽略的底層實現細節。包括:

  • 拖拽
  • 組件圖層層級
  • 放大/縮小
  • 撤銷/重作
  • 吸附
  • 綁定事件/動畫

這些細節我就不一一展開說了,推薦一篇文章:可視化拖拽組件庫一些技術要點原理分析。文章對於上面提到的技術要點都有很詳細的說明。

low code/no code/pro code

上面說了這麼多,下面讓咱們回到文章最開始提到的low code/no code/pro code。我會結合咱們的可視化編輯器來闡述一下這三者。

首先來看下運營/開發同窗使用編輯器建立活動的大體流程:

no code

首先來簡單說明一下,什麼是no code:從字面上來看就是無代碼,也就是不寫代碼。

從上面的流程圖中,能夠看到運營/產品同窗經過可視化編輯器,不用寫一行代碼,就能夠搭建出功能齊全的活動頁面。這種對應的就是no code

low code

low code的定義則是低代碼、少寫代碼。

在上面的流程圖中,更多體如今前端同窗開發組件庫。須要寫部分代碼,總體經過拖拽的方式生成的方式。對應的就是low code

pro code

pro code的定義是純代碼,也就是不經過任何可視化工具,全靠開發手寫的代碼形式。在low codeno code出現以前,這種方式是最爲廣泛的研發方式。

在上面的流程圖中,這部分並無體現。可是在實際的業務開發中,這種場景倒是常常存在的。可能當前的一個營銷活動,交互複雜、鏈路長,那經過本文這種可視化編輯器是很難去定製的。只能經過開發去手動寫代碼的方式去知足業務需求。

可視化編輯器更多的是去知足規則相似的頁面開發,首要職責是去減輕重複業務的開發

展望

至此,一個營銷系統的搭建探索演進流程我就大體梳理完畢了。

但,這只是一個開始。本文更多的是側重於前端側的探索,也僅僅是向可視化編輯器邁出了第一步,只是一個更傾向於純前端的項目,不少邏輯都尚未考慮。這裏列一下後面要作的吧:

  • 模板市場
  • 數據中心
  • 埋點
  • 組件調試/預覽
  • 緩存
  • 開放 api 能力
  • CDN
  • 跨端
  • ...

❤️ 愛心三連

1.若是以爲這篇文章還不錯,來個分享、點贊、在看三連吧,讓更多的人也看到~

2.關注公衆號前端森林,按期爲你推送新鮮乾貨好文。

3.特殊階段,帶好口罩,作好我的防禦。