background

Because the company has a lot of landing pages, as well as buried data reporting and other requirements (data reporting directly inserted into the corresponding components), we need to make a tool to simplify the work of colleagues, but also reduce the frequency of looking for development students, each happy ~

Analyze requirements

Because it is the landing page, so need to consider a certain amount of concurrency and the stability of the site, in order to prevent accidents directly generated static pages is better. The end result is a complete HTML page, which is written by Vue, so the easiest way to render the components is through JSON

The technology stack I’m using here is Vite + TS + VUe-Router

The technical implementation

  1. Left base Component, click Add Base Component

The base component is stored as an object. Each click pushes the current component type into an array (for display of the landing page, the landing page renders the component from this array). Each click on the base component generates a unique random ID (to store configuration information for each component). This random ID is mapped to the intermediate render page components so that when editing components, they can be mapped to each of them.

{currentComponent: 'button', listComponents: ['image-9592', 'button-317', 'video-2387']}Copy the code
  1. In the middle render page, click component to pop up configuration information

When each component is clicked on the middle page, the ID of the clicked component is recorded in real time. The corresponding configuration information is displayed on the right

{ 
  currentSetting: 'setting' 
}
Copy the code
  1. Edit component on the right, edit component configuration

Configure basic information about each component.

Vuex configuration information state: {value: ", setting: {}, button: {'button-317': {}... },... },Copy the code
  1. Save all configuration information to generate a page

The configuration information of the page is sent to the backend students by request, the backend generates the page, and adds the configuration information to the HTML page

The final HTML page code can be accessed on the server

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" href="/favicon.ico" /> <meta name="viewport" content="width=device-width, Initial-scale =1.0" /> <title> test </title> <script> let data = {"componentsSetting":{"currentComponent":"button","listComponents":["image-9592","button-317","video-2387"],"saveHtml":" ","previewVal":""},"currentCompenentsSetting":{"currentSetting":"setting"},"editSetting":{"value":"setting","setting":{" LandPageName ":" test ", "title" : ""}," upon maturity of lingzhi only text ": {}," button ": {" button - 317" : {" button ":" click to enter the shop jingdong ", "color" : "# F2B00A", "bgcolor" : "# B80314","size":"20","align":"center","layoutStyle":"","sizeStyle":"margin-top:0px; margin-right:0px; margin-bottom:0px; margin-left:0px; width:100%; height:39px; line-height:39px; border-radius:27px; color:#F2B00A; font-size:20px; background:#B80314; float:none","h5Url":"https://shop.m.jd.com/?shopId=119921&utm_user=plusmember&gx=RnEwyjVYbjKMy9RG_sYoAXcAFA&ad_od=share& utm_source=androidapp&utm_medium=appshare&utm_campaign=t_335139774&utm_term=Wxfriends","schemesUrl":"","transform":[{"tr AnslationId ":100004,"translationName":" click the button ","translationTarget":"buy"}],"transformValue":" click the button ","dividerSize":[{"title" : "width", "label" : "width", "value" : 100, the "unit", "%"}, {" title ":" high ", "label" : "height", "value" : 39, "min" : 32}, {" title ":" round ", "label" : "border-radius","value":27,"min":0,"max":50}]}}},"userSetting":{"userInfo":{},"advPageInfo":{}},"post":{"id":"23","advAc countId":"670"}} window.localStorage.setItem('lp',JSON.stringify(data)) </script> </head> <body> <div id="app"></div> <script type="module" src="/src/main.ts"></script> </body> </html>Copy the code

The main code paste, vuex will not paste, I believe that we will use ~

basic button code

<template> <div style="display:inline-block; Text - align: center: "id =" value ": style =" sizeStyle @ click "=" handleTranslate "> {{post. The button | | 'this is the button'}} < / div > < / template > <script lang="ts"> export default defineComponent({ props: { value: { type: String, default: () => '', }, }, setup(props) { const store = useStore() const route = useRoute() const state = reactive({ style: '', button: '', post: {}, sizeStyle:'', value: props.value }) const getData = (d: any) => { let { value } = props let res = d && d[value] if(! res) return state.post = res state.sizeStyle = res.sizeStyle } const handleTranslate = () => { let{ h5Url, schemesUrl } = state.post nextTick(() => { if(Object.keys(state.post).includes('transform') && (state.post as any).transform){ let { translationId, translationTarget } = (state.post as any).transform[0] transformEvent(translationTarget, translationId) } if(h5Url || schemesUrl){ openApp(state.value, h5Url,schemesUrl) } }) } watch(store.state.editSetting, (val) => { let { button } = val getData(button) }); onMounted(() => { let { button } = store.state.editSetting getData(button) }) return { ... toRefs(state), handleTranslate } }, }) </script>Copy the code

setting button code

<template>
  <div class="text">
    <h3>基础设置</h3>
    <el-row :gutter="20">
      <el-col :span="5">点击跳转</el-col>
      <el-col :span="19">
        <el-radio v-model="radio" label="1">外链</el-radio>
      </el-col>
      <el-col :span="5"><div class="i-right-label">网页链接</div></el-col>
      <el-col :span="19">
        <el-input
          placeholder="http://"
          v-model="post.h5Url"
          @change="postDate"
        />
      </el-col>
      <el-col :span="5"><div class="i-right-label">直达链接</div></el-col>
      <el-col :span="19">
        <el-input
          placeholder="mttbrowser://url=https://www.qq.com"
          v-model="post.schemesUrl"
          @change="postDate"
        />
      </el-col>
    </el-row>
    <el-divider></el-divider>
    <h3>样式</h3>
    <el-row :gutter="20">
      <el-col :span="5">按钮文案</el-col>
      <el-col :span="19">
        <el-input
          placeholder="这是按钮"
          v-model="post.button"
          @change="postDate"
        />
      </el-col>
      <el-col :span="5"><div class="i-right-label">按钮颜色</div></el-col>
      <el-col :span="19">
        <div class="i-right-label">文字</div>
        <el-color-picker v-model="post.color" size="small" @change="postDate"> </el-color-picker>
        <div class="i-right-label" style="margin-left: 30px">背景</div>
        <el-color-picker v-model="post.bgcolor" size="small" @change="postDate" show-alpha> </el-color-picker>
      </el-col>
      <el-col :span="5"><div class="i-right-label">字号</div></el-col>
      <el-col :span="19">
        <el-select v-model="post.size" placeholder="请选择" style="width:70px" @change="postDate">
          <el-option
            v-for="item in options"
            :key="item"
            :label="item"
            :value="item">
          </el-option>
        </el-select>
      </el-col>
    </el-row>
    <Divider @handleDivider="getDividerSize" :data="dividerSize"/>
    <el-divider></el-divider>
    <h3>布局</h3>
    <el-row :gutter="20">
      <el-col :span="5"><div class="i-right-label">对齐方式</div></el-col>
      <el-col :span="19">
        <el-radio-group v-model="post.align" class="text-align" @change="postDate">
          <el-radio-button v-for="item in options2" :key="item" :label="item" value="item"></el-radio-button>
        </el-radio-group>
      </el-col>
    </el-row>
    <Divider @handleDivider="getDivider" :data="dividerData"/>
    <Transform @handleTranslate="getTranslate" :data="transformData"/>
  </div>
</template>

<script lang="ts">
export default defineComponent({
  components: { Transform, Divider },
    name: 'buttonSetting',
    setup() {
    const { proxy } = getCurrentInstance() as any;
    const store = useStore();
    const route = useRoute()
    const clearData = (params) => {
      return {
        dividerSize: [
          {
            title: '宽度',
            label: 'width',
            value: 80,
            unit: '%',
          },
          {
            title: '高度',
            label: 'height',
            value: 32,
            min: 32,
          },
          {
            title: '圆角',
            label: 'border-radius',
            value: 3,
            min: 0,
            max: 50,
          },
        ],
        dividerData: [
          {
            title: '上边距',
            label: 'margin-top',
            value: 0
          },
          {
            title: '右边距',
            label: 'margin-right',
            value: 0
          },
          {
            title: '下边距',
            label: 'margin-bottom',
            value: 0
          },
          {
            title: '左边距',
            label: 'margin-left',
            value: 0
          },
        ],
        post: {
          button: '这是按钮',
          color: '#fff',
          bgcolor: 'rgba(64, 158, 255, 1)',
          size: '16',
          align: 'center',
          layoutStyle: '',
          sizeStyle: '',
          h5Url: '',
          schemesUrl: '',
        },
        transform: []
      }[params]
    }
    
    const state = reactive({
      radio: '1',
      options: ['12', '14', '16', '18', '20', '24'],
      options1: ['normal', 'bold'],
      options2: ['left', 'center', 'right'],
      post: clearData('post'),
      dividerSize: clearData('dividerSize'),
      dividerData: clearData('dividerData'),
      transformData: clearData('transform'),
    })

    const value = computed(() => store.state.editSetting.value);
    const getData = (d: any) => {
      state.dividerData = clearData('dividerData')
      state.dividerSize = clearData('dividerSize')
      state.transformData = clearData('transform')
      state.post = clearData('post')
      if(!d || !value.value || !d[value.value]) return
      let data = d[value.value]
      state.dividerData = data.dividerData
      state.dividerSize = data.dividerSize
      state.transformData = data.transformValue
      state.post = data
    }

    watch(value, (val) => {
      if(val.indexOf('button') < 0) return
      let { button } = store.state.editSetting
      getData(button)
      postDate()
		});

    const postDate = () => {
      let { post } = state
      let { color,bgcolor, size, align } = post
      state.dividerData = post.dividerData ||  clearData('dividerData')
      state.dividerSize = post.dividerSize ||  clearData('dividerSize')
      let layoutStyle = styleTransform(state.dividerData)
      let sizeStyle = styleTransform(state.dividerSize)
      let style = `${layoutStyle}${sizeStyle}color:${color};font-size:${size}px;background:${bgcolor};float:${align=='center'?'none':align}`
      post.sizeStyle = style
      const params = {[value.value]: post}
      store.dispatch('editSetting/setButton', params);
    }

    const getTranslate = (val: object) => {
      Object.assign(state.post,val),postDate()
    }
    const getDivider = (val: object) => {
      let { data } = val
      Object.assign(state.post,{dividerData: data})
      postDate()
    }
    const getDividerSize = (val: object) => {
      let { data } = val
      Object.assign(state.post,{dividerSize: data})
      postDate()
    }

    const styleTransform = (data) => {
      let str = ''
      data.forEach(element => {
        str += `${element.label}:${element.value}${element.unit || 'px'};`
        if(element.label == 'height') str += `line-height:${element.value}${element.unit || 'px'};`
      })
      return str
    }

    onMounted(() => {
      let { button } = store.state.editSetting
      getData(button)
      postDate()
    })
    return {
      ...toRefs(state),
      postDate,
      getTranslate,
      getDivider,
      getDividerSize
    }
	},
});
</script>
Copy the code

Post a finished picture

Ha ha ha, it looks very complicated, in fact, careful analysis, it is very easy to complete, write this is mainly to record, if you have any questions, welcome to discuss ah ~