Browse Source

Merge pull request #715 from colinin/dynamic-query

add dynamic query ui
pull/731/head
yx lin 3 years ago
committed by GitHub
parent
commit
87d148a97f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      apps/vue/src/api/task-management/backgroundJobInfo.ts
  2. 3
      apps/vue/src/components/Form/src/componentMap.ts
  3. 3
      apps/vue/src/components/Form/src/types/index.ts
  4. 56
      apps/vue/src/components/Table/src/BasicTable.vue
  5. 282
      apps/vue/src/components/Table/src/components/AdvancedSearch.vue
  6. 20
      apps/vue/src/components/Table/src/hooks/useDataSource.ts
  7. 35
      apps/vue/src/components/Table/src/hooks/useTableForm.ts
  8. 82
      apps/vue/src/components/Table/src/types/advancedSearch.ts
  9. 6
      apps/vue/src/components/Table/src/types/table.ts
  10. 24
      apps/vue/src/locales/lang/en/component.ts
  11. 1
      apps/vue/src/locales/lang/en/table.ts
  12. 26
      apps/vue/src/locales/lang/zh-CN/component.ts
  13. 1
      apps/vue/src/locales/lang/zh-CN/table.ts
  14. 7
      apps/vue/src/views/task-management/background-jobs/components/JobTable.vue
  15. 1
      aspnet-core/modules/dynamic-queryable/LINGYUN.Abp.Dynamic.Queryable.Application.Contracts/LINGYUN/Abp/Dynamic/Queryable/Dto/DynamicParamterDto.cs
  16. 48
      aspnet-core/modules/dynamic-queryable/LINGYUN.Abp.Dynamic.Queryable.Application/LINGYUN/Abp/Dynamic/Queryable/DynamicQueryableAppService.cs
  17. 9
      aspnet-core/modules/dynamic-queryable/LINGYUN.Linq.Dynamic.Queryable/System/Reflection/NullableTypeExtensions.cs

16
apps/vue/src/api/task-management/backgroundJobInfo.ts

@ -8,6 +8,7 @@ import {
} from './model/backgroundJobInfoModel';
import { format } from '/@/utils/strings';
import { ListResultDto, PagedResultDto } from '../model/baseModel';
import { DefineParamter, DynamicQueryable } from '/@/components/Table/src/types/advancedSearch';
enum Api {
GetById = '/api/task-management/background-jobs/{id}',
@ -27,6 +28,8 @@ enum Api {
BulkStop = '/api/task-management/background-jobs/bulk-stop',
BulkDelete = '/api/task-management/background-jobs/bulk-delete',
GetDefinitions = '/api/task-management/background-jobs/definitions',
GetAvailableFields = '/api/task-management/background-jobs/available-fields',
AdvancedSearch = '/api/task-management/background-jobs/search',
}
export const getById = (id: string) => {
@ -42,6 +45,19 @@ export const getList = (input: BackgroundJobInfoGetListInput) => {
});
};
export const getAvailableFields = () => {
return defAbpHttp.get<ListResultDto<DefineParamter>>({
url: Api.GetAvailableFields,
});
}
export const advancedSearch = (input: DynamicQueryable) => {
return defAbpHttp.post<PagedResultDto<BackgroundJobInfo>>({
url: Api.AdvancedSearch,
data: input,
});
}
export const getDefinitions = () => {
return defAbpHttp.get<ListResultDto<BackgroundJobDefinition>>({
url: Api.GetDefinitions,

3
apps/vue/src/components/Form/src/componentMap.ts

@ -33,9 +33,12 @@ import { StrengthMeter } from '/@/components/StrengthMeter';
import { IconPicker } from '/@/components/Icon';
import { CountdownInput } from '/@/components/CountDown';
import { Input as BInput } from '/@/components/Input';
import { CodeEditorX } from '/@/components/CodeEditor';
const componentMap = new Map<ComponentType, Component>();
componentMap.set('CodeEditorX', CodeEditorX);
componentMap.set('Input', BInput);
componentMap.set('InputGroup', Input.Group);
componentMap.set('InputPassword', Input.Password);

3
apps/vue/src/components/Form/src/types/index.ts

@ -114,4 +114,5 @@ export type ComponentType =
| 'Slider'
| 'Rate'
| 'Divider'
| 'ApiTransfer';
| 'ApiTransfer'
| 'CodeEditorX';

56
apps/vue/src/components/Table/src/BasicTable.vue

@ -8,11 +8,22 @@
:tableAction="tableAction"
@register="registerForm"
@submit="handleSearchInfoChange"
@reset="handleSearchInfoReset"
@advanced-change="redoHeight"
>
<template #[replaceFormSlotKey(item)]="data" v-for="item in getFormSlotKeys">
<slot :name="item" v-bind="data || {}"></slot>
</template>
<template #advanceBefore>
<Button
v-if="getAdvancedSearchProps?.useAdvancedSearch"
type="link"
size="small"
@click="handleAdvanceSearch"
>
{{ t('component.table.advancedSearch.title') }}
</Button>
</template>
</BasicForm>
<Table
@ -36,6 +47,12 @@
<!-- <HeaderCell :column="column" />-->
<!-- </template>-->
</Table>
<AdvancedSearch
ref="advancedSearchRef"
@register="registerAdSearchModal"
v-bind="getAdvancedSearchProps"
@search="handleAdvanceSearchChange"
/>
</div>
</template>
<script lang="ts">
@ -46,11 +63,13 @@
ColumnChangeParam,
} from './types/table';
import { defineComponent, ref, reactive, computed, unref, toRaw, inject, watchEffect } from 'vue';
import { Table } from 'ant-design-vue';
import { defineComponent, ref, reactive, computed, unref, toRaw, inject, watchEffect, nextTick } from 'vue';
import { Button, Table } from 'ant-design-vue';
import { BasicForm, useForm } from '/@/components/Form/index';
import { useModal } from '/@/components/Modal/index';
import { PageWrapperFixedHeightKey } from '/@/components/Page';
import HeaderCell from './components/HeaderCell.vue';
import AdvancedSearch from './components/AdvancedSearch.vue';
import { InnerHandlers } from './types/table';
import { usePagination } from './hooks/usePagination';
@ -68,6 +87,7 @@
import { useTableFooter } from './hooks/useTableFooter';
import { useTableForm } from './hooks/useTableForm';
import { useDesign } from '/@/hooks/web/useDesign';
import { useI18n } from '/@/hooks/web/useI18n';
import { omit } from 'lodash-es';
import { basicProps } from './props';
@ -78,7 +98,9 @@
components: {
Table,
BasicForm,
Button,
HeaderCell,
AdvancedSearch,
},
props: basicProps,
emits: [
@ -100,15 +122,18 @@
'columns-change',
],
setup(props, { attrs, emit, slots, expose }) {
const { t } = useI18n();
const tableElRef = ref(null);
const tableData = ref<Recordable[]>([]);
const wrapRef = ref(null);
const formRef = ref(null);
const advancedSearchRef = ref<any>(null);
const innerPropsRef = ref<Partial<BasicTableProps>>();
const { prefixCls } = useDesign('basic-table');
const [registerForm, formActions] = useForm();
const [registerAdSearchModal, { openModal: openAdSearchModal }] = useModal();
const getProps = computed(() => {
return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
@ -232,8 +257,15 @@
getDataSourceRef,
);
const { getFormProps, replaceFormSlotKey, getFormSlotKeys, handleSearchInfoChange } =
useTableForm(getProps, slots, fetch, getLoading);
const {
getFormProps,
getAdvancedSearchProps,
replaceFormSlotKey,
getFormSlotKeys,
handleSearchInfoChange,
handleAdvanceSearchChange
} =
useTableForm(getProps, slots, fetch, getLoading, formActions.setFieldsValue);
const getBindValues = computed(() => {
const dataSource = unref(getDataSourceRef);
@ -333,13 +365,28 @@
}
}
function handleSearchInfoReset() {
const advancedSearch = unref(advancedSearchRef);
advancedSearch?.resetFields();
}
function handleAdvanceSearch() {
nextTick(() => openAdSearchModal(true));
}
return {
t,
formRef,
tableElRef,
advancedSearchRef,
getBindValues,
getLoading,
registerForm,
handleSearchInfoChange,
registerAdSearchModal,
handleAdvanceSearchChange,
handleSearchInfoReset,
handleAdvanceSearch,
getEmptyDataIsShowTable,
handleTableChange,
getRowClassName,
@ -347,6 +394,7 @@
tableAction,
redoHeight,
getFormProps: getFormProps as any,
getAdvancedSearchProps,
replaceFormSlotKey,
getFormSlotKeys,
getWrapperClass,

282
apps/vue/src/components/Table/src/components/AdvancedSearch.vue

@ -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>

20
apps/vue/src/components/Table/src/hooks/useDataSource.ts

@ -245,13 +245,24 @@ export function useDataSource(
searchInfo,
defSort,
fetchSetting,
useSearchForm,
beforeFetch,
beforeResponse,
afterFetch,
useSearchForm,
pagination,
advancedSearchConfig,
} = unref(propsRef);
if (!api || !isFunction(api)) return;
let fetchApi = api;
// 高级查询条件支持
if (advancedSearchConfig?.useAdvancedSearch) {
const searchInput = getFieldsValue();
console.log(searchInput);
if (Reflect.has(searchInput, 'queryable') &&
Array.isArray(searchInput.queryable?.paramters) &&
searchInput.queryable.paramters.length > 0)
fetchApi = advancedSearchConfig?.fetchApi;
}
if (!fetchApi || !isFunction(fetchApi)) return;
try {
setLoading(true);
const { pageField, sizeField, listField, totalField } = Object.assign(
@ -283,11 +294,12 @@ export function useDataSource(
opt?.sortInfo ?? {},
opt?.filterInfo ?? {},
);
console.log(params);
if (beforeFetch && isFunction(beforeFetch)) {
params = (await beforeFetch(params)) || params;
}
let res = await api(params);
let res = await fetchApi(params);
// 增加用户自定义的返回数据处理函数
if (beforeResponse && isFunction(beforeResponse)) {
@ -340,7 +352,7 @@ export function useDataSource(
}
}
function setTableData<T = Recordable>(values: T[]) {
function setTableData(values: any[]) {
dataSourceRef.value = values;
}

35
apps/vue/src/components/Table/src/hooks/useTableForm.ts

@ -1,7 +1,9 @@
import type { ComputedRef, Slots } from 'vue';
import type { BasicTableProps, FetchParams } from '../types/table';
import { unref, computed } from 'vue';
import type { FormProps } from '/@/components/Form';
import type { DynamicQueryable } from '../types/advancedSearch';
import { unref, computed } from 'vue';
import { isFunction } from '/@/utils/is';
export function useTableForm(
@ -9,9 +11,23 @@ export function useTableForm(
slots: Slots,
fetch: (opt?: FetchParams | undefined) => Promise<void>,
getLoading: ComputedRef<boolean | undefined>,
setFieldsValue: <T>(values: T) => Promise<void>,
) {
const getFormProps = computed((): Partial<FormProps> => {
const { formConfig } = unref(propsRef);
const { formConfig, advancedSearchConfig } = unref(propsRef);
if (advancedSearchConfig?.useAdvancedSearch && formConfig?.schemas) {
const advIndex = formConfig.schemas.findIndex(s => s.field === 'queryable');
if (advIndex < 0) {
// 加入高级条件的隐藏字段
formConfig.schemas.push({
label: 'queryable',
field: 'queryable',
component: 'CodeEditorX',
show: false,
colProps: { span: 24 },
});
}
}
const { submitButtonOptions } = formConfig || {};
return {
showAdvancedButton: true,
@ -28,6 +44,12 @@ export function useTableForm(
.filter((item) => !!item) as string[];
});
const getAdvancedSearchProps = computed(() => {
const { advancedSearchConfig } = unref(propsRef);
return advancedSearchConfig;
});
function replaceFormSlotKey(key: string) {
if (!key) return '';
return key?.replace?.(/form\-/, '') ?? '';
@ -41,10 +63,19 @@ export function useTableForm(
fetch({ searchInfo: info, page: 1 });
}
function handleAdvanceSearchChange(queryable: DynamicQueryable) {
setFieldsValue({ queryable: queryable });
setTimeout(() => {
fetch({ page: 1 });
}, 300);
}
return {
getFormProps,
replaceFormSlotKey,
getFormSlotKeys,
getAdvancedSearchProps,
handleSearchInfoChange,
handleAdvanceSearchChange,
};
}

82
apps/vue/src/components/Table/src/types/advancedSearch.ts

@ -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[];
}

6
apps/vue/src/components/Table/src/types/table.ts

@ -1,8 +1,8 @@
import type { VNodeChild } from 'vue';
import type { PaginationProps } from './pagination';
import type { FormProps } from '/@/components/Form';
import type { TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface';
import type { ColumnProps } from 'ant-design-vue/lib/table';
import type { ColumnProps, TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface';
import type { AdvanceSearchProps } from './advancedSearch';
import { ComponentType } from './componentType';
import { VueNode } from '/@/utils/propTypes';
@ -185,6 +185,8 @@ export interface BasicTableProps<T = any> {
useSearchForm?: boolean;
// 表单配置
formConfig?: Partial<FormProps>;
// 高级查询配置
advancedSearchConfig?: Partial<AdvanceSearchProps>;
// 列配置
columns: BasicColumn[];
// 是否显示序号列

24
apps/vue/src/locales/lang/en/component.ts

@ -68,6 +68,30 @@ export default {
settingFullScreen: 'Full Screen',
index: 'Index',
total: 'total of {total}',
advancedSearch: {
title: 'Advanced Search',
conditions: 'Condition',
addCondition: 'Add Condition',
delCondition: 'Del Condition',
field: 'Field',
logic: 'Logic',
and: 'And',
or: 'Or',
comparison: 'Comparison',
value: 'Value',
equal: 'Equal',
notEqual: 'Not Equal',
lessThan: 'Less Than',
lessThanOrEqual: 'less Than Or Equal',
greaterThan: 'Greater Than',
greaterThanOrEqual: 'Greater Than Or Equal',
startsWith: 'Starts With',
notStartsWith: 'Not Starts With',
endsWith: 'Ends With',
notEndsWith: 'Not Ends With',
contains: 'Contains',
notContains: 'Not Contains',
}
},
time: {
before: ' ago',

1
apps/vue/src/locales/lang/en/table.ts

@ -1,3 +1,4 @@
export default {
action: 'Actions',
sureToDelete: 'Sure to delete?',
};

26
apps/vue/src/locales/lang/zh-CN/component.ts

@ -68,10 +68,32 @@ export default {
settingFixedLeft: '固定到左侧',
settingFixedRight: '固定到右侧',
settingFullScreen: '全屏',
index: '序号',
total: '共 {total} 条数据',
advancedSearch: {
title: '高级查询',
conditions: '查询条件',
addCondition: '增加条件',
delCondition: '删除条件',
field: '字段',
logic: '连接条件',
and: '且',
or: '或',
comparison: '运算符',
value: '比较值',
equal: '等于',
notEqual: '不等于',
lessThan: '小于',
lessThanOrEqual: '小于等于',
greaterThan: '大于',
greaterThanOrEqual: '大于等于',
startsWith: '左包含',
notStartsWith: '左不包含',
endsWith: '右包含',
notEndsWith: '右不包含',
contains: '包含',
notContains: '不包含',
}
},
time: {
before: '前',

1
apps/vue/src/locales/lang/zh-CN/table.ts

@ -1,3 +1,4 @@
export default {
action: '操作方法',
sureToDelete: '你确定要删除吗?',
};

7
apps/vue/src/views/task-management/background-jobs/components/JobTable.vue

@ -112,6 +112,8 @@
deleteById,
bulkStop,
bulkStart,
getAvailableFields,
advancedSearch,
} from '/@/api/task-management/backgroundJobInfo';
import { JobStatus } from '/@/api/task-management/model/backgroundJobInfoModel';
import { getDataColumns } from '../datas/TableData';
@ -145,6 +147,11 @@
immediate: true,
clickToRowSelect: false,
formConfig: getSearchFormSchemas(),
advancedSearchConfig: {
useAdvancedSearch: true,
defineFieldApi: getAvailableFields,
fetchApi: advancedSearch,
},
rowSelection: {
type: 'checkbox',
onChange: handleSelectChange,

1
aspnet-core/modules/dynamic-queryable/LINGYUN.Abp.Dynamic.Queryable.Application.Contracts/LINGYUN/Abp/Dynamic/Queryable/Dto/DynamicParamterDto.cs

@ -5,4 +5,5 @@ public class DynamicParamterDto
public string Name { get; set; }
public string Description { get; set; }
public string Type { get; set; }
public string JavaScriptType { get; set; }
}

48
aspnet-core/modules/dynamic-queryable/LINGYUN.Abp.Dynamic.Queryable.Application/LINGYUN/Abp/Dynamic/Queryable/DynamicQueryableAppService.cs

@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
@ -36,7 +37,8 @@ public abstract class DynamicQueryableAppService<TEntity, TEntityDto> : Applicat
{
Name = propertyInfo.Name,
Type = propertyInfo.PropertyType.FullName,
Description = localizedProp.Value ?? propertyInfo.Name
Description = localizedProp.Value ?? propertyInfo.Name,
JavaScriptType = ConvertToJavaScriptType(propertyInfo.PropertyType)
});
}
@ -71,4 +73,48 @@ public abstract class DynamicQueryableAppService<TEntity, TEntityDto> : Applicat
{
return ObjectMapper.Map<List<TEntity>, List<TEntityDto>>(entities);
}
protected virtual string ConvertToJavaScriptType(Type propertyType)
{
if (propertyType.IsNullableType())
{
propertyType = propertyType.GetGenericArguments().FirstOrDefault();
}
var typeCode = Type.GetTypeCode(propertyType);
switch (typeCode)
{
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
case TypeCode.Single:
case TypeCode.Byte:
case TypeCode.Double:
case TypeCode.SByte:
case TypeCode.Decimal:
return "number";
case TypeCode.Boolean:
return "boolean";
case TypeCode.Char:
case TypeCode.String:
return "string";
case TypeCode.DateTime:
return "Date";
case TypeCode.Object:
if (propertyType.IsArray)
{
return "array";
}
else
{
return "object";
}
default:
case TypeCode.Empty:
case TypeCode.DBNull:
return "object";
}
}
}

9
aspnet-core/modules/dynamic-queryable/LINGYUN.Linq.Dynamic.Queryable/System/Reflection/NullableTypeExtensions.cs

@ -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…
Cancel
Save