最近有个需求,用户要线上编辑模板文件,以前直接考虑用富文本编辑方式,用户想怎么编辑就怎么编辑,但这次需求后端提到输入的关键信息要入库,并且有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"
}
]
}
]
}
}
思路:
- 替换字符串中 ${xxx} 为 <u id="xxx"></u>
- 替换<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>
效果
暂时type只区分了select,可以加入radio、checkbox等表单形式。