committed by
GitHub
17 changed files with 604 additions and 16 deletions
@ -0,0 +1,282 @@ |
|||||
|
<template> |
||||
|
<BasicModal |
||||
|
@register="registerModal" |
||||
|
:width="800" |
||||
|
:title="t('component.table.advancedSearch.title')" |
||||
|
:can-fullscreen="false" |
||||
|
@ok="handleSubmit" |
||||
|
> |
||||
|
<Card :title="t('component.table.advancedSearch.conditions')"> |
||||
|
<template #extra> |
||||
|
<Button @click="resetFields" danger>{{ t('component.table.advancedSearch.clearCondition') }}</Button> |
||||
|
<Button @click="handleAddField" type="primary" style="margin-left: 20px;">{{ t('component.table.advancedSearch.addCondition') }}</Button> |
||||
|
</template> |
||||
|
<Table |
||||
|
size="small" |
||||
|
rowKey="field" |
||||
|
:columns="columns" |
||||
|
:data-source="formMdel.paramters" |
||||
|
:pagination="false" |
||||
|
> |
||||
|
<template #bodyCell="{ column, record }"> |
||||
|
<template v-if="column.dataIndex==='field'"> |
||||
|
<Select |
||||
|
style="width: 100%;" |
||||
|
v-model:value="record.field" |
||||
|
:options="getAvailableParams" |
||||
|
:field-names="{ label: 'description', value: 'name' }" |
||||
|
@change="(field) => handleFieldChange(field, record)" |
||||
|
/> |
||||
|
</template> |
||||
|
<template v-else-if="column.dataIndex==='comparison'"> |
||||
|
<Select |
||||
|
style="width: 100%;" |
||||
|
v-model:value="record.comparison" |
||||
|
:options="comparisonOptions" |
||||
|
/> |
||||
|
</template> |
||||
|
<template v-else-if="column.dataIndex==='value'"> |
||||
|
<Input v-if="record.javaScriptType==='string'" v-model:value="record.value" /> |
||||
|
<InputNumber v-else-if="record.javaScriptType==='number'" style="width: 100%;" v-model:value="record.value" /> |
||||
|
<Switch v-else-if="record.javaScriptType==='boolean'" v-model:checked="record.value" /> |
||||
|
<DatePicker v-else-if="record.javaScriptType==='Date'" style="width: 100%;" v-model:value="record.value" /> |
||||
|
<CodeEditorX |
||||
|
v-else-if="['array', 'object'].includes(record.javaScriptType)" |
||||
|
style="width: 100%; height: 300px" |
||||
|
:mode="MODE.JSON" |
||||
|
v-model="record.value" |
||||
|
/> |
||||
|
</template> |
||||
|
<template v-else-if="column.dataIndex==='logic'"> |
||||
|
<Select |
||||
|
style="width: 100%;" |
||||
|
v-model:value="record.logic" |
||||
|
:options="logicOptions" |
||||
|
/> |
||||
|
</template> |
||||
|
<template v-else-if="column.dataIndex==='actions'"> |
||||
|
<Popconfirm |
||||
|
v-if="formMdel.paramters.length" |
||||
|
:title="t('table.sureToDelete')" |
||||
|
@confirm="handleDelField(record)" |
||||
|
> |
||||
|
<DeleteTwoTone two-tone-color="#FF4500" /> |
||||
|
</Popconfirm> |
||||
|
</template> |
||||
|
</template> |
||||
|
</Table> |
||||
|
</Card> |
||||
|
</BasicModal> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
import { computed, ref, reactive, unref, onMounted } from 'vue'; |
||||
|
import { DeleteTwoTone } from '@ant-design/icons-vue'; |
||||
|
import { |
||||
|
Button, |
||||
|
Card, |
||||
|
DatePicker, |
||||
|
Input, |
||||
|
InputNumber, |
||||
|
Popconfirm, |
||||
|
Select, |
||||
|
Switch, |
||||
|
Table |
||||
|
} from 'ant-design-vue'; |
||||
|
import { CodeEditorX, MODE } from '/@/components/CodeEditor'; |
||||
|
import { BasicModal, useModalInner } from '/@/components/Modal'; |
||||
|
import { useI18n } from '/@/hooks/web/useI18n'; |
||||
|
import { DefineParamter, DynamicLogic, DynamicComparison, DynamicQueryable } from '../types/advancedSearch'; |
||||
|
import { isFunction } from '/@/utils/is'; |
||||
|
import { get } from 'lodash-es'; |
||||
|
|
||||
|
const emits = defineEmits(['register', 'search']); |
||||
|
const props = defineProps({ |
||||
|
useAdvancedSearch: { |
||||
|
type: Boolean, |
||||
|
default: false, |
||||
|
}, |
||||
|
defineFieldApi: { |
||||
|
type: Function as PropType<() => Promise<any>> |
||||
|
}, |
||||
|
listField: { |
||||
|
type: String, |
||||
|
} |
||||
|
}); |
||||
|
const { t } = useI18n(); |
||||
|
const loadingRef = ref(false); |
||||
|
const formMdel = reactive<DynamicQueryable>({ |
||||
|
paramters: [], |
||||
|
}); |
||||
|
|
||||
|
const columns = [ |
||||
|
{ |
||||
|
dataIndex: 'field', |
||||
|
key: 'field', |
||||
|
title: t('component.table.advancedSearch.field'), |
||||
|
}, |
||||
|
{ |
||||
|
dataIndex: 'comparison', |
||||
|
key: 'comparison', |
||||
|
title: t('component.table.advancedSearch.comparison'), |
||||
|
}, |
||||
|
{ |
||||
|
dataIndex: 'value', |
||||
|
key: 'value', |
||||
|
title: t('component.table.advancedSearch.value'), |
||||
|
}, |
||||
|
{ |
||||
|
dataIndex: 'logic', |
||||
|
key: 'logic', |
||||
|
title: t('component.table.advancedSearch.logic'), |
||||
|
}, |
||||
|
{ |
||||
|
title: t('table.action'), |
||||
|
dataIndex: 'actions', |
||||
|
align: 'center', |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
const defineParamsRef = ref<DefineParamter[]>([]); |
||||
|
|
||||
|
const logicOptions = reactive([ |
||||
|
{ |
||||
|
label: t('component.table.advancedSearch.and'), |
||||
|
value: DynamicLogic.And, |
||||
|
}, |
||||
|
{ |
||||
|
label: t('component.table.advancedSearch.or'), |
||||
|
value: DynamicLogic.Or, |
||||
|
}, |
||||
|
]); |
||||
|
|
||||
|
const comparisonOptions = reactive([ |
||||
|
{ |
||||
|
label: t('component.table.advancedSearch.equal'), |
||||
|
value: DynamicComparison.Equal, |
||||
|
}, |
||||
|
{ |
||||
|
label: t('component.table.advancedSearch.notEqual'), |
||||
|
value: DynamicComparison.NotEqual, |
||||
|
}, |
||||
|
{ |
||||
|
label: t('component.table.advancedSearch.lessThan'), |
||||
|
value: DynamicComparison.LessThan, |
||||
|
}, |
||||
|
{ |
||||
|
label: t('component.table.advancedSearch.lessThanOrEqual'), |
||||
|
value: DynamicComparison.LessThanOrEqual, |
||||
|
}, |
||||
|
{ |
||||
|
label: t('component.table.advancedSearch.greaterThan'), |
||||
|
value: DynamicComparison.GreaterThan, |
||||
|
}, |
||||
|
{ |
||||
|
label: t('component.table.advancedSearch.greaterThanOrEqual'), |
||||
|
value: DynamicComparison.GreaterThanOrEqual, |
||||
|
}, |
||||
|
{ |
||||
|
label: t('component.table.advancedSearch.startsWith'), |
||||
|
value: DynamicComparison.StartsWith, |
||||
|
}, |
||||
|
{ |
||||
|
label: t('component.table.advancedSearch.notStartsWith'), |
||||
|
value: DynamicComparison.NotStartsWith, |
||||
|
}, |
||||
|
{ |
||||
|
label: t('component.table.advancedSearch.endsWith'), |
||||
|
value: DynamicComparison.EndsWith, |
||||
|
}, |
||||
|
{ |
||||
|
label: t('component.table.advancedSearch.notEndsWith'), |
||||
|
value: DynamicComparison.NotEndsWith, |
||||
|
}, |
||||
|
{ |
||||
|
label: t('component.table.advancedSearch.contains'), |
||||
|
value: DynamicComparison.Contains, |
||||
|
}, |
||||
|
{ |
||||
|
label: t('component.table.advancedSearch.notContains'), |
||||
|
value: DynamicComparison.NotContains, |
||||
|
}, |
||||
|
]); |
||||
|
|
||||
|
const getAvailableParams = computed(() => { |
||||
|
const defineParams = unref(defineParamsRef); |
||||
|
if (!defineParams.length) return[]; |
||||
|
return defineParams.filter(dp => !formMdel.paramters.some(fp => fp.field === dp.name)); |
||||
|
}); |
||||
|
|
||||
|
onMounted(fetch); |
||||
|
|
||||
|
const [registerModal, { closeModal }] = useModalInner(); |
||||
|
|
||||
|
function fetch() { |
||||
|
const { useAdvancedSearch, defineFieldApi, listField } = props; |
||||
|
if (!useAdvancedSearch) return; |
||||
|
if (!defineFieldApi || !isFunction(defineFieldApi)) return; |
||||
|
setLoading(true); |
||||
|
defineFieldApi().then((res) => { |
||||
|
const isArrayResult = Array.isArray(res); |
||||
|
const resultItems: DefineParamter[] = isArrayResult ? res : get(res, listField || 'items'); |
||||
|
defineParamsRef.value = resultItems; |
||||
|
}).finally(() => { |
||||
|
setLoading(false); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function handleAddField() { |
||||
|
const availableParams = unref(getAvailableParams); |
||||
|
if (availableParams.length > 0) { |
||||
|
const bindParamter = availableParams[availableParams.length - 1]; |
||||
|
formMdel.paramters.push({ |
||||
|
field: bindParamter.name, |
||||
|
logic: DynamicLogic.And, |
||||
|
comparison: DynamicComparison.Equal, |
||||
|
javaScriptType: bindParamter.javaScriptType, |
||||
|
value: undefined, |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function handleDelField(paramter) { |
||||
|
const index = formMdel.paramters.findIndex(p => p.field === paramter.field); |
||||
|
formMdel.paramters.splice(index, 1); |
||||
|
} |
||||
|
|
||||
|
function handleFieldChange(field, record) { |
||||
|
const defineParams = unref(defineParamsRef); |
||||
|
const defineParam = defineParams.find(dp => dp.name === field); |
||||
|
if (defineParam) { |
||||
|
record.field = defineParam.name; |
||||
|
record.javaScriptType = defineParam.javaScriptType; |
||||
|
record.value = undefined; |
||||
|
if (defineParam.javaScriptType === 'boolean') { |
||||
|
record.value = false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function handleSubmit() { |
||||
|
const searchInput = { |
||||
|
// 过滤未定义值 |
||||
|
paramters: formMdel.paramters.filter(p => p.value !== undefined) |
||||
|
}; |
||||
|
emits('search', searchInput); |
||||
|
closeModal(); |
||||
|
} |
||||
|
|
||||
|
function resetFields() { |
||||
|
formMdel.paramters = []; |
||||
|
} |
||||
|
|
||||
|
function setLoading(loading: boolean) { |
||||
|
loadingRef.value = loading; |
||||
|
} |
||||
|
|
||||
|
defineExpose({ resetFields }); |
||||
|
</script> |
||||
|
|
||||
|
<style lang="less" scoped> |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,82 @@ |
|||||
|
/** 高级查询条件属性 */ |
||||
|
export interface AdvanceSearchProps { |
||||
|
/** 使用高级查询 */ |
||||
|
useAdvancedSearch?: boolean; |
||||
|
/** 字段列表api */ |
||||
|
defineFieldApi?: () => Promise<any>; |
||||
|
/** |
||||
|
* 字段列表api返回结果字段 |
||||
|
* @remarks 从服务器返回字段列表在数据结构中的字段名,默认: items |
||||
|
*/ |
||||
|
listField?: string; |
||||
|
/** 高级查询api */ |
||||
|
fetchApi?: (...arg: any) => Promise<any>, |
||||
|
} |
||||
|
|
||||
|
/** 自定义字段 */ |
||||
|
export interface DefineParamter { |
||||
|
/** 字段名称 */ |
||||
|
name: string; |
||||
|
/** 字段描述 */ |
||||
|
description?: string; |
||||
|
/** 数据类型(后端) */ |
||||
|
type: string; |
||||
|
/** 数据类型(js) */ |
||||
|
javaScriptType: string; |
||||
|
} |
||||
|
|
||||
|
/** 连接条件 */ |
||||
|
export enum DynamicLogic { |
||||
|
/** 且 */ |
||||
|
And = 0, |
||||
|
/** 或 */ |
||||
|
Or = 1 |
||||
|
} |
||||
|
|
||||
|
/** 运算条件 */ |
||||
|
export enum DynamicComparison { |
||||
|
/** 等于 */ |
||||
|
Equal = 0, |
||||
|
/** 不等于 */ |
||||
|
NotEqual = 1, |
||||
|
/** 小于 */ |
||||
|
LessThan = 2, |
||||
|
/** 小于等于 */ |
||||
|
LessThanOrEqual = 3, |
||||
|
/** 大于 */ |
||||
|
GreaterThan = 4, |
||||
|
/** 大于等于 */ |
||||
|
GreaterThanOrEqual = 5, |
||||
|
/** 左包含 */ |
||||
|
StartsWith = 6, |
||||
|
/** 左不包含 */ |
||||
|
NotStartsWith = 7, |
||||
|
/** 右包含 */ |
||||
|
EndsWith = 8, |
||||
|
/** 右不包含 */ |
||||
|
NotEndsWith = 9, |
||||
|
/** 包含 */ |
||||
|
Contains = 10, |
||||
|
/** 不包含 */ |
||||
|
NotContains = 11 |
||||
|
} |
||||
|
|
||||
|
/** 动态查询字段 */ |
||||
|
export interface DynamicParamter { |
||||
|
/** 字段名称 */ |
||||
|
field: string; |
||||
|
/** 连接条件 */ |
||||
|
logic: DynamicLogic; |
||||
|
/** 运算条件 */ |
||||
|
comparison: DynamicComparison; |
||||
|
/** 比较值 */ |
||||
|
value: any; |
||||
|
/** 数据类型(js), 仅作为前端输入控件切换 */ |
||||
|
javaScriptType?: string; |
||||
|
} |
||||
|
|
||||
|
/** 动态查询条件 */ |
||||
|
export interface DynamicQueryable { |
||||
|
/** 参数列表 */ |
||||
|
paramters: DynamicParamter[]; |
||||
|
} |
||||
@ -1,3 +1,4 @@ |
|||||
export default { |
export default { |
||||
action: 'Actions', |
action: 'Actions', |
||||
|
sureToDelete: 'Sure to delete?', |
||||
}; |
}; |
||||
|
|||||
@ -1,3 +1,4 @@ |
|||||
export default { |
export default { |
||||
action: '操作方法', |
action: '操作方法', |
||||
|
sureToDelete: '你确定要删除吗?', |
||||
}; |
}; |
||||
|
|||||
@ -0,0 +1,9 @@ |
|||||
|
namespace System.Reflection; |
||||
|
|
||||
|
public static class NullableTypeExtensions |
||||
|
{ |
||||
|
public static bool IsNullableType(this Type theType) |
||||
|
{ |
||||
|
return (theType.IsGenericType && theType.GetGenericTypeDefinition().Equals(typeof(Nullable<>))); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue