12 changed files with 1044 additions and 40 deletions
@ -0,0 +1,18 @@ |
|||
<template> |
|||
<div class="bracketBorders w-[10px]"></div> |
|||
</template> |
|||
|
|||
<style lang="css"> |
|||
.bracketBorders { |
|||
border-width: 1px 0px 1px 1px; |
|||
border-top-style: solid; |
|||
border-bottom-style: solid; |
|||
border-left-style: solid; |
|||
border-top-color: rgb(208, 208, 208); |
|||
border-bottom-color: rgb(208, 208, 208); |
|||
border-left-color: rgb(208, 208, 208); |
|||
border-image: initial; |
|||
border-right-style: initial; |
|||
border-right-color: initial; |
|||
} |
|||
</style> |
@ -0,0 +1,420 @@ |
|||
<template> |
|||
<div v-show="fieldMethodsClass.getShow(props, modelValue)" class="box"> |
|||
<div class="queryBuilderFlex gap-x-[5px]"> |
|||
<div style="min-width: 100px" class="content-center"> |
|||
<w-select |
|||
v-model="modelValueObject['operator']" |
|||
label="模式" |
|||
:options="CriteriaModeFactory.buildOptions()" |
|||
@update-value=" |
|||
(args) => { |
|||
if (props.mode === 'criteria') { |
|||
modelValue = { |
|||
operator: args.value, |
|||
criteria: modelValue['criteria'], |
|||
}; |
|||
} else { |
|||
modelValueObject['operator'] = args.value; |
|||
modelValueObject['criteria'] = modelValueObject['criteria']; |
|||
} |
|||
} |
|||
" |
|||
></w-select> |
|||
</div> |
|||
<Bracket></Bracket> |
|||
<div class="grid pt-1.5 pb-1.5 gap-y-[10px]"> |
|||
<template v-for="(condition, index) in modelValueObject['criteria']" :key="index"> |
|||
<div v-if="Tools.hasOwnProperty(condition, 'fieldName')" class="queryBuilderFlex gap-x-[5px]"> |
|||
<div class="content-center"> |
|||
<q-btn v-if="modelValueObject.criteria.length > 1" round unelevated push color="red" icon="remove_circle" size="8px" @click="removeClick(index)" |
|||
><q-tooltip>删除</q-tooltip></q-btn |
|||
> |
|||
<q-btn v-else round unelevated push color="grey" :disable="true" icon="remove_circle" size="8px"><q-tooltip>删除</q-tooltip></q-btn> |
|||
</div> |
|||
<w-select v-model="modelValueObject['criteria'][index]['fieldName']" :options="props.options" @update-value="fieldUpdateValue"></w-select> |
|||
<w-select |
|||
v-model="modelValueObject['criteria'][index]['operator']" |
|||
:options="getUseOperator(modelValueObject['criteria'][index]['fieldName'])" |
|||
@update-value=" |
|||
(args) => { |
|||
operatorUpdateValue(args, modelValueObject['criteria'][index]['fieldName']); |
|||
} |
|||
" |
|||
></w-select> |
|||
<template |
|||
v-if=" |
|||
modelValueObject['criteria'][index]['operator'] === criteriaOperator.between.name || |
|||
modelValueObject['criteria'][index]['operator'] === criteriaOperator.notBetween.name |
|||
" |
|||
> |
|||
<component |
|||
:is="getUseField(modelValueObject['criteria'][index]['fieldName'])?.type" |
|||
v-model="modelValueObject['criteria'][index]['start']" |
|||
v-bind="getUseField(modelValueObject['criteria'][index]['fieldName'])" |
|||
@update:model-value="fieldValueUpdate" |
|||
></component> |
|||
<div class="content-center">—</div> |
|||
<component |
|||
:is="getUseField(modelValueObject['criteria'][index]['fieldName'])?.type" |
|||
v-model="modelValueObject['criteria'][index]['end']" |
|||
v-bind="getUseField(modelValueObject['criteria'][index]['fieldName'])" |
|||
@update:model-value="fieldValueUpdate" |
|||
></component> |
|||
</template> |
|||
<template |
|||
v-else-if=" |
|||
modelValueObject['criteria'][index]['operator'] !== criteriaOperator.isBlank.name && |
|||
modelValueObject['criteria'][index]['operator'] !== criteriaOperator.notBlank.name && |
|||
modelValueObject['criteria'][index]['operator'] !== criteriaOperator.isNull.name && |
|||
modelValueObject['criteria'][index]['operator'] !== criteriaOperator.notNull.name |
|||
" |
|||
> |
|||
<component |
|||
:is="getUseField(modelValueObject['criteria'][index]['fieldName'])?.type" |
|||
v-model="modelValueObject['criteria'][index]['value']" |
|||
v-bind="getUseField(modelValueObject['criteria'][index]['fieldName'])" |
|||
@update:model-value="fieldValueUpdate" |
|||
></component> |
|||
</template> |
|||
</div> |
|||
<div v-else-if="!Tools.hasOwnProperty(condition, 'fieldName')" class="queryBuilderFlex gap-x-[5px]"> |
|||
<div class="content-center"> |
|||
<q-btn |
|||
v-if="modelValueObject['criteria'].length > 1" |
|||
round |
|||
unelevated |
|||
push |
|||
color="red" |
|||
icon="remove_circle" |
|||
size="8px" |
|||
@click="removeClick(index)" |
|||
><q-tooltip>删除</q-tooltip></q-btn |
|||
> |
|||
<q-btn v-else round unelevated push color="grey" :disable="true" icon="remove_circle" size="8px"><q-tooltip>删除</q-tooltip></q-btn> |
|||
</div> |
|||
<w-query-builder |
|||
v-model="modelValueObject['criteria'][index]" |
|||
:options="props.options" |
|||
@update-parent-model-value=" |
|||
() => { |
|||
/** |
|||
* 事件说明: |
|||
* 无论最外层使用的 sql 还是 criteria 模式,嵌套组件绑定值一定是 criteria 对象值, |
|||
* 只有 sql 模式下最顶层的模型值需要随着用户的选择被更改,所以要进行判断: |
|||
* 当模式为 sql 时当前组件一定是顶层组件,更新其模型显示值; |
|||
* 当模式为 criteria 时当前组件可能是嵌套组件也可能是顶层组件,直接使用 emit 往上抛事件即可。 |
|||
*/ |
|||
if (props.mode !== 'criteria') { |
|||
modelValue = CriteriaUtil.criteriaToSql(modelValueObject); |
|||
} else { |
|||
emit('updateParentModelValue'); |
|||
fieldMethodsClass.updateValue(modelValue); |
|||
} |
|||
} |
|||
" |
|||
></w-query-builder> |
|||
</div> |
|||
</template> |
|||
<div class="queryBuilderFlex gap-x-[5px]"> |
|||
<div class="content-center"> |
|||
<q-btn round unelevated push color="green" icon="add_circle" size="8px" @click="addClick"><q-tooltip>新增</q-tooltip></q-btn> |
|||
</div> |
|||
<div class="content-center"> |
|||
<q-btn round unelevated push color="orange" icon="add_box" size="8px" @click="addGroupClick"><q-tooltip>新增组</q-tooltip></q-btn> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { onBeforeMount, reactive, watch, toRaw } from 'vue'; |
|||
import { Tools } from '@/platform'; |
|||
import { FormFieldMethods } from '../form/FormField'; |
|||
import { FormFieldProps, getDefaultValue } from '@/platform/components/form/FormField.ts'; |
|||
import Bracket from './Bracket.vue'; |
|||
import { criteriaMode, criteriaOperator, CriteriaModeFactory, CriteriaOperatorFactory, CriteriaUtil } from './criteria'; |
|||
|
|||
const modelValue = defineModel<object | string>(); |
|||
const modelValueObject = reactive({}); |
|||
|
|||
interface FieldProps extends FormFieldProps { |
|||
options: Array<object>; |
|||
/** |
|||
* 组件模式 |
|||
* criteria:默认模式,组件绑定值为 criteria 查询对象。 |
|||
* sql:组件绑定值为 sql 条件字符串。 |
|||
*/ |
|||
mode?: string; |
|||
} |
|||
const props = withDefaults(defineProps<FieldProps>(), { |
|||
showIf: true, |
|||
options: () => [], |
|||
mode: 'criteria', |
|||
}); |
|||
const emit = defineEmits<{ |
|||
// 该事件仅用作嵌套组件中的子组件往上层组件传递更新模型值的消息。 |
|||
(e: 'updateParentModelValue'): void; |
|||
}>(); |
|||
class FieldMethods extends FormFieldMethods { |
|||
updateValue = (value_) => { |
|||
if (props['onUpdateValue']) { |
|||
props['onUpdateValue']({ |
|||
value: value_, |
|||
form: props['form'], |
|||
}); |
|||
} |
|||
}; |
|||
validate = () => { |
|||
return true; |
|||
}; |
|||
setValue = (value) => { |
|||
modelValue.value = value; |
|||
}; |
|||
getValue = () => { |
|||
return modelValue.value; |
|||
}; |
|||
// 组件清空值 |
|||
clearValue = () => { |
|||
modelValue.value = |
|||
props.mode === 'criteria' |
|||
? { |
|||
operator: criteriaMode.and.name, |
|||
criteria: [{ fieldName: '', operator: criteriaOperator.equals.name, value: undefined }], |
|||
} |
|||
: ''; |
|||
}; |
|||
} |
|||
const fieldMethodsClass = new FieldMethods(); |
|||
|
|||
// 通过 options 配置,得到每个组件应该设置的默认值,否则组件需要一个数组时给 undefined 控制台会有警告 |
|||
const componentDefaultValue = {}; |
|||
if (props.options && props.options.length > 0) { |
|||
props.options.forEach((item) => { |
|||
componentDefaultValue[item['value']] = getDefaultValue(item['useComponent']); |
|||
}); |
|||
} |
|||
|
|||
// 根据字段名查找使用的组件配置 |
|||
const getUseField = (field) => { |
|||
const option = props.options.find((item) => item['value'] === field); |
|||
if (!Tools.isEmpty(option)) { |
|||
return option['useComponent']; |
|||
} |
|||
return { type: 'w-text' }; |
|||
}; |
|||
// 根据字段名查找配置的 operator |
|||
const getUseOperator = (field) => { |
|||
if (!Tools.isEmpty(field)) { |
|||
const option = props.options.find((item) => item['value'] === field); |
|||
if (!Tools.isEmpty(option)) { |
|||
if (option['operator']) { |
|||
return CriteriaOperatorFactory.buildOptions(option['operator']); |
|||
} else if (option['excludeOperator']) { |
|||
return CriteriaOperatorFactory.buildOptions([], option['excludeOperator']); |
|||
} |
|||
} |
|||
} |
|||
return CriteriaOperatorFactory.buildOptions(); |
|||
}; |
|||
// 选择的字段发生变化 |
|||
const fieldUpdateValue = (args) => { |
|||
if (props.mode === 'criteria') { |
|||
modelValue.value.criteria.forEach((item) => { |
|||
if (item['fieldName'] === args['value']) { |
|||
if (item['operator'] === criteriaOperator.between.name || item['operator'] === criteriaOperator.notBetween.name) { |
|||
item['start'] = componentDefaultValue[item['fieldName']]; |
|||
item['end'] = componentDefaultValue[item['fieldName']]; |
|||
item['value'] = undefined; |
|||
} else { |
|||
item['start'] = undefined; |
|||
item['end'] = undefined; |
|||
item['value'] = componentDefaultValue[item['fieldName']]; |
|||
} |
|||
} |
|||
}); |
|||
modelValue.value = { |
|||
operator: modelValue.value.operator, |
|||
criteria: modelValue.value.criteria, |
|||
}; |
|||
emit('updateParentModelValue'); |
|||
} else { |
|||
modelValueObject['criteria'].forEach((item) => { |
|||
if (item['fieldName'] === args['value']) { |
|||
if (item['operator'] === criteriaOperator.between.name || item['operator'] === criteriaOperator.notBetween.name) { |
|||
item['start'] = componentDefaultValue[item['fieldName']]; |
|||
item['end'] = componentDefaultValue[item['fieldName']]; |
|||
item['value'] = undefined; |
|||
} else { |
|||
item['start'] = undefined; |
|||
item['end'] = undefined; |
|||
item['value'] = componentDefaultValue[item['fieldName']]; |
|||
} |
|||
} |
|||
}); |
|||
modelValue.value = CriteriaUtil.criteriaToSql(modelValueObject); |
|||
} |
|||
}; |
|||
// 选择的匹配操作发生变化 |
|||
const operatorUpdateValue = (args, fieldName) => { |
|||
if (props.mode === 'criteria') { |
|||
modelValue.value.criteria.forEach((item) => { |
|||
if (item['fieldName'] === fieldName) { |
|||
if (args['value'] === criteriaOperator.between.name || args['value'] === criteriaOperator.notBetween.name) { |
|||
item['start'] = componentDefaultValue[fieldName]; |
|||
item['end'] = componentDefaultValue[fieldName]; |
|||
item['value'] = undefined; |
|||
} else { |
|||
item['start'] = undefined; |
|||
item['end'] = undefined; |
|||
item['value'] = componentDefaultValue[fieldName]; |
|||
} |
|||
} |
|||
}); |
|||
modelValue.value = { |
|||
operator: modelValue.value.operator, |
|||
criteria: modelValue.value.criteria, |
|||
}; |
|||
emit('updateParentModelValue'); |
|||
} else { |
|||
modelValueObject['criteria'].forEach((item) => { |
|||
if (item['fieldName'] === fieldName) { |
|||
if (args['value'] === criteriaOperator.between.name || args['value'] === criteriaOperator.notBetween.name) { |
|||
item['start'] = componentDefaultValue[fieldName]; |
|||
item['end'] = componentDefaultValue[fieldName]; |
|||
item['value'] = undefined; |
|||
} else { |
|||
item['start'] = undefined; |
|||
item['end'] = undefined; |
|||
item['value'] = componentDefaultValue[fieldName]; |
|||
} |
|||
} |
|||
}); |
|||
modelValue.value = CriteriaUtil.criteriaToSql(modelValueObject); |
|||
} |
|||
}; |
|||
// 字段匹配值发生变化 |
|||
const fieldValueUpdate = (value) => { |
|||
if (props.mode !== 'criteria') { |
|||
modelValue.value = CriteriaUtil.criteriaToSql(modelValueObject); |
|||
} else { |
|||
emit('updateParentModelValue'); |
|||
fieldMethodsClass.updateValue(modelValue.value); |
|||
} |
|||
}; |
|||
|
|||
const addClick = () => { |
|||
if (props.mode === 'criteria') { |
|||
modelValue.value.criteria.push({ fieldName: '', operator: criteriaOperator.equals.name, value: undefined }); |
|||
modelValue.value = { |
|||
operator: modelValue.value.operator, |
|||
criteria: modelValue.value.criteria, |
|||
}; |
|||
emit('updateParentModelValue'); |
|||
} else { |
|||
modelValueObject['criteria'].push({ fieldName: '', operator: criteriaOperator.equals.name, value: undefined }); |
|||
modelValue.value = CriteriaUtil.criteriaToSql(modelValueObject); |
|||
} |
|||
}; |
|||
const removeClick = (index) => { |
|||
if (props.mode === 'criteria') { |
|||
modelValue.value.criteria.splice(index, 1); |
|||
modelValue.value = { |
|||
operator: modelValue.value.operator, |
|||
criteria: modelValue.value.criteria, |
|||
}; |
|||
emit('updateParentModelValue'); |
|||
} else { |
|||
modelValueObject['criteria'].splice(index, 1); |
|||
modelValue.value = CriteriaUtil.criteriaToSql(modelValueObject); |
|||
} |
|||
}; |
|||
const addGroupClick = () => { |
|||
if (props.mode === 'criteria') { |
|||
modelValue.value.criteria.push({ |
|||
operator: criteriaMode.and.name, |
|||
criteria: [{ fieldName: '', operator: criteriaOperator.equals.name, value: undefined }], |
|||
}); |
|||
modelValue.value = { |
|||
operator: modelValue.value.operator, |
|||
criteria: modelValue.value.criteria, |
|||
}; |
|||
emit('updateParentModelValue'); |
|||
} else { |
|||
modelValueObject['criteria'].push({ |
|||
operator: criteriaMode.and.name, |
|||
criteria: [{ fieldName: '', operator: criteriaOperator.equals.name, value: undefined }], |
|||
}); |
|||
modelValue.value = CriteriaUtil.criteriaToSql(modelValueObject); |
|||
} |
|||
}; |
|||
|
|||
watch( |
|||
() => modelValue.value, |
|||
(newVal, oldVal) => { |
|||
if (props.mode === 'criteria') { |
|||
if (!Tools.isEmpty(newVal)) { |
|||
modelValueObject['operator'] = newVal['operator']; |
|||
modelValueObject['criteria'] = newVal['criteria']; |
|||
} else { |
|||
modelValueObject['operator'] = criteriaMode.and.name; |
|||
modelValueObject['criteria'] = [{ fieldName: '', operator: criteriaOperator.equals.name, value: undefined }]; |
|||
} |
|||
} else { |
|||
if (Tools.isEmpty(newVal)) { |
|||
modelValueObject['operator'] = criteriaMode.and.name; |
|||
modelValueObject['criteria'] = [{ fieldName: '', operator: criteriaOperator.equals.name, value: undefined }]; |
|||
} |
|||
} |
|||
if (newVal !== oldVal) { |
|||
fieldMethodsClass.updateValue(newVal); |
|||
} |
|||
}, |
|||
); |
|||
|
|||
const getSqlValue = () => { |
|||
return props.mode === 'criteria' ? CriteriaUtil.criteriaToSql(modelValue.value) : modelValue.value; |
|||
}; |
|||
const getCriteriaValue = () => { |
|||
return props.mode === 'criteria' ? modelValue.value : toRaw(modelValueObject); |
|||
}; |
|||
|
|||
onBeforeMount(() => { |
|||
if (Tools.isEmpty(modelValue.value)) { |
|||
modelValue.value = |
|||
props.mode === 'criteria' |
|||
? { |
|||
operator: criteriaMode.and.name, |
|||
criteria: [{ fieldName: '', operator: criteriaOperator.equals.name, value: undefined }], |
|||
} |
|||
: ''; |
|||
modelValueObject['operator'] = criteriaMode.and.name; |
|||
modelValueObject['criteria'] = [{ fieldName: '', operator: criteriaOperator.equals.name, value: undefined }]; |
|||
} else { |
|||
// 当 modelValue 不为空时,需要初始化 modelValueObject |
|||
if (props.mode === 'criteria') { |
|||
modelValueObject['operator'] = modelValue.value['operator']; |
|||
modelValueObject['criteria'] = modelValue.value['criteria']; |
|||
} else { |
|||
const sqlPrefix = 'select * from t where '; |
|||
const criteria = CriteriaUtil.sqlToCriteria(sqlPrefix + modelValue.value); |
|||
modelValueObject['operator'] = criteria['operator']; |
|||
modelValueObject['criteria'] = criteria['criteria']; |
|||
} |
|||
} |
|||
}); |
|||
|
|||
defineExpose({ |
|||
validate: fieldMethodsClass.validate, |
|||
setValue: fieldMethodsClass.setValue, |
|||
getValue: fieldMethodsClass.getValue, |
|||
clearValue: fieldMethodsClass.clearValue, |
|||
getSqlValue, |
|||
getCriteriaValue, |
|||
}); |
|||
</script> |
|||
|
|||
<style lang="css"> |
|||
@import './css/comm.css'; |
|||
</style> |
@ -0,0 +1,378 @@ |
|||
import { Parser } from 'node-sql-parser'; |
|||
import { Tools } from '@/platform'; |
|||
|
|||
/** |
|||
* criteria 模式 |
|||
*/ |
|||
export const criteriaMode = { |
|||
and: { |
|||
name: 'and', |
|||
option: { label: '并且', value: 'and' }, |
|||
}, |
|||
or: { |
|||
name: 'or', |
|||
option: { label: '或者', value: 'or' }, |
|||
}, |
|||
not: { |
|||
name: 'not', |
|||
option: { label: '不满足', value: 'not' }, |
|||
}, |
|||
}; |
|||
|
|||
/** |
|||
* criteria 匹配操作 |
|||
*/ |
|||
export const criteriaOperator = { |
|||
equals: { |
|||
name: 'equals', |
|||
option: { label: '等于', value: 'equals' }, |
|||
sqlTemplate: ` #{fieldName} = #{value} `, |
|||
}, |
|||
notEquals: { |
|||
name: 'notEquals', |
|||
option: { label: '不等于', value: 'notEquals' }, |
|||
sqlTemplate: ` #{fieldName} <> #{value} `, |
|||
}, |
|||
contains: { |
|||
name: 'contains', |
|||
option: { label: '包含', value: 'contains' }, |
|||
sqlTemplate: ` #{fieldName} LIKE '%#{value}%' `, |
|||
}, |
|||
notContains: { |
|||
name: 'notContains', |
|||
option: { label: '不包含', value: 'notContains' }, |
|||
sqlTemplate: ` #{fieldName} NOT LIKE '%#{value}%' `, |
|||
}, |
|||
greaterThan: { |
|||
name: 'greaterThan', |
|||
option: { label: '大于', value: 'greaterThan' }, |
|||
sqlTemplate: ` #{fieldName} > #{value} `, |
|||
}, |
|||
greaterOrEqual: { |
|||
name: 'greaterOrEqual', |
|||
option: { label: '大于等于', value: 'greaterOrEqual' }, |
|||
sqlTemplate: ` #{fieldName} >= #{value} `, |
|||
}, |
|||
lessThan: { |
|||
name: 'lessThan', |
|||
option: { label: '小于', value: 'lessThan' }, |
|||
sqlTemplate: ` #{fieldName} < #{value} `, |
|||
}, |
|||
lessOrEqual: { |
|||
name: 'lessOrEqual', |
|||
option: { label: '小于等于', value: 'lessOrEqual' }, |
|||
sqlTemplate: ` #{fieldName} <= #{value} `, |
|||
}, |
|||
inSet: { |
|||
name: 'inSet', |
|||
option: { label: '在...之内', value: 'inSet' }, |
|||
sqlTemplate: ' #{fieldName} IN #{value} ', |
|||
}, |
|||
notInSet: { |
|||
name: 'notInSet', |
|||
option: { label: '不在...之内', value: 'notInSet' }, |
|||
sqlTemplate: ' #{fieldName} NOT IN #{value} ', |
|||
}, |
|||
startWith: { |
|||
name: 'startWith', |
|||
option: { label: '以...开始', value: 'startWith' }, |
|||
sqlTemplate: ` #{fieldName} LIKE '#{value}%' `, |
|||
}, |
|||
notStartWith: { |
|||
name: 'notStartWith', |
|||
option: { label: '不以...开始', value: 'notStartWith' }, |
|||
sqlTemplate: ` #{fieldName} NOT LIKE '#{value}%' `, |
|||
}, |
|||
endWith: { |
|||
name: 'endWith', |
|||
option: { label: '以...结束', value: 'endWith' }, |
|||
sqlTemplate: ` #{fieldName} LIKE '%#{value}' `, |
|||
}, |
|||
notEndWith: { |
|||
name: 'notEndWith', |
|||
option: { label: '不以...结束', value: 'notEndWith' }, |
|||
sqlTemplate: ` #{fieldName} NOT LIKE '%#{value}' `, |
|||
}, |
|||
between: { |
|||
name: 'between', |
|||
option: { label: '在...之间', value: 'between' }, |
|||
sqlTemplate: ` #{fieldName} BETWEEN #{start} AND #{end} `, |
|||
}, |
|||
notBetween: { |
|||
name: 'notBetween', |
|||
option: { label: '不在...之间', value: 'notBetween' }, |
|||
sqlTemplate: ` #{fieldName} NOT BETWEEN #{start} AND #{end} `, |
|||
}, |
|||
isBlank: { |
|||
name: 'isBlank', |
|||
option: { label: '为空', value: 'isBlank' }, |
|||
sqlTemplate: ` #{fieldName} = '' `, |
|||
}, |
|||
notBlank: { |
|||
name: 'notBlank', |
|||
option: { label: '不为空', value: 'notBlank' }, |
|||
sqlTemplate: ` #{fieldName} <> '' `, |
|||
}, |
|||
isNull: { |
|||
name: 'isNull', |
|||
option: { label: '为 null', value: 'isNull' }, |
|||
sqlTemplate: ` #{fieldName} IS NULL `, |
|||
}, |
|||
notNull: { |
|||
name: 'notNull', |
|||
option: { label: '不为 null', value: 'notNull' }, |
|||
sqlTemplate: ` #{fieldName} IS NOT NULL `, |
|||
}, |
|||
}; |
|||
|
|||
export class CriteriaModeFactory { |
|||
/** |
|||
* 构建下拉框、checkbox多选按钮等组件所需的 options |
|||
* @param names 默认为空数组,将所有支持的模式都构建进去,否则根据传入的名字数组进行构建。 |
|||
*/ |
|||
public static buildOptions(names: Array<string> = []) { |
|||
const options = <any>[]; |
|||
if (names && names.length > 0) { |
|||
names.forEach((name) => { |
|||
if (criteriaMode[name]) { |
|||
options.push(criteriaMode[name]['option']); |
|||
} |
|||
}); |
|||
} else { |
|||
Object.keys(criteriaMode).forEach((operator) => { |
|||
options.push(criteriaMode[operator]['option']); |
|||
}); |
|||
} |
|||
return options; |
|||
} |
|||
} |
|||
|
|||
export class CriteriaOperatorFactory { |
|||
/** |
|||
* 构建下拉框、checkbox多选按钮等组件所需的 options |
|||
* @param names 默认为空数组,将所有匹配操作都构建进去,否则根据传入的名字数组进行构建。 |
|||
* @param exclude 默认为空数组,否则根据传入的名字数组进行排除 |
|||
*/ |
|||
public static buildOptions(names: Array<string> = [], exclude: Array<string> = []) { |
|||
const options = <any>[]; |
|||
if (names && names.length > 0) { |
|||
names.forEach((name) => { |
|||
if (criteriaOperator[name]) { |
|||
options.push(criteriaOperator[name]['option']); |
|||
} |
|||
}); |
|||
} else { |
|||
Object.keys(criteriaOperator).forEach((operator) => { |
|||
if (exclude && !exclude.includes(operator)) { |
|||
options.push(criteriaOperator[operator]['option']); |
|||
} |
|||
}); |
|||
} |
|||
return options; |
|||
} |
|||
} |
|||
|
|||
export class CriteriaUtil { |
|||
/** |
|||
* 将SQL语句转换为 criteria 对象 |
|||
* @param sql |
|||
* @returns |
|||
*/ |
|||
public static sqlToCriteria = (sql) => { |
|||
const parser = new Parser(); |
|||
const ast = parser.astify(sql); |
|||
const parserResult = astParserToCriteria(ast['where']); |
|||
const criteriaResult = { |
|||
operator: ast['where']['operator'].toLowerCase(), |
|||
criteria: parserResult, |
|||
}; |
|||
return criteriaResult; |
|||
}; |
|||
|
|||
/** |
|||
* 将 criteria 对象转换为 SQL 语句 |
|||
* @param criteria |
|||
*/ |
|||
public static criteriaToSql = (criteria) => { |
|||
let sql = ''; |
|||
criteria.criteria.forEach((item, index) => { |
|||
if (Tools.hasOwnProperty(item, 'fieldName')) { |
|||
if (index === criteria.criteria.length - 1) { |
|||
sql += replaceSql(criteriaOperator[item['operator']]['sqlTemplate'], item); |
|||
} else { |
|||
sql += |
|||
replaceSql(criteriaOperator[item['operator']]['sqlTemplate'], item) + |
|||
(criteria.operator === criteriaMode.not.name ? criteriaMode.and.name : criteria.operator); |
|||
} |
|||
} else if (Tools.hasOwnProperty(item, 'criteria')) { |
|||
if (index === criteria.criteria.length - 1) { |
|||
sql += ' ( ' + CriteriaUtil.criteriaToSql(item) + ' ) '; |
|||
} else { |
|||
sql += ' ( ' + CriteriaUtil.criteriaToSql(item) + ' ) ' + (criteria.operator === criteriaMode.not.name ? criteriaMode.and.name : criteria.operator); |
|||
} |
|||
} |
|||
}); |
|||
if (criteria.operator === criteriaMode.not.name) { |
|||
sql = `not (` + sql + `)`; |
|||
} |
|||
return sql; |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* SQL解析语法树中的操作转换为 criteria 中的操作 |
|||
* @param astOperator |
|||
* @param astValue |
|||
*/ |
|||
const astOperatorToCriteriaOperator = (astOperator, astValue) => { |
|||
if (astOperator === '=') { |
|||
return criteriaOperator.equals.name; |
|||
} else if (astOperator === '<>') { |
|||
return criteriaOperator.notEquals.name; |
|||
} else if (astOperator === 'LIKE' && astValue.startsWith('%') && astValue.endsWith('%')) { |
|||
return criteriaOperator.contains.name; |
|||
} else if (astOperator === 'NOT LIKE' && astValue.startsWith('%') && astValue.endsWith('%')) { |
|||
return criteriaOperator.notContains.name; |
|||
} else if (astOperator === '>') { |
|||
return criteriaOperator.greaterThan.name; |
|||
} else if (astOperator === '>=') { |
|||
return criteriaOperator.greaterOrEqual.name; |
|||
} else if (astOperator === '<') { |
|||
return criteriaOperator.lessThan.name; |
|||
} else if (astOperator === '<=') { |
|||
return criteriaOperator.lessOrEqual.name; |
|||
} else if (astOperator === 'IN') { |
|||
return criteriaOperator.inSet.name; |
|||
} else if (astOperator === 'NOT IN') { |
|||
return criteriaOperator.notInSet.name; |
|||
} else if (astOperator === 'LIKE' && astValue.endsWith('%')) { |
|||
return criteriaOperator.startWith.name; |
|||
} else if (astOperator === 'NOT LIKE' && astValue.endsWith('%')) { |
|||
return criteriaOperator.notStartWith.name; |
|||
} else if (astOperator === 'LIKE' && astValue.startsWith('%')) { |
|||
return criteriaOperator.endWith.name; |
|||
} else if (astOperator === 'NOT LIKE' && astValue.startsWith('%')) { |
|||
return criteriaOperator.notEndWith.name; |
|||
} else if (astOperator === 'BETWEEN') { |
|||
return criteriaOperator.between.name; |
|||
} else if (astOperator === 'NOT BETWEEN') { |
|||
return criteriaOperator.notBetween.name; |
|||
} |
|||
return astOperator; |
|||
}; |
|||
|
|||
/** |
|||
* SQL解析语法树中的值转换为 criteria 中的值 |
|||
* @param astOperator 操作类型 |
|||
* @param astValue 值 |
|||
*/ |
|||
const astValueToCriteriaValue = (astOperator, astValue) => { |
|||
if ((astOperator === 'LIKE' || astOperator === 'NOT LIKE') && typeof astValue === 'string' && astValue.indexOf('%') > -1) { |
|||
return astValue.replace(/%/g, ''); |
|||
} else if ((astOperator === 'IN' || astOperator === 'NOT IN') && Array.isArray(astValue)) { |
|||
const result = <any>[]; |
|||
astValue.forEach((value) => { |
|||
result.push(value['value']); |
|||
}); |
|||
return result; |
|||
} |
|||
return astValue; |
|||
}; |
|||
|
|||
// AST语法树中需要往下循环的类型
|
|||
const astForEachType = { |
|||
binary_expr: 'binary_expr', |
|||
function: 'function', |
|||
}; |
|||
|
|||
/** |
|||
* 将SQL解析语法树结构解析转换为 criteria 对象 |
|||
* @param astObj |
|||
* @returns |
|||
*/ |
|||
const astParserToCriteria = (astObj) => { |
|||
const criterias = <any>[]; |
|||
let flag = true; |
|||
if (astObj['parentheses'] && astObj['type'] === astForEachType.binary_expr) { |
|||
// 有括号且类型为 binary_expr 为一组表达式子规则
|
|||
const childCriterias = <any>[]; |
|||
if (astObj['left'] && astForEachType[astObj['left']['type']]) { |
|||
flag = false; |
|||
childCriterias.push(...astParserToCriteria(astObj['left'])); |
|||
} |
|||
if (astObj['right'] && astForEachType[astObj['right']['type']]) { |
|||
flag = false; |
|||
childCriterias.push(...astParserToCriteria(astObj['right'])); |
|||
} |
|||
criterias.push({ operator: astObj['operator'].toLowerCase(), criteria: childCriterias }); |
|||
} else if (astObj['parentheses'] && astObj['type'] === astForEachType.function) { |
|||
// 有括号且类型为 function 为一组子规则
|
|||
if (astObj['name']['name'][0]['value'] === criteriaMode.not.name) { |
|||
flag = false; |
|||
const childCriterias = <any>[]; |
|||
childCriterias.push(...astParserToCriteria(astObj['args']['value'][0])); |
|||
criterias.push({ operator: criteriaMode.not.name, criteria: childCriterias }); |
|||
} |
|||
} else { |
|||
if (astObj && astObj['left'] && astForEachType[astObj['left']['type']]) { |
|||
flag = false; |
|||
const leftCriterias = astParserToCriteria(astObj['left']); |
|||
criterias.push(...leftCriterias); |
|||
} |
|||
if (astObj && astObj['right'] && astForEachType[astObj['right']['type']]) { |
|||
flag = false; |
|||
const rightCriterias = astParserToCriteria(astObj['right']); |
|||
criterias.push(...rightCriterias); |
|||
} |
|||
} |
|||
if (flag) { |
|||
if (astObj['operator'] === 'BETWEEN' || astObj['operator'] === 'NOT BETWEEN') { |
|||
const criteria = { |
|||
fieldName: astObj['left']['column'], |
|||
operator: astOperatorToCriteriaOperator(astObj['operator'], astObj['right']['value']), |
|||
start: astObj['right']['value'][0]['value'], |
|||
end: astObj['right']['value'][1]['value'], |
|||
}; |
|||
criterias.push(criteria); |
|||
} else { |
|||
const criteria = { |
|||
fieldName: astObj['left']['column'], |
|||
operator: astOperatorToCriteriaOperator(astObj['operator'], astObj['right']['value']), |
|||
value: astValueToCriteriaValue(astObj['operator'], astObj['right']['value']), |
|||
}; |
|||
criterias.push(criteria); |
|||
} |
|||
} |
|||
return criterias; |
|||
}; |
|||
|
|||
const replaceSql = (str, data) => { |
|||
const regex = /#{(\w+)}/g; |
|||
return str.replace(regex, (match, fieldName) => { |
|||
if ((data['operator'] === criteriaOperator.inSet.name || data['operator'] === criteriaOperator.notInSet.name) && fieldName === 'value') { |
|||
let replaceValue = '(' + data[fieldName] + ')' || ''; |
|||
if (Array.isArray(data[fieldName]) && data[fieldName].length > 0 && typeof data[fieldName][0] === 'string') { |
|||
replaceValue = '('; |
|||
data[fieldName].forEach((item) => { |
|||
replaceValue += `'` + item + `',`; |
|||
}); |
|||
replaceValue = replaceValue.substring(0, replaceValue.length - 1) + ')'; |
|||
} |
|||
return replaceValue; |
|||
} else if ( |
|||
data['operator'] === criteriaOperator.contains.name || |
|||
data['operator'] === criteriaOperator.notContains.name || |
|||
data['operator'] === criteriaOperator.startWith.name || |
|||
data['operator'] === criteriaOperator.notStartWith.name || |
|||
data['operator'] === criteriaOperator.endWith.name || |
|||
data['operator'] === criteriaOperator.notEndWith.name |
|||
) { |
|||
return data[fieldName] || ''; |
|||
} else { |
|||
if (fieldName !== 'fieldName' && typeof data[fieldName] === 'string') { |
|||
return `'` + data[fieldName] + `'` || ''; |
|||
} |
|||
return data[fieldName] || ''; |
|||
} |
|||
}); |
|||
}; |
@ -0,0 +1,22 @@ |
|||
.bracketBorders { |
|||
border-width: 1px 0px 1px 1px; |
|||
border-top-style: solid; |
|||
border-bottom-style: solid; |
|||
border-left-style: solid; |
|||
border-top-color: rgb(208, 208, 208); |
|||
border-bottom-color: rgb(208, 208, 208); |
|||
border-left-color: rgb(208, 208, 208); |
|||
border-image: initial; |
|||
border-right-style: initial; |
|||
border-right-color: initial; |
|||
} |
|||
.box { |
|||
width: 100%; |
|||
white-space: nowrap; |
|||
overflow: hidden; |
|||
overflow: auto; |
|||
} |
|||
.queryBuilderFlex { |
|||
display: flex; |
|||
flex-wrap: nowrap; |
|||
} |
Loading…
Reference in new issue