实战说明:本文主要讲解前后端分离框架实现动态增加、删除文本框组,前端VUE+ElementUI,介绍如何通过JS动态增;后端:springboot+mybatis,介绍如何设计主子表对表单提交的数据进行存储,实现如下界面效果:
前端vue界面设计:
以打开一个对话框为例,在对话框中可以点击加号,增加多个文本组;点击减号,减少文本组。核心代码从 <span class="title">组合</span>开始到 <el-button @click="addList()">添加</el-button>,为上图的代码描述。
//打开一个对话框
el-dialog :title="title" :visible.sync="open" width="600px">
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-form-item label="名称" prop="modelName" >
<el-input v-model="form.modelNameMix" placeholder="请输入名称" disabled/>
</el-form-item>
<el-form-item label="分类" prop="assortMix" :rules="{required: true, message:'请输入类别', trigger: 'blur'}">
<el-select v-model="form.assortMix" placeholder="云智能分类">
<el-option
v-for="dict in typeOptions"
:key="dict.dictValue"
:label="dict.dictLabel"
:value="dict.dictValue"
/>
</el-select>
</el-form-item>
<span class="title">组合</span>
<el-row :gutter="20" v-for="(item,index) in form.modeList" :key="index">
<el-col :span="9">
<el-form-item :label="'范围'+index" :prop="'modeList.'+index+'.forecastRange'"
:rules="{required: true, message:'请输入范围', trigger: 'blur'}">
<el-input v-model="item.forecastRange" placeholder="请输入范围"/>
</el-form-item>
</el-col>
<el-col :span="9">
<el-form-item :label="'字段'+index" :prop="'modeList.'+index+'.functionalDirectiveEn'"
:rules="{required: true, message:'请输入字段', trigger: 'blur'}">
<el-input v-model="item.functionalDirectiveEn" placeholder="请输入字段"/>
</el-form-item>
</el-col>
<el-col :span="9">
<el-form-item :label="'名称'+index" :prop="'modeList.'+index+'.functionalDirectiveCn'"
:rules="{required: true, message:'请输入名称', trigger: 'blur'}">
<el-input v-model="item.functionalDirectiveCn" placeholder="请输入名称"/>
</el-form-item>
</el-col>
<el-col :span="9">
<el-form-item :label="'温度'+index" :prop="'modeList.'+index+'.targetTemperature'"
:rules="{required: true, message:'请输入温度', trigger: 'blur'}">
<el-input v-model="item.targetTemperature" placeholder="请输入温度"/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-button circle icon="el-icon-plus" @click="addList()"></el-button>
<el-button circle icon="el-icon-minus" @click="subList(item)"></el-button>
</el-col>
</el-row>
<el-form-item>
<el-button @click="addList()">添加</el-button>
</el-form-item>
<el-form-item label="公式" prop="formulaDescMix">
<el-input v-model="form.formulaDescMix" placeholder="请输入公式"/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-dialog>
前端脚本设计:需要定义数据列表,列表中的值是需要动态增加的值,因文本框中默认数据都是空,因此初始化设置时全部都是空字符串。
modeList: [
{
forecastRange: '', functionalDirectiveCn: '', targetTemperature: '',
functionalDirectiveEn: ''
}
]
当点击加号时,通过push动态的增加文本框数据到modeList;当点击减号时,通过indexOf找到要移除项的位置,并通过splice移除数组中的值。
export default {
name: "SmartSchema",
data() {
return {
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 模式表格数据
smartSchemaList: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 表单参数
form: {},
// 表单校验
rules: {
modelNameMix: [
{required: true, message: "名称不能为空", trigger: "blur"},
],
functionalDirectiveEnMix: [
{required: true, message: "字段不能为空", trigger: "blur"},
],
functionalDirectiveCnMix: [
{required: true, message: "名称不能为空", trigger: "blur"},
],
forecastRangeMix: [
{required: true, message: "范围不能为空", trigger: "blur"},
],
targetTemperatureMix: [
{required: true, message: "温度不能为空", trigger: "blur"},
],
},
requestMapping: '/smart/smartSchema',
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listSmartSchema(this.queryParams).then(response => {
this.smartSchemaList = response.rows;
// this.smartSchemaList = this.handleTree(response.data, "id","subId","modeList");
this.total = response.total;
this.loading = false;
});
},
// 取消按钮
cancel() {
this.open = false;
this.reset();
},
// 表单重置
reset() {
this.form = {
id: null,
modelNameMix: null,
forecastRangeMix: null,
assortMix: null,
functionalDirectiveCnMix: null,
targetTemperatureMix: null,
formulaDescMix: null,
modelDescMix: null,
applyUserId: null,
applyUserName: null,
applyTime: null,
instanceId: null,
processKey: null,
delFlag: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
modeList: [
{
forecastRange: '', functionalDirectiveCn: '', targetTemperature: '',
functionalDirectiveEn: ''
}
]
};
this.resetForm("form");
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
updateSmartSchema(this.form).then(response => {
this.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addSmartSchema(this.form).then(response => {
this.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
//加号
addList() {
this.form.modeList.push({
forecastRange: '',
functionalDirectiveCn: '',
targetTemperature: '',
functionalDirectiveEn: '',
key: Date.now()
});
},
//减号
subList(item) {
var index = this.form.modeList.indexOf(item)
if (index !== -1) {
this.form.modeList.splice(index, 1)
}
}
}
};
JS脚本,实现服务端接口的访问:
// 新增
export function addSmartSchema(data) {
return request({
url: '/smart/smartSchema',
method: 'post',
data: data
})
}
// 修改
export function updateSmartSchema(data) {
return request({
url: '/smart/smartSchema',
method: 'put',
data: data
})
}
服务端业务实体类:前端提交到服务端的数据存在一对多的关系,因此在设计业务实体类的时候需要考虑在一个实体类中引入另一个实体类,而且时多个。因此,这里设计两个实体类,把文本框组合看成一个实体类ModelVo,表单中所有的属性值看作一个实体类BizSmartSchema,并在其实体类下面定义列表List<ModelVo>。
public class BizSmartSchema extends ProcessEntity {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
private Long id;
/**
* 分类
*/
@Excel(name = "分类")
private String assortMix;
/**
* 名称
*/
@Excel(name = "名称")
private String modelNameMix;
/**
* 描述
*/
@Excel(name = "描述")
private String modelDescMix;
/**
* 预测范围
*/
@Excel(name = "范围")
private String forecastRangeMix;
/**
* 字段
*/
@Excel(name = "字段")
private String functionalDirectiveEnMix;
/**
* 名称
*/
@Excel(name = "名称")
private String functionalDirectiveCnMix;
/**
* 温度
*/
@Excel(name = "温度")
private String targetTemperatureMix;
/**
* 适配公式
*/
@Excel(name = "公式")
private String formulaDescMix;
/**
* 当前模式生效状态
*/
@Excel(name = "状态")
private String state;
/**
*模型文本框列表
*/
private List<ModelVo> modeList;
//表示当前字段是否为数据库字段,false标识不是
@TableField(exist=false)
private boolean hasChildren;
}
controller层,直接拿到请求参数,往后传给服务层。
@PreAuthorize("@ss.hasPermi('smart:smartSchema:add')")
@Log(title = "模式", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody BizSmartSchema bizSmartSchema) {
return toAjax(bizSmartSchemaService.insertBizSmartSchema(bizSmartSchema));
}
业务实现层: 设计主表跟子表,主表存form表单的主数据字段,子表存多个文本框数据。插入时考虑事务的ACID特性,此处重点关注的事务的原子性,主表插入后,子表也要插入。主表插入时,需要将前端传过来的多个文本框的同一类数据组合成一个字符串,然后存入相应的字段。
@Override
@Transactional
public int insertBizSmartSchema(BizSmartSchema bizSmartSchema) {
bizSmartSchema.setCreateBy(SecurityUtils.getUsername());
bizSmartSchema.setCreateTime(DateUtils.getNowDate());
//主子表设计
// //遍历modeList将温度范围值、目标温度值进行封装,模式描述里面拼接具体情况。
List<ModelVo> modeList = bizSmartSchema.getModeList();
//描述数据
StringBuffer sbModelDesc = new StringBuffer();
//范围数据
StringBuffer sbForecastRange = new StringBuffer();
//温度数据
StringBuffer sbTargetTemperature = new StringBuffer();
//字段数据
StringBuffer sbFunctionalDirectiveEn = new StringBuffer();
//名称数据
StringBuffer sbFunctionalDirectiveCn = new StringBuffer();
modeList.forEach(str -> {
sbModelDesc.append(str.getFunctionalDirectiveCn() + ",")
.append(str.getFunctionalDirectiveEn() + ",")
.append(str.getForecastRange() + ",")
.append(str.getTargetTemperature() + ";" + "\n");
sbForecastRange.append(str.getForecastRange() + ";");
sbTargetTemperature.append(str.getTargetTemperature() + ";");
sbFunctionalDirectiveEn.append(str.getFunctionalDirectiveEn() + ";");
sbFunctionalDirectiveCn.append(str.getFunctionalDirectiveCn() + ";");
}
);
bizSmartSchema.setForecastRangeMix(sbForecastRange.toString());
bizSmartSchema.setFunctionalDirectiveCnMix(sbFunctionalDirectiveCn.toString());
bizSmartSchema.setFunctionalDirectiveEnMix(sbFunctionalDirectiveEn.toString());
bizSmartSchema.setTargetTemperatureMix(sbTargetTemperature.toString());
bizSmartSchema.setModelDescMix(sbModelDesc.toString());
int a = bizSmartSchemaMapper.insertBizSmartSchema(bizSmartSchema);
modeList.forEach(str -> {
//每次使用之前清空数据
sbModelDesc.setLength(0);
sbModelDesc.append(str.getFunctionalDirectiveCn() + ",")
.append(str.getFunctionalDirectiveEn() + ",")
.append(str.getForecastRange() + ",")
.append(str.getTargetTemperature() + ";" + "\n");
str.setAssort(bizSmartSchema.getAssortMix());
str.setModelDesc(sbModelDesc.toString());
str.setModelName(bizSmartSchema.getModelNameMix());
str.setSubId(bizSmartSchema.getId());
str.setState("0");//未提交申请
str.setFormulaDesc(bizSmartSchema.getFormulaDescMix());
});
int b = bizSmartSchemaMapper.insertBizSmartSchemaSub(modeList);
return b;
}
MAPPER层: 主表字段根据动态插入库中一条数据,子表则根据文本框组的个数生成多条数据,同时在子表设计时还要引入主表的主键作为外键使用。
<insert id="insertBizSmartSchema" parameterType="BizSmartSchema" useGeneratedKeys="true" keyProperty="id">
insert into con_data.app_electricheater_zero_tb_adaptive_model_detail
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="assortMix != null">
assort,
</if>
<if test="modelNameMix != null">
model_name,
</if>
<if test="modelDescMix != null">
model_desc,
</if>
<if test="functionalDirectiveCnMix != null">
functional_directive_cn,
</if>
<if test="functionalDirectiveEnMix != null">
functional_directive_en,
</if>
<if test="forecastRangeMix != null">
forecast_range,
</if>
<if test="targetTemperatureMix != null">
target_temperature,
</if>
<if test="formulaDescMix != null">
formula_desc,
</if>
<if test="applyUserId != null">
apply_user_id,
</if>
<if test="applyUserName != null">
apply_user_name,
</if>
<if test="applyTime != null">
apply_time,
</if>
<if test="instanceId != null">
instance_id,
</if>
<if test="processKey != null">
process_key,
</if>
<if test="delFlag != null">
del_flag,
</if>
<if test="createBy != null">
create_by,
</if>
<if test="createTime != null">
create_time,
</if>
<if test="updateBy != null">
update_by,
</if>
<if test="updateTime != null">
update_time,
</if>
<if test="remark != null">
remark,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="assortMix != null">
#{assortMix},
</if>
<if test="modelNameMix != null">
#{modelNameMix},
</if>
<if test="modelDescMix != null">
#{modelDescMix},
</if>
<if test="functionalDirectiveCnMix != null">
#{functionalDirectiveCnMix},
</if>
<if test="functionalDirectiveEnMix != null">
#{functionalDirectiveEnMix},
</if>
<if test="forecastRangeMix != null">
#{forecastRangeMix},
</if>
<if test="targetTemperatureMix != null">
#{targetTemperatureMix},
</if>
<if test="formulaDescMix != null">
#{formulaDescMix},
</if>
<if test="applyUserId != null">
#{applyUserId},
</if>
<if test="applyUserName != null">
#{applyUserName},
</if>
<if test="applyTime != null">
#{applyTime},
</if>
<if test="instanceId != null">
#{instanceId},
</if>
<if test="processKey != null">
#{processKey},
</if>
<if test="delFlag != null">
#{delFlag},
</if>
<if test="createBy != null">
#{createBy},
</if>
<if test="createTime != null">
#{createTime},
</if>
<if test="updateBy != null">
#{updateBy},
</if>
<if test="updateTime != null">
#{updateTime},
</if>
<if test="remark != null">
#{remark},
</if>
</trim>
</insert>
<insert id="insertBizSmartSchemaSub" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="modelId">
<!--列表选填list,跟前面起的名字没有关系-->
insert into con_data.app_electricheater_zero_tb_adaptive_model
( sub_id,
model_name,
model_desc,
assort,
functional_directive_cn,
functional_directive_en,
forecast_range,
target_temperature,
formula_desc,
state) values
<foreach collection='list' item='modelVo' separator=",">
(#{modelVo.subId},
#{modelVo.modelName},
#{modelVo.modelDesc},
#{modelVo.assort},
#{modelVo.functionalDirectiveCn},
#{modelVo.functionalDirectiveEn},
#{modelVo.forecastRange},
#{modelVo.targetTemperature},
#{modelVo.formulaDesc},
#{modelVo.state})
</foreach>
</insert>
详细代码可留言,发邮箱。