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等表单形式。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容