One, foreword

This article will give you a quick understanding of how to build a code-free platform. Here is the difference between a code-free and a low-code platform:

  • Code-free development platform refers to the practice system development without any code development, only need to pull pull, suitable for operators and other people who are not familiar with programming
  • Low code development platform is the use of a small amount of the simplest code can be completed program development

Usage Scenarios:

  • Code-free development platforms are ideal for building for specific scenarios, such as surveys, home page content, double 12 events, etc
  • Low code development platforms are not only suitable for specific small applications, but can be more flexible for customization.

The content of this picture is the content of the specific building block platform, which is also the focus of this article

Ii. Platform structure

We can see that the building platform is divided into four parts: the head toolbar, the left material library, the middle display area and the right dynamic modification area.

Materials warehouse on the left

The warehouse stores our various active component libraries. But because our modular platform structures involved in the activities and build the scene very diverse (such as: our agencies show home page content page, website content, marketing activities, analysis of questionnaire survey page, reading course, page, etc), each activity has its own special component libraries, so how to reasonable design of data structure is very important.

The general data structure is as follows:

{id: 'XXX ', title: 'OrgIntro', type: 'OrgIntro', icon:' httpXXXX. PNG ', belong: ['home'], limit: 1, identity:3,}, {id: 'XXX ', title: 'Banner', icon:' httpXXXX. PNG ', belong: ['sitesEditor','questionnaire'...] , limit: 'infinity', identity:1, },Copy the code

Let me outline the overall structure:

  • Title Indicates the title of the component

  • Type is the type of each material. Correspond to the Type of the Component in our middle display area

    <component :is='item.type'></componet>
    Copy the code
  • An icon is an icon

  • Belong represents the current activity scene. We have a corresponding type for each scene. For example, home represents the home page, questionnaire represents the questionnaire type and so on. Therefore, an array can be used to represent the current activity scene to dynamically decide whether to display the material

    v-show="item.belong.includes(sitesPath)"
    Copy the code

    Item represents each material and sitesPath represents the current activity scenario.

  • Limit is used to limit the amount of material that can be added to the middle display area. Because there are some scenarios where we don’t want to add a material more than once, limit is a good way to limit it. Here the principle is to drag the material to the middle display area, according to the material type to determine how many same material in the middle display area, and compare with the limit can be limited.

  • Identity can be understood as user level. 1 to 3 represent experience users, ordinary members, and advanced VIPs. And the user level is different corresponding function also is different. Such as:

    • At higher levels, the limit may not be limited
    • The higher the level, the more customizations the user can have on the current component
    • .

Three, the middle display area

Let’s see how to render the middle display:

      <draggable
        :disabled="isDisable"
        :list="items"
        :group="{ name: 'sites-editor', pull: false }"
        @add="onAdd"
        @sort="onSort"
      >
        <transition-group class="item-list" id='item-list'>
          <div
            :class="{ item: true, active: activeItemId === item.id }"
            v-for="(item, index) in items"
            :key="index"
            @click="activeItem(item)"
            @contextmenu.prevent="onContextmenu"
          >
              <component :is="item.type" :moduleProp="item"></component>
            	<item-action :index="index" :total="total" @doAction="doAction"></item-action>
          </div>
        </transition-group>
      </draggable>
Copy the code

Analysis is as follows:

3.1 vuedraggable

The VueDraggable library we use for drag is an excellent open source library. Specific use you can see the official website, here is not much introduction

3.2 Dynamic Component

The rendering component adopts the dynamic component in VUE, depending on the material library each material has a unique type to render the corresponding component.

Also, lazy loading of the components rendered here is recommended

  components: {
    OrgIntro: () = > import('.. /modules/home/OrgIntro/Playground'),
    Banner: () = > import('.. /modules/common/Banner/Playground'),
   // ...
  }
Copy the code

3.3 item-action Selected component

When we click the component, there will be a small icon on the right side of the move up and down and delete (move up and down can also be triggered by dragging).

Item-action represents the currently selected component and can be moved up, down, and deleted.

  <div class="item-action-wrap">
    <a-icon type="up-circle" @click="$emit('doAction', 'up', index)" v-if=! "" isFirst" />
    <a-icon type="down-circle" @click="$emit('doAction', 'down', index)" v-if=! "" isLast" />
    <a-icon class="close" type="close-circle" @click="$emit('doAction', 'delete', index)" />
  </div>
<script>
  
export default {
  props: {
    index: {
      type: Number.default: 0,},total: {
      type: Number.default: 0,}},computed: {
    isFirst() {
      if (this.index === 0) {
        return true
      }
      return false
    },
    isLast() {
      if (this.index === this.total - 1) {
        return true
      }
      return false}},}</script>
Copy the code

Analysis:

  • IsFirst and isLast because the first component and the last component areUnable to move up or down
  • Finally, they all send an event externallydoAction, this function does:
    • It’s not hard to delete or move things externallyspliceoperate
    • Records the currently selected component object.Modify the content of the data in the right halfThis is where it was delivered

3.4 onContextmenu

A VUE-ContextMenuJS library is recommended to easily display the right click popup menu. The specific writing is as follows

    onContextmenu(item,index) {
      this.$contextmenu({
        zIndex: 99999999.items: [{label: The 'top'.onClick: () = > this.onTop(index)
          },
          {
            label: 'after'.onClick: () = > this.onBack(index)
          },
          {
            label: 'copy'.onClick: () = > this.onCopy(item,index)
          },
          {
            label: 'delete'.onClick: () = > this.onDelete(index)
          },
          {
            label: 'paste'.disabled:! (this.copyItem&&this.copyItem.type)
            onClick: () = > this.onCopy(item,index)
          },
        ],
        event,
        customClass: 'class-a'.minWidth: 230.zIndex:9999,})return false
    }
Copy the code

Analysis:

  • The top, back, and delete are essentially splice methods to replace or delete components

  • Replication requires a deep copy of the current element

    this.copyItem = _.cloneDeep(item)
    Copy the code

    CloneDeep here is the deep-copy method of the classic loadsh library

  • The premise of pasting is that it has already been copied, so we can judge based on whether there is a copyItem

Iv. Content modification area on the right

CurrentEdit is generated by the doAction method mentioned in the middle display area. CurrentEdit is the corresponding component when clicked, and the corresponding content modification area is rendered by the dynamic component of vue.

<component :is="`${currentEdit.type}RightSider`" :moduleProp="currentEdit"></component>
Copy the code

5. Top toolbar

Toolbar can greatly improve our efficiency, there are download template, upload template, undo, forward, copy, delete, preview poster + save poster functions. So let’s look at one by one how do we do that

5.1 Undo Forward

First, understand how undo moves forward:

  • Undo advance is actuallyThe snapshotThe principle of.
  • Let’s use oneAn array ofTo store the components after the current various operations (this involves sorting, adding, deleting, pasting, topping, and pasting)Tabular data
  • And then rely onPointer to the indexTo keep adding plus one or minus one, keep goingforwardandbackAnd get the data of various statesTo assign a value
  • It must bePay attention toOne point: is when the undo, and after a new operation, this time need to putData was cleared before the original undoInstead,New to joinList data after the operation

Take a look at the illustration:

Various operations:

Undo advance:

After undoing and performing various operations:


Then look at how the code is implemented:

    const snapshot = []  // Snapshot array
    let currentSnapshotIndex = -1 // Snapshot index
    
    / / cancel
    undo() {
      if (this.currentSnapshotIndex >= 0) {
        this.currentSnapshotIndex--
        this.items = _.cloneDeep(this.snapshot[this.currentSnapshotIndex])
      }
    },
      
    / / to go forward
    forward() {
      if (this.currentSnapshotIndex < this.snapshot.length - 1) {
        this.currentSnapshotIndex++
        this.items = _.cloneDeep(this.snapshot[this.currentSnapshotIndex])
      }
    },
      
    // Add a new snapshot
    addSnapshot() {
      this.snapshot[++this.currentSnapshotIndex] = _.cloneDeep(this.items)
      if (this.currentSnapshotIndex < this.snapshot.length - 1) {
        this.snapshot = this.snapshot.slice(0.this.currentSnapshotIndex + 1)}}Copy the code

Analysis:

  • The undo function reverses the currentSnapshotIndex pointer (-1), so consider that currentSnapshotIndex is less than 0. If the value is less than 0, it is the earliest operation and cannot be revoked.

  • If currentSnapshotIndex is greater than the length of the snapshot array, it is the latest operation.

  • AddSnapshot adds a record of the current action to the snapshot at the time of the action (sort, Add, Delete, Paste, top, and post-action) and is the basis for undo and forward.

  • Note that in addSnapshot, when the undo is performed and a new operation is performed, the data after the undo needs to be emptied. Therefore, if currentSnapshotIndex is less than the current snapshot number, the undo has been performed and the subsequent state should be empty. So just remember that addSnapshot is always up to date.

5.2 Import and Export

Because our platform is targeted at b-end users of various educational institutions, we provide various templates to facilitate the rapid establishment of pages by various institutions. The diagram below:

These templates are generated by following these steps

  1. Clicking the Export template will export the JSON file
  2. Clicking On the Save poster will save the image
  3. To template management, get the picture and JSON file form submission to the database
  4. The template library now has the template poster and corresponding JSON data

These processes do not need our front-end development to cooperate, relying on the operation personnel can be generated according to the activity arrangement, greatly freeing the front-end personnel.


Take a look at the export functionality

    downTemplateJson() {
      const blob = new Blob([JSON.stringify(this.items)], { type: ' ' })
      saveAs(blob, 'template.json')}Copy the code

Analysis:

  • Here’s a good library to use:file-saver. There is no native supportsaveAs()SaveAs () interface is implemented on the browser to save files
  • We put the currently displayed component listserializationAnd then save the download

Then look at the implementation of the import (is there a good GIF software? You can add mosaics according to the time)

Import I am using ant-Design upload

    customRequest(date) {
      const reader = new FileReader()
      reader.onload = (e) = > {
        const data = e.target.result
        this.items = JSON.parse(data)
      }
      reader.readAsText(date.file)
    }
Copy the code

The code is also very simple to read the contents of the file

5.3 Preview poster + Save Poster

First look at the preview poster:

Preview posters and save posters are used to generate images of the contents of the display area in the middlecanvasTo implement. It is recommended to usehtml2canvasLibrary, it can putDom elementsGenerate the corresponding canvas.

Let’s look at the code first

    <a-modal
      :visible="imgPreviewSrc ! = = ""
      @ok="savePoster"
      @cancel="() => (imgPreviewSrc = '')"
      width="500"
      title="Poster display"
      okText="Save the poster"
    >
      <img :src="imgPreviewSrc" class="postImage" />
    </a-modal>
<script>
/ /...
  methods: {showPoster() {
      const itemListElement = document.getElementById('item-list')
      html2canvas(itemListElement, {
        dpi: window.devicePixelRatio,
        useCORS: true.// Enable cross-domain configuration
        scale: 1,
      }).then((canvas) = > {
        const url = canvas.toDataURL('image/png')
        this.imgPreviewSrc = url
      })
    },
  }

</script>
Copy the code

Analysis:

  • Generate canvas through html2Canvas

  • Then through toDataURL to base64 for display

  • ImgPreviewSrc pops up when it has a value

  • Note that HTML2Canvas has a bug when setting display: -webkit-box; Element content cannot be displayed when

    Scenario: I set an ellipsis to overflow the text, which will not show the contents of the element

              h4 {
                color: # 333333;
                width: 200px;
                display: -webkit-box;
                -webkit-box-orient: vertical;
                -webkit-line-clamp: 1;
                overflow: hidden;
              }
    Copy the code

Save the posters

You can see that when you open the popup to show the poster, there is a savePoster button

    savePoster() {
      saveAs(this.dataURLtoBlob(this.imgPreviewSrc), 'poster.png')}dataURLtoBlob(dataurl) {
      const arr = dataurl.split(', ')
      const mime = arr[0].match(/ : (. *?) ; /) [1]
      const bstr = atob(arr[1])
      let n = bstr.length
      const u8arr = new Uint8Array(n)
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n)
      }
      return new Blob([u8arr], { type: mime })
    }
Copy the code

Analysis:

  • DataURLtoBlob canbase6toblob
  • And then get the BLOBfile-saverIt’s time to export the image