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 { |
|||
action: 'Actions', |
|||
sureToDelete: 'Sure to delete?', |
|||
}; |
|||
|
|||
@ -1,3 +1,4 @@ |
|||
export default { |
|||
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