One, foreword

Hi helo, I’m Peryl. Today I want to share my own page visual editor. When it comes to page visualization, the first thing that comes to mind is “low code platform”, “visual drag and drop form generation” and so on. But today to share this tool is not low code form, as shown in the title, this tool is used for the administration of the content of the page more scenario is used to design the store home page, e-commerce sites page, activity page, etc., to support adaptive display screen width and rendering of a service, embedded small programs and jump page.

Recently, the Gitee Pages service has been slow to open, students will have to wait while they look at some examples…

Online examples

  • The editor looks much like a low-code platform or editor, and is available in Vue3 and React versions.
  • The React component is plain-design and plain-design-composition. The React component is plain-design and composition.

  • Vue3 is a Vue3 component library based on plain-UI and plain-UI-Composition. Vue – CMS – Visual – Editor: Different from the React version, the default color is green to distinguish it from the React version.

  • Nuxt3(Vue3) and Next(React) render with the code in the React visual editor. The same code runs in Next React and Nuxt Vue3, not web Component. Simply introducing code usage. For example, Nuxt and Next use the same code to render the multicast components. So the following refers to the visual editor, all refer to the React version. The VUE version has nothing to do with what follows.
  • As usual, the warehouse address is at the end of the article;

Edit preview effect

Small screen (mobile, 0~719px)

Vertical screen

Landscape (in fact, the width change has become the display content of the pad)

Middle screen (pad end, 720~1439px)

Vertical screen

landscape

Large screen (PC, 1440~ Max)

The maximum width of the container node is set to 1440px, which can be configured during editing. The current width is simply modeled after the design of the gold-digging article list, and some content with longer width will look ugly when the screen is larger.

Webview embedded preview in applets

Developer Tools iPhone

Developer Tools iPad

Mobile scan preview

Three, functional characteristics

structure

component

  • Although there are so many components in the left panel, in fact, only the components in the first category “available components” are valid. The components in the other categories are not available and have not been implemented for the time being, and there is no plan to implement the rest of the components at present. No other meaning, is too few components appear empty.
  • Currently supported components are:
    • The picture
    • video
    • button
    • The text
    • Multi-line container
    • Multiple columns container
    • By the container
    • Fixed container (with adaptive height)
    • Square container (width adaptive, height consistent with width)
  • Composite components:
    • The data rendered on the entire page is a tree structure of data.
    • When a node hover (mouse on the node) or focus (click on the node), several special small buttons will appear in the upper right corner of the node. After the button “combination” is clicked, the node will be removed into a new component for operation in the left menu component list.

Operation node

  • New node: When you drag a node from the left menu, you can place the node in the node that shows “+”. To place means to insert a new node in the target position.
  • Copy node: Click the “Copy” icon in the upper right corner of the node to copy the node in the target position.
  • Move node: Move the node from the current position to the target position.
  • Clear a node: Clears the node content. If the node is the root node of the page, it is automatically removed. In some containers, such as multi-column containers, multi-row containers, etc., when a node in the container empties its contents, it will retain its position. Only the contents are empty, showing “+” node to be placed.

The operation page

By default, there is only one page. A page is a way to display different layouts at different resolutions. For example, display four columns of products on PC (screen width >1440px) and three columns on pad (720px< screen width <1440px), but not on mobile. In this case, you need to create three pages, and then set the minimum width and maximum width in the Settings panel [Page Settings].

  • New page: Click new page button in the operation bar;
  • Copy page: a copy of the current page;
  • Delete page: Delete the current page.
  • Clear the page: Clear all contents of the current page.
  • Page switching: The created page is displayed on the right of the operation bar. Click to switch to the corresponding page.

Settings panel

  • Setting panel, you can set the node or page information; When the node (no focus) is not selected, the Settings panel displays the edit page information. After a node is selected, information about the node to be edited is displayed.
  • After editing in the Settings panel, click the Edit content area to automatically apply the editing Settings, or click the “Apply” button in the upper right corner of the Settings panel to apply the Settings. Click reset to retract the edited changes to the original message.
  • All nodes have “Basic Style”, “Background Settings” and “Action Settings”.

Page structure

  • Click the Page Structure TAB in the left menu panel to display the content structure of the current page. For example, to select the parent node of a node, click the “parent” button on the upper right of the node to select the parent node, or click the corresponding node in the “Page Structure” TAB to select the parent node.

Operating history

  • All operations of adding, deleting and modifying nodes and pages will be recorded in the operation history. You can click the history of a certain point in time to roll back the data to the specific version.

Four, preview

After clicking the “Save and preview” button in the operation bar, the popup box will open. After entering the remarks, there are four ways to preview:

  • New Window preview: The preview. HTML page of the current project will be opened to preview the data. In other words, if the current project is the React-cms-Visual-Editor project, the vite React code will render the content. In the case of vuE-CMS-Visual-Editor, render the content with Vite Vue3.
  • Qr code preview: the same as “New window preview”, only qr code is provided for the mobile terminal to scan the code preview. When you click “Save Preview,” the current data is saved to the back end and an ID of the data is returned. The parameter of the link address of two-dimensional code is this ID. So mobile can preview the data.
  • Preview in nuxtvu3 server render: NuxtVue3 uses the react-cms-Visual-Editor code to render content, as the name suggests. NuxtVue3 uses the react-cms-Visual-Editor code to render content, as the react-cms-visual-Editor code renders content. Unlike the new window preview, Nuxt welcomes relevant content at the bottom of the content;
  • Preview in NextReact server render: NuxtVue3 uses the react-cms-Visual-Editor code to render content, as the name suggests. NuxtVue3 uses the react-cms-Visual-Editor code to render content, as the react-cms-visual-Editor code renders content. Unlike the new window preview, this preview has the Next welcome at the bottom of the content;

When the preview window opens, the query parameter code of the current URL will change to the new ID. So when the page is refreshed, the data source for the current page edit depends on the code parameter in the URL.

Five, about rendering content

Render data in Nuxt

Nuxt uses the react-cms-Visual-Editor code to render data, which consists of the following lines:

import {computed, designComponent, inject, onBeforeUnmount, onMounted, provide, reactive, watch} from 'plain-ui-composition'
import {iVisualData} from 'react-cms-visual-editor/src/packages/utils/types.base'
import {createCmsPreview} from "react-cms-visual-editor";
import 'react-cms-visual-editor/src/libs/icon/iconfont.css'
import {PropType} from "vue";
import {createReactivityApi} from "react-cms-visual-editor/src/packages/utils/createReactivityApi";
import {processActionRender} from "./processActionRender";

export const CmsPreviewApp = designComponent({
    props: {
        data: {type: Object as PropType<iVisualData>}
    },
    setup({props}) {
        const reactivityApi = createReactivityApi({reactive, computed, type: 'vue', designComponent, onMounted, onBeforeUnmount, watch, provide, inject})
        const CmsPreview = createCmsPreview({api: reactivityApi, processActionRender})
        return () = >(!!!!! props.data &&<>
                <CmsPreview data={props.data}/>
            </>)}})Copy the code

The data is queried in Pages /index.vue and passed to the CmsPreview component

<template>
  <div>
    <CmsPreviewApp :data="state.data"/>
    <nuxt-welcome/>
  </div>
</template>

<script setup>
import {CmsPreviewApp} from ".. /cms/Preview";

const route = useRoute()
const id = route.query.code
const state = reactive({
  data: null
})
if(!!!!! id) {const {data} = await useAsyncData(id, async() = > {const {result} = await $fetch('http://xxx.xxx.xxx/cms/item', {method: 'post'.body: {id}})
    return result
  })

  state.data = JSON.parse(data.value.json)
}
</script>

<style lang="scss">
html.body {
  margin: 0;
  padding: 0;
}
</style>
Copy the code

Render the data in Next

Next uses some of the code in react-cms-Visual-Editor to render data, with the following simple lines:

import {computed, designComponent, inject, onBeforeUnmount, onMounted, PropType, provide, reactive, watch} from 'plain-design-composition'
import {iVisualData} from 'react-cms-visual-editor/src/packages/utils/types.base'
import {createCmsPreview} from "react-cms-visual-editor";
import 'react-cms-visual-editor/src/libs/icon/iconfont.css'
import {createReactivityApi} from "react-cms-visual-editor/src/packages/utils/createReactivityApi";
import {processActionRender} from "./processActionRender";

// export const CmsPreviewApp = 'CmsPreviewApp'
export const CmsPreviewApp = designComponent({
    props: {
        data: {type: Object as PropType<iVisualData>}
    },
    setup({props}) {
        const reactivityApi = createReactivityApi({reactive, computed, type: 'react', designComponent, onMounted, onBeforeUnmount, watch, provide, inject})
        const CmsPreview = createCmsPreview({api: reactivityApi, processActionRender})
        return () = >(!!!!! props.data &&<>
                <CmsPreview data={props.data}/>
            </>)}})Copy the code

The data is queried in pages/index.tsx and passed to component rendering

import {Welcome} from ".. /cms/Welcome";
import {GetServerSideProps, NextPage} from "next";
import {CmsPreviewApp} from ".. /cms/Preview";

const Page: NextPage = ({data}: any) = > {
    return (
        <div>{!!!!! data &&<CmsPreviewApp data={data}/>}
            <Welcome/>
        </div>)}export default Page

export const getServerSideProps: GetServerSideProps = async (ctx) => {

    const id = ctx.query.code
    let data = null
    if(!!!!! id) {const resp = await fetch('http://1.116.13.72:7001/cms/item', {
            method: 'post'.body: JSON.stringify({id}),
            headers: {
                'content-type': 'application/json'
            },
        })
        data = JSON.parse((await resp.json()).result.json)
    }

    return {
        props: {
            data,
        },
    }
}
Copy the code

Render content in applets

  • It is also possible to render data manually in applets like nuxt or Next, but some components have to be written again in applets. Swiper implements a carousel component that, of course, cannot run inside a miniprogram.
  • The easier way to do this is to render the content in a small program by embedding a WebView. For example, the applets use webView to embed the addresses of deployed Nuxt or next. Nuxt or Next in the implementation of route jump, determine whether the current environment is wechat applets. If so, call wechat JSSDK jump, as shown in the example of next project;

First of all, dynamic loading of wechat JSSDK during client rendering;

const state = reactive({
   isWeapp: false
})
onMounted(() = > {
   const el = document.createElement('script') as HTMLScriptElement
   el.src = 'https://res.wx.qq.com/open/js/jweixin-1.3.2.js'
   el.onload = () = > {
       const {__wxjs_environment} = window as any
       state.isWeapp = __wxjs_environment === 'miniprogram'
   }
   document.body.appendChild(el)
})
Copy the code

Next render Link jump time, determine whether the current is small program environment, if yes, call JSSDK jump small program page, not according to the original way jump;

<Link href={`/pdp/${data.data.id}`} key={attrs.key}>
    <a {. attrs} onClick={e= >{ e.stopPropagation() e.preventDefault() const Win = window as any const {__wxjs_environment, Wx} = Win the if (__wxjs_environment = = = 'miniprogram') {/ * current is a small program, go small program jump. * / wx miniprogram. NavigateTo ({url: `/pages/cms/cms-detail? Id = ${data. The data. The id} `})} else {/ * current is not a small program, go next single page routing * / router. Push (` / PDP / ${data. The data. The id} `)}}} > {children}</a>
</Link>
Copy the code

6. Some remaining problems

  • The multicast component can be configured to display the switch button or not. In the case that the switch button is not displayed, the multicast component must be dragged to realize the switch. Swiper blocks the mousedown event, making it impossible to drag the “copy” and “move” buttons to manipulate nodes when the switch button is not displayed in the editor. Try onMousedownCapture in React but it doesn’t work.
  • Nuxt project, in the rendering of NuxT-link, custom processing onClick when detected is a small program environment, call JSSDK jump small program page. However, it was found that e. topPropagation and E. preventDefault did not prevent the nuxt jump. Later, it may be considered to be processed in the route interceptor. When intercepting route jumps, if the current route supports the jump applet and the current environment is also a small program environment, then the route jump is stopped and the JSSDK jump is called.
  • There is no way to know the screen resolution of the client when rendering on the server. So the content returned is the fixed large screen content. As a result, when the network is slow, the content on the mobile terminal will be displayed on the large screen at the beginning of the page opening, and the normal content on the mobile terminal will be displayed after all resources are loaded. If there are students have a better solution this can also comment section please leave a message, thank you! The only thing you can think of so far is to start with everything hidden at zero transparency and wait until the client initializes to display it.

Seven, conclusion

  • So far this is just a shared solution and there are no plans to refine the tool to fit more scenarios. This is because the editor needs to use the component library, and actually use it to select items, select activities, upload pictures and videos, and so on. If a specific company or product needs to use such an editor, the first step would be to replace the components in the editor with the component library that the company or product is using, so there is no plan to make this a widely available tool.
  • The original intention of this solution is to develop as few components as possible when implementing this kind of content management function, so the “composition” component function in this example is the soul of the development tool, although it is less complex than the other functions, but it is the most meaningful one.
  • Once components are written in react-CMS-Visual-Editor, projects (next or NUxT) that render data do not need to implement specific component code, nor do they require additional dependencies. For example, the editor uses a large component library to edit the data, which is not needed in the data rendering project and will naturally be small.
  • In the example of the Carousel component, createCmsCarousel is called in the react-cms-Visual-Editor function to create a Carousel component. During the creation process, the createCmsCarousel component is used to determine whether vUE or React is usedplain-ui-compositionThe exposeddesignComponentThe render component, if react, will use itplain-design-compositionthedesignComponentRender component. Of course, there is some other code that needs to be switched. For example, whether the style passed to the div node is class or className; Swiper is then used to process the rotation logic. The end result is that when rendering data in NuxT and next, you don’t need to implement specific components to render the data. But the jump action needs to be handled. For example, nuxt uses nuxt-link, and next uses link in next/link. ProcessActionRender handles this logic.

Viii. Related resources

instructions address
Address of online video explanation Bilibili: Plankton is very PI
React version editor repository address react-cms-visual-editor > gitee
React version editor preview address react-cms-visual-edirot > gitee pages
Vue version editor repository address vue-cms-visual-editor > gitee
Vue version editor preview address vue-cms-visual-editor > gitee pages
Nuxt3 server render preview address nuxt + react-cms-viusal-editor
Nuxt3 warehouse address nuxt-vue3_react-cms-visual-editor
Next server render preview address next + react-cms-visual-editor
Next Warehouse address next-react_react-cms-visual-editor