查询方案功能实现
上面实现了自定义查询功能框架,从用户角度出发,有些条件组合可以形成特定的查询方案,对应着业务查询场景。诸多查询条件的组合,不能每次都让用户来设置,而是应该保存下来,下次可以直接使用或者在其基础上按需调整,也就是将查询方案持久化。
实体配置
使用平台的低代码配置功能来实现查询方案的实体配置。
考虑到自定义查询属于通用性功能,因此放到了平台架构体系中位于底层的系统管理模块下。
实体配置如下:
实体模型配置如下:
查询方案内置于自定义查询功能内部的,因此无需配置相关视图。
使用平台功能生成库表和代码、拷贝代码、编译、配置权限项。
功能整合
首先上整体效果图,有个直观的了解,如下图:
在原有基础上,顶部增加了查询方案的下拉列表,用户可以通过下拉选择快速加载先前保存的查询方案,并可以通过修改按钮来修改名称、通过删除按钮来删除方案。
查询方案的主体,仍是由筛选器组件来承担,集成整合过程中增加了交互。
底部通过保存按钮来保存查询方案;通过另存为按钮来拷贝新增方案,点击查询按钮后,关闭对话框,将查询方案对应查询条件组合,传递给父窗口(自定义查询功能页面),发起查询操作。
查询方案下拉列表实现
在查询方案目录下,新增一个select.vue的页面,源码如下:
<template>
<el-select
v-model="selectedValue"
:size="size"
clearable
:disabled="readonly"
style="width: 200px; margin: 20 auto"
@change="change"
>
<el-option
v-for="item in dictionaryItemList"
:key="item.id"
:value="item.id"
:label="item.name"
/>
</el-select>
</template>
<script>
export default {
name: 'QueryPlanSelect',
label: '查询方案下拉',
props: {
modelValue: {
type: String,
required: false,
default: ''
},
code: {
type: String,
default: ''
},
readonly: {
type: Boolean,
required: false,
default: false
},
size: {
type: String,
default: ''
}
},
data() {
return {
dictionaryItemList: [],
selectedValue: ''
}
},
watch: {
modelValue: {
immediate: true,
handler: 'setSelected'
}
},
mounted() {
this.loadData()
},
methods: {
change(value) {
let rowData = null
this.dictionaryItemList.forEach((item) => {
if (item.id === value) {
rowData = item
return
}
})
// 更新绑定值
this.$emit('update:modelValue', value)
// 注意,此处若命令为change,则可能会与底层实现冲突,导致执行两次
this.$emit('my-change', value, rowData)
},
setSelected() {
this.selectedValue = this.modelValue
},
loadData() {
this.dictionaryItemList = []
this.$api.system.queryPlan.list().then((res) => {
this.dictionaryItemList = res.data
if (this.dictionaryItemList.length == 1) {
this.selectedValue = this.dictionaryItemList[0].id
this.$emit('my-change', this.dictionaryItemList[0].id, this.dictionaryItemList[0])
} else {
this.selectedValue = ''
this.$emit('my-change', '', null)
}
})
}
}
}
</script>
就是把查询方案实体的列表数据,转换为下拉列表的数据格式,并对传入值以及下拉选择变更后,将数据通过事件触发返回给父页面,也就是自定义查询页面。
查询方案下拉列表选中项变更
自定义查询页面会监听查询方案下拉列表的选择的变更,可能有两种情况,一是变更选择项,二是清空选择,对应的事件处理如下:
// 查询方案变更
queryPlanChanged(value, data) {
// 保存为当前查询方案
this.currentQueryPlan = data
if (data) {
//加载条件
this.$refs.everrightFilter.setData(JSON.parse(data.content))
} else {
this.$refs.everrightFilter.clearData()
}
}
注意当查询方案内容不为空时,调用筛选器的setData方法,来填充条件组合,便于用户查看或在其基础上调整。
查询方案改名
点击下拉列表右侧的“修改”按钮,弹出输入框,这里实际是用于改名,系统会弹出对话框,默认填充当前选中的查询方案名称,用户修改后点击确定按钮后保存,如下图:
功能实现使用了element plus的MessageBox的Prompt模式,代码如下:
// 修改方案名称
modify() {
if (this.currentQueryPlan == null) {
this.$message.warning('请先选择要改名的查询方案')
return
}
//获取查询方案内容,并做非空判断
const content = JSON.stringify(this.$refs.everrightFilter.getData(), null, '\t')
if (this.checkContentIsNull(content)) {
return
}
// 输入新的方案名称
ElMessageBox.prompt('请输入新的方案名称', '修改查询方案', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputValue: this.currentQueryPlan.name,
inputPattern: /.+/,
inputErrorMessage: '不能为空'
}).then(({ value }) => {
this.currentQueryPlan.name = value
this.currentQueryPlan.content = content
this.$api.system.queryPlan.modify(this.currentQueryPlan).then((res) => {
//刷新查询方案列表
this.refreshQuerPlanList()
})
})
},
查询方案删除
点击下拉列表右侧的“删除”按钮,弹出确认框,确定后删除,如下:
源码实现如下:
// 删除
remove() {
if (this.currentQueryPlan == null) {
this.$message.warning('请先选择要删除的查询方案')
return
}
this.$confirm('确定要删除该查询方案吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$api.system.queryPlan.remove(this.currentQueryPlan.id).then((res) => {
// 刷新查询方案列表
this.refreshQuerPlanList()
})
})
}
查询方案新增/修改
在查询方案下拉列表无选中项的情况下,通过筛选器配置查询条件组合后,点击保存按钮,弹出对话框,输入方案名称后,新增查询方案,如下:
在查询方案下拉列表有选中项的情况下,点击保存按钮,直接调用后端服务,保存数据,实际更新的是条件组合。
代码如下:
// 保存查询方案
save() {
//获取查询方案内容,并做非空判断
const content = JSON.stringify(this.$refs.everrightFilter.getData(), null, '\t')
if (this.checkContentIsNull(content)) {
return
}
if (this.currentQueryPlan) {
// 如当前查询方案不会空,更新数据
this.currentQueryPlan.content = content
this.$api.system.queryPlan.modify(this.currentQueryPlan)
} else {
// 如当前查询方案为空,新增数据
ElMessageBox.prompt('请输入方案名称', '新增查询方案', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /.+/,
inputErrorMessage: '不能为空'
}).then(({ value }) => {
const data = {
name: value,
entityModelCode: this.entityModelCode,
content: content
}
this.$api.system.queryPlan.add(data).then((res) => {
//刷新查询方案列表
this.refreshQuerPlanList()
})
})
}
}
查询方案另存为
用户往往需要基于某个现有的查询方案调整,另存为另一个查询方案,通过功能按钮实现。
这里没有根据当前是否有选中的查询方案来控制另存为按钮的可用,而是在逻辑上进行了处理,如当前查询方案为空,新增数据,否则参照当前数据新增(即另存),实现如下:
//另存为
saveAs() {
//获取查询方案内容,并做非空判断
const content = JSON.stringify(this.$refs.everrightFilter.getData(), null, '\t')
if (this.checkContentIsNull(content)) {
return
}
if (this.currentQueryPlan == null) {
// 如当前查询方案为空,新增数据
ElMessageBox.prompt('请输入方案名称', '新增查询方案', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /.+/,
inputErrorMessage: '不能为空'
}).then(({ value }) => {
const data = {
name: value,
entityModelCode: this.entityModelCode,
content: content
}
this.$api.system.queryPlan.add(data).then((res) => {
//刷新查询方案列表
this.refreshQuerPlanList()
})
})
} else {
// 如当前查询方案不为空,参照当前数据新增
ElMessageBox.prompt('请输入方案名称', '查询方案另存为', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /.+/,
inputErrorMessage: '不能为空'
}).then(({ value }) => {
const data = {
name: value,
entityModelCode: this.entityModelCode,
content: content
}
this.$api.system.queryPlan.add(data).then((res) => {
//刷新查询方案列表
this.refreshQuerPlanList()
})
})
}
}
辅助方法
有几个公共辅助方法,在这里也简单列一下:
保存数据前需要判断是否已添加了条件,即进行数据验证,如下:
// 判断方案内容是否为空
checkContentIsNull(content) {
if (content == '{}') {
this.$message.warning('请先添加至少一个查询条件')
return true
}
return false
}
在对方案新增、修改、删除时,需要刷新下拉列表,如下:
// 刷新查询方案列表
refreshQuerPlanList() {
this.$refs.queryPlanSelect.loadData()
}
后端处理
先前搭建框架的时候,仅实现了单组单条件,接下来,就来完善该部分逻辑以及其他细节。
单组多条件
配置查询方案条件如下:
后端逻辑完善如下:
// 转换数据
DataFilterRuleVO dataFilterRule = JSON.parseObject(customQueryString, DataFilterRuleVO.class);
// 获取组集合
List<DataFilterGroupVO> dataFilterGroupList = dataFilterRule.getFilters();
// 遍历组集合
for (DataFilterGroupVO dataFilterGroup : dataFilterGroupList) {
// 获取条件集合
List<DataFilterConditionVO> conditionList = dataFilterGroup.getConditions();
// 组内条件逻辑关系
String logicalOperator = dataFilterGroup.getLogicalOperator();
if (CollectionUtils.isNotEmpty(conditionList)) {
// 遍历条件集合
for (DataFilterConditionVO condition : conditionList) {
if (logicalOperator.equals(OR)) {
queryWrapper = queryWrapper.or();
}
// 获取字段名,命名风格驼峰转换成下划线
String fieldName = condition.getProperty();
Object value = condition.getValue();
// 获取操作
String operator = condition.getOperator();
QueryRuleEnum queryRule = EnumUtils.getEnum(QueryRuleEnum.class, operator, QueryRuleEnum.EQ);
addEasyQuery(queryWrapper, fieldName, queryRule, value);
}
}
}
运行,查看生成SQL,如下:
SELECT COUNT(*) AS total FROM cfg_template WHERE delete_flag = 'NO' AND (name = ? AND content LIKE ?)
将组内逻辑关系调整为或,如下:
运行,查看生成SQL,如下:
SELECT COUNT(*) AS total FROM cfg_template WHERE delete_flag = 'NO' AND (name = ? OR content LIKE ?)
多组多条件
配置查询方案条件如下:
后端逻辑增加组间逻辑关系处理,调整后如下:
// 转换数据
DataFilterRuleVO dataFilterRule = JSON.parseObject(customQueryString, DataFilterRuleVO.class);
// 获取组集合
List<DataFilterGroupVO> dataFilterGroupList = dataFilterRule.getFilters();
// 组间条件逻辑关系
String logicalOperatorGroup = dataFilterRule.getLogicalOperator();
// 遍历组集合
for (DataFilterGroupVO dataFilterGroup : dataFilterGroupList) {
if (logicalOperatorGroup.equals(OR)) {
queryWrapper = queryWrapper.or();
}
// 获取条件集合
List<DataFilterConditionVO> conditionList = dataFilterGroup.getConditions();
// 组内条件逻辑关系
String logicalOperator = dataFilterGroup.getLogicalOperator();
if (CollectionUtils.isNotEmpty(conditionList)) {
// 遍历条件集合
for (DataFilterConditionVO condition : conditionList) {
if (logicalOperator.equals(OR)) {
queryWrapper = queryWrapper.or();
}
// 获取字段名,命名风格驼峰转换成下划线
String fieldName = condition.getProperty();
Object value = condition.getValue();
// 获取操作
String operator = condition.getOperator();
QueryRuleEnum queryRule = EnumUtils.getEnum(QueryRuleEnum.class, operator, QueryRuleEnum.EQ);
addEasyQuery(queryWrapper, fieldName, queryRule, value);
}
}
}
运行,查看生成SQL,如下:
==> Preparing: SELECT COUNT(*) AS total FROM cfg_template WHERE delete_flag = 'NO' AND (name = ? AND content LIKE ? AND name LIKE ? AND name LIKE ?)
==> Parameters: 1(String), %2%(String), 3%(String), %4(String)
看上去,SQL从最终查询结果而言是正确的,但是没有分组,如果组内关系是or,推测应该会出问题,我们继续验证下。
将组间逻辑关系调整为或,如下:
运行,查看生成SQL,如下:
==> Preparing: SELECT COUNT(*) AS total FROM cfg_template WHERE delete_flag = 'NO' AND (name = ? AND content LIKE ? OR name LIKE ? AND name LIKE ?)
==> Parameters: 1(String), %2%(String), 3%(String), %4(String)
可以看出来,果然出了问题,我们希望各条件组相对独立,用小括号包裹,然后再参与组间逻辑运算。
基于上述需求,对逻辑转换代码进行调整,如下:
private static <E, VO> void build(QueryWrapper<E> queryWrapper, Class<E> entityClass, String customQueryString, SortInfo sortInfo) {
// 转换数据
DataFilterRuleVO dataFilterRule = JSON.parseObject(customQueryString, DataFilterRuleVO.class);
// 获取组集合
List<DataFilterGroupVO> dataFilterGroupList = dataFilterRule.getFilters();
// 组间条件逻辑关系
String logicalOperatorGroup = dataFilterRule.getLogicalOperator();
// 遍历组集合
for (DataFilterGroupVO dataFilterGroup : dataFilterGroupList) {
if (logicalOperatorGroup.equals(OR)) {
queryWrapper.or(x ->
generateQueryWrapper(x, dataFilterGroup)
);
} else {
queryWrapper.and(x ->
generateQueryWrapper(x, dataFilterGroup)
);
}
}
// 附加排序
if (sortInfo != null && StringUtils.isNotBlank(sortInfo.getField())) {
// 此处未使用注解,而是按照约定的规则,将驼峰命名转换为下划线,从而获取到数据库表字段名
String orderField = CommonUtil.camelToUnderline(sortInfo.getField());
if (sortInfo.getAscType()) {
queryWrapper.orderByAsc(orderField);
} else {
queryWrapper.orderByDesc(orderField);
}
}
}
private static <E> QueryWrapper<E> generateQueryWrapper(QueryWrapper<E> queryWrapper, DataFilterGroupVO dataFilterGroup) {
// 获取条件集合
List<DataFilterConditionVO> conditionList = dataFilterGroup.getConditions();
// 组内条件逻辑关系
String logicalOperator = dataFilterGroup.getLogicalOperator();
if (CollectionUtils.isNotEmpty(conditionList)) {
// 遍历条件集合
for (DataFilterConditionVO condition : conditionList) {
if (logicalOperator.equals(OR)) {
queryWrapper = queryWrapper.or();
}
// 获取字段名,命名风格驼峰转换成下划线
String fieldName = condition.getProperty();
Object value = condition.getValue();
// 获取操作
String operator = condition.getOperator();
QueryRuleEnum queryRule = EnumUtils.getEnum(QueryRuleEnum.class, operator, QueryRuleEnum.EQ);
addEasyQuery(queryWrapper, fieldName, queryRule, value);
}
}
return queryWrapper;
}
运行,查看生成SQL,如下:
==> Preparing: SELECT COUNT(*) AS total FROM cfg_template WHERE delete_flag = 'NO' AND ((name = ? AND content LIKE ?) OR (name LIKE ? AND name LIKE ?))
==> Parameters: 1(String), %2%(String), 3%(String), %4(String)
可以看到,条件组外层使用了小括号包裹,组间的逻辑运算也是正确的。
日期类型的处理
前面提到过,在做筛选器组件前端功能验证时,发现日期类型的属性,生成查询规则与其他类型不同,如下:
value属性不是一个值,而是对应了一个对象,并且也不是日期字符串,而是时间戳,这种情况下,需要后端另行解析处理。
设置测试条件如下:
增加逻辑处理如下:
运行,查看生成SQL,如下:
==> Preparing: SELECT COUNT(*) AS total FROM cfg_template WHERE delete_flag = 'NO' AND ((name = ? AND content LIKE ?) OR (name LIKE ? AND name LIKE ?))
==> Parameters: 1(String), %2%(String), 3%(String), %4(String)
开源平台资料
平台名称:一二三开发平台
简介: 企业级通用开发平台
设计资料:[csdn专栏]
开源地址:[Gitee]
开源协议:MIT
如果您在阅读本文时获得了帮助或受到了启发,希望您能够喜欢并收藏这篇文章,为它点赞~
请在评论区与我分享您的想法和心得,一起交流学习,不断进步,遇见更加优秀的自己!