vue 根据后端数据渲染文件模板,编辑并实时预览效果

最近有个需求,用户要线上编辑模板文件,以前直接考虑用富文本编辑方式,用户想怎么编辑就怎么编辑,但这次需求后端提到输入的关键信息要入库,并且有select选项插入。整篇带样式的html由后端返回。
初步想法是分左右栏,左侧预览右侧form,最开始想收到后端的html后再插入document,但动态插入的dom无法做到数据响应,变换下思路,把需要动态显示的dom事先初始化dom中,等模板html渲染完成后再插入到对应位置。

和后端约定的数据格式,tempStr是html,动态插值用${}表示,form是表单描述,用于渲染表单:

{
  "code": 0,
  "msg": "",
  "data": {
    "tempStr": "<p>尊敬的用户:${username},项目名称:${projname}。</p><p>公司名称:${company}</p><table><tr><th width=200>选项一</th><td>${select1}</td></tr><tr><th>选项二</th><td>${select2}</td></tr><tr><th>选项三</th><td>${select3}</td></tr></table> ",
    "form": [
      {
        "label": "用户名",
        "name": "username",
        "type": "input",
        "value": ""
      },
      {
        "label": "项目名称",
        "name": "projname",
        "type": "input",
        "value": ""
      },
      {
        "label": "公司名称",
        "name": "company",
        "type": "input",
        "value": ""
      },
      {
        "label": "选项一",
        "name": "select1",
        "type": "select",
        "value": "",
        "options": [
          {
            "label": "第一个选项名",
            "value": "v1"
          },
          {
            "label": "第二个选项名",
            "value": "v2"
          },
          {
            "label": "第三个选项名",
            "value": "v3"
          }
        ]
      },
      {
        "label": "选项二",
        "name": "select2",
        "type": "select",
        "value": "",
        "options": [
          {
            "label": "第一个选项名",
            "value": "v1"
          },
          {
            "label": "第二个选项名",
            "value": "v2"
          },
          {
            "label": "第三个选项名",
            "value": "v3"
          },
          {
            "label": "第四个选项名",
            "value": "v4"
          }
        ]
      },
      {
        "label": "选项三",
        "name": "select3",
        "type": "select",
        "value": "",
        "options": [
          {
            "label": "第一个选项名",
            "value": "v1"
          },
          {
            "label": "第二个选项名",
            "value": "v2"
          }
        ]
      }
    ]
  }
}

思路:

  1. 替换字符串中 ${xxx} 为 <u id="xxx"></u>
  2. 替换<u id="xxx"></u> 为先前初始化的 <u ref="xxx"></u>

template.vue:


<template>
    <div class="temp-wrap">
        <!-- 模板 -->
        <div class="content" id="tempBox">
            <h3>模板预览</h3>
            <!-- 动态值标签,tempData渲染后 -->
            <u class="temp-u" :ref="'u-' + description.name" v-for="(description, index) in formItemsDescription" :key="index">{{templateResultsTranslate[description.name]}}</u>

            <div id="template" v-html="tempData"  style="flex:0 0 50%"></div>
        </div>
        <!--/. 模板 -->

        <!-- form -->
        <div class="form" >
            <el-form ref="form" size="mini" :model="formData" label-width="80px" label-position="left">
                <el-form-item v-for="formItem in formItemsDescription" :label="formItem.label" :key="formItem.name">
                    <template v-if="formItem.type === 'input'" >
                        <el-input v-model="formData[formItem.name]" :placeholder="'请输入' + formItem.label"></el-input>
                    </template>
                    <template v-if="formItem.type === 'select'">
                        <el-select v-model="formData[formItem.name]" :placeholder="'请选择' + formItem.label">
                            <el-option v-for="option in formItem.options" :label="option.label" :value="option.value" :key="option.value"></el-option>
                        </el-select>
                    </template>
                </el-form-item>
            </el-form>
            <div class="">
                <el-button @click="submit" type="primary" block>提交</el-button>
            </div>
        </div>
        <!--/. form -->
    </div>
</template>

<script>
    import $ from "jquery"

export default {
    name: 'templateTest',
    data() {
        return {
            tempData:'',
            formData:{},
            formItemsDescription:[]
        };
    },
    methods: {
        submit(){
            this.$HTTP.post('submit/', this.formData)
                    .then(res=>{
                        console.log(res)
                    })
        },
        getData(){
            this.$HTTP
                .get('/static-assets/data.json')
                .then(res => {
                    let _tempStr = res.data.data.tempStr;
                    let reg = new RegExp('\\$\\{([^}]*)\\}', 'g')

                    //表单描述
                    this.formItemsDescription = res.data.data.form;
                    //根据表单项描述,生成表单数据
                    this.formItemsDescription.forEach(item => {
                        this.$set(this.formData, item.name, item.value)
                    })


                    //获取${xxx}(表单项字段名)
                    let formNameArr = _tempStr.match(reg)
                    //${xxx}去壳,得到xxx
                    let formItemPropertyNameArr = formNameArr.map(formName => {
                        return formName.replace(new RegExp(/\$\{|\}/, 'gm'), '')
                    });

                    // _tempStr中的 ${xxx} 替换为标签 <u id="xxx"></u>
                    formNameArr.forEach((formName, index)=>{
                        _tempStr = _tempStr.replace(formName, `<u id="${formItemPropertyNameArr[index]}"></u>`)
                    });

                    //赋值,渲染模板
                    this.tempData = _tempStr;

                    //dom渲染成功后执行dom插入
                    this.$nextTick(()=>{
                        formNameArr.forEach((formName, index)=>{
                            //对应字段名的dom-u
                            let u = this.$refs['u-' + formItemPropertyNameArr[index]][0];
                            let $tempU = $(document).find('#' + formItemPropertyNameArr[index]);
                            $tempU.replaceWith(u)
                        });
                    })
                })
                .catch(err => {

                });
        },
    },
    mounted() {
        this.getData();
    },
    computed:{

        /*
        * 表单结果转换为结果显示
        * 1. input直接显示
        * 2. select转换显示label
        * */
        templateResultsTranslate:{
            get() {
                let formData = this.formData;
                let results = {};
                this.formItemsDescription.forEach(obj => {
                    let _type = obj.type.toLowerCase();
                    let _propertyName = obj.name.toLowerCase();
                    let _value = formData[_propertyName];
                    switch (_type) {
                        case 'select':
                            //通过_value的值获取对应的option对象
                            if(_value) {
                                results[_propertyName] = obj.options.filter( obj => {
                                    return obj.value === _value
                                })[0].label;
                            }
                            break;
                        default:
                            results[_propertyName] = _value
                            break
                    }
                })
                return results
            }
        }
    }
};
</script>
<style lang="scss">
    #template {
        line-height: 2em;
        p {
            font-size: 18px !important;
            margin: 10px 0 10px;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            text-align: left;
            th, td {
                padding: 10px;
                border: 1px solid #000;
            }
        }
    }
</style>
<style scoped lang="scss">
    .temp-wrap {
        @include display-flex();
        height: 100%;
        padding: 40px;
        .content, .form {
            height: 100%;
            overflow: auto;
        }
        .content {
            @include flex(1);
            @include box-shadow(3px 3px 16px rgba(0,0,0,0.1));
            background-color: #fff;
            border-radius:8px;
            padding: 1rem;
            margin-right: 2rem;
        }
        .form {
            @include flex(0 0 30%);
            @include box-shadow(3px 3px 16px rgba(0,0,0,0.1));
            background-color: #fff;
            border-radius:8px;
            padding: 1rem;

        }
        .temp-u {
            text-decoration: none;
            padding: 0 10px;
            border-bottom: 1px solid #666;
        }
    }
</style>

效果


QQ录屏20210528154328_20210528154932.gif

暂时type只区分了select,可以加入radio、checkbox等表单形式。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容