feat(filter-blocks): support filter-blocks (#1505)
* refactor: audit log (#1516) * refactor: audit-log * refactor: audit-log fix view action * refactor: audit-log view fix * refactor(audit-log): collection field fix * refactor: audit-log view field fix * refactor(audit-log): support fixedBlock * refactor(audit-log): i18n fix * feat(filter-blocks): support form-block * feat(filter-blocks): add FilterProvider * feat: filter data blocks * perf: use useMemo * refactor: rename * feat: collect filter params * refactor: rename * refactor: remove useless code * feat: add 'Connect data blocks' option * feat: support 'x-filter-targets' to save data-blocks * refactor: extract Form.FilterFormDesigner * feat: extract FormItem.FilterFormDesigner * refactor: extract common editing options * feat: support to set operator * feat: use operator created by user * fix: improve loading * fix: merge prev params * feat: support reset * refactor: rename * chore: left a TODO * feat: add Table.FilterDesigner * feat: support filter-table * refactor: reduce code * feat: handle click event of table row * feat: support to connect association collection * feat: support Collapse * feat: show empty * refactor: optimize readability * fix: keep state as latest * fix: highlight row on selected * feat: highlight data block on hover * fix: avoid misuse * chore: reduce code * fix(Table): support to cancel select * fix(Collapse): merge multiple filter params * chore: make to pass CI * feat: merge all filter params * refactor: remove useless code * fix: undefined * fix(Form): fix bug with association fields * chore: fix typo * fix: use title * chore: avoid infinite loops * test: cancel comments * fix: make ci normal * fix: filter down non-association fields * fix: fix page crash * fix: use correct operator * fix: avoid infinte loops * style: optimize style on hover * fix: avoid crash * chore: optimize empty description * fix: avoid targetKey empty * refactor: use getTargetKey instead * fix: filter out unfilterable fields * refactor: avoid to invoke hook multiple times * refactor: reduce the judgment conditions of component * fix: group fields in the right way * fix: fix error of type * fix: fix error on no FilterBlockProvider * fix(Table): fix fexed-block bug * chore: reduce gap * fix(Form): use AssociationSelect by default * fix: remove g2plot blocks * fix(Form): remove 'Display association fields' * fix(Table): use radio * fix(Table): no need Actions * fix: fix template problem * fix(Table): keep only 'filter' and 'refresh' * fix: use collection name as identifier for data blocks * fix: make sure all fields are editable * fix(Form): remove custom actions * fix(Details): display empty component on no data * feat(Form): support association fields * refactor: rename * feat(Form): support for deep-level association fields * Revert "fix(Table): keep only 'filter' and 'refresh'" This reverts commit 61a1d101a7d15223cfd3523adb33567fff545568. * Revert "fix(Table): no need Actions" This reverts commit 8314629e92fb3b5b7ec1c97904a42c76c43e4d4a. * Revert "fix(Table): use radio" This reverts commit c6f009740e1835f9762653a721ff64f52d0994cf. * feat(Table): highlight row on selected * feat: support to cacel highlight * fix: type error * feat: remove Table from filter list * fix(Table): highlight rows problem * refactor: remove usless code * refactor(Collapse): detach from Table * fix(Table): highlighting row problem * fix: translate problem * fix(Collapse): fix error of useProps * fix(Table): avoid undefined * Update Details.tsx * Update DetailsBlockProvider.tsx * refactor: rename target.name to target.uid * fix: add translate * style: add padding --------- Co-authored-by: anuoua <anuoua@gmail.com> Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
ef47cac5e6
commit
070c9c21ec
@ -42,7 +42,7 @@ export class APIClient extends APIClientSDK {
|
|||||||
if (redirectTo) {
|
if (redirectTo) {
|
||||||
return (window.location.href = redirectTo);
|
return (window.location.href = redirectTo);
|
||||||
}
|
}
|
||||||
if (error.response.data.type === 'application/json') {
|
if (error?.response?.data?.type === 'application/json') {
|
||||||
handleErrorMessage(error);
|
handleErrorMessage(error);
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notification.error({
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
WithoutTableFieldResource,
|
WithoutTableFieldResource,
|
||||||
} from '../';
|
} from '../';
|
||||||
import { CollectionProvider, useCollection, useCollectionManager } from '../collection-manager';
|
import { CollectionProvider, useCollection, useCollectionManager } from '../collection-manager';
|
||||||
|
import { FilterBlockRecord } from '../filter-provider/FilterProvider';
|
||||||
import { useRecordIndex } from '../record-provider';
|
import { useRecordIndex } from '../record-provider';
|
||||||
import { SharedFilterProvider } from './SharedFilterProvider';
|
import { SharedFilterProvider } from './SharedFilterProvider';
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ interface UseResourceProps {
|
|||||||
block?: any;
|
block?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useAssociation = (props) => {
|
export const useAssociation = (props) => {
|
||||||
const { association } = props;
|
const { association } = props;
|
||||||
const { getCollectionField } = useCollectionManager();
|
const { getCollectionField } = useCollectionManager();
|
||||||
if (typeof association === 'string') {
|
if (typeof association === 'string') {
|
||||||
@ -191,6 +192,7 @@ export const RenderChildrenWithAssociationFilter: React.FC<any> = (props) => {
|
|||||||
width: 200px;
|
width: 200px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
`}
|
`}
|
||||||
|
style={props.associationFilterStyle}
|
||||||
>
|
>
|
||||||
<RecursionField
|
<RecursionField
|
||||||
schema={fieldSchema}
|
schema={fieldSchema}
|
||||||
@ -233,7 +235,9 @@ export const BlockProvider = (props) => {
|
|||||||
<BlockAssociationContext.Provider value={association}>
|
<BlockAssociationContext.Provider value={association}>
|
||||||
<BlockResourceContext.Provider value={resource}>
|
<BlockResourceContext.Provider value={resource}>
|
||||||
<BlockRequestProvider {...props}>
|
<BlockRequestProvider {...props}>
|
||||||
<SharedFilterProvider {...props} />
|
<SharedFilterProvider {...props}>
|
||||||
|
<FilterBlockRecord {...props}>{props.children}</FilterBlockRecord>
|
||||||
|
</SharedFilterProvider>
|
||||||
</BlockRequestProvider>
|
</BlockRequestProvider>
|
||||||
</BlockResourceContext.Provider>
|
</BlockResourceContext.Provider>
|
||||||
</BlockAssociationContext.Provider>
|
</BlockAssociationContext.Provider>
|
||||||
|
@ -53,7 +53,9 @@ export const useDetailsBlockProps = () => {
|
|||||||
const ctx = useDetailsBlockContext();
|
const ctx = useDetailsBlockContext();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ctx.service.loading) {
|
if (!ctx.service.loading) {
|
||||||
ctx.form.setValues(ctx.service?.data?.data?.[0] || {});
|
ctx.form.reset().then(() => {
|
||||||
|
ctx.form.setValues(ctx.service?.data?.data?.[0] || {});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [ctx.service.loading]);
|
}, [ctx.service.loading]);
|
||||||
return {
|
return {
|
||||||
|
@ -12,11 +12,11 @@ export const FormFieldContext = createContext<any>({});
|
|||||||
const InternalFormFieldProvider = (props) => {
|
const InternalFormFieldProvider = (props) => {
|
||||||
const { action, readPretty, fieldName } = props;
|
const { action, readPretty, fieldName } = props;
|
||||||
const formBlockCtx = useFormBlockContext();
|
const formBlockCtx = useFormBlockContext();
|
||||||
|
|
||||||
if (!formBlockCtx?.updateAssociationValues?.includes(fieldName)) {
|
if (!formBlockCtx?.updateAssociationValues?.includes(fieldName)) {
|
||||||
formBlockCtx?.updateAssociationValues?.push(fieldName);
|
formBlockCtx?.updateAssociationValues?.push(fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
const field = useField();
|
const field = useField();
|
||||||
|
|
||||||
const form = useMemo(
|
const form = useMemo(
|
||||||
@ -37,8 +37,6 @@ const InternalFormFieldProvider = (props) => {
|
|||||||
return <Spin />;
|
return <Spin />;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('InternalFormFieldProvider', fieldName);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecordProvider record={service?.data?.data}>
|
<RecordProvider record={service?.data?.data}>
|
||||||
<FormFieldContext.Provider
|
<FormFieldContext.Provider
|
||||||
@ -54,14 +52,13 @@ const InternalFormFieldProvider = (props) => {
|
|||||||
{props.children}
|
{props.children}
|
||||||
</FormFieldContext.Provider>
|
</FormFieldContext.Provider>
|
||||||
</RecordProvider>
|
</RecordProvider>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WithoutFormFieldResource = createContext(null);
|
export const WithoutFormFieldResource = createContext(null);
|
||||||
|
|
||||||
export const FormFieldProvider = (props) => {
|
export const FormFieldProvider = (props) => {
|
||||||
console.log('FormFieldProvider', props);
|
|
||||||
return (
|
return (
|
||||||
<WithoutFormFieldResource.Provider value={false}>
|
<WithoutFormFieldResource.Provider value={false}>
|
||||||
<BlockProvider block={'FormField'} {...props}>
|
<BlockProvider block={'FormField'} {...props}>
|
||||||
@ -84,4 +81,4 @@ export const useFormFieldProps = () => {
|
|||||||
form: ctx.form,
|
form: ctx.form,
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,19 +3,27 @@ import { FormContext, Schema, useField, useFieldSchema } from '@formily/react';
|
|||||||
import uniq from 'lodash/uniq';
|
import uniq from 'lodash/uniq';
|
||||||
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
import { useCollectionManager } from '../collection-manager';
|
import { useCollectionManager } from '../collection-manager';
|
||||||
import { SchemaComponentOptions, useFixedSchema } from '../schema-component';
|
import { SchemaComponentOptions, useFixedSchema, removeNullCondition } from '../schema-component';
|
||||||
import { BlockProvider, RenderChildrenWithAssociationFilter, useBlockRequestContext } from './BlockProvider';
|
import { BlockProvider, RenderChildrenWithAssociationFilter, useBlockRequestContext } from './BlockProvider';
|
||||||
|
import { useFilterBlock } from '../filter-provider/FilterProvider';
|
||||||
|
import { findFilterTargets } from './hooks';
|
||||||
|
import { mergeFilter } from './SharedFilterProvider';
|
||||||
|
|
||||||
export const TableBlockContext = createContext<any>({});
|
export const TableBlockContext = createContext<any>({});
|
||||||
|
|
||||||
const InternalTableBlockProvider = (props) => {
|
interface Props {
|
||||||
|
params?: any;
|
||||||
|
showIndex?: boolean;
|
||||||
|
dragSort?: boolean;
|
||||||
|
rowKey?: string;
|
||||||
|
childrenColumnName: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const InternalTableBlockProvider = (props: Props) => {
|
||||||
const { params, showIndex, dragSort, rowKey, childrenColumnName } = props;
|
const { params, showIndex, dragSort, rowKey, childrenColumnName } = props;
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const { resource, service } = useBlockRequestContext();
|
const { resource, service } = useBlockRequestContext();
|
||||||
const [expandFlag, setExpandFlag] = useState(false);
|
const [expandFlag, setExpandFlag] = useState(false);
|
||||||
// if (service.loading) {
|
|
||||||
// return <Spin />;
|
|
||||||
// }
|
|
||||||
useFixedSchema();
|
useFixedSchema();
|
||||||
return (
|
return (
|
||||||
<TableBlockContext.Provider
|
<TableBlockContext.Provider
|
||||||
@ -91,7 +99,7 @@ export const TableBlockProvider = (props) => {
|
|||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { getCollection, getCollectionField } = useCollectionManager();
|
const { getCollection, getCollectionField } = useCollectionManager();
|
||||||
const collection = getCollection(props.collection);
|
const collection = getCollection(props.collection);
|
||||||
const { treeTable } = fieldSchema?.['x-decorator-props']||{};
|
const { treeTable } = fieldSchema?.['x-decorator-props'] || {};
|
||||||
if (props.dragSort) {
|
if (props.dragSort) {
|
||||||
params['sort'] = ['sort'];
|
params['sort'] = ['sort'];
|
||||||
}
|
}
|
||||||
@ -104,7 +112,7 @@ export const TableBlockProvider = (props) => {
|
|||||||
params['tree'] = true;
|
params['tree'] = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const f = collection.fields.find(f => f.treeChildren);
|
const f = collection.fields.find((f) => f.treeChildren);
|
||||||
if (f) {
|
if (f) {
|
||||||
childrenColumnName = f.name;
|
childrenColumnName = f.name;
|
||||||
}
|
}
|
||||||
@ -135,6 +143,8 @@ export const useTableBlockProps = () => {
|
|||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const ctx = useTableBlockContext();
|
const ctx = useTableBlockContext();
|
||||||
const globalSort = fieldSchema.parent?.['x-decorator-props']?.['params']?.['sort'];
|
const globalSort = fieldSchema.parent?.['x-decorator-props']?.['params']?.['sort'];
|
||||||
|
const { getDataBlocks } = useFilterBlock();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ctx?.service?.loading) {
|
if (!ctx?.service?.loading) {
|
||||||
field.value = ctx?.service?.data?.data;
|
field.value = ctx?.service?.data?.data;
|
||||||
@ -179,5 +189,53 @@ export const useTableBlockProps = () => {
|
|||||||
: globalSort || ctx.service.params?.[0]?.sort;
|
: globalSort || ctx.service.params?.[0]?.sort;
|
||||||
ctx.service.run({ ...ctx.service.params?.[0], page: current, pageSize, sort });
|
ctx.service.run({ ...ctx.service.params?.[0], page: current, pageSize, sort });
|
||||||
},
|
},
|
||||||
|
onClickRow(record, setSelectedRow, selectedRow) {
|
||||||
|
const { targets, uid } = findFilterTargets(fieldSchema);
|
||||||
|
|
||||||
|
// 如果是之前创建的区块是没有 x-filter-targets 属性的,所以这里需要判断一下避免报错
|
||||||
|
if (!targets || !targets.length) return;
|
||||||
|
|
||||||
|
const value = [record[ctx.rowKey]];
|
||||||
|
|
||||||
|
getDataBlocks().forEach((block) => {
|
||||||
|
const target = targets.find((target) => target.uid === block.uid);
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
const param = block.service.params?.[0] || {};
|
||||||
|
// 保留原有的 filter
|
||||||
|
const storedFilter = block.service.params?.[1]?.filters || {};
|
||||||
|
|
||||||
|
if (selectedRow.includes(record[ctx.rowKey])) {
|
||||||
|
delete storedFilter[uid];
|
||||||
|
} else {
|
||||||
|
storedFilter[uid] = {
|
||||||
|
$and: [
|
||||||
|
{
|
||||||
|
[target.field || ctx.rowKey]: {
|
||||||
|
[target.field ? '$in' : '$eq']: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergedFilter = mergeFilter([
|
||||||
|
...Object.values(storedFilter).map((filter) => removeNullCondition(filter)),
|
||||||
|
block.defaultFilter,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return block.doFilter(
|
||||||
|
{
|
||||||
|
...param,
|
||||||
|
page: 1,
|
||||||
|
filter: mergedFilter,
|
||||||
|
},
|
||||||
|
{ filters: storedFilter },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新表格的选中状态
|
||||||
|
setSelectedRow((prev) => (prev?.includes(record[ctx.rowKey]) ? [] : [...value]));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -4,19 +4,22 @@ import parse from 'json-templates';
|
|||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
import { useContext } from 'react';
|
import { ChangeEvent, useContext } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { useReactToPrint } from 'react-to-print';
|
import { useReactToPrint } from 'react-to-print';
|
||||||
import { useFormBlockContext, useTableBlockContext } from '../..';
|
import { AssociationFilter, useFormBlockContext, useTableBlockContext } from '../..';
|
||||||
import { useAPIClient } from '../../api-client';
|
import { useAPIClient, useRequest } from '../../api-client';
|
||||||
import { useCollection } from '../../collection-manager';
|
import { useCollection } from '../../collection-manager';
|
||||||
|
import { useFilterBlock } from '../../filter-provider/FilterProvider';
|
||||||
|
import { transformToFilter } from '../../filter-provider/utils';
|
||||||
import { useRecord } from '../../record-provider';
|
import { useRecord } from '../../record-provider';
|
||||||
import { useActionContext, useCompile } from '../../schema-component';
|
import { removeNullCondition, useActionContext, useCompile } from '../../schema-component';
|
||||||
import { BulkEditFormItemValueType } from '../../schema-initializer/components';
|
import { BulkEditFormItemValueType } from '../../schema-initializer/components';
|
||||||
import { useCurrentUserContext } from '../../user';
|
import { useCurrentUserContext } from '../../user';
|
||||||
import { useBlockRequestContext, useFilterByTk } from '../BlockProvider';
|
import { useBlockRequestContext, useFilterByTk } from '../BlockProvider';
|
||||||
import { useDetailsBlockContext } from '../DetailsBlockProvider';
|
import { useDetailsBlockContext } from '../DetailsBlockProvider';
|
||||||
|
import { mergeFilter } from '../SharedFilterProvider';
|
||||||
import { TableFieldResource } from '../TableFieldProvider';
|
import { TableFieldResource } from '../TableFieldProvider';
|
||||||
|
|
||||||
export const usePickActionProps = () => {
|
export const usePickActionProps = () => {
|
||||||
@ -199,6 +202,134 @@ export const useCreateActionProps = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface FilterTarget {
|
||||||
|
targets?: {
|
||||||
|
/** field uid */
|
||||||
|
uid: string;
|
||||||
|
/** associated fields */
|
||||||
|
field?: string;
|
||||||
|
}[];
|
||||||
|
uid?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const findFilterTargets = (fieldSchema): FilterTarget => {
|
||||||
|
while (fieldSchema) {
|
||||||
|
if (fieldSchema['x-filter-targets']) {
|
||||||
|
return {
|
||||||
|
targets: fieldSchema['x-filter-targets'],
|
||||||
|
uid: fieldSchema['x-uid'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
fieldSchema = fieldSchema.parent;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateFilterTargets = (fieldSchema, targets: FilterTarget['targets']) => {
|
||||||
|
while (fieldSchema) {
|
||||||
|
if (fieldSchema['x-filter-targets']) {
|
||||||
|
fieldSchema['x-filter-targets'] = targets;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fieldSchema = fieldSchema.parent;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFilterBlockActionProps = () => {
|
||||||
|
const form = useForm();
|
||||||
|
const actionField = useField();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { getDataBlocks } = useFilterBlock();
|
||||||
|
|
||||||
|
actionField.data = actionField.data || {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
async onClick() {
|
||||||
|
const { targets = [], uid } = findFilterTargets(fieldSchema);
|
||||||
|
|
||||||
|
actionField.data.loading = true;
|
||||||
|
try {
|
||||||
|
// 收集 filter 的值
|
||||||
|
await Promise.all(
|
||||||
|
getDataBlocks().map(async (block) => {
|
||||||
|
const target = targets.find((target) => target.uid === block.uid);
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
const param = block.service.params?.[0] || {};
|
||||||
|
// 保留原有的 filter
|
||||||
|
const storedFilter = block.service.params?.[1]?.filters || {};
|
||||||
|
|
||||||
|
storedFilter[uid] = removeNullCondition(transformToFilter(form.values, fieldSchema));
|
||||||
|
|
||||||
|
const mergedFilter = mergeFilter([
|
||||||
|
...Object.values(storedFilter).map((filter) => removeNullCondition(filter)),
|
||||||
|
block.defaultFilter,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return block.doFilter(
|
||||||
|
{
|
||||||
|
...param,
|
||||||
|
page: 1,
|
||||||
|
filter: mergedFilter,
|
||||||
|
},
|
||||||
|
{ filters: storedFilter },
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
actionField.data.loading = false;
|
||||||
|
} catch (error) {
|
||||||
|
actionField.data.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useResetBlockActionProps = () => {
|
||||||
|
const form = useForm();
|
||||||
|
const actionField = useField();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { getDataBlocks } = useFilterBlock();
|
||||||
|
|
||||||
|
actionField.data = actionField.data || {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
async onClick() {
|
||||||
|
const { targets, uid } = findFilterTargets(fieldSchema);
|
||||||
|
|
||||||
|
form.reset();
|
||||||
|
actionField.data.loading = true;
|
||||||
|
try {
|
||||||
|
// 收集 filter 的值
|
||||||
|
await Promise.all(
|
||||||
|
getDataBlocks().map(async (block) => {
|
||||||
|
const target = targets.find((target) => target.uid === block.uid);
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
const param = block.service.params?.[0] || {};
|
||||||
|
// 保留原有的 filter
|
||||||
|
const storedFilter = block.service.params?.[1]?.filters || {};
|
||||||
|
|
||||||
|
delete storedFilter[uid];
|
||||||
|
const mergedFilter = mergeFilter([...Object.values(storedFilter), block.defaultFilter]);
|
||||||
|
|
||||||
|
return block.doFilter(
|
||||||
|
{
|
||||||
|
...param,
|
||||||
|
page: 1,
|
||||||
|
filter: mergedFilter,
|
||||||
|
},
|
||||||
|
{ filters: storedFilter },
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
actionField.data.loading = false;
|
||||||
|
} catch (error) {
|
||||||
|
actionField.data.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const useCustomizeUpdateActionProps = () => {
|
export const useCustomizeUpdateActionProps = () => {
|
||||||
const { resource, __parent, service } = useBlockRequestContext();
|
const { resource, __parent, service } = useBlockRequestContext();
|
||||||
const filterByTk = useFilterByTk();
|
const filterByTk = useFilterByTk();
|
||||||
@ -663,3 +794,188 @@ export const useDetailsPaginationProps = () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useAssociationFilterProps = () => {
|
||||||
|
const collectionField = AssociationFilter.useAssociationField();
|
||||||
|
const { service, props: blockProps } = useBlockRequestContext();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const valueKey = collectionField?.targetKey || 'id';
|
||||||
|
const labelKey = fieldSchema['x-component-props']?.fieldNames?.label || valueKey;
|
||||||
|
const collectionFieldName = collectionField.name;
|
||||||
|
const { data, params, run } = useRequest(
|
||||||
|
{
|
||||||
|
resource: collectionField.target,
|
||||||
|
action: 'list',
|
||||||
|
params: {
|
||||||
|
fields: [labelKey, valueKey],
|
||||||
|
pageSize: 200,
|
||||||
|
page: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
refreshDeps: [labelKey, valueKey],
|
||||||
|
debounceWait: 300,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const list = data?.data || [];
|
||||||
|
const onSelected = (value) => {
|
||||||
|
const filters = service.params?.[1]?.filters || {};
|
||||||
|
|
||||||
|
if (value.length) {
|
||||||
|
filters[`af.${collectionFieldName}`] = {
|
||||||
|
[`${collectionFieldName}.${valueKey}.$in`]: value,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
delete filters[`af.${collectionFieldName}`];
|
||||||
|
}
|
||||||
|
|
||||||
|
service.run(
|
||||||
|
{
|
||||||
|
...service.params?.[0],
|
||||||
|
pageSize: 200,
|
||||||
|
page: 1,
|
||||||
|
filter: mergeFilter([...Object.values(filters), blockProps?.params?.filter]),
|
||||||
|
},
|
||||||
|
{ filters },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const handleSearchInput = (e: ChangeEvent<any>) => {
|
||||||
|
run({
|
||||||
|
...params?.[0],
|
||||||
|
filter: {
|
||||||
|
[`${labelKey}.$includes`]: e.target.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
/** 渲染 Collapse 的列表数据 */
|
||||||
|
list,
|
||||||
|
onSelected,
|
||||||
|
handleSearchInput,
|
||||||
|
params,
|
||||||
|
run,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useOptionalFieldList = () => {
|
||||||
|
const { currentFields = [] } = useCollection();
|
||||||
|
|
||||||
|
return currentFields.filter((field) => isOptionalField(field) && field.uiSchema.enum);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isOptionalField = (field) => {
|
||||||
|
const optionalInterfaces = ['select', 'multipleSelect', 'checkbox', 'checkboxGroup', 'chinaRegion'];
|
||||||
|
return optionalInterfaces.includes(field.interface);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAssociationFilterBlockProps = () => {
|
||||||
|
const collectionField = AssociationFilter.useAssociationField();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const optionalFieldList = useOptionalFieldList();
|
||||||
|
const { getDataBlocks } = useFilterBlock();
|
||||||
|
const collectionFieldName = collectionField.name;
|
||||||
|
|
||||||
|
let list, onSelected, handleSearchInput, params, run, data, valueKey, labelKey, filterKey;
|
||||||
|
|
||||||
|
if (isOptionalField(fieldSchema)) {
|
||||||
|
const field = optionalFieldList.find((field) => field.name === fieldSchema.name);
|
||||||
|
const operatorMap = {
|
||||||
|
select: '$in',
|
||||||
|
multipleSelect: '$anyOf',
|
||||||
|
checkbox: '$in',
|
||||||
|
checkboxGroup: '$anyOf',
|
||||||
|
};
|
||||||
|
const _list = field?.uiSchema?.enum || [];
|
||||||
|
valueKey = 'value';
|
||||||
|
labelKey = 'label';
|
||||||
|
list = _list;
|
||||||
|
params = {};
|
||||||
|
run = () => {};
|
||||||
|
filterKey = `${field.name}.${operatorMap[field.interface]}`;
|
||||||
|
handleSearchInput = (e) => {
|
||||||
|
// TODO: 列表没有刷新,在这个 hook 中使用 useState 会产生 re-render 次数过多的错误
|
||||||
|
const value = e.target.value;
|
||||||
|
if (!value) {
|
||||||
|
list = _list;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
list = (_list as any[]).filter((item) => item.label.includes(value));
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
valueKey = collectionField?.targetKey || 'id';
|
||||||
|
labelKey = fieldSchema['x-component-props']?.fieldNames?.label || valueKey;
|
||||||
|
({ data, params, run } = useRequest(
|
||||||
|
{
|
||||||
|
resource: collectionField.target,
|
||||||
|
action: 'list',
|
||||||
|
params: {
|
||||||
|
fields: [labelKey, valueKey],
|
||||||
|
pageSize: 200,
|
||||||
|
page: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
refreshDeps: [labelKey, valueKey],
|
||||||
|
debounceWait: 300,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
filterKey = `${collectionFieldName}.${valueKey}.$in`;
|
||||||
|
|
||||||
|
list = data?.data || [];
|
||||||
|
|
||||||
|
handleSearchInput = (e: ChangeEvent<any>) => {
|
||||||
|
run({
|
||||||
|
...params?.[0],
|
||||||
|
filter: {
|
||||||
|
[`${labelKey}.$includes`]: e.target.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelected = (value) => {
|
||||||
|
const { targets, uid } = findFilterTargets(fieldSchema);
|
||||||
|
|
||||||
|
getDataBlocks().forEach((block) => {
|
||||||
|
const target = targets.find((target) => target.uid === block.uid);
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
const key = `${uid}${fieldSchema.name}`;
|
||||||
|
const param = block.service.params?.[0] || {};
|
||||||
|
// 保留原有的 filter
|
||||||
|
const storedFilter = block.service.params?.[1]?.filters || {};
|
||||||
|
|
||||||
|
if (value.length) {
|
||||||
|
storedFilter[key] = {
|
||||||
|
[filterKey]: value,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
delete storedFilter[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergedFilter = mergeFilter([...Object.values(storedFilter), block.defaultFilter]);
|
||||||
|
|
||||||
|
return block.doFilter(
|
||||||
|
{
|
||||||
|
...param,
|
||||||
|
page: 1,
|
||||||
|
filter: mergedFilter,
|
||||||
|
},
|
||||||
|
{ filters: storedFilter },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
/** 渲染 Collapse 的列表数据 */
|
||||||
|
list,
|
||||||
|
onSelected,
|
||||||
|
handleSearchInput,
|
||||||
|
params,
|
||||||
|
run,
|
||||||
|
valueKey,
|
||||||
|
labelKey,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -6,6 +6,8 @@ import { CollectionContext } from '../context';
|
|||||||
import { CollectionFieldOptions } from '../types';
|
import { CollectionFieldOptions } from '../types';
|
||||||
import { useCollectionManager } from './useCollectionManager';
|
import { useCollectionManager } from './useCollectionManager';
|
||||||
|
|
||||||
|
export type Collection = ReturnType<typeof useCollection>;
|
||||||
|
|
||||||
export const useCollection = () => {
|
export const useCollection = () => {
|
||||||
const collection = useContext(CollectionContext);
|
const collection = useContext(CollectionContext);
|
||||||
const api = useAPIClient();
|
const api = useAPIClient();
|
||||||
|
121
packages/core/client/src/filter-provider/FilterProvider.tsx
Normal file
121
packages/core/client/src/filter-provider/FilterProvider.tsx
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
|
import React, { createContext, useEffect, useRef } from 'react';
|
||||||
|
import { useBlockRequestContext } from '../block-provider';
|
||||||
|
import { SharedFilter } from '../block-provider/SharedFilterProvider';
|
||||||
|
import { CollectionFieldOptions, useCollection } from '../collection-manager';
|
||||||
|
import { useAssociatedFields } from './utils';
|
||||||
|
|
||||||
|
type Collection = ReturnType<typeof useCollection>;
|
||||||
|
|
||||||
|
export interface DataBlock {
|
||||||
|
/** 唯一标识符,schema 中的 name 值 */
|
||||||
|
uid: string;
|
||||||
|
/** 用户自行设置的区块名称 */
|
||||||
|
title?: string;
|
||||||
|
/** 与当前区块相关的数据表信息 */
|
||||||
|
collection: Collection;
|
||||||
|
/** 根据提供的参数执行该方法即可刷新数据区块的数据 */
|
||||||
|
doFilter: (params: any, params2?: any) => Promise<void>;
|
||||||
|
/** 数据区块表中所有的关系字段 */
|
||||||
|
associatedFields?: CollectionFieldOptions[];
|
||||||
|
/** 通过右上角菜单设置的过滤条件 */
|
||||||
|
defaultFilter?: SharedFilter;
|
||||||
|
service?: any;
|
||||||
|
/** 区块所对应的 DOM 容器 */
|
||||||
|
dom: HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FilterContextValue {
|
||||||
|
dataBlocks: DataBlock[];
|
||||||
|
setDataBlocks: React.Dispatch<React.SetStateAction<DataBlock[]>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FilterContext = createContext<FilterContextValue>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主要用于记录当前页面中的数据区块的信息,用于在过滤区块中使用
|
||||||
|
* @param props
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const FilterBlockProvider: React.FC = ({ children }) => {
|
||||||
|
const [dataBlocks, setDataBlocks] = React.useState<DataBlock[]>([]);
|
||||||
|
return <FilterContext.Provider value={{ dataBlocks, setDataBlocks }}>{children}</FilterContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FilterBlockRecord = ({
|
||||||
|
children,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
params?: { filter: SharedFilter };
|
||||||
|
}) => {
|
||||||
|
const collection = useCollection();
|
||||||
|
const { recordDataBlocks, removeDataBlock } = useFilterBlock();
|
||||||
|
const { service } = useBlockRequestContext();
|
||||||
|
const field = useField();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const associatedFields = useAssociatedFields();
|
||||||
|
const container = useRef(null);
|
||||||
|
|
||||||
|
const shouldApplyFilter = field.decoratorType !== 'FormBlockProvider' && field.decoratorProps.blockType !== 'filter';
|
||||||
|
|
||||||
|
const addBlockToDataBlocks = () => {
|
||||||
|
recordDataBlocks({
|
||||||
|
uid: fieldSchema['x-uid'],
|
||||||
|
title: field.componentProps.title,
|
||||||
|
doFilter: service.runAsync,
|
||||||
|
collection,
|
||||||
|
associatedFields,
|
||||||
|
defaultFilter: params?.filter || {},
|
||||||
|
service,
|
||||||
|
dom: container.current,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (shouldApplyFilter) addBlockToDataBlocks();
|
||||||
|
}, [params?.filter, service]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
removeDataBlock(field.props.name as string);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <div ref={container}>{children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回一些方法用于收集和获取当前页面中的数据区块的信息
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const useFilterBlock = () => {
|
||||||
|
const ctx = React.useContext(FilterContext);
|
||||||
|
// 有可能存在页面没有提供 FilterBlockProvider 的情况,比如内部使用的数据表管理页面
|
||||||
|
if (!ctx) {
|
||||||
|
return {
|
||||||
|
recordDataBlocks: () => {},
|
||||||
|
getDataBlocks: () => [] as DataBlock[],
|
||||||
|
removeDataBlock: () => {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const { dataBlocks, setDataBlocks } = ctx;
|
||||||
|
const recordDataBlocks = (block: DataBlock) => {
|
||||||
|
const existingBlock = dataBlocks.find((item) => item.uid === block.uid);
|
||||||
|
|
||||||
|
if (existingBlock) {
|
||||||
|
// 这里的值有可能会变化,所以需要更新
|
||||||
|
existingBlock.service = block.service;
|
||||||
|
existingBlock.defaultFilter = block.defaultFilter;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDataBlocks((prev) => [...prev, block]);
|
||||||
|
};
|
||||||
|
const getDataBlocks = () => dataBlocks;
|
||||||
|
const removeDataBlock = (name: string) => {
|
||||||
|
setDataBlocks((prev) => prev.filter((item) => item.uid !== name));
|
||||||
|
};
|
||||||
|
|
||||||
|
return { recordDataBlocks, getDataBlocks, removeDataBlock };
|
||||||
|
};
|
@ -0,0 +1,54 @@
|
|||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { FilterBlockProvider, useFilterBlock } from '../FilterProvider';
|
||||||
|
|
||||||
|
describe('useFilter', () => {
|
||||||
|
test('should get a empty array', () => {
|
||||||
|
let getDataBlocks = null;
|
||||||
|
const Comp = () => {
|
||||||
|
({ getDataBlocks } = useFilterBlock());
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
const App = () => {
|
||||||
|
return (
|
||||||
|
<FilterBlockProvider>
|
||||||
|
<Comp />
|
||||||
|
</FilterBlockProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(<App />);
|
||||||
|
expect(getDataBlocks()).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not repeat', () => {
|
||||||
|
let getDataBlocks = null,
|
||||||
|
recordDataBlocks = null;
|
||||||
|
const Comp = () => {
|
||||||
|
({ getDataBlocks, recordDataBlocks } = useFilterBlock());
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
const App = () => {
|
||||||
|
return (
|
||||||
|
<FilterBlockProvider>
|
||||||
|
<Comp />
|
||||||
|
</FilterBlockProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(<App />);
|
||||||
|
|
||||||
|
recordDataBlocks({
|
||||||
|
name: 'test',
|
||||||
|
collection: {},
|
||||||
|
doFilter: () => {},
|
||||||
|
});
|
||||||
|
expect(getDataBlocks().length).toBe(1);
|
||||||
|
|
||||||
|
// avoid repeat
|
||||||
|
recordDataBlocks({
|
||||||
|
name: 'test',
|
||||||
|
collection: {},
|
||||||
|
doFilter: () => {},
|
||||||
|
});
|
||||||
|
expect(getDataBlocks().length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
89
packages/core/client/src/filter-provider/utils.ts
Normal file
89
packages/core/client/src/filter-provider/utils.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { Schema, useFieldSchema } from '@formily/react';
|
||||||
|
import { isPlainObject, isEmpty } from '@nocobase/utils/client';
|
||||||
|
import { Collection, FieldOptions, useCollection } from '../collection-manager';
|
||||||
|
import { findFilterOperators } from '../schema-component/antd/form-item/SchemaSettingOptions';
|
||||||
|
import { useFilterBlock } from './FilterProvider';
|
||||||
|
|
||||||
|
export enum FilterBlockType {
|
||||||
|
FORM,
|
||||||
|
TABLE,
|
||||||
|
TREE,
|
||||||
|
COLLAPSE,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据筛选区块类型筛选出支持的数据区块(同表或具有关系字段的表)
|
||||||
|
* @param filterBlockType
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const useSupportedBlocks = (filterBlockType: FilterBlockType) => {
|
||||||
|
const { getDataBlocks } = useFilterBlock();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const collection = useCollection();
|
||||||
|
|
||||||
|
// Form 和 Collapse 仅支持同表的数据区块
|
||||||
|
if (filterBlockType === FilterBlockType.FORM || filterBlockType === FilterBlockType.COLLAPSE) {
|
||||||
|
return getDataBlocks().filter((block) => {
|
||||||
|
return isSameCollection(block.collection, collection);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Table 和 Tree 支持同表或者关系表的数据区块
|
||||||
|
if (filterBlockType === FilterBlockType.TABLE || filterBlockType === FilterBlockType.TREE) {
|
||||||
|
return getDataBlocks().filter((block) => {
|
||||||
|
return (
|
||||||
|
fieldSchema['x-uid'] !== block.uid &&
|
||||||
|
(isSameCollection(block.collection, collection) ||
|
||||||
|
block.associatedFields.some((field) => field.target === collection.name))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively flatten an object and generate a query object.
|
||||||
|
* @param result - The resulting query object.
|
||||||
|
* @param obj - The object to be flattened and queried.
|
||||||
|
* @param key - The current key in the object tree.
|
||||||
|
* @param operators - The operators to use for each field.
|
||||||
|
* @returns The resulting query object.
|
||||||
|
*/
|
||||||
|
const flattenAndQueryObject = (result: Record<string, any>, obj: any, key: string, operators: Record<string, any>) => {
|
||||||
|
if (!obj) return result;
|
||||||
|
|
||||||
|
if (!isPlainObject(obj)) {
|
||||||
|
result[key] = {
|
||||||
|
[operators[key] || '$eq']: obj,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
Object.keys(obj).forEach((k) => {
|
||||||
|
flattenAndQueryObject(result, obj[k], `${key}.${k}`, operators);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const transformToFilter = (values: Record<string, any>, fieldSchema: Schema) => {
|
||||||
|
const { operators } = findFilterOperators(fieldSchema);
|
||||||
|
|
||||||
|
return {
|
||||||
|
$and: Object.keys(values)
|
||||||
|
.map((key) => flattenAndQueryObject({}, values[key], key, operators))
|
||||||
|
.filter((item) => !isEmpty(item)),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAssociatedFields = () => {
|
||||||
|
const { fields } = useCollection();
|
||||||
|
|
||||||
|
return fields.filter((field) => isAssocField(field)) || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isAssocField = (field?: FieldOptions) => {
|
||||||
|
return ['o2o', 'oho', 'obo', 'm2o', 'createdBy', 'updatedBy', 'o2m', 'm2m', 'linkTo'].includes(field?.interface);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isSameCollection = (c1: Collection, c2: Collection) => {
|
||||||
|
return c1.name === c2.name;
|
||||||
|
};
|
@ -15,6 +15,7 @@ export default {
|
|||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"Event": "Event",
|
"Event": "Event",
|
||||||
"None": "None",
|
"None": "None",
|
||||||
|
"Unconnected": "Unconnected",
|
||||||
"System settings": "System settings",
|
"System settings": "System settings",
|
||||||
"System title": "System title",
|
"System title": "System title",
|
||||||
"Logo": "Logo",
|
"Logo": "Logo",
|
||||||
@ -73,8 +74,10 @@ export default {
|
|||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
"Set the data scope": "Set the data scope",
|
"Set the data scope": "Set the data scope",
|
||||||
"Data blocks": "Data blocks",
|
"Data blocks": "Data blocks",
|
||||||
|
"Filter blocks": "Filter blocks",
|
||||||
"Table": "Table",
|
"Table": "Table",
|
||||||
"Form": "Form",
|
"Form": "Form",
|
||||||
|
"Collapse": "Collapse",
|
||||||
"Select data source": "Select data source",
|
"Select data source": "Select data source",
|
||||||
"Calendar": "Calendar",
|
"Calendar": "Calendar",
|
||||||
"Delete events": "Delete events",
|
"Delete events": "Delete events",
|
||||||
@ -127,6 +130,7 @@ export default {
|
|||||||
"Are you sure you want to delete it?": "Are you sure you want to delete it?",
|
"Are you sure you want to delete it?": "Are you sure you want to delete it?",
|
||||||
"This is a demo text, **supports Markdown syntax**.": "This is a demo text, **supports Markdown syntax**.",
|
"This is a demo text, **supports Markdown syntax**.": "This is a demo text, **supports Markdown syntax**.",
|
||||||
"Filter": "Filter",
|
"Filter": "Filter",
|
||||||
|
"Connect data blocks": "Connect data blocks",
|
||||||
"Action type": "Action type",
|
"Action type": "Action type",
|
||||||
"Actions": "Actions",
|
"Actions": "Actions",
|
||||||
"Insert": "Insert",
|
"Insert": "Insert",
|
||||||
@ -166,6 +170,7 @@ export default {
|
|||||||
"Association fields filter": "Association fields filter",
|
"Association fields filter": "Association fields filter",
|
||||||
"PK & FK fields": "PK & FK fields",
|
"PK & FK fields": "PK & FK fields",
|
||||||
"Association fields": "Association fields",
|
"Association fields": "Association fields",
|
||||||
|
"Optional fields": "Optional fields",
|
||||||
"System fields": "System fields",
|
"System fields": "System fields",
|
||||||
"General fields": "General fields",
|
"General fields": "General fields",
|
||||||
"Parent collection fields": "Parent collection fields",
|
"Parent collection fields": "Parent collection fields",
|
||||||
@ -244,6 +249,7 @@ export default {
|
|||||||
"Edit block title": "Edit block title",
|
"Edit block title": "Edit block title",
|
||||||
"Block title": "Block title",
|
"Block title": "Block title",
|
||||||
"Pattern": "Pattern",
|
"Pattern": "Pattern",
|
||||||
|
"Operator": "Operator",
|
||||||
"Editable": "Editable",
|
"Editable": "Editable",
|
||||||
"Readonly": "Readonly",
|
"Readonly": "Readonly",
|
||||||
"Easy-reading": "Easy-reading",
|
"Easy-reading": "Easy-reading",
|
||||||
@ -414,6 +420,7 @@ export default {
|
|||||||
"Convert reference to duplicate": "Convert reference to duplicate",
|
"Convert reference to duplicate": "Convert reference to duplicate",
|
||||||
"Template name": "Template name",
|
"Template name": "Template name",
|
||||||
"Block type": "Block type",
|
"Block type": "Block type",
|
||||||
|
"No blocks to connect": "No blocks to connect",
|
||||||
"Action column": "Action column",
|
"Action column": "Action column",
|
||||||
"Records per page": "Records per page",
|
"Records per page": "Records per page",
|
||||||
"(Fields only)": "(Fields only)",
|
"(Fields only)": "(Fields only)",
|
||||||
|
@ -15,6 +15,7 @@ export default {
|
|||||||
"Time": "時間",
|
"Time": "時間",
|
||||||
"Event": "イベント",
|
"Event": "イベント",
|
||||||
"None": "なし",
|
"None": "なし",
|
||||||
|
"Unconnected": "未接続",
|
||||||
"System settings": "システム設定",
|
"System settings": "システム設定",
|
||||||
"System title": "システム名",
|
"System title": "システム名",
|
||||||
"Logo": "ロゴ",
|
"Logo": "ロゴ",
|
||||||
@ -77,8 +78,10 @@ export default {
|
|||||||
"Close": "閉じる",
|
"Close": "閉じる",
|
||||||
"Set the data scope": "データ範囲の設定",
|
"Set the data scope": "データ範囲の設定",
|
||||||
"Data blocks": "データブロック",
|
"Data blocks": "データブロック",
|
||||||
|
"Filter blocks": "フィルターブロック",
|
||||||
"Table": "テーブル",
|
"Table": "テーブル",
|
||||||
"Form": "フォーム",
|
"Form": "フォーム",
|
||||||
|
"Collapse": "折りたたみ",
|
||||||
"Select data source": "データソースを選択",
|
"Select data source": "データソースを選択",
|
||||||
"Calendar": "カレンダー",
|
"Calendar": "カレンダー",
|
||||||
"Kanban": "かんばん",
|
"Kanban": "かんばん",
|
||||||
@ -120,6 +123,7 @@ export default {
|
|||||||
"Are you sure you want to delete it?": "本当に削除しますか?",
|
"Are you sure you want to delete it?": "本当に削除しますか?",
|
||||||
"This is a demo text, **supports Markdown syntax**.": "これはデモテキストです。 **マークダウン構文をサポートしています。**",
|
"This is a demo text, **supports Markdown syntax**.": "これはデモテキストです。 **マークダウン構文をサポートしています。**",
|
||||||
"Filter": "フィルター",
|
"Filter": "フィルター",
|
||||||
|
"Connect data blocks": "データブロックを連結",
|
||||||
"Action type": "操作タイプ",
|
"Action type": "操作タイプ",
|
||||||
"Actions": "操作",
|
"Actions": "操作",
|
||||||
"Insert": "作成",
|
"Insert": "作成",
|
||||||
@ -346,6 +350,7 @@ export default {
|
|||||||
"Convert reference to duplicate": "参照を複製に変換",
|
"Convert reference to duplicate": "参照を複製に変換",
|
||||||
"Template name": "テンプレート名",
|
"Template name": "テンプレート名",
|
||||||
"Block type": "ブロックタイプ",
|
"Block type": "ブロックタイプ",
|
||||||
|
"No blocks to connect": "接続するブロックがありません",
|
||||||
"Action column": "操作カラム",
|
"Action column": "操作カラム",
|
||||||
"Records per page": "ページごとのレコード数",
|
"Records per page": "ページごとのレコード数",
|
||||||
"(Fields only)": "(フィールドのみ)",
|
"(Fields only)": "(フィールドのみ)",
|
||||||
@ -531,6 +536,7 @@ export default {
|
|||||||
"Enable SMS authentication": "SMS認証を有効にする",
|
"Enable SMS authentication": "SMS認証を有効にする",
|
||||||
"Display association fields": "関連付けられたコレクションのフィールドを表示",
|
"Display association fields": "関連付けられたコレクションのフィールドを表示",
|
||||||
"Set default value": "デフォルト値を設定",
|
"Set default value": "デフォルト値を設定",
|
||||||
|
"Optional fields": "オプションフィールド",
|
||||||
"Editable": "編集可能",
|
"Editable": "編集可能",
|
||||||
"Readonly": "読み取り専用(編集不可)",
|
"Readonly": "読み取り専用(編集不可)",
|
||||||
"Easy-reading": "読取り専用(読取りモード)",
|
"Easy-reading": "読取り専用(読取りモード)",
|
||||||
|
@ -15,6 +15,7 @@ export default {
|
|||||||
"Time": "Время",
|
"Time": "Время",
|
||||||
"Event": "Событие",
|
"Event": "Событие",
|
||||||
"None": "Ничего",
|
"None": "Ничего",
|
||||||
|
"Unconnected": "Не подключен",
|
||||||
"System settings": "Системные настройки",
|
"System settings": "Системные настройки",
|
||||||
"System title": "Системный заголовок",
|
"System title": "Системный заголовок",
|
||||||
"Logo": "Логотип",
|
"Logo": "Логотип",
|
||||||
@ -49,8 +50,10 @@ export default {
|
|||||||
"Close": "Закрыть",
|
"Close": "Закрыть",
|
||||||
"Set the data scope": "Установить область данных",
|
"Set the data scope": "Установить область данных",
|
||||||
"Data blocks": "Блоки данных",
|
"Data blocks": "Блоки данных",
|
||||||
|
"Filter blocks": "Просеивающие блоки",
|
||||||
"Table": "Таблица",
|
"Table": "Таблица",
|
||||||
"Form": "Форма",
|
"Form": "Форма",
|
||||||
|
"Collapse": "Свернуть",
|
||||||
"Select data source": "Выбрать источник данных",
|
"Select data source": "Выбрать источник данных",
|
||||||
"Calendar": "Календарь",
|
"Calendar": "Календарь",
|
||||||
"Kanban": "Канбан",
|
"Kanban": "Канбан",
|
||||||
@ -92,6 +95,7 @@ export default {
|
|||||||
"Are you sure you want to delete it?": "Вы уверены, что хотите удалить это?",
|
"Are you sure you want to delete it?": "Вы уверены, что хотите удалить это?",
|
||||||
"This is a demo text, **supports Markdown syntax**.": "Это демо текст, **поддерживает синтаксис Markdown**.",
|
"This is a demo text, **supports Markdown syntax**.": "Это демо текст, **поддерживает синтаксис Markdown**.",
|
||||||
"Filter": "Фильтр",
|
"Filter": "Фильтр",
|
||||||
|
"Connect data blocks": "Соединить блоки данных",
|
||||||
"Action type": "Тип действия",
|
"Action type": "Тип действия",
|
||||||
"Actions": "Действия",
|
"Actions": "Действия",
|
||||||
"Insert": "Вставить",
|
"Insert": "Вставить",
|
||||||
@ -307,6 +311,7 @@ export default {
|
|||||||
"Convert reference to duplicate": "Преобразовать ссылку в дубликат",
|
"Convert reference to duplicate": "Преобразовать ссылку в дубликат",
|
||||||
"Template name": "Имя Шаблона",
|
"Template name": "Имя Шаблона",
|
||||||
"Block type": "Тип Блока",
|
"Block type": "Тип Блока",
|
||||||
|
"No blocks to connect": "Нет Блоков для подключения",
|
||||||
"Action column": "Колонка действий",
|
"Action column": "Колонка действий",
|
||||||
"Records per page": "Записей на страницу",
|
"Records per page": "Записей на страницу",
|
||||||
"(Fields only)": "(Только поля)",
|
"(Fields only)": "(Только поля)",
|
||||||
|
@ -15,6 +15,7 @@ export default {
|
|||||||
"Time": "Saat",
|
"Time": "Saat",
|
||||||
"Event": "Olay",
|
"Event": "Olay",
|
||||||
"None": "Boş",
|
"None": "Boş",
|
||||||
|
"Unconnected": "Bağlantı yok",
|
||||||
"System settings": "Sistem ayarları",
|
"System settings": "Sistem ayarları",
|
||||||
"System title": "Sistem başlığı",
|
"System title": "Sistem başlığı",
|
||||||
"Logo": "Logo",
|
"Logo": "Logo",
|
||||||
@ -49,8 +50,10 @@ export default {
|
|||||||
"Close": "Kapat",
|
"Close": "Kapat",
|
||||||
"Set the data scope": "Veri kapsamını ayarla",
|
"Set the data scope": "Veri kapsamını ayarla",
|
||||||
"Data blocks": "Veri Blokları",
|
"Data blocks": "Veri Blokları",
|
||||||
|
"Filter blocks": "Filtre blokları",
|
||||||
"Table": "Tablo",
|
"Table": "Tablo",
|
||||||
"Form": "Form",
|
"Form": "Form",
|
||||||
|
"Collapse": "Daralt",
|
||||||
"Select data source": "Veri kaynağını seç",
|
"Select data source": "Veri kaynağını seç",
|
||||||
"Calendar": "Takvim",
|
"Calendar": "Takvim",
|
||||||
"Kanban": "Kanban",
|
"Kanban": "Kanban",
|
||||||
@ -92,6 +95,7 @@ export default {
|
|||||||
"Are you sure you want to delete it?": "Silmek istediğinizden emin misiniz?",
|
"Are you sure you want to delete it?": "Silmek istediğinizden emin misiniz?",
|
||||||
"This is a demo text, **supports Markdown syntax**.": "Bu bir örnek yazıdır, **işaretleme yazısı destekleniyor**.",
|
"This is a demo text, **supports Markdown syntax**.": "Bu bir örnek yazıdır, **işaretleme yazısı destekleniyor**.",
|
||||||
"Filter": "Filtre",
|
"Filter": "Filtre",
|
||||||
|
"Connect data blocks": "Veri bloklarını bağla",
|
||||||
"Action type": "İşlem Türü",
|
"Action type": "İşlem Türü",
|
||||||
"Actions": "İşlemler",
|
"Actions": "İşlemler",
|
||||||
"Insert": "Ekle",
|
"Insert": "Ekle",
|
||||||
@ -306,6 +310,7 @@ export default {
|
|||||||
"Convert reference to duplicate": "Referansı kopyaya dönüştür",
|
"Convert reference to duplicate": "Referansı kopyaya dönüştür",
|
||||||
"Template name": "Şablon adı",
|
"Template name": "Şablon adı",
|
||||||
"Block type": "Blok türü",
|
"Block type": "Blok türü",
|
||||||
|
"No blocks to connect": "Bağlanacak blok yok",
|
||||||
"Action column": "İşlem sütunu",
|
"Action column": "İşlem sütunu",
|
||||||
"Records per page": "Sayfa başına kayıt",
|
"Records per page": "Sayfa başına kayıt",
|
||||||
"(Fields only)": "(Sadece alanlar)",
|
"(Fields only)": "(Sadece alanlar)",
|
||||||
@ -461,6 +466,7 @@ export default {
|
|||||||
"Use the same time zone (GMT) for all users": "Tüm kullanıcılar için aynı saat dilimini (GMT) kullanın",
|
"Use the same time zone (GMT) for all users": "Tüm kullanıcılar için aynı saat dilimini (GMT) kullanın",
|
||||||
"Block title": "Blok başlığı",
|
"Block title": "Blok başlığı",
|
||||||
"Edit block title": "Blok başlığını düzenle",
|
"Edit block title": "Blok başlığını düzenle",
|
||||||
|
"operater": "operatör",
|
||||||
"Province/city/area name": "Semt/şehir/bölge adı",
|
"Province/city/area name": "Semt/şehir/bölge adı",
|
||||||
"Field component": "Alan bileşeni",
|
"Field component": "Alan bileşeni",
|
||||||
"Subtable": "Alttablo",
|
"Subtable": "Alttablo",
|
||||||
|
@ -15,6 +15,7 @@ export default {
|
|||||||
"Time": "时间",
|
"Time": "时间",
|
||||||
"Event": "事件",
|
"Event": "事件",
|
||||||
"None": "无",
|
"None": "无",
|
||||||
|
"Unconnected": "未连接",
|
||||||
"System settings": "系统设置",
|
"System settings": "系统设置",
|
||||||
"System title": "系统名称",
|
"System title": "系统名称",
|
||||||
"Logo": "Logo",
|
"Logo": "Logo",
|
||||||
@ -83,8 +84,10 @@ export default {
|
|||||||
"Close": "关闭",
|
"Close": "关闭",
|
||||||
"Set the data scope": "设置数据范围",
|
"Set the data scope": "设置数据范围",
|
||||||
"Data blocks": "数据区块",
|
"Data blocks": "数据区块",
|
||||||
|
"Filter blocks": "筛选区块",
|
||||||
"Table": "表格",
|
"Table": "表格",
|
||||||
"Form": "表单",
|
"Form": "表单",
|
||||||
|
"Collapse": "折叠面板",
|
||||||
"Select data source": "选择数据源",
|
"Select data source": "选择数据源",
|
||||||
"Calendar": "日历",
|
"Calendar": "日历",
|
||||||
'Delete events': '删除日程',
|
'Delete events': '删除日程',
|
||||||
@ -133,6 +136,7 @@ export default {
|
|||||||
"This is a demo text, **supports Markdown syntax**.": "这是一段演示文本,**支持 Markdown 语法**。",
|
"This is a demo text, **supports Markdown syntax**.": "这是一段演示文本,**支持 Markdown 语法**。",
|
||||||
|
|
||||||
"Filter": "筛选",
|
"Filter": "筛选",
|
||||||
|
"Connect data blocks": "连接数据区块",
|
||||||
"Action type": "操作类型",
|
"Action type": "操作类型",
|
||||||
"Actions": "操作",
|
"Actions": "操作",
|
||||||
"Insert": "新增",
|
"Insert": "新增",
|
||||||
@ -175,6 +179,7 @@ export default {
|
|||||||
"Association fields filter": "关系筛选",
|
"Association fields filter": "关系筛选",
|
||||||
"PK & FK fields": "主外键字段",
|
"PK & FK fields": "主外键字段",
|
||||||
"Association fields": "关系字段",
|
"Association fields": "关系字段",
|
||||||
|
"Optional fields": "可选字段",
|
||||||
"System fields": "系统字段",
|
"System fields": "系统字段",
|
||||||
"General fields": "普通字段",
|
"General fields": "普通字段",
|
||||||
"Parent collection fields": "父表字段",
|
"Parent collection fields": "父表字段",
|
||||||
@ -260,6 +265,7 @@ export default {
|
|||||||
"Edit block title": "编辑区块标题",
|
"Edit block title": "编辑区块标题",
|
||||||
"Block title": "区块标题",
|
"Block title": "区块标题",
|
||||||
"Pattern": "模式",
|
"Pattern": "模式",
|
||||||
|
"Operator": "运算符",
|
||||||
"Editable": "可编辑",
|
"Editable": "可编辑",
|
||||||
"Readonly": "只读(禁止编辑)",
|
"Readonly": "只读(禁止编辑)",
|
||||||
"Easy-reading": "只读(阅读模式)",
|
"Easy-reading": "只读(阅读模式)",
|
||||||
@ -446,6 +452,7 @@ export default {
|
|||||||
"Convert reference to duplicate": "模板引用转为复制",
|
"Convert reference to duplicate": "模板引用转为复制",
|
||||||
"Template name": "模板名称",
|
"Template name": "模板名称",
|
||||||
"Block type": "区块类型",
|
"Block type": "区块类型",
|
||||||
|
"No blocks to connect": "没有可连接的区块",
|
||||||
"Action column": "操作列",
|
"Action column": "操作列",
|
||||||
"Records per page": "每页显示数量",
|
"Records per page": "每页显示数量",
|
||||||
"(Fields only)": "(仅字段)",
|
"(Fields only)": "(仅字段)",
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
import { useFieldSchema } from '@formily/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useCollection } from '../../../collection-manager';
|
||||||
|
import { FilterBlockType } from '../../../filter-provider/utils';
|
||||||
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
|
|
||||||
|
export const AssociationFilterBlockDesigner = () => {
|
||||||
|
const { name, title } = useCollection();
|
||||||
|
const template = useSchemaTemplate();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||||
|
<SchemaSettings.BlockTitleItem />
|
||||||
|
<SchemaSettings.Template componentName={'FilterCollapse'} collectionName={name} resourceName={defaultResource} />
|
||||||
|
<SchemaSettings.ConnectDataBlocks type={FilterBlockType.COLLAPSE} emptyDescription={t('No blocks to connect')} />
|
||||||
|
<SchemaSettings.Divider />
|
||||||
|
<SchemaSettings.Remove
|
||||||
|
removeParentsIfNoChildren
|
||||||
|
breakRemoveOn={{
|
||||||
|
'x-component': 'Grid',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</GeneralSchemaDesigner>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,90 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useOptionalFieldList } from '../../../block-provider/hooks';
|
||||||
|
import { useAssociatedFields } from '../../../filter-provider/utils';
|
||||||
|
import { SchemaInitializer, SchemaInitializerItemOptions } from '../../../schema-initializer';
|
||||||
|
|
||||||
|
export const AssociationFilterFilterBlockInitializer = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const associatedFields = useAssociatedFields();
|
||||||
|
const optionalList = useOptionalFieldList();
|
||||||
|
const useProps = '{{useAssociationFilterBlockProps}}';
|
||||||
|
const children: SchemaInitializerItemOptions[] = associatedFields.map((field) => ({
|
||||||
|
type: 'item',
|
||||||
|
key: field.key,
|
||||||
|
title: field.uiSchema?.title,
|
||||||
|
component: 'AssociationFilterDesignerDisplayField',
|
||||||
|
schema: {
|
||||||
|
name: field.name,
|
||||||
|
title: field.uiSchema?.title,
|
||||||
|
type: 'void',
|
||||||
|
'x-designer': 'AssociationFilter.Item.Designer',
|
||||||
|
'x-component': 'AssociationFilter.Item',
|
||||||
|
'x-component-props': {
|
||||||
|
fieldNames: {
|
||||||
|
label: field.targetKey || 'id',
|
||||||
|
},
|
||||||
|
useProps,
|
||||||
|
},
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
const optionalChildren: SchemaInitializerItemOptions[] = optionalList.map((field) => ({
|
||||||
|
type: 'item',
|
||||||
|
key: field.key,
|
||||||
|
title: field.uiSchema.title,
|
||||||
|
component: 'AssociationFilterDesignerDisplayField',
|
||||||
|
schema: {
|
||||||
|
name: field.name,
|
||||||
|
title: field.uiSchema.title,
|
||||||
|
interface: field.interface,
|
||||||
|
type: 'void',
|
||||||
|
'x-designer': 'AssociationFilter.Item.Designer',
|
||||||
|
'x-component': 'AssociationFilter.Item',
|
||||||
|
'x-component-props': {
|
||||||
|
fieldNames: {
|
||||||
|
label: field.name,
|
||||||
|
},
|
||||||
|
useProps,
|
||||||
|
},
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const associatedFieldGroup: SchemaInitializerItemOptions = {
|
||||||
|
type: 'itemGroup',
|
||||||
|
title: t('Association fields'),
|
||||||
|
children,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 可选项字段
|
||||||
|
const optionalFieldGroup: SchemaInitializerItemOptions = {
|
||||||
|
type: 'itemGroup',
|
||||||
|
title: t('Optional fields'),
|
||||||
|
children: optionalChildren,
|
||||||
|
};
|
||||||
|
|
||||||
|
const dividerItem: SchemaInitializerItemOptions = {
|
||||||
|
type: 'divider',
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteItem: SchemaInitializerItemOptions = {
|
||||||
|
type: 'item',
|
||||||
|
title: t('Delete'),
|
||||||
|
component: 'AssociationFilterDesignerDelete',
|
||||||
|
};
|
||||||
|
|
||||||
|
const items = [associatedFieldGroup, optionalFieldGroup];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaInitializer.Button
|
||||||
|
className={css`
|
||||||
|
margin-top: 16px;
|
||||||
|
`}
|
||||||
|
icon={'SettingOutlined'}
|
||||||
|
title={t('Configure fields')}
|
||||||
|
items={items}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -1,18 +1,14 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useCollection } from '../../../collection-manager';
|
import { useAssociatedFields } from '../../../filter-provider/utils';
|
||||||
import { SchemaInitializer, SchemaInitializerItemOptions } from '../../../schema-initializer';
|
import { SchemaInitializer, SchemaInitializerItemOptions } from '../../../schema-initializer';
|
||||||
|
|
||||||
export const AssociationFilterInitializer = () => {
|
export const AssociationFilterInitializer = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { fields } = useCollection();
|
const associatedFields = useAssociatedFields();
|
||||||
|
const useProps = '{{useAssociationFilterProps}}';
|
||||||
const associatedFields = fields.filter((field) =>
|
const children: SchemaInitializerItemOptions[] = associatedFields.map((field) => ({
|
||||||
['o2o', 'oho', 'obo', 'm2o', 'createdBy', 'updatedBy', 'o2m', 'm2m', 'linkTo'].includes(field.interface),
|
|
||||||
);
|
|
||||||
|
|
||||||
const items: SchemaInitializerItemOptions[] = associatedFields.map((field) => ({
|
|
||||||
type: 'item',
|
type: 'item',
|
||||||
key: field.key,
|
key: field.key,
|
||||||
title: field.uiSchema?.title,
|
title: field.uiSchema?.title,
|
||||||
@ -27,6 +23,7 @@ export const AssociationFilterInitializer = () => {
|
|||||||
fieldNames: {
|
fieldNames: {
|
||||||
label: field.targetKey || 'id',
|
label: field.targetKey || 'id',
|
||||||
},
|
},
|
||||||
|
useProps,
|
||||||
},
|
},
|
||||||
properties: {},
|
properties: {},
|
||||||
},
|
},
|
||||||
@ -35,7 +32,7 @@ export const AssociationFilterInitializer = () => {
|
|||||||
const associatedFieldGroup: SchemaInitializerItemOptions = {
|
const associatedFieldGroup: SchemaInitializerItemOptions = {
|
||||||
type: 'itemGroup',
|
type: 'itemGroup',
|
||||||
title: t('Association fields'),
|
title: t('Association fields'),
|
||||||
children: items,
|
children,
|
||||||
};
|
};
|
||||||
|
|
||||||
const dividerItem: SchemaInitializerItemOptions = {
|
const dividerItem: SchemaInitializerItemOptions = {
|
||||||
@ -48,6 +45,8 @@ export const AssociationFilterInitializer = () => {
|
|||||||
component: 'AssociationFilterDesignerDelete',
|
component: 'AssociationFilterDesignerDelete',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const items = [associatedFieldGroup, dividerItem, deleteItem];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Button
|
<SchemaInitializer.Button
|
||||||
className={css`
|
className={css`
|
||||||
@ -55,7 +54,7 @@ export const AssociationFilterInitializer = () => {
|
|||||||
`}
|
`}
|
||||||
icon={'SettingOutlined'}
|
icon={'SettingOutlined'}
|
||||||
title={t('Configure fields')}
|
title={t('Configure fields')}
|
||||||
items={[associatedFieldGroup, dividerItem, deleteItem]}
|
items={items}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -4,9 +4,7 @@ import { useFieldSchema } from '@formily/react';
|
|||||||
import { Col, Collapse, Input, Row, Tree } from 'antd';
|
import { Col, Collapse, Input, Row, Tree } from 'antd';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import React, { ChangeEvent, MouseEvent, useState } from 'react';
|
import React, { ChangeEvent, MouseEvent, useState } from 'react';
|
||||||
import { useRequest } from '../../../api-client';
|
import { useAssociationFilterProps } from '../../../block-provider/hooks';
|
||||||
import { useBlockRequestContext } from '../../../block-provider';
|
|
||||||
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
|
||||||
import { SortableItem } from '../../common';
|
import { SortableItem } from '../../common';
|
||||||
import { useCompile, useDesigner } from '../../hooks';
|
import { useCompile, useDesigner } from '../../hooks';
|
||||||
import { AssociationFilter } from './AssociationFilter';
|
import { AssociationFilter } from './AssociationFilter';
|
||||||
@ -16,49 +14,40 @@ const { Panel } = Collapse;
|
|||||||
export const AssociationFilterItem = (props) => {
|
export const AssociationFilterItem = (props) => {
|
||||||
const collectionField = AssociationFilter.useAssociationField();
|
const collectionField = AssociationFilter.useAssociationField();
|
||||||
|
|
||||||
if (!collectionField) {
|
// 把一些可定制的状态通过 hook 提取出去了,为了兼容之前添加的 Table 区块,这里加了个默认值
|
||||||
return null;
|
const { useProps = useAssociationFilterProps } = props;
|
||||||
}
|
|
||||||
|
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const Designer = useDesigner();
|
const Designer = useDesigner();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const { service, props: blockProps } = useBlockRequestContext();
|
|
||||||
|
const {
|
||||||
|
list,
|
||||||
|
onSelected,
|
||||||
|
handleSearchInput: _handleSearchInput,
|
||||||
|
params,
|
||||||
|
run,
|
||||||
|
valueKey: _valueKey,
|
||||||
|
labelKey: _labelKey,
|
||||||
|
} = useProps();
|
||||||
|
|
||||||
const [searchVisible, setSearchVisible] = useState(false);
|
const [searchVisible, setSearchVisible] = useState(false);
|
||||||
|
|
||||||
const collectionFieldName = collectionField.name;
|
const valueKey = _valueKey || collectionField?.targetKey || 'id';
|
||||||
|
const labelKey = _labelKey || fieldSchema['x-component-props']?.fieldNames?.label || valueKey;
|
||||||
const valueKey = collectionField?.targetKey || 'id';
|
|
||||||
const labelKey = fieldSchema['x-component-props']?.fieldNames?.label || valueKey;
|
|
||||||
|
|
||||||
const fieldNames = {
|
const fieldNames = {
|
||||||
title: labelKey || valueKey,
|
title: labelKey || valueKey,
|
||||||
key: valueKey,
|
key: valueKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data, params, loading, run } = useRequest(
|
|
||||||
{
|
|
||||||
resource: collectionField.target,
|
|
||||||
action: 'list',
|
|
||||||
params: {
|
|
||||||
fields: [labelKey, valueKey],
|
|
||||||
pageSize: 200,
|
|
||||||
page: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
refreshDeps: [labelKey, valueKey],
|
|
||||||
debounceWait: 300,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const treeData = data?.data || [];
|
|
||||||
|
|
||||||
const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
|
const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
|
||||||
const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
|
const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
|
||||||
const [autoExpandParent, setAutoExpandParent] = useState<boolean>(true);
|
const [autoExpandParent, setAutoExpandParent] = useState<boolean>(true);
|
||||||
|
|
||||||
|
if (!collectionField) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const onExpand = (expandedKeysValue: React.Key[]) => {
|
const onExpand = (expandedKeysValue: React.Key[]) => {
|
||||||
setExpandedKeys(expandedKeysValue);
|
setExpandedKeys(expandedKeysValue);
|
||||||
setAutoExpandParent(false);
|
setAutoExpandParent(false);
|
||||||
@ -66,26 +55,7 @@ export const AssociationFilterItem = (props) => {
|
|||||||
|
|
||||||
const onSelect = (selectedKeysValue: React.Key[]) => {
|
const onSelect = (selectedKeysValue: React.Key[]) => {
|
||||||
setSelectedKeys(selectedKeysValue);
|
setSelectedKeys(selectedKeysValue);
|
||||||
|
onSelected(selectedKeysValue);
|
||||||
const filters = service.params?.[1]?.filters || {};
|
|
||||||
|
|
||||||
if (selectedKeysValue.length) {
|
|
||||||
filters[`af.${collectionFieldName}`] = {
|
|
||||||
[`${collectionFieldName}.${valueKey}.$in`]: selectedKeysValue,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
delete filters[`af.${collectionFieldName}`];
|
|
||||||
}
|
|
||||||
|
|
||||||
service.run(
|
|
||||||
{
|
|
||||||
...service.params?.[0],
|
|
||||||
pageSize: 200,
|
|
||||||
page: 1,
|
|
||||||
filter: mergeFilter([...Object.values(filters), blockProps?.params?.filter]),
|
|
||||||
},
|
|
||||||
{ filters },
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearchToggle = (e: MouseEvent) => {
|
const handleSearchToggle = (e: MouseEvent) => {
|
||||||
@ -105,12 +75,7 @@ export const AssociationFilterItem = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSearchInput = (e: ChangeEvent<any>) => {
|
const handleSearchInput = (e: ChangeEvent<any>) => {
|
||||||
run({
|
_handleSearchInput(e);
|
||||||
...params?.[0],
|
|
||||||
filter: {
|
|
||||||
[`${labelKey}.$includes`]: e.target.value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const title = fieldSchema.title ?? collectionField.uiSchema?.title;
|
const title = fieldSchema.title ?? collectionField.uiSchema?.title;
|
||||||
@ -258,7 +223,7 @@ export const AssociationFilterItem = (props) => {
|
|||||||
onExpand={onExpand}
|
onExpand={onExpand}
|
||||||
expandedKeys={expandedKeys}
|
expandedKeys={expandedKeys}
|
||||||
autoExpandParent={autoExpandParent}
|
autoExpandParent={autoExpandParent}
|
||||||
treeData={treeData}
|
treeData={list}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
fieldNames={fieldNames}
|
fieldNames={fieldNames}
|
||||||
titleRender={(node) => compile(node[labelKey])}
|
titleRender={(node) => compile(node[labelKey])}
|
||||||
|
@ -6,15 +6,18 @@ import { useCollection } from '../../../collection-manager';
|
|||||||
import { useSchemaInitializer } from '../../../schema-initializer';
|
import { useSchemaInitializer } from '../../../schema-initializer';
|
||||||
import { DndContext, SortableItem } from '../../common';
|
import { DndContext, SortableItem } from '../../common';
|
||||||
import { useDesigner } from '../../hooks';
|
import { useDesigner } from '../../hooks';
|
||||||
|
import { AssociationFilterBlockDesigner } from './AssociationFilter.BlockDesigner';
|
||||||
|
import { AssociationFilterFilterBlockInitializer } from './AssociationFilter.FilterBlockInitializer';
|
||||||
import { AssociationFilterInitializer } from './AssociationFilter.Initializer';
|
import { AssociationFilterInitializer } from './AssociationFilter.Initializer';
|
||||||
import { AssociationFilterItem } from './AssociationFilter.Item';
|
import { AssociationFilterItem } from './AssociationFilter.Item';
|
||||||
import { AssociationFilterItemDesigner } from './AssociationFilter.Item.Designer';
|
import { AssociationFilterItemDesigner } from './AssociationFilter.Item.Designer';
|
||||||
|
import { AssociationFilterProvider } from './AssociationFilterProvider';
|
||||||
|
|
||||||
export const AssociationFilter = (props) => {
|
export const AssociationFilter = (props) => {
|
||||||
const Designer = useDesigner();
|
const Designer = useDesigner();
|
||||||
const filedSchema = useFieldSchema();
|
const filedSchema = useFieldSchema();
|
||||||
|
|
||||||
const { exists, render } = useSchemaInitializer(filedSchema['x-initializer']);
|
const { render } = useSchemaInitializer(filedSchema['x-initializer']);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DndContext>
|
<DndContext>
|
||||||
@ -75,11 +78,14 @@ export const AssociationFilter = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AssociationFilter.Provider = AssociationFilterProvider;
|
||||||
AssociationFilter.Initializer = AssociationFilterInitializer;
|
AssociationFilter.Initializer = AssociationFilterInitializer;
|
||||||
|
AssociationFilter.FilterBlockInitializer = AssociationFilterFilterBlockInitializer;
|
||||||
AssociationFilter.Item = AssociationFilterItem as typeof AssociationFilterItem & {
|
AssociationFilter.Item = AssociationFilterItem as typeof AssociationFilterItem & {
|
||||||
Designer: typeof AssociationFilterItemDesigner;
|
Designer: typeof AssociationFilterItemDesigner;
|
||||||
};
|
};
|
||||||
AssociationFilter.Item.Designer = AssociationFilterItemDesigner;
|
AssociationFilter.Item.Designer = AssociationFilterItemDesigner;
|
||||||
|
AssociationFilter.BlockDesigner = AssociationFilterBlockDesigner;
|
||||||
|
|
||||||
AssociationFilter.useAssociationField = () => {
|
AssociationFilter.useAssociationField = () => {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
// TODO: 因他们之间功能相同,所以先直接复用,后续有需要再拆分
|
||||||
|
export { TableBlockProvider as AssociationFilterProvider } from '../../../block-provider';
|
@ -0,0 +1,5 @@
|
|||||||
|
import { CollectionFieldOptions } from '../../../collection-manager';
|
||||||
|
|
||||||
|
export const getTargetKey = (field?: CollectionFieldOptions) => {
|
||||||
|
return field?.targetKey || 'id';
|
||||||
|
};
|
@ -84,6 +84,7 @@ const InternalAssociationSelect = connect(
|
|||||||
interface AssociationSelectInterface {
|
interface AssociationSelectInterface {
|
||||||
(props: any): React.ReactElement;
|
(props: any): React.ReactElement;
|
||||||
Designer: React.FC;
|
Designer: React.FC;
|
||||||
|
FilterDesigner: React.FC;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AssociationSelect = InternalAssociationSelect as unknown as AssociationSelectInterface;
|
export const AssociationSelect = InternalAssociationSelect as unknown as AssociationSelectInterface;
|
||||||
@ -680,4 +681,481 @@ AssociationSelect.Designer = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于筛选表单区块
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
AssociationSelect.FilterDesigner = () => {
|
||||||
|
const { getCollectionFields, getInterface, getCollectionJoinField } = useCollectionManager();
|
||||||
|
const { getField } = useCollection();
|
||||||
|
const { form } = useFormBlockContext();
|
||||||
|
const field = useField<Field>();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const tk = useFilterByTk();
|
||||||
|
const {} = useCollection();
|
||||||
|
const { dn, refresh, insertAdjacent } = useDesignable();
|
||||||
|
const compile = useCompile();
|
||||||
|
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
||||||
|
const fieldComponentOptions = useFieldComponentOptions();
|
||||||
|
const isSubFormAssocitionField = field.address.segments.includes('__form_grid');
|
||||||
|
const interfaceConfig = getInterface(collectionField?.interface);
|
||||||
|
const validateSchema = interfaceConfig?.['validateSchema']?.(fieldSchema);
|
||||||
|
const originalTitle = collectionField?.uiSchema?.title;
|
||||||
|
const targetFields = collectionField?.target ? getCollectionFields(collectionField.target) : [];
|
||||||
|
const initialValue = {
|
||||||
|
title: field.title === originalTitle ? undefined : field.title,
|
||||||
|
};
|
||||||
|
const sortFields = useSortFields(collectionField?.target);
|
||||||
|
|
||||||
|
const defaultSort = field.componentProps?.service?.params?.sort || [];
|
||||||
|
const defaultFilter = field.componentProps?.service?.params?.filter || {};
|
||||||
|
const dataSource = useCollectionFilterOptions(collectionField?.target);
|
||||||
|
|
||||||
|
const sort = defaultSort?.map((item: string) => {
|
||||||
|
return item.startsWith('-')
|
||||||
|
? {
|
||||||
|
field: item.substring(1),
|
||||||
|
direction: 'desc',
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
field: item,
|
||||||
|
direction: 'asc',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (!field.readPretty) {
|
||||||
|
initialValue['required'] = field.required;
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = targetFields
|
||||||
|
.filter((field) => !field?.target && field.type !== 'boolean')
|
||||||
|
.map((field) => ({
|
||||||
|
value: field?.name,
|
||||||
|
label: compile(field?.uiSchema?.title) || field?.name,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let readOnlyMode = 'editable';
|
||||||
|
if (fieldSchema['x-disabled'] === true) {
|
||||||
|
readOnlyMode = 'readonly';
|
||||||
|
}
|
||||||
|
if (fieldSchema['x-read-pretty'] === true) {
|
||||||
|
readOnlyMode = 'read-pretty';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GeneralSchemaDesigner>
|
||||||
|
{collectionField && (
|
||||||
|
<SchemaSettings.ModalItem
|
||||||
|
key="edit-field-title"
|
||||||
|
title={t('Edit field title')}
|
||||||
|
schema={
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
title: t('Edit field title'),
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
title: t('Field title'),
|
||||||
|
default: field?.title,
|
||||||
|
description: `${t('Original field title: ')}${collectionField?.uiSchema?.title}`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ISchema
|
||||||
|
}
|
||||||
|
onSubmit={({ title }) => {
|
||||||
|
if (title) {
|
||||||
|
field.title = title;
|
||||||
|
fieldSchema.title = title;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
'x-uid': fieldSchema['x-uid'],
|
||||||
|
title: fieldSchema.title,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
dn.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!field.readPretty && (
|
||||||
|
<SchemaSettings.ModalItem
|
||||||
|
key="edit-description"
|
||||||
|
title={t('Edit description')}
|
||||||
|
schema={
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
title: t('Edit description'),
|
||||||
|
properties: {
|
||||||
|
description: {
|
||||||
|
// title: t('Description'),
|
||||||
|
default: field?.description,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ISchema
|
||||||
|
}
|
||||||
|
onSubmit={({ description }) => {
|
||||||
|
field.description = description;
|
||||||
|
fieldSchema.description = description;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
'x-uid': fieldSchema['x-uid'],
|
||||||
|
description: fieldSchema.description,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dn.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{field.readPretty && (
|
||||||
|
<SchemaSettings.ModalItem
|
||||||
|
key="edit-tooltip"
|
||||||
|
title={t('Edit tooltip')}
|
||||||
|
schema={
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
title: t('Edit description'),
|
||||||
|
properties: {
|
||||||
|
tooltip: {
|
||||||
|
default: fieldSchema?.['x-decorator-props']?.tooltip,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ISchema
|
||||||
|
}
|
||||||
|
onSubmit={({ tooltip }) => {
|
||||||
|
field.decoratorProps.tooltip = tooltip;
|
||||||
|
fieldSchema['x-decorator-props'] = fieldSchema['x-decorator-props'] || {};
|
||||||
|
fieldSchema['x-decorator-props']['tooltip'] = tooltip;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
'x-uid': fieldSchema['x-uid'],
|
||||||
|
'x-decorator-props': fieldSchema['x-decorator-props'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dn.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{form && !form?.readPretty && validateSchema && (
|
||||||
|
<SchemaSettings.ModalItem
|
||||||
|
title={t('Set validation rules')}
|
||||||
|
components={{ ArrayCollapse, FormLayout }}
|
||||||
|
schema={
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
title: t('Set validation rules'),
|
||||||
|
properties: {
|
||||||
|
rules: {
|
||||||
|
type: 'array',
|
||||||
|
default: fieldSchema?.['x-validator'],
|
||||||
|
'x-component': 'ArrayCollapse',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component-props': {
|
||||||
|
accordion: true,
|
||||||
|
},
|
||||||
|
maxItems: 3,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
'x-component': 'ArrayCollapse.CollapsePanel',
|
||||||
|
'x-component-props': {
|
||||||
|
header: '{{ t("Validation rule") }}',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
index: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ArrayCollapse.Index',
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'FormLayout',
|
||||||
|
'x-component-props': {
|
||||||
|
labelStyle: {
|
||||||
|
marginTop: '6px',
|
||||||
|
},
|
||||||
|
labelCol: 8,
|
||||||
|
wrapperCol: 16,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
...validateSchema,
|
||||||
|
message: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{ t("Error message") }}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
'x-component-props': {
|
||||||
|
autoSize: {
|
||||||
|
minRows: 2,
|
||||||
|
maxRows: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remove: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ArrayCollapse.Remove',
|
||||||
|
},
|
||||||
|
moveUp: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ArrayCollapse.MoveUp',
|
||||||
|
},
|
||||||
|
moveDown: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ArrayCollapse.MoveDown',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
add: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Add validation rule") }}',
|
||||||
|
'x-component': 'ArrayCollapse.Addition',
|
||||||
|
'x-reactions': {
|
||||||
|
dependencies: ['rules'],
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
disabled: '{{$deps[0].length >= 3}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ISchema
|
||||||
|
}
|
||||||
|
onSubmit={(v) => {
|
||||||
|
const rules = [];
|
||||||
|
for (const rule of v.rules) {
|
||||||
|
rules.push(_.pickBy(rule, _.identity));
|
||||||
|
}
|
||||||
|
const schema = {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
};
|
||||||
|
// return;
|
||||||
|
// if (['number'].includes(collectionField?.interface) && collectionField?.uiSchema?.['x-component-props']?.['stringMode'] === true) {
|
||||||
|
// rules['numberStringMode'] = true;
|
||||||
|
// }
|
||||||
|
if (['percent'].includes(collectionField?.interface)) {
|
||||||
|
for (const rule of rules) {
|
||||||
|
if (!!rule.maxValue || !!rule.minValue) {
|
||||||
|
rule['percentMode'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule.percentFormat) {
|
||||||
|
rule['percentFormats'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const concatValidator = _.concat([], collectionField?.uiSchema?.['x-validator'] || [], rules);
|
||||||
|
field.validator = concatValidator;
|
||||||
|
fieldSchema['x-validator'] = rules;
|
||||||
|
schema['x-validator'] = rules;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{form && !form?.readPretty && collectionField?.uiSchema?.type && (
|
||||||
|
<SchemaSettings.ModalItem
|
||||||
|
title={t('Set default value')}
|
||||||
|
components={{ ArrayCollapse, FormLayout }}
|
||||||
|
schema={
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
title: t('Set default value'),
|
||||||
|
properties: {
|
||||||
|
default: {
|
||||||
|
...collectionField.uiSchema,
|
||||||
|
name: 'default',
|
||||||
|
title: t('Default value'),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
default: fieldSchema.default || collectionField.defaultValue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ISchema
|
||||||
|
}
|
||||||
|
onSubmit={(v) => {
|
||||||
|
const schema: ISchema = {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
};
|
||||||
|
if (field.value !== v.default) {
|
||||||
|
field.value = v.default;
|
||||||
|
}
|
||||||
|
fieldSchema.default = v.default;
|
||||||
|
schema.default = v.default;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<SchemaSettings.ModalItem
|
||||||
|
title={t('Set the data scope')}
|
||||||
|
schema={
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
title: t('Set the data scope'),
|
||||||
|
properties: {
|
||||||
|
filter: {
|
||||||
|
default: defaultFilter,
|
||||||
|
// title: '数据范围',
|
||||||
|
enum: dataSource,
|
||||||
|
'x-component': 'Filter',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ISchema
|
||||||
|
}
|
||||||
|
onSubmit={({ filter }) => {
|
||||||
|
_.set(field.componentProps, 'service.params.filter', filter);
|
||||||
|
fieldSchema['x-component-props'] = field.componentProps;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
'x-component-props': field.componentProps,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<SchemaSettings.ModalItem
|
||||||
|
title={t('Set default sorting rules')}
|
||||||
|
components={{ ArrayItems }}
|
||||||
|
schema={
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
title: t('Set default sorting rules'),
|
||||||
|
properties: {
|
||||||
|
sort: {
|
||||||
|
type: 'array',
|
||||||
|
default: sort,
|
||||||
|
'x-component': 'ArrayItems',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
space: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Space',
|
||||||
|
properties: {
|
||||||
|
sort: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ArrayItems.SortHandle',
|
||||||
|
},
|
||||||
|
field: {
|
||||||
|
type: 'string',
|
||||||
|
enum: sortFields,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
width: 260,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
direction: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Radio.Group',
|
||||||
|
'x-component-props': {
|
||||||
|
optionType: 'button',
|
||||||
|
},
|
||||||
|
enum: [
|
||||||
|
{
|
||||||
|
label: t('ASC'),
|
||||||
|
value: 'asc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('DESC'),
|
||||||
|
value: 'desc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
remove: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ArrayItems.Remove',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
add: {
|
||||||
|
type: 'void',
|
||||||
|
title: t('Add sort field'),
|
||||||
|
'x-component': 'ArrayItems.Addition',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ISchema
|
||||||
|
}
|
||||||
|
onSubmit={({ sort }) => {
|
||||||
|
const sortArr = sort.map((item) => {
|
||||||
|
return item.direction === 'desc' ? `-${item.field}` : item.field;
|
||||||
|
});
|
||||||
|
|
||||||
|
_.set(field.componentProps, 'service.params.sort', sortArr);
|
||||||
|
fieldSchema['x-component-props'] = field.componentProps;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
'x-component-props': field.componentProps,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{collectionField?.target && ['CollectionField', 'AssociationSelect'].includes(fieldSchema['x-component']) && (
|
||||||
|
<SchemaSettings.SelectItem
|
||||||
|
key="title-field"
|
||||||
|
title={t('Title field')}
|
||||||
|
options={options}
|
||||||
|
value={field?.componentProps?.fieldNames?.label}
|
||||||
|
onChange={(label) => {
|
||||||
|
const schema = {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
};
|
||||||
|
const fieldNames = {
|
||||||
|
...collectionField?.uiSchema?.['x-component-props']?.['fieldNames'],
|
||||||
|
...field.componentProps.fieldNames,
|
||||||
|
label,
|
||||||
|
};
|
||||||
|
field.componentProps.fieldNames = fieldNames;
|
||||||
|
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||||
|
fieldSchema['x-component-props']['fieldNames'] = fieldNames;
|
||||||
|
schema['x-component-props'] = fieldSchema['x-component-props'];
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
dn.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{collectionField && <SchemaSettings.Divider />}
|
||||||
|
<SchemaSettings.Remove
|
||||||
|
key="remove"
|
||||||
|
removeParentsIfNoChildren
|
||||||
|
confirm={{
|
||||||
|
title: t('Delete field'),
|
||||||
|
}}
|
||||||
|
breakRemoveOn={{
|
||||||
|
'x-component': 'Grid',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</GeneralSchemaDesigner>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default AssociationSelect;
|
export default AssociationSelect;
|
||||||
|
@ -9,6 +9,7 @@ export const CardItem: React.FC = (props) => {
|
|||||||
const template = useSchemaTemplate();
|
const template = useSchemaTemplate();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const templateKey = fieldSchema['x-template-key'];
|
const templateKey = fieldSchema['x-template-key'];
|
||||||
|
|
||||||
return templateKey && !template ? null : (
|
return templateKey && !template ? null : (
|
||||||
<BlockItem className={'noco-card-item'}>
|
<BlockItem className={'noco-card-item'}>
|
||||||
<Card style={{ marginBottom: 24 }} bordered={false} {...restProps}>
|
<Card style={{ marginBottom: 24 }} bordered={false} {...restProps}>
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
import { FormV2 } from '../form-v2';
|
||||||
|
|
||||||
|
export const Details = FormV2;
|
@ -0,0 +1 @@
|
|||||||
|
export * from './Details';
|
@ -92,7 +92,7 @@ export const useFilterActionProps = () => {
|
|||||||
const { name } = useCollection();
|
const { name } = useCollection();
|
||||||
const options = useFilterOptions(name);
|
const options = useFilterOptions(name);
|
||||||
const { service, props } = useBlockRequestContext();
|
const { service, props } = useBlockRequestContext();
|
||||||
return useFilterFieldProps({ options, service, params: props.params });
|
return useFilterFieldProps({ options, service, params: props?.params });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useFilterFieldProps = ({ options, service, params }) => {
|
export const useFilterFieldProps = ({ options, service, params }) => {
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
import { useFieldSchema } from '@formily/react';
|
||||||
|
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前字段所支持的操作符列表
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const useOperatorList = () => {
|
||||||
|
const schema = useFieldSchema();
|
||||||
|
const fieldInterface = schema['x-designer-props']?.interface;
|
||||||
|
const { name } = useCollection();
|
||||||
|
const { getCollectionFields, getInterface } = useCollectionManager();
|
||||||
|
const collectionFields = getCollectionFields(name);
|
||||||
|
|
||||||
|
if (fieldInterface) {
|
||||||
|
return getInterface(fieldInterface)?.filterable?.operators || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const field = collectionFields.find((item) => item.name === schema.name);
|
||||||
|
return getInterface(field?.interface)?.filterable?.operators || [];
|
||||||
|
};
|
@ -0,0 +1,45 @@
|
|||||||
|
import { useFieldSchema } from '@formily/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
|
import {
|
||||||
|
EditComponent,
|
||||||
|
EditDescription,
|
||||||
|
EditOperator,
|
||||||
|
EditTitle,
|
||||||
|
EditTitleField,
|
||||||
|
EditTooltip,
|
||||||
|
EditValidationRules,
|
||||||
|
} from './SchemaSettingOptions';
|
||||||
|
|
||||||
|
export const FilterFormDesigner = () => {
|
||||||
|
const { getCollectionJoinField } = useCollectionManager();
|
||||||
|
const { getField } = useCollection();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GeneralSchemaDesigner>
|
||||||
|
<EditTitle />
|
||||||
|
<EditDescription />
|
||||||
|
<EditTooltip />
|
||||||
|
<EditValidationRules />
|
||||||
|
<EditComponent />
|
||||||
|
<EditOperator />
|
||||||
|
<EditTitleField />
|
||||||
|
{collectionField ? <SchemaSettings.Divider /> : null}
|
||||||
|
<SchemaSettings.Remove
|
||||||
|
key="remove"
|
||||||
|
removeParentsIfNoChildren
|
||||||
|
confirm={{
|
||||||
|
title: t('Delete field'),
|
||||||
|
}}
|
||||||
|
breakRemoveOn={{
|
||||||
|
'x-component': 'Grid',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</GeneralSchemaDesigner>
|
||||||
|
);
|
||||||
|
};
|
@ -13,6 +13,8 @@ import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'
|
|||||||
import { useCompile, useDesignable, useFieldComponentOptions } from '../../hooks';
|
import { useCompile, useDesignable, useFieldComponentOptions } from '../../hooks';
|
||||||
import { BlockItem } from '../block-item';
|
import { BlockItem } from '../block-item';
|
||||||
import { HTMLEncode } from '../input/shared';
|
import { HTMLEncode } from '../input/shared';
|
||||||
|
import { FilterFormDesigner } from './FormItem.FilterFormDesigner';
|
||||||
|
import { useEnsureOperatorsValid } from './SchemaSettingOptions';
|
||||||
|
|
||||||
const divWrap = (schema: ISchema) => {
|
const divWrap = (schema: ISchema) => {
|
||||||
return {
|
return {
|
||||||
@ -25,9 +27,12 @@ const divWrap = (schema: ISchema) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const FormItem: any = observer((props: any) => {
|
export const FormItem: any = observer((props: any) => {
|
||||||
|
useEnsureOperatorsValid();
|
||||||
|
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const ctx = useContext(BlockRequestContext);
|
const ctx = useContext(BlockRequestContext);
|
||||||
const schema = useFieldSchema();
|
const schema = useFieldSchema();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ctx?.block === 'form') {
|
if (ctx?.block === 'form') {
|
||||||
ctx.field.data = ctx.field.data || {};
|
ctx.field.data = ctx.field.data || {};
|
||||||
@ -62,8 +67,8 @@ export const FormItem: any = observer((props: any) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
FormItem.Designer = (props) => {
|
FormItem.Designer = () => {
|
||||||
const { getCollectionFields, getCollection, getInterface, getCollectionJoinField } = useCollectionManager();
|
const { getCollectionFields, getInterface, getCollectionJoinField } = useCollectionManager();
|
||||||
const { getField } = useCollection();
|
const { getField } = useCollection();
|
||||||
const tk = useFilterByTk();
|
const tk = useFilterByTk();
|
||||||
const { form } = useFormBlockContext();
|
const { form } = useFormBlockContext();
|
||||||
@ -537,3 +542,5 @@ FormItem.Designer = (props) => {
|
|||||||
</GeneralSchemaDesigner>
|
</GeneralSchemaDesigner>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FormItem.FilterFormDesigner = FilterFormDesigner;
|
||||||
|
@ -0,0 +1,598 @@
|
|||||||
|
import { ArrayCollapse, FormLayout } from '@formily/antd';
|
||||||
|
import { Field } from '@formily/core';
|
||||||
|
import { ISchema, Schema, useField, useFieldSchema } from '@formily/react';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useFilterByTk, useFormBlockContext } from '../../../block-provider';
|
||||||
|
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
|
import { SchemaSettings } from '../../../schema-settings';
|
||||||
|
import { useCompile, useDesignable, useFieldComponentOptions } from '../../hooks';
|
||||||
|
import { useOperatorList } from '../filter/useOperators';
|
||||||
|
|
||||||
|
export const findFilterOperators = (schema: Schema) => {
|
||||||
|
while (schema) {
|
||||||
|
if (schema['x-filter-operators']) {
|
||||||
|
return {
|
||||||
|
operators: schema['x-filter-operators'],
|
||||||
|
uid: schema['x-uid'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
schema = schema.parent;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
const divWrap = (schema: ISchema) => {
|
||||||
|
return {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'div',
|
||||||
|
properties: {
|
||||||
|
[schema.name || uid()]: schema,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditTitle = () => {
|
||||||
|
const { getCollectionJoinField } = useCollectionManager();
|
||||||
|
const { getField } = useCollection();
|
||||||
|
const field = useField<Field>();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
||||||
|
|
||||||
|
return collectionField ? (
|
||||||
|
<SchemaSettings.ModalItem
|
||||||
|
key="edit-field-title"
|
||||||
|
title={t('Edit field title')}
|
||||||
|
schema={
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
title: t('Edit field title'),
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
title: t('Field title'),
|
||||||
|
default: field?.title,
|
||||||
|
description: `${t('Original field title: ')}${collectionField?.uiSchema?.title}`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ISchema
|
||||||
|
}
|
||||||
|
onSubmit={({ title }) => {
|
||||||
|
if (title) {
|
||||||
|
field.title = title;
|
||||||
|
fieldSchema.title = title;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
'x-uid': fieldSchema['x-uid'],
|
||||||
|
title: fieldSchema.title,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
dn.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditDescription = () => {
|
||||||
|
const field = useField<Field>();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
|
||||||
|
return !field.readPretty ? (
|
||||||
|
<SchemaSettings.ModalItem
|
||||||
|
key="edit-description"
|
||||||
|
title={t('Edit description')}
|
||||||
|
schema={
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
title: t('Edit description'),
|
||||||
|
properties: {
|
||||||
|
description: {
|
||||||
|
// title: t('Description'),
|
||||||
|
default: field?.description,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ISchema
|
||||||
|
}
|
||||||
|
onSubmit={({ description }) => {
|
||||||
|
field.description = description;
|
||||||
|
fieldSchema.description = description;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
'x-uid': fieldSchema['x-uid'],
|
||||||
|
description: fieldSchema.description,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dn.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditTooltip = () => {
|
||||||
|
const field = useField<Field>();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
|
||||||
|
return field.readPretty ? (
|
||||||
|
<SchemaSettings.ModalItem
|
||||||
|
key="edit-tooltip"
|
||||||
|
title={t('Edit tooltip')}
|
||||||
|
schema={
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
title: t('Edit description'),
|
||||||
|
properties: {
|
||||||
|
tooltip: {
|
||||||
|
default: fieldSchema?.['x-decorator-props']?.tooltip,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ISchema
|
||||||
|
}
|
||||||
|
onSubmit={({ tooltip }) => {
|
||||||
|
field.decoratorProps.tooltip = tooltip;
|
||||||
|
fieldSchema['x-decorator-props'] = fieldSchema['x-decorator-props'] || {};
|
||||||
|
fieldSchema['x-decorator-props']['tooltip'] = tooltip;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
'x-uid': fieldSchema['x-uid'],
|
||||||
|
'x-decorator-props': fieldSchema['x-decorator-props'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dn.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditRequired = () => {
|
||||||
|
const field = useField<Field>();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { dn, refresh } = useDesignable();
|
||||||
|
|
||||||
|
return !field.readPretty && fieldSchema['x-component'] !== 'FormField' ? (
|
||||||
|
<SchemaSettings.SwitchItem
|
||||||
|
key="required"
|
||||||
|
title={t('Required')}
|
||||||
|
checked={fieldSchema.required as boolean}
|
||||||
|
onChange={(required) => {
|
||||||
|
const schema = {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
};
|
||||||
|
field.required = required;
|
||||||
|
fieldSchema['required'] = required;
|
||||||
|
schema['required'] = required;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditValidationRules = () => {
|
||||||
|
const { getInterface, getCollectionJoinField } = useCollectionManager();
|
||||||
|
const { getField } = useCollection();
|
||||||
|
const { form } = useFormBlockContext();
|
||||||
|
const field = useField<Field>();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { dn, refresh } = useDesignable();
|
||||||
|
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
||||||
|
const interfaceConfig = getInterface(collectionField?.interface);
|
||||||
|
const validateSchema = interfaceConfig?.['validateSchema']?.(fieldSchema);
|
||||||
|
|
||||||
|
return form && !form?.readPretty && validateSchema ? (
|
||||||
|
<SchemaSettings.ModalItem
|
||||||
|
title={t('Set validation rules')}
|
||||||
|
components={{ ArrayCollapse, FormLayout }}
|
||||||
|
schema={
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
title: t('Set validation rules'),
|
||||||
|
properties: {
|
||||||
|
rules: {
|
||||||
|
type: 'array',
|
||||||
|
default: fieldSchema?.['x-validator'],
|
||||||
|
'x-component': 'ArrayCollapse',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component-props': {
|
||||||
|
accordion: true,
|
||||||
|
},
|
||||||
|
maxItems: 3,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
'x-component': 'ArrayCollapse.CollapsePanel',
|
||||||
|
'x-component-props': {
|
||||||
|
header: '{{ t("Validation rule") }}',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
index: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ArrayCollapse.Index',
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'FormLayout',
|
||||||
|
'x-component-props': {
|
||||||
|
labelStyle: {
|
||||||
|
marginTop: '6px',
|
||||||
|
},
|
||||||
|
labelCol: 8,
|
||||||
|
wrapperCol: 16,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
...validateSchema,
|
||||||
|
message: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{ t("Error message") }}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
'x-component-props': {
|
||||||
|
autoSize: {
|
||||||
|
minRows: 2,
|
||||||
|
maxRows: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remove: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ArrayCollapse.Remove',
|
||||||
|
},
|
||||||
|
moveUp: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ArrayCollapse.MoveUp',
|
||||||
|
},
|
||||||
|
moveDown: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ArrayCollapse.MoveDown',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
add: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Add validation rule") }}',
|
||||||
|
'x-component': 'ArrayCollapse.Addition',
|
||||||
|
'x-reactions': {
|
||||||
|
dependencies: ['rules'],
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
disabled: '{{$deps[0].length >= 3}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ISchema
|
||||||
|
}
|
||||||
|
onSubmit={(v) => {
|
||||||
|
const rules = [];
|
||||||
|
for (const rule of v.rules) {
|
||||||
|
rules.push(_.pickBy(rule, _.identity));
|
||||||
|
}
|
||||||
|
const schema = {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
};
|
||||||
|
// return;
|
||||||
|
// if (['number'].includes(collectionField?.interface) && collectionField?.uiSchema?.['x-component-props']?.['stringMode'] === true) {
|
||||||
|
// rules['numberStringMode'] = true;
|
||||||
|
// }
|
||||||
|
if (['percent'].includes(collectionField?.interface)) {
|
||||||
|
for (const rule of rules) {
|
||||||
|
if (!!rule.maxValue || !!rule.minValue) {
|
||||||
|
rule['percentMode'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule.percentFormat) {
|
||||||
|
rule['percentFormats'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const concatValidator = _.concat([], collectionField?.uiSchema?.['x-validator'] || [], rules);
|
||||||
|
field.validator = concatValidator;
|
||||||
|
fieldSchema['x-validator'] = rules;
|
||||||
|
schema['x-validator'] = rules;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditDefaultValue = () => {
|
||||||
|
const { getCollectionJoinField } = useCollectionManager();
|
||||||
|
const { getField } = useCollection();
|
||||||
|
const { form } = useFormBlockContext();
|
||||||
|
const field = useField<Field>();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { dn, refresh } = useDesignable();
|
||||||
|
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
||||||
|
|
||||||
|
return form && !form?.readPretty && collectionField?.uiSchema?.type ? (
|
||||||
|
<SchemaSettings.ModalItem
|
||||||
|
title={t('Set default value')}
|
||||||
|
components={{ ArrayCollapse, FormLayout }}
|
||||||
|
schema={
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
title: t('Set default value'),
|
||||||
|
properties: {
|
||||||
|
default: {
|
||||||
|
...collectionField.uiSchema,
|
||||||
|
name: 'default',
|
||||||
|
title: t('Default value'),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
default: fieldSchema.default || collectionField.defaultValue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ISchema
|
||||||
|
}
|
||||||
|
onSubmit={(v) => {
|
||||||
|
const schema: ISchema = {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
};
|
||||||
|
if (field.value !== v.default) {
|
||||||
|
field.value = v.default;
|
||||||
|
}
|
||||||
|
fieldSchema.default = v.default;
|
||||||
|
schema.default = v.default;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditComponent = () => {
|
||||||
|
const { getInterface, getCollectionJoinField } = useCollectionManager();
|
||||||
|
const { getField } = useCollection();
|
||||||
|
const tk = useFilterByTk();
|
||||||
|
const { form } = useFormBlockContext();
|
||||||
|
const field = useField<Field>();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { dn, insertAdjacent } = useDesignable();
|
||||||
|
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
||||||
|
const interfaceConfig = getInterface(collectionField?.interface);
|
||||||
|
const fieldComponentOptions = useFieldComponentOptions();
|
||||||
|
const isSubFormAssociationField = field.address.segments.includes('__form_grid');
|
||||||
|
|
||||||
|
return form && !isSubFormAssociationField && fieldComponentOptions ? (
|
||||||
|
<SchemaSettings.SelectItem
|
||||||
|
title={t('Field component')}
|
||||||
|
options={fieldComponentOptions}
|
||||||
|
value={fieldSchema['x-component']}
|
||||||
|
onChange={(type) => {
|
||||||
|
const schema: ISchema = {
|
||||||
|
name: collectionField.name,
|
||||||
|
type: 'void',
|
||||||
|
required: fieldSchema['required'],
|
||||||
|
description: fieldSchema['description'],
|
||||||
|
default: fieldSchema['default'],
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-designer': 'FormItem.Designer',
|
||||||
|
'x-component': type,
|
||||||
|
'x-validator': fieldSchema['x-validator'],
|
||||||
|
'x-collection-field': fieldSchema['x-collection-field'],
|
||||||
|
'x-decorator-props': fieldSchema['x-decorator-props'],
|
||||||
|
'x-component-props': {
|
||||||
|
...collectionField?.uiSchema?.['x-component-props'],
|
||||||
|
...fieldSchema['x-component-props'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
interfaceConfig?.schemaInitialize?.(schema, {
|
||||||
|
field: collectionField,
|
||||||
|
block: 'Form',
|
||||||
|
readPretty: field.readPretty,
|
||||||
|
action: tk ? 'get' : null,
|
||||||
|
});
|
||||||
|
|
||||||
|
insertAdjacent('beforeBegin', divWrap(schema), {
|
||||||
|
onSuccess: () => {
|
||||||
|
dn.remove(null, {
|
||||||
|
removeParentsIfNoChildren: true,
|
||||||
|
breakRemoveOn: {
|
||||||
|
'x-component': 'Grid',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditPattern = () => {
|
||||||
|
const { getCollectionJoinField } = useCollectionManager();
|
||||||
|
const { getField } = useCollection();
|
||||||
|
const { form } = useFormBlockContext();
|
||||||
|
const field = useField<Field>();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
||||||
|
let readOnlyMode = 'editable';
|
||||||
|
if (fieldSchema['x-disabled'] === true) {
|
||||||
|
readOnlyMode = 'readonly';
|
||||||
|
}
|
||||||
|
if (fieldSchema['x-read-pretty'] === true) {
|
||||||
|
readOnlyMode = 'read-pretty';
|
||||||
|
}
|
||||||
|
|
||||||
|
return form &&
|
||||||
|
!form?.readPretty &&
|
||||||
|
collectionField?.interface !== 'o2m' &&
|
||||||
|
fieldSchema?.['x-component-props']?.['pattern-disable'] != true ? (
|
||||||
|
<SchemaSettings.SelectItem
|
||||||
|
key="pattern"
|
||||||
|
title={t('Pattern')}
|
||||||
|
options={[
|
||||||
|
{ label: t('Editable'), value: 'editable' },
|
||||||
|
{ label: t('Readonly'), value: 'readonly' },
|
||||||
|
{ label: t('Easy-reading'), value: 'read-pretty' },
|
||||||
|
]}
|
||||||
|
value={readOnlyMode}
|
||||||
|
onChange={(v) => {
|
||||||
|
const schema: ISchema = {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (v) {
|
||||||
|
case 'readonly': {
|
||||||
|
fieldSchema['x-read-pretty'] = false;
|
||||||
|
fieldSchema['x-disabled'] = true;
|
||||||
|
schema['x-read-pretty'] = false;
|
||||||
|
schema['x-disabled'] = true;
|
||||||
|
field.readPretty = false;
|
||||||
|
field.disabled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'read-pretty': {
|
||||||
|
fieldSchema['x-read-pretty'] = true;
|
||||||
|
fieldSchema['x-disabled'] = false;
|
||||||
|
schema['x-read-pretty'] = true;
|
||||||
|
schema['x-disabled'] = false;
|
||||||
|
field.readPretty = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
fieldSchema['x-read-pretty'] = false;
|
||||||
|
fieldSchema['x-disabled'] = false;
|
||||||
|
schema['x-read-pretty'] = false;
|
||||||
|
schema['x-disabled'] = false;
|
||||||
|
field.readPretty = false;
|
||||||
|
field.disabled = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
|
||||||
|
dn.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果用户没有手动设置过 operator,那么在筛选的时候 operator 会是空的,
|
||||||
|
* 该方法确保 operator 一定有值(需要在 FormItem 中调用)
|
||||||
|
*/
|
||||||
|
export const useEnsureOperatorsValid = () => {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const operatorList = useOperatorList();
|
||||||
|
const { operators: storedOperators } = findFilterOperators(fieldSchema);
|
||||||
|
|
||||||
|
if (storedOperators && operatorList.length && !storedOperators[fieldSchema.name]) {
|
||||||
|
storedOperators[fieldSchema.name] = operatorList[0].value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditOperator = () => {
|
||||||
|
const compile = useCompile();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
const operatorList = useOperatorList();
|
||||||
|
const { operators: storedOperators, uid } = findFilterOperators(fieldSchema);
|
||||||
|
|
||||||
|
if (operatorList.length && !storedOperators[fieldSchema.name]) {
|
||||||
|
storedOperators[fieldSchema.name] = operatorList[0].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return operatorList.length ? (
|
||||||
|
<SchemaSettings.SelectItem
|
||||||
|
key="operator"
|
||||||
|
title={t('Operator')}
|
||||||
|
value={storedOperators[fieldSchema.name]}
|
||||||
|
options={compile(operatorList)}
|
||||||
|
onChange={(v) => {
|
||||||
|
storedOperators[fieldSchema.name] = v;
|
||||||
|
const schema: ISchema = {
|
||||||
|
['x-uid']: uid,
|
||||||
|
['x-filter-operators']: storedOperators,
|
||||||
|
};
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
dn.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditTitleField = () => {
|
||||||
|
const { getCollectionFields, getCollectionJoinField } = useCollectionManager();
|
||||||
|
const { getField } = useCollection();
|
||||||
|
const field = useField<Field>();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
const compile = useCompile();
|
||||||
|
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
||||||
|
const targetFields = collectionField?.target
|
||||||
|
? getCollectionFields(collectionField.target)
|
||||||
|
: getCollectionFields(collectionField?.targetCollection) ?? [];
|
||||||
|
const options = targetFields
|
||||||
|
.filter((field) => !field?.target && field.type !== 'boolean')
|
||||||
|
.map((field) => ({
|
||||||
|
value: field?.name,
|
||||||
|
label: compile(field?.uiSchema?.title) || field?.name,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return options.length > 0 && fieldSchema['x-component'] === 'CollectionField' ? (
|
||||||
|
<SchemaSettings.SelectItem
|
||||||
|
key="title-field"
|
||||||
|
title={t('Title field')}
|
||||||
|
options={options}
|
||||||
|
value={field?.componentProps?.fieldNames?.label}
|
||||||
|
onChange={(label) => {
|
||||||
|
const schema = {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
};
|
||||||
|
const fieldNames = {
|
||||||
|
...collectionField?.uiSchema?.['x-component-props']?.['fieldNames'],
|
||||||
|
...field.componentProps.fieldNames,
|
||||||
|
label,
|
||||||
|
};
|
||||||
|
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||||
|
fieldSchema['x-component-props']['fieldNames'] = fieldNames;
|
||||||
|
schema['x-component-props'] = fieldSchema['x-component-props'];
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
dn.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
};
|
@ -21,11 +21,16 @@ export const FormDesigner = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { visible } = useActionContext();
|
const { visible } = useActionContext();
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||||
{/* <SchemaSettings.Template componentName={'FormItem'} collectionName={name} /> */}
|
{/* <SchemaSettings.Template componentName={'FormItem'} collectionName={name} /> */}
|
||||||
<SchemaSettings.BlockTitleItem />
|
<SchemaSettings.BlockTitleItem />
|
||||||
<SchemaSettings.FormItemTemplate componentName={'FormItem'} collectionName={name} resourceName={defaultResource} />
|
<SchemaSettings.FormItemTemplate
|
||||||
|
componentName={'FormItem'}
|
||||||
|
collectionName={name}
|
||||||
|
resourceName={defaultResource}
|
||||||
|
/>
|
||||||
<SchemaSettings.LinkageRules collectionName={name} />
|
<SchemaSettings.LinkageRules collectionName={name} />
|
||||||
<SchemaSettings.Divider />
|
<SchemaSettings.Divider />
|
||||||
<SchemaSettings.Remove
|
<SchemaSettings.Remove
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
import { useFieldSchema } from '@formily/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useCollection } from '../../../collection-manager';
|
||||||
|
import { FilterBlockType } from '../../../filter-provider/utils';
|
||||||
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 筛选区块所使用的表单设计器
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const FilterDesigner = () => {
|
||||||
|
const { name, title } = useCollection();
|
||||||
|
const template = useSchemaTemplate();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||||
|
<SchemaSettings.BlockTitleItem />
|
||||||
|
<SchemaSettings.FormItemTemplate
|
||||||
|
componentName={'FilterFormItem'}
|
||||||
|
collectionName={name}
|
||||||
|
resourceName={defaultResource}
|
||||||
|
/>
|
||||||
|
<SchemaSettings.LinkageRules collectionName={name} />
|
||||||
|
<SchemaSettings.ConnectDataBlocks type={FilterBlockType.FORM} emptyDescription={t('No blocks to connect')} />
|
||||||
|
<SchemaSettings.Divider />
|
||||||
|
<SchemaSettings.Remove
|
||||||
|
removeParentsIfNoChildren
|
||||||
|
breakRemoveOn={{
|
||||||
|
'x-component': 'Grid',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</GeneralSchemaDesigner>
|
||||||
|
);
|
||||||
|
};
|
@ -162,21 +162,23 @@ const WithoutForm = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Form: React.FC<FormProps> & { Designer?: any; ReadPrettyDesigner?: any } = observer((props) => {
|
export const Form: React.FC<FormProps> & { Designer?: any; FilterDesigner?: any; ReadPrettyDesigner?: any } = observer(
|
||||||
const field = useField<Field>();
|
(props) => {
|
||||||
const { form, disabled, ...others } = useProps(props);
|
const field = useField<Field>();
|
||||||
const formDisabled = disabled || field.disabled;
|
const { form, disabled, ...others } = useProps(props);
|
||||||
return (
|
const formDisabled = disabled || field.disabled;
|
||||||
<ConfigProvider componentDisabled={formDisabled}>
|
return (
|
||||||
<form>
|
<ConfigProvider componentDisabled={formDisabled}>
|
||||||
<Spin spinning={field.loading || false}>
|
<form>
|
||||||
{form ? (
|
<Spin spinning={field.loading || false}>
|
||||||
<WithForm form={form} {...others} disabled={formDisabled} />
|
{form ? (
|
||||||
) : (
|
<WithForm form={form} {...others} disabled={formDisabled} />
|
||||||
<WithoutForm {...others} disabled={formDisabled} />
|
) : (
|
||||||
)}
|
<WithoutForm {...others} disabled={formDisabled} />
|
||||||
</Spin>
|
)}
|
||||||
</form>
|
</Spin>
|
||||||
</ConfigProvider>
|
</form>
|
||||||
);
|
</ConfigProvider>
|
||||||
});
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
import { FilterDesigner } from './Form.FilterDesigner';
|
||||||
import { Form as FormV2 } from './Form';
|
import { Form as FormV2 } from './Form';
|
||||||
import { DetailsDesigner, FormDesigner, ReadPrettyFormDesigner } from './Form.Designer';
|
import { DetailsDesigner, FormDesigner, ReadPrettyFormDesigner } from './Form.Designer';
|
||||||
|
|
||||||
FormV2.Designer = FormDesigner;
|
FormV2.Designer = FormDesigner;
|
||||||
|
FormV2.FilterDesigner = FilterDesigner;
|
||||||
FormV2.ReadPrettyDesigner = ReadPrettyFormDesigner;
|
FormV2.ReadPrettyDesigner = ReadPrettyFormDesigner;
|
||||||
|
|
||||||
export { FormV2, DetailsDesigner };
|
export { FormV2, DetailsDesigner };
|
||||||
|
@ -358,7 +358,7 @@ export const Grid: any = observer((props: any) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Grid.Row = observer((props) => {
|
Grid.Row = observer(() => {
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const addr = field.address.toString();
|
const addr = field.address.toString();
|
||||||
@ -416,12 +416,12 @@ Grid.Col = observer((props: any) => {
|
|||||||
const { cols = [] } = useContext(GridRowContext);
|
const { cols = [] } = useContext(GridRowContext);
|
||||||
const schema = useFieldSchema();
|
const schema = useFieldSchema();
|
||||||
const field = useField();
|
const field = useField();
|
||||||
let width = '100%';
|
let width = '';
|
||||||
if (cols?.length) {
|
if (cols?.length) {
|
||||||
const w = schema?.['x-component-props']?.['width'] || 100 / cols.length;
|
const w = schema?.['x-component-props']?.['width'] || 100 / cols.length;
|
||||||
width = `calc(${w}% - 24px - 24px / ${cols.length})`;
|
width = `calc(${w}% - 24px - 24px / ${cols.length})`;
|
||||||
}
|
}
|
||||||
const { isOver, setNodeRef } = useDroppable({
|
const { setNodeRef } = useDroppable({
|
||||||
id: field.address.toString(),
|
id: field.address.toString(),
|
||||||
data: {
|
data: {
|
||||||
insertAdjacent: 'beforeEnd',
|
insertAdjacent: 'beforeEnd',
|
||||||
@ -431,17 +431,7 @@ Grid.Col = observer((props: any) => {
|
|||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<GridColContext.Provider value={{ cols, schema }}>
|
<GridColContext.Provider value={{ cols, schema }}>
|
||||||
<div
|
<div ref={setNodeRef} style={{ width }} className={cls('nb-grid-col')}>
|
||||||
ref={setNodeRef}
|
|
||||||
style={{ width }}
|
|
||||||
className={cls(
|
|
||||||
'nb-grid-col',
|
|
||||||
css`
|
|
||||||
position: relative;
|
|
||||||
/* z-index: 0; */
|
|
||||||
`,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
</GridColContext.Provider>
|
</GridColContext.Provider>
|
||||||
|
@ -7,6 +7,7 @@ export * from './checkbox';
|
|||||||
export * from './color-select';
|
export * from './color-select';
|
||||||
export * from './cron';
|
export * from './cron';
|
||||||
export * from './date-picker';
|
export * from './date-picker';
|
||||||
|
export * from './details';
|
||||||
export * from './filter';
|
export * from './filter';
|
||||||
export * from './form';
|
export * from './form';
|
||||||
export * from './form-item';
|
export * from './form-item';
|
||||||
|
@ -104,36 +104,31 @@ const FixedBlock: React.FC<FixedBlockProps> = (props) => {
|
|||||||
{schema ? (
|
{schema ? (
|
||||||
<div
|
<div
|
||||||
className={css`
|
className={css`
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
height: calc(100vh - ${height}px);
|
||||||
.noco-card-item {
|
.noco-card-item {
|
||||||
height: 100%;
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
.ant-card {
|
.ant-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
.ant-card-body {
|
.ant-card-body {
|
||||||
|
height: 1px;
|
||||||
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
// padding-bottom: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
& .ant-spin-nested-loading {
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
& .ant-spin-container {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
style={{
|
|
||||||
height: `calc(100vh - ${height}px)`,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<RecursionField onlyRenderProperties={false} schema={schema} />
|
<RecursionField onlyRenderProperties={false} name={schema.name} schema={schema} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
props.children
|
props.children
|
||||||
|
@ -8,6 +8,7 @@ import React, { useContext, useEffect, useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useHistory, useLocation } from 'react-router-dom';
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
import { useDocumentTitle } from '../../../document-title';
|
import { useDocumentTitle } from '../../../document-title';
|
||||||
|
import { FilterBlockProvider } from '../../../filter-provider/FilterProvider';
|
||||||
import { Icon } from '../../../icon';
|
import { Icon } from '../../../icon';
|
||||||
import { DndContext } from '../../common';
|
import { DndContext } from '../../common';
|
||||||
import { SortableItem } from '../../common/sortable-item';
|
import { SortableItem } from '../../common/sortable-item';
|
||||||
@ -138,168 +139,170 @@ export const Page = (props) => {
|
|||||||
const [height, setHeight] = useState(0);
|
const [height, setHeight] = useState(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={pageDesignerCss}>
|
<FilterBlockProvider>
|
||||||
<PageDesigner title={fieldSchema.title || title} />
|
<div className={pageDesignerCss}>
|
||||||
<div
|
<PageDesigner title={fieldSchema.title || title} />
|
||||||
ref={(ref) => {
|
<div
|
||||||
setHeight(Math.floor(ref?.getBoundingClientRect().height || 0) + 1);
|
ref={(ref) => {
|
||||||
}}
|
setHeight(Math.floor(ref?.getBoundingClientRect().height || 0) + 1);
|
||||||
>
|
}}
|
||||||
{!disablePageHeader && (
|
>
|
||||||
<AntdPageHeader
|
{!disablePageHeader && (
|
||||||
className={css`
|
<AntdPageHeader
|
||||||
&.has-footer {
|
|
||||||
padding-top: 12px;
|
|
||||||
.ant-page-header-heading-left {
|
|
||||||
/* margin: 0; */
|
|
||||||
}
|
|
||||||
.ant-page-header-footer {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
ghost={false}
|
|
||||||
title={hidePageTitle ? undefined : fieldSchema.title || compile(title)}
|
|
||||||
{...others}
|
|
||||||
footer={
|
|
||||||
enablePageTabs && (
|
|
||||||
<DndContext>
|
|
||||||
<Tabs
|
|
||||||
size={'small'}
|
|
||||||
activeKey={activeKey}
|
|
||||||
onTabClick={(activeKey) => {
|
|
||||||
setLoading(true);
|
|
||||||
setActiveKey(activeKey);
|
|
||||||
window.history.pushState({}, '', window.location.pathname + `?tab=` + activeKey);
|
|
||||||
setTimeout(() => {
|
|
||||||
setLoading(false);
|
|
||||||
}, 50);
|
|
||||||
}}
|
|
||||||
tabBarExtraContent={
|
|
||||||
dn.designable && (
|
|
||||||
<Button
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
className={css`
|
|
||||||
border-color: rgb(241, 139, 98) !important;
|
|
||||||
color: rgb(241, 139, 98) !important;
|
|
||||||
`}
|
|
||||||
type={'dashed'}
|
|
||||||
onClick={async () => {
|
|
||||||
const values = await FormDialog(t('Add tab'), () => {
|
|
||||||
return (
|
|
||||||
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
|
|
||||||
<FormLayout layout={'vertical'}>
|
|
||||||
<SchemaComponent
|
|
||||||
schema={{
|
|
||||||
properties: {
|
|
||||||
title: {
|
|
||||||
title: t('Tab name'),
|
|
||||||
'x-component': 'Input',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
title: t('Icon'),
|
|
||||||
'x-component': 'IconPicker',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormLayout>
|
|
||||||
</SchemaComponentOptions>
|
|
||||||
);
|
|
||||||
}).open({
|
|
||||||
initialValues: {},
|
|
||||||
});
|
|
||||||
const { title, icon } = values;
|
|
||||||
dn.insertBeforeEnd({
|
|
||||||
type: 'void',
|
|
||||||
title,
|
|
||||||
'x-icon': icon,
|
|
||||||
'x-component': 'Grid',
|
|
||||||
'x-initializer': 'BlockInitializers',
|
|
||||||
properties: {},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('Add tab')}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{fieldSchema.mapProperties((schema) => {
|
|
||||||
return (
|
|
||||||
<Tabs.TabPane
|
|
||||||
tab={
|
|
||||||
<SortableItem
|
|
||||||
id={schema.name}
|
|
||||||
schema={schema}
|
|
||||||
className={classNames('nb-action-link', designerCss, props.className)}
|
|
||||||
>
|
|
||||||
{schema['x-icon'] && <Icon style={{ marginRight: 8 }} type={schema['x-icon']} />}
|
|
||||||
<span>{schema.title || t('Unnamed')}</span>
|
|
||||||
<PageTabDesigner schema={schema} />
|
|
||||||
</SortableItem>
|
|
||||||
}
|
|
||||||
key={schema.name}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Tabs>
|
|
||||||
</DndContext>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className={'m24'} style={{ margin: 24 }}>
|
|
||||||
{loading ? (
|
|
||||||
<Spin />
|
|
||||||
) : !disablePageHeader && enablePageTabs ? (
|
|
||||||
fieldSchema.mapProperties((schema) => {
|
|
||||||
if (schema.name !== activeKey) return null;
|
|
||||||
return (
|
|
||||||
<FixedBlock
|
|
||||||
key={schema.name}
|
|
||||||
height={
|
|
||||||
// header 46 margin 48
|
|
||||||
height + 46 + 48
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SchemaComponent
|
|
||||||
schema={
|
|
||||||
new Schema({
|
|
||||||
properties: {
|
|
||||||
[schema.name]: schema,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</FixedBlock>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<FixedBlock
|
|
||||||
height={
|
|
||||||
// header 46 margin 48
|
|
||||||
height + 46 + 48
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={css`
|
className={css`
|
||||||
> .nb-grid:not(:last-child) {
|
&.has-footer {
|
||||||
> .nb-schema-initializer-button {
|
padding-top: 12px;
|
||||||
display: none;
|
.ant-page-header-heading-left {
|
||||||
|
/* margin: 0; */
|
||||||
|
}
|
||||||
|
.ant-page-header-footer {
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
|
ghost={false}
|
||||||
|
title={hidePageTitle ? undefined : fieldSchema.title || compile(title)}
|
||||||
|
{...others}
|
||||||
|
footer={
|
||||||
|
enablePageTabs && (
|
||||||
|
<DndContext>
|
||||||
|
<Tabs
|
||||||
|
size={'small'}
|
||||||
|
activeKey={activeKey}
|
||||||
|
onTabClick={(activeKey) => {
|
||||||
|
setLoading(true);
|
||||||
|
setActiveKey(activeKey);
|
||||||
|
window.history.pushState({}, '', window.location.pathname + `?tab=` + activeKey);
|
||||||
|
setTimeout(() => {
|
||||||
|
setLoading(false);
|
||||||
|
}, 50);
|
||||||
|
}}
|
||||||
|
tabBarExtraContent={
|
||||||
|
dn.designable && (
|
||||||
|
<Button
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
className={css`
|
||||||
|
border-color: rgb(241, 139, 98) !important;
|
||||||
|
color: rgb(241, 139, 98) !important;
|
||||||
|
`}
|
||||||
|
type={'dashed'}
|
||||||
|
onClick={async () => {
|
||||||
|
const values = await FormDialog(t('Add tab'), () => {
|
||||||
|
return (
|
||||||
|
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
|
||||||
|
<FormLayout layout={'vertical'}>
|
||||||
|
<SchemaComponent
|
||||||
|
schema={{
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
title: t('Tab name'),
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
title: t('Icon'),
|
||||||
|
'x-component': 'IconPicker',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormLayout>
|
||||||
|
</SchemaComponentOptions>
|
||||||
|
);
|
||||||
|
}).open({
|
||||||
|
initialValues: {},
|
||||||
|
});
|
||||||
|
const { title, icon } = values;
|
||||||
|
dn.insertBeforeEnd({
|
||||||
|
type: 'void',
|
||||||
|
title,
|
||||||
|
'x-icon': icon,
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer': 'BlockInitializers',
|
||||||
|
properties: {},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Add tab')}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{fieldSchema.mapProperties((schema) => {
|
||||||
|
return (
|
||||||
|
<Tabs.TabPane
|
||||||
|
tab={
|
||||||
|
<SortableItem
|
||||||
|
id={schema.name}
|
||||||
|
schema={schema}
|
||||||
|
className={classNames('nb-action-link', designerCss, props.className)}
|
||||||
|
>
|
||||||
|
{schema['x-icon'] && <Icon style={{ marginRight: 8 }} type={schema['x-icon']} />}
|
||||||
|
<span>{schema.title || t('Unnamed')}</span>
|
||||||
|
<PageTabDesigner schema={schema} />
|
||||||
|
</SortableItem>
|
||||||
|
}
|
||||||
|
key={schema.name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Tabs>
|
||||||
|
</DndContext>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={'m24'} style={{ margin: 24 }}>
|
||||||
|
{loading ? (
|
||||||
|
<Spin />
|
||||||
|
) : !disablePageHeader && enablePageTabs ? (
|
||||||
|
fieldSchema.mapProperties((schema) => {
|
||||||
|
if (schema.name !== activeKey) return null;
|
||||||
|
return (
|
||||||
|
<FixedBlock
|
||||||
|
key={schema.name}
|
||||||
|
height={
|
||||||
|
// header 46 margin 48
|
||||||
|
height + 46 + 48
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SchemaComponent
|
||||||
|
schema={
|
||||||
|
new Schema({
|
||||||
|
properties: {
|
||||||
|
[schema.name]: schema,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FixedBlock>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<FixedBlock
|
||||||
|
height={
|
||||||
|
// header 46 margin 48
|
||||||
|
height + 46 + 48
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{props.children}
|
<div
|
||||||
</div>
|
className={css`
|
||||||
</FixedBlock>
|
> .nb-grid:not(:last-child) {
|
||||||
)}
|
> .nb-schema-initializer-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
</FixedBlock>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</FilterBlockProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@ import { reaction } from '@formily/reactive';
|
|||||||
import { useEventListener, useMemoizedFn } from 'ahooks';
|
import { useEventListener, useMemoizedFn } from 'ahooks';
|
||||||
import { Table as AntdTable, TableColumnProps } from 'antd';
|
import { Table as AntdTable, TableColumnProps } from 'antd';
|
||||||
import { default as classNames, default as cls } from 'classnames';
|
import { default as classNames, default as cls } from 'classnames';
|
||||||
import React, { RefCallback, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { DndContext, useDesignable } from '../..';
|
import { DndContext, useDesignable } from '../..';
|
||||||
import {
|
import {
|
||||||
@ -15,7 +15,7 @@ import {
|
|||||||
RecordProvider,
|
RecordProvider,
|
||||||
useSchemaInitializer,
|
useSchemaInitializer,
|
||||||
useTableBlockContext,
|
useTableBlockContext,
|
||||||
useTableSelectorContext
|
useTableSelectorContext,
|
||||||
} from '../../../';
|
} from '../../../';
|
||||||
import { useACLFieldWhitelist } from '../../../acl/ACLProvider';
|
import { useACLFieldWhitelist } from '../../../acl/ACLProvider';
|
||||||
import { extractIndex, getIdsWithChildren, isCollectionFieldComponent, isColumnComponent } from './utils';
|
import { extractIndex, getIdsWithChildren, isCollectionFieldComponent, isColumnComponent } from './utils';
|
||||||
@ -96,7 +96,7 @@ const SortableRow = (props) => {
|
|||||||
<tr
|
<tr
|
||||||
ref={active?.id !== id ? setNodeRef : null}
|
ref={active?.id !== id ? setNodeRef : null}
|
||||||
{...props}
|
{...props}
|
||||||
className={classNames({ [className]: active && isOver })}
|
className={classNames(props.className, { [className]: active && isOver })}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -157,7 +157,7 @@ export const Table: any = observer((props: any) => {
|
|||||||
const field = useField<ArrayField>();
|
const field = useField<ArrayField>();
|
||||||
const columns = useTableColumns();
|
const columns = useTableColumns();
|
||||||
const { pagination: pagination1, useProps, onChange, ...others1 } = props;
|
const { pagination: pagination1, useProps, onChange, ...others1 } = props;
|
||||||
const { pagination: pagination2, ...others2 } = useProps?.() || {};
|
const { pagination: pagination2, onClickRow, ...others2 } = useProps?.() || {};
|
||||||
const {
|
const {
|
||||||
dragSort = false,
|
dragSort = false,
|
||||||
showIndex = true,
|
showIndex = true,
|
||||||
@ -178,6 +178,28 @@ export const Table: any = observer((props: any) => {
|
|||||||
const { treeTable } = schema?.parent?.['x-decorator-props'] || {};
|
const { treeTable } = schema?.parent?.['x-decorator-props'] || {};
|
||||||
const [expandedKeys, setExpandesKeys] = useState([]);
|
const [expandedKeys, setExpandesKeys] = useState([]);
|
||||||
const [allIncludesChildren, setAllIncludesChildren] = useState([]);
|
const [allIncludesChildren, setAllIncludesChildren] = useState([]);
|
||||||
|
const [selectedRowKeys, setSelectedRowKeys] = useState<any[]>(field?.data?.selectedRowKeys || []);
|
||||||
|
const [selectedRow, setSelectedRow] = useState([]);
|
||||||
|
|
||||||
|
let onRow = null,
|
||||||
|
highlightRow = '';
|
||||||
|
|
||||||
|
if (onClickRow) {
|
||||||
|
onRow = (record) => {
|
||||||
|
return {
|
||||||
|
onClick: () => onClickRow(record, setSelectedRow, selectedRow),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
highlightRow = css`
|
||||||
|
& > td {
|
||||||
|
background-color: #caedff !important;
|
||||||
|
}
|
||||||
|
&:hover > td {
|
||||||
|
background-color: #caedff !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
field.setValidator((value) => {
|
field.setValidator((value) => {
|
||||||
if (requiredValidator) {
|
if (requiredValidator) {
|
||||||
@ -294,10 +316,11 @@ export const Table: any = observer((props: any) => {
|
|||||||
rowSelection: rowSelection
|
rowSelection: rowSelection
|
||||||
? {
|
? {
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
selectedRowKeys: field?.data?.selectedRowKeys || [],
|
selectedRowKeys: selectedRowKeys,
|
||||||
onChange(selectedRowKeys: any[], selectedRows: any[]) {
|
onChange(selectedRowKeys: any[], selectedRows: any[]) {
|
||||||
field.data = field.data || {};
|
field.data = field.data || {};
|
||||||
field.data.selectedRowKeys = selectedRowKeys;
|
field.data.selectedRowKeys = selectedRowKeys;
|
||||||
|
setSelectedRowKeys(selectedRowKeys);
|
||||||
onRowSelectionChange?.(selectedRowKeys, selectedRows);
|
onRowSelectionChange?.(selectedRowKeys, selectedRows);
|
||||||
},
|
},
|
||||||
renderCell: (checked, record, index, originNode) => {
|
renderCell: (checked, record, index, originNode) => {
|
||||||
@ -396,8 +419,10 @@ export const Table: any = observer((props: any) => {
|
|||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const fixedBlock = fieldSchema?.parent?.['x-decorator-props']?.fixedBlock;
|
const fixedBlock = fieldSchema?.parent?.['x-decorator-props']?.fixedBlock;
|
||||||
const [tableHeight, setTableHeight] = useState(0);
|
const [tableHeight, setTableHeight] = useState(0);
|
||||||
|
|
||||||
const [headerAndPaginationHeight, setHeaderAndPaginationHeight] = useState(0);
|
const [headerAndPaginationHeight, setHeaderAndPaginationHeight] = useState(0);
|
||||||
|
const tableRef = useRef(null);
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const scroll = useMemo(() => {
|
const scroll = useMemo(() => {
|
||||||
return fixedBlock
|
return fixedBlock
|
||||||
? {
|
? {
|
||||||
@ -409,22 +434,21 @@ export const Table: any = observer((props: any) => {
|
|||||||
};
|
};
|
||||||
}, [fixedBlock, tableHeight, headerAndPaginationHeight]);
|
}, [fixedBlock, tableHeight, headerAndPaginationHeight]);
|
||||||
|
|
||||||
const elementRef = useRef<HTMLDivElement>();
|
|
||||||
const calcTableSize = () => {
|
const calcTableSize = () => {
|
||||||
if (!elementRef.current) return;
|
if (!containerRef.current || !tableRef.current) return;
|
||||||
const clientRect = elementRef.current?.getBoundingClientRect();
|
setTableHeight(Math.ceil(containerRef.current.clientHeight || 0));
|
||||||
setTableHeight(Math.ceil(clientRect?.height || 0));
|
|
||||||
};
|
|
||||||
useEventListener('resize', calcTableSize);
|
|
||||||
|
|
||||||
const mountedRef: RefCallback<HTMLDivElement> = (ref) => {
|
const headerHeight = tableRef.current.querySelector('.ant-table-header')?.clientHeight || 0;
|
||||||
elementRef.current = ref;
|
const paginationHeight = tableRef.current.querySelector('.ant-table-pagination')?.clientHeight || 0;
|
||||||
calcTableSize();
|
setHeaderAndPaginationHeight(Math.ceil(headerHeight + paginationHeight + 18));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(calcTableSize, [field.value]);
|
||||||
|
useEventListener('resize', calcTableSize);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={mountedRef}
|
ref={containerRef}
|
||||||
className={css`
|
className={css`
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -439,11 +463,7 @@ export const Table: any = observer((props: any) => {
|
|||||||
>
|
>
|
||||||
<SortableWrapper>
|
<SortableWrapper>
|
||||||
<AntdTable
|
<AntdTable
|
||||||
ref={(ref) => {
|
ref={tableRef}
|
||||||
const headerHeight = ref?.querySelector('.ant-table-header')?.getBoundingClientRect().height || 0;
|
|
||||||
const paginationHeight = ref?.querySelector('.ant-table-pagination')?.getBoundingClientRect().height || 0;
|
|
||||||
setHeaderAndPaginationHeight(Math.ceil(headerHeight + paginationHeight + 16));
|
|
||||||
}}
|
|
||||||
rowKey={rowKey ?? defaultRowKey}
|
rowKey={rowKey ?? defaultRowKey}
|
||||||
{...others}
|
{...others}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
@ -452,6 +472,8 @@ export const Table: any = observer((props: any) => {
|
|||||||
onChange={(pagination, filters, sorter, extra) => {
|
onChange={(pagination, filters, sorter, extra) => {
|
||||||
onTableChange?.(pagination, filters, sorter, extra);
|
onTableChange?.(pagination, filters, sorter, extra);
|
||||||
}}
|
}}
|
||||||
|
onRow={onRow}
|
||||||
|
rowClassName={(record) => (selectedRow.includes(record[rowKey]) ? highlightRow : '')}
|
||||||
tableLayout={'auto'}
|
tableLayout={'auto'}
|
||||||
scroll={scroll}
|
scroll={scroll}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
@ -6,6 +6,7 @@ import { useTableBlockContext } from '../../../block-provider';
|
|||||||
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
||||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
import { useCollectionFilterOptions, useSortFields } from '../../../collection-manager/action-hooks';
|
import { useCollectionFilterOptions, useSortFields } from '../../../collection-manager/action-hooks';
|
||||||
|
import { FilterBlockType } from '../../../filter-provider/utils';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
import { useDesignable } from '../../hooks';
|
import { useDesignable } from '../../hooks';
|
||||||
@ -234,6 +235,7 @@ export const TableBlockDesigner = () => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<SchemaSettings.ConnectDataBlocks type={FilterBlockType.TABLE} emptyDescription={t('No blocks to connect')} />
|
||||||
{supportTemplate && <SchemaSettings.Divider />}
|
{supportTemplate && <SchemaSettings.Divider />}
|
||||||
{supportTemplate && (
|
{supportTemplate && (
|
||||||
<SchemaSettings.Template componentName={'Table'} collectionName={name} resourceName={defaultResource} />
|
<SchemaSettings.Template componentName={'Table'} collectionName={name} resourceName={defaultResource} />
|
||||||
|
@ -6,7 +6,7 @@ function toSchema(schema?: any) {
|
|||||||
if (Schema.isSchemaInstance(schema)) {
|
if (Schema.isSchemaInstance(schema)) {
|
||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
if (schema.name) {
|
if (schema?.name) {
|
||||||
return new Schema({
|
return new Schema({
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -44,6 +44,25 @@ export const BlockInitializers = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'filterBlocks',
|
||||||
|
type: 'itemGroup',
|
||||||
|
title: '{{t("Filter blocks")}}',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: 'filterForm',
|
||||||
|
type: 'item',
|
||||||
|
title: '{{t("Form")}}',
|
||||||
|
component: 'FilterFormBlockInitializer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'filterCollapse',
|
||||||
|
type: 'item',
|
||||||
|
title: '{{t("Collapse")}}',
|
||||||
|
component: 'FilterCollapseBlockInitializer',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'media',
|
key: 'media',
|
||||||
type: 'itemGroup',
|
type: 'itemGroup',
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
// 表单的操作配置
|
||||||
|
export const FilterFormActionInitializers = {
|
||||||
|
title: '{{t("Configure actions")}}',
|
||||||
|
icon: 'SettingOutlined',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'itemGroup',
|
||||||
|
title: '{{t("Enable actions")}}',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'item',
|
||||||
|
title: '{{t("Filter")}}',
|
||||||
|
component: 'CreateFilterActionInitializer',
|
||||||
|
schema: {
|
||||||
|
'x-action-settings': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'item',
|
||||||
|
title: '{{t("Reset")}}',
|
||||||
|
component: 'CreateResetActionInitializer',
|
||||||
|
schema: {
|
||||||
|
'x-action-settings': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
@ -1,21 +1,25 @@
|
|||||||
import { union } from 'lodash';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SchemaInitializer } from '../SchemaInitializer';
|
import { SchemaInitializer } from '../SchemaInitializer';
|
||||||
import {
|
import {
|
||||||
gridRowColWrap,
|
gridRowColWrap,
|
||||||
useAssociatedFormItemInitializerFields,
|
useAssociatedFormItemInitializerFields,
|
||||||
|
useFilterAssociatedFormItemInitializerFields,
|
||||||
|
useFilterFormItemInitializerFields,
|
||||||
|
useFilterInheritsFormItemInitializerFields,
|
||||||
useFormItemInitializerFields,
|
useFormItemInitializerFields,
|
||||||
useInheritsFormItemInitializerFields,
|
useInheritsFormItemInitializerFields,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { useCompile } from '../../schema-component';
|
import { useCompile } from '../../schema-component';
|
||||||
|
|
||||||
|
|
||||||
// 表单里配置字段
|
// 表单里配置字段
|
||||||
export const FormItemInitializers = (props: any) => {
|
export const FormItemInitializers = (props: any) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { insertPosition, component } = props;
|
const { insertPosition, component } = props;
|
||||||
const associationFields = useAssociatedFormItemInitializerFields({ readPretty: true, block: 'Form' });
|
const associationFields = useAssociatedFormItemInitializerFields({
|
||||||
|
readPretty: true,
|
||||||
|
block: 'Form',
|
||||||
|
});
|
||||||
const inheritFields = useInheritsFormItemInitializerFields();
|
const inheritFields = useInheritsFormItemInitializerFields();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const fieldItems: any[] = [
|
const fieldItems: any[] = [
|
||||||
@ -27,16 +31,17 @@ export const FormItemInitializers = (props: any) => {
|
|||||||
];
|
];
|
||||||
if (inheritFields?.length > 0) {
|
if (inheritFields?.length > 0) {
|
||||||
inheritFields.forEach((inherit) => {
|
inheritFields.forEach((inherit) => {
|
||||||
Object.values(inherit)[0].length&&fieldItems.push(
|
Object.values(inherit)[0].length &&
|
||||||
{
|
fieldItems.push(
|
||||||
type: 'divider',
|
{
|
||||||
},
|
type: 'divider',
|
||||||
{
|
},
|
||||||
type: 'itemGroup',
|
{
|
||||||
title: t(`Parent collection fields`) + '(' + compile(`${Object.keys(inherit)[0]}`) + ')',
|
type: 'itemGroup',
|
||||||
children: Object.values(inherit)[0],
|
title: t(`Parent collection fields`) + '(' + compile(`${Object.keys(inherit)[0]}`) + ')',
|
||||||
},
|
children: Object.values(inherit)[0],
|
||||||
);
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
associationFields.length > 0 &&
|
associationFields.length > 0 &&
|
||||||
@ -82,3 +87,76 @@ export const FormItemInitializers = (props: any) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const FilterFormItemInitializers = (props: any) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { insertPosition, component } = props;
|
||||||
|
const associationFields = useFilterAssociatedFormItemInitializerFields();
|
||||||
|
const inheritFields = useFilterInheritsFormItemInitializerFields();
|
||||||
|
const compile = useCompile();
|
||||||
|
const fieldItems: any[] = [
|
||||||
|
{
|
||||||
|
type: 'itemGroup',
|
||||||
|
title: t('Display fields'),
|
||||||
|
children: useFilterFormItemInitializerFields(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
if (inheritFields?.length > 0) {
|
||||||
|
inheritFields.forEach((inherit) => {
|
||||||
|
Object.values(inherit)[0].length &&
|
||||||
|
fieldItems.push(
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'itemGroup',
|
||||||
|
title: t(`Parent collection fields`) + '(' + compile(`${Object.keys(inherit)[0]}`) + ')',
|
||||||
|
children: Object.values(inherit)[0],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
associationFields.length > 0 &&
|
||||||
|
fieldItems.push(
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'itemGroup',
|
||||||
|
title: t('Display association fields'),
|
||||||
|
children: associationFields,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
fieldItems.push(
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'item',
|
||||||
|
title: t('Add text'),
|
||||||
|
component: 'BlockInitializer',
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
'x-editable': false,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-designer': 'Markdown.Void.Designer',
|
||||||
|
'x-component': 'Markdown.Void',
|
||||||
|
'x-component-props': {
|
||||||
|
content: t('This is a demo text, **supports Markdown syntax**.'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<SchemaInitializer.Button
|
||||||
|
wrap={gridRowColWrap}
|
||||||
|
icon={'SettingOutlined'}
|
||||||
|
items={fieldItems}
|
||||||
|
insertPosition={insertPosition}
|
||||||
|
component={component}
|
||||||
|
title={component ? null : t('Configure fields')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -80,7 +80,7 @@ export const TableColumnInitializers = (props: any) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
items={itemsMerge(fieldItems, items)}
|
items={itemsMerge(fieldItems)}
|
||||||
>
|
>
|
||||||
{t('Configure columns')}
|
{t('Configure columns')}
|
||||||
</SchemaInitializer.Button>
|
</SchemaInitializer.Button>
|
||||||
|
@ -6,6 +6,7 @@ export * from './CreateFormBulkEditBlockInitializers';
|
|||||||
export * from './CustomFormItemInitializers';
|
export * from './CustomFormItemInitializers';
|
||||||
export * from './DetailsActionInitializers';
|
export * from './DetailsActionInitializers';
|
||||||
export * from './FormActionInitializers';
|
export * from './FormActionInitializers';
|
||||||
|
export * from './FilterFormActionInitializers';
|
||||||
export * from './FormItemInitializers';
|
export * from './FormItemInitializers';
|
||||||
export * from './KanbanActionInitializers';
|
export * from './KanbanActionInitializers';
|
||||||
export * from './ReadPrettyFormActionInitializers';
|
export * from './ReadPrettyFormActionInitializers';
|
||||||
|
@ -6,6 +6,7 @@ export {
|
|||||||
gridRowColWrap,
|
gridRowColWrap,
|
||||||
useRecordCollectionDataSourceItems,
|
useRecordCollectionDataSourceItems,
|
||||||
createTableBlockSchema,
|
createTableBlockSchema,
|
||||||
|
createFilterFormBlockSchema,
|
||||||
useAssociatedTableColumnInitializerFields,
|
useAssociatedTableColumnInitializerFields,
|
||||||
useInheritsTableColumnInitializerFields,
|
useInheritsTableColumnInitializerFields,
|
||||||
useTableColumnInitializerFields,
|
useTableColumnInitializerFields,
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { ActionInitializer } from './ActionInitializer';
|
||||||
|
|
||||||
|
export const CreateFilterActionInitializer = (props) => {
|
||||||
|
const schema = {
|
||||||
|
title: '{{ t("Filter") }}',
|
||||||
|
'x-action': 'submit',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-designer': 'Action.Designer',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
useProps: '{{ useFilterBlockActionProps }}',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return <ActionInitializer {...props} schema={schema} />;
|
||||||
|
};
|
@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { ActionInitializer } from './ActionInitializer';
|
||||||
|
|
||||||
|
export const CreateResetActionInitializer = (props) => {
|
||||||
|
const schema = {
|
||||||
|
title: '{{ t("Reset") }}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-designer': 'Action.Designer',
|
||||||
|
'x-component-props': {
|
||||||
|
useProps: '{{ useResetBlockActionProps }}',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return <ActionInitializer {...props} schema={schema} />;
|
||||||
|
};
|
@ -0,0 +1,33 @@
|
|||||||
|
import { TableOutlined } from '@ant-design/icons';
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
|
||||||
|
import { SchemaInitializer, SchemaInitializerButtonContext } from '..';
|
||||||
|
import { useSchemaTemplateManager } from '../../schema-templates';
|
||||||
|
import { useCollectionDataSourceItems } from '../utils';
|
||||||
|
|
||||||
|
export const FilterBlockInitializer = (props) => {
|
||||||
|
const { templateWrap, onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props;
|
||||||
|
const { getTemplateSchemaByMode } = useSchemaTemplateManager();
|
||||||
|
const { setVisible } = useContext(SchemaInitializerButtonContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaInitializer.Item
|
||||||
|
icon={<TableOutlined />}
|
||||||
|
{...others}
|
||||||
|
onClick={async ({ item }) => {
|
||||||
|
if (item.template) {
|
||||||
|
const s = await getTemplateSchemaByMode(item);
|
||||||
|
templateWrap ? insert(templateWrap(s, { item })) : insert(s);
|
||||||
|
} else {
|
||||||
|
if (onCreateBlockSchema) {
|
||||||
|
onCreateBlockSchema({ item });
|
||||||
|
} else if (createBlockSchema) {
|
||||||
|
insert(createBlockSchema({ collection: item.name }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setVisible(false);
|
||||||
|
}}
|
||||||
|
items={useCollectionDataSourceItems(componentType)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { TableOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
import { DataBlockInitializer } from './DataBlockInitializer';
|
||||||
|
import { createCollapseBlockSchema } from '../utils';
|
||||||
|
|
||||||
|
export const FilterCollapseBlockInitializer = (props) => {
|
||||||
|
const { insert } = props;
|
||||||
|
return (
|
||||||
|
<DataBlockInitializer
|
||||||
|
{...props}
|
||||||
|
icon={<TableOutlined />}
|
||||||
|
componentType={'FilterCollapse'}
|
||||||
|
onCreateBlockSchema={async ({ item }) => {
|
||||||
|
const schema = createCollapseBlockSchema({
|
||||||
|
collection: item.name,
|
||||||
|
// 与数据区块做区分
|
||||||
|
blockType: 'filter',
|
||||||
|
});
|
||||||
|
insert(schema);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormOutlined } from '@ant-design/icons';
|
||||||
|
import { createFilterFormBlockSchema } from '../utils';
|
||||||
|
import { FilterBlockInitializer } from './FilterBlockInitializer';
|
||||||
|
|
||||||
|
export const FilterFormBlockInitializer = (props) => {
|
||||||
|
return (
|
||||||
|
<FilterBlockInitializer
|
||||||
|
{...props}
|
||||||
|
icon={<FormOutlined />}
|
||||||
|
componentType={'FilterFormItem'}
|
||||||
|
templateWrap={(templateSchema, { item }) => {
|
||||||
|
const s = createFilterFormBlockSchema({
|
||||||
|
template: templateSchema,
|
||||||
|
collection: item.name,
|
||||||
|
});
|
||||||
|
if (item.template && item.mode === 'reference') {
|
||||||
|
s['x-template-key'] = item.template.key;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}}
|
||||||
|
createBlockSchema={createFilterFormBlockSchema}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -13,6 +13,8 @@ export * from './CreateActionInitializer';
|
|||||||
export * from './CreateFormBlockInitializer';
|
export * from './CreateFormBlockInitializer';
|
||||||
export * from './CreateFormBulkEditBlockInitializer';
|
export * from './CreateFormBulkEditBlockInitializer';
|
||||||
export * from './CreateSubmitActionInitializer';
|
export * from './CreateSubmitActionInitializer';
|
||||||
|
export * from './CreateFilterActionInitializer';
|
||||||
|
export * from './CreateResetActionInitializer';
|
||||||
export * from './CustomizeActionInitializer';
|
export * from './CustomizeActionInitializer';
|
||||||
export * from './CustomizeBulkEditActionInitializer';
|
export * from './CustomizeBulkEditActionInitializer';
|
||||||
export * from './DataBlockInitializer';
|
export * from './DataBlockInitializer';
|
||||||
@ -20,6 +22,7 @@ export * from './DeleteEventActionInitializer';
|
|||||||
export * from './DestroyActionInitializer';
|
export * from './DestroyActionInitializer';
|
||||||
export * from './DetailsBlockInitializer';
|
export * from './DetailsBlockInitializer';
|
||||||
export * from './FilterActionInitializer';
|
export * from './FilterActionInitializer';
|
||||||
|
export * from './FilterFormBlockInitializer';
|
||||||
export * from './FormBlockInitializer';
|
export * from './FormBlockInitializer';
|
||||||
export * from './G2PlotInitializer';
|
export * from './G2PlotInitializer';
|
||||||
export * from './InitializerWithSwitch';
|
export * from './InitializerWithSwitch';
|
||||||
@ -37,6 +40,7 @@ export * from './RefreshActionInitializer';
|
|||||||
export * from './SubmitActionInitializer';
|
export * from './SubmitActionInitializer';
|
||||||
export * from './TableActionColumnInitializer';
|
export * from './TableActionColumnInitializer';
|
||||||
export * from './TableBlockInitializer';
|
export * from './TableBlockInitializer';
|
||||||
|
export * from './FilterCollapseBlockInitializer';
|
||||||
export * from './TableCollectionFieldInitializer';
|
export * from './TableCollectionFieldInitializer';
|
||||||
export * from './TableSelectorInitializer';
|
export * from './TableSelectorInitializer';
|
||||||
export * from './UpdateActionInitializer';
|
export * from './UpdateActionInitializer';
|
||||||
|
@ -3,12 +3,13 @@ import { uid } from '@formily/shared';
|
|||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { BlockRequestContext, SchemaInitializerItemOptions } from '../';
|
import { BlockRequestContext, SchemaInitializerItemOptions } from '../';
|
||||||
import { useCollection, useCollectionManager } from '../collection-manager';
|
import { FieldOptions, useCollection, useCollectionManager } from '../collection-manager';
|
||||||
|
import { isAssocField } from '../filter-provider/utils';
|
||||||
import { useActionContext, useDesignable } from '../schema-component';
|
import { useActionContext, useDesignable } from '../schema-component';
|
||||||
import { useSchemaTemplateManager } from '../schema-templates';
|
import { useSchemaTemplateManager } from '../schema-templates';
|
||||||
import { SelectCollection } from './SelectCollection';
|
import { SelectCollection } from './SelectCollection';
|
||||||
|
|
||||||
export const itemsMerge = (items1, items2) => {
|
export const itemsMerge = (items1) => {
|
||||||
return items1;
|
return items1;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -197,9 +198,8 @@ export const useFormItemInitializerFields = (options?: any) => {
|
|||||||
const { getInterface } = useCollectionManager();
|
const { getInterface } = useCollectionManager();
|
||||||
const form = useForm();
|
const form = useForm();
|
||||||
const { readPretty = form.readPretty, block = 'Form' } = options || {};
|
const { readPretty = form.readPretty, block = 'Form' } = options || {};
|
||||||
const actionCtx = useActionContext();
|
const { snapshot, fieldSchema } = useActionContext();
|
||||||
const action = actionCtx?.fieldSchema?.['x-action'];
|
const action = fieldSchema?.['x-action'];
|
||||||
const { snapshot } = useActionContext();
|
|
||||||
|
|
||||||
return currentFields
|
return currentFields
|
||||||
?.filter((field) => field?.interface && !field?.isForeignKey && !field?.treeChildren)
|
?.filter((field) => field?.interface && !field?.isForeignKey && !field?.treeChildren)
|
||||||
@ -237,6 +237,56 @@ export const useFormItemInitializerFields = (options?: any) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 筛选表单相关
|
||||||
|
export const useFilterFormItemInitializerFields = (options?: any) => {
|
||||||
|
const { name, currentFields } = useCollection();
|
||||||
|
const { getInterface } = useCollectionManager();
|
||||||
|
const form = useForm();
|
||||||
|
const { readPretty = form.readPretty, block = 'FilterForm' } = options || {};
|
||||||
|
const { snapshot, fieldSchema } = useActionContext();
|
||||||
|
const action = fieldSchema?.['x-action'];
|
||||||
|
|
||||||
|
return currentFields
|
||||||
|
?.filter((field) => field?.interface && !field?.isForeignKey && getInterface(field.interface)?.filterable)
|
||||||
|
?.map((field) => {
|
||||||
|
const interfaceConfig = getInterface(field.interface);
|
||||||
|
let schema = {
|
||||||
|
type: 'string',
|
||||||
|
name: field.name,
|
||||||
|
required: false,
|
||||||
|
'x-designer': 'FormItem.FilterFormDesigner',
|
||||||
|
'x-component': field.interface === 'o2m' && !snapshot ? 'TableField' : 'CollectionField',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-collection-field': `${name}.${field.name}`,
|
||||||
|
'x-component-props': {},
|
||||||
|
};
|
||||||
|
if (isAssocField(field)) {
|
||||||
|
schema = {
|
||||||
|
type: 'string',
|
||||||
|
name: field.name,
|
||||||
|
required: false,
|
||||||
|
'x-designer': 'AssociationSelect.FilterDesigner',
|
||||||
|
'x-component': 'AssociationSelect',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-collection-field': `${name}.${field.name}`,
|
||||||
|
'x-component-props': field.uiSchema?.['x-component-props'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const resultItem = {
|
||||||
|
type: 'item',
|
||||||
|
title: field?.uiSchema?.title || field.name,
|
||||||
|
component: 'CollectionFieldInitializer',
|
||||||
|
remove: removeGridFormItem,
|
||||||
|
schemaInitialize: (s) => {
|
||||||
|
interfaceConfig?.schemaInitialize?.(s, { field, block, readPretty, action });
|
||||||
|
},
|
||||||
|
schema,
|
||||||
|
} as SchemaInitializerItemOptions;
|
||||||
|
|
||||||
|
return resultItem;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useAssociatedFormItemInitializerFields = (options?: any) => {
|
export const useAssociatedFormItemInitializerFields = (options?: any) => {
|
||||||
const { name, fields } = useCollection();
|
const { name, fields } = useCollection();
|
||||||
const { getInterface, getCollectionFields } = useCollectionManager();
|
const { getInterface, getCollectionFields } = useCollectionManager();
|
||||||
@ -288,6 +338,57 @@ export const useAssociatedFormItemInitializerFields = (options?: any) => {
|
|||||||
return groups;
|
return groups;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getItem = (field: FieldOptions, schemaName: string, collectionName: string, getCollectionFields) => {
|
||||||
|
if (field.interface === 'm2o') {
|
||||||
|
const subFields = getCollectionFields(field.target);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'subMenu',
|
||||||
|
title: field.uiSchema?.title,
|
||||||
|
children: subFields
|
||||||
|
.map((subField) => getItem(subField, `${schemaName}.${subField.name}`, collectionName, getCollectionFields))
|
||||||
|
.filter(Boolean),
|
||||||
|
} as SchemaInitializerItemOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAssocField(field)) return null;
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
type: 'string',
|
||||||
|
name: schemaName,
|
||||||
|
'x-designer': 'FormItem.FilterFormDesigner',
|
||||||
|
'x-designer-props': {
|
||||||
|
// 在 useOperatorList 中使用,用于获取对应的操作符列表
|
||||||
|
interface: field.interface,
|
||||||
|
},
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-read-pretty': false,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-collection-field': `${collectionName}.${schemaName}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'item',
|
||||||
|
title: field.uiSchema?.title || field.name,
|
||||||
|
component: 'CollectionFieldInitializer',
|
||||||
|
remove: removeGridFormItem,
|
||||||
|
schema,
|
||||||
|
} as SchemaInitializerItemOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 筛选表单相关
|
||||||
|
export const useFilterAssociatedFormItemInitializerFields = () => {
|
||||||
|
const { name, fields } = useCollection();
|
||||||
|
const { getCollectionFields } = useCollectionManager();
|
||||||
|
const interfaces = ['m2o'];
|
||||||
|
const groups = fields
|
||||||
|
?.filter((field) => {
|
||||||
|
return interfaces.includes(field.interface);
|
||||||
|
})
|
||||||
|
?.map((field) => getItem(field, field.name, name, getCollectionFields));
|
||||||
|
return groups;
|
||||||
|
};
|
||||||
|
|
||||||
export const useInheritsFormItemInitializerFields = (options?) => {
|
export const useInheritsFormItemInitializerFields = (options?) => {
|
||||||
const { name } = useCollection();
|
const { name } = useCollection();
|
||||||
const { getInterface, getInheritCollections, getCollection, getParentCollectionFields } = useCollectionManager();
|
const { getInterface, getInheritCollections, getCollection, getParentCollectionFields } = useCollectionManager();
|
||||||
@ -329,6 +430,50 @@ export const useInheritsFormItemInitializerFields = (options?) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 筛选表单相关
|
||||||
|
export const useFilterInheritsFormItemInitializerFields = (options?) => {
|
||||||
|
const { name } = useCollection();
|
||||||
|
const { getInterface, getInheritCollections, getCollection, getParentCollectionFields } = useCollectionManager();
|
||||||
|
const inherits = getInheritCollections(name);
|
||||||
|
const { snapshot } = useActionContext();
|
||||||
|
|
||||||
|
return inherits?.map((v) => {
|
||||||
|
const fields = getParentCollectionFields(v, name);
|
||||||
|
const form = useForm();
|
||||||
|
const { readPretty = form.readPretty, block = 'Form' } = options || {};
|
||||||
|
const targetCollection = getCollection(v);
|
||||||
|
return {
|
||||||
|
[targetCollection.title]: fields
|
||||||
|
?.filter((field) => field?.interface && !field?.isForeignKey && getInterface(field.interface)?.filterable)
|
||||||
|
?.map((field) => {
|
||||||
|
const interfaceConfig = getInterface(field.interface);
|
||||||
|
const schema = {
|
||||||
|
type: 'string',
|
||||||
|
name: field.name,
|
||||||
|
title: field?.uiSchema?.title || field.name,
|
||||||
|
required: false,
|
||||||
|
'x-designer': 'FormItem.FilterFormDesigner',
|
||||||
|
'x-component': field.interface === 'o2m' && !snapshot ? 'TableField' : 'CollectionField',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-collection-field': `${name}.${field.name}`,
|
||||||
|
'x-component-props': {},
|
||||||
|
'x-read-pretty': field?.uiSchema?.['x-read-pretty'],
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
type: 'item',
|
||||||
|
title: field?.uiSchema?.title || field.name,
|
||||||
|
component: 'CollectionFieldInitializer',
|
||||||
|
remove: removeGridFormItem,
|
||||||
|
schemaInitialize: (s) => {
|
||||||
|
interfaceConfig?.schemaInitialize?.(s, { field, block, readPretty });
|
||||||
|
},
|
||||||
|
schema,
|
||||||
|
} as SchemaInitializerItemOptions;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
export const useCustomFormItemInitializerFields = (options?: any) => {
|
export const useCustomFormItemInitializerFields = (options?: any) => {
|
||||||
const { name, currentFields } = useCollection();
|
const { name, currentFields } = useCollection();
|
||||||
const { getInterface } = useCollectionManager();
|
const { getInterface } = useCollectionManager();
|
||||||
@ -434,7 +579,6 @@ export const useCurrentSchema = (action: string, key: string, find = findSchema,
|
|||||||
const { remove } = useDesignable();
|
const { remove } = useDesignable();
|
||||||
const schema = find(fieldSchema, key, action);
|
const schema = find(fieldSchema, key, action);
|
||||||
const ctx = useContext(BlockRequestContext);
|
const ctx = useContext(BlockRequestContext);
|
||||||
const exists = !!schema;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
schema,
|
schema,
|
||||||
@ -658,7 +802,7 @@ export const createDetailsBlockSchema = (options) => {
|
|||||||
properties: {
|
properties: {
|
||||||
[uid()]: {
|
[uid()]: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'FormV2',
|
'x-component': 'Details',
|
||||||
'x-read-pretty': true,
|
'x-read-pretty': true,
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
useProps: '{{ useDetailsBlockProps }}',
|
useProps: '{{ useDetailsBlockProps }}',
|
||||||
@ -760,6 +904,67 @@ export const createFormBlockSchema = (options) => {
|
|||||||
return schema;
|
return schema;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createFilterFormBlockSchema = (options) => {
|
||||||
|
const {
|
||||||
|
formItemInitializers = 'FilterFormItemInitializers',
|
||||||
|
actionInitializers = 'FilterFormActionInitializers',
|
||||||
|
collection,
|
||||||
|
resource,
|
||||||
|
association,
|
||||||
|
action,
|
||||||
|
template,
|
||||||
|
...others
|
||||||
|
} = options;
|
||||||
|
const resourceName = resource || association || collection;
|
||||||
|
const schema: ISchema = {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'FormBlockProvider',
|
||||||
|
'x-decorator-props': {
|
||||||
|
...others,
|
||||||
|
action,
|
||||||
|
resource: resourceName,
|
||||||
|
collection,
|
||||||
|
association,
|
||||||
|
},
|
||||||
|
'x-designer': 'FormV2.FilterDesigner',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
// 保存当前筛选区块所能过滤的数据区块
|
||||||
|
'x-filter-targets': [],
|
||||||
|
// 用于存储用户设置的每个字段的运算符,目前仅筛选表单区块支持自定义
|
||||||
|
'x-filter-operators': {},
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'FormV2',
|
||||||
|
'x-component-props': {
|
||||||
|
useProps: '{{ useFormBlockProps }}',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
grid: template || {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer': formItemInitializers,
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
'x-initializer': actionInitializers,
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {
|
||||||
|
layout: 'one-column',
|
||||||
|
style: {
|
||||||
|
float: 'right',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return schema;
|
||||||
|
};
|
||||||
|
|
||||||
export const createReadPrettyFormBlockSchema = (options) => {
|
export const createReadPrettyFormBlockSchema = (options) => {
|
||||||
const {
|
const {
|
||||||
formItemInitializers = 'ReadPrettyFormItemInitializers',
|
formItemInitializers = 'ReadPrettyFormItemInitializers',
|
||||||
@ -830,6 +1035,9 @@ export const createTableBlockSchema = (options) => {
|
|||||||
tableActionColumnInitializers,
|
tableActionColumnInitializers,
|
||||||
tableBlockProvider,
|
tableBlockProvider,
|
||||||
disableTemplate,
|
disableTemplate,
|
||||||
|
TableBlockDesigner,
|
||||||
|
blockType,
|
||||||
|
pageSize = 20,
|
||||||
...others
|
...others
|
||||||
} = options;
|
} = options;
|
||||||
const schema: ISchema = {
|
const schema: ISchema = {
|
||||||
@ -841,16 +1049,18 @@ export const createTableBlockSchema = (options) => {
|
|||||||
resource: resource || collection,
|
resource: resource || collection,
|
||||||
action: 'list',
|
action: 'list',
|
||||||
params: {
|
params: {
|
||||||
pageSize: 20,
|
pageSize,
|
||||||
},
|
},
|
||||||
rowKey,
|
rowKey,
|
||||||
showIndex: true,
|
showIndex: true,
|
||||||
dragSort: false,
|
dragSort: false,
|
||||||
disableTemplate: disableTemplate ?? false,
|
disableTemplate: disableTemplate ?? false,
|
||||||
|
blockType,
|
||||||
...others,
|
...others,
|
||||||
},
|
},
|
||||||
'x-designer': 'TableBlockDesigner',
|
'x-designer': TableBlockDesigner ?? 'TableBlockDesigner',
|
||||||
'x-component': 'CardItem',
|
'x-component': 'CardItem',
|
||||||
|
'x-filter-targets': [],
|
||||||
properties: {
|
properties: {
|
||||||
actions: {
|
actions: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
@ -903,6 +1113,35 @@ export const createTableBlockSchema = (options) => {
|
|||||||
return schema;
|
return schema;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createCollapseBlockSchema = (options) => {
|
||||||
|
const { collection, blockType } = options;
|
||||||
|
const schema: ISchema = {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'AssociationFilter.Provider',
|
||||||
|
'x-decorator-props': {
|
||||||
|
collection,
|
||||||
|
blockType,
|
||||||
|
associationFilterStyle: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-designer': 'AssociationFilter.BlockDesigner',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-filter-targets': [],
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
'x-action': 'associateFilter',
|
||||||
|
'x-initializer': 'AssociationFilter.FilterBlockInitializer',
|
||||||
|
'x-component': 'AssociationFilter',
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return schema;
|
||||||
|
};
|
||||||
|
|
||||||
export const createTableSelectorSchema = (options) => {
|
export const createTableSelectorSchema = (options) => {
|
||||||
const { collection, resource, rowKey, ...others } = options;
|
const { collection, resource, rowKey, ...others } = options;
|
||||||
const schema: ISchema = {
|
const schema: ISchema = {
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
Cascader,
|
Cascader,
|
||||||
CascaderProps,
|
CascaderProps,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
|
Empty,
|
||||||
Menu,
|
Menu,
|
||||||
MenuItemProps,
|
MenuItemProps,
|
||||||
Modal,
|
Modal,
|
||||||
@ -19,7 +20,7 @@ import {
|
|||||||
} from 'antd';
|
} from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import React, { createContext, useContext, useMemo, useState } from 'react';
|
import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
@ -43,7 +44,10 @@ import { useSchemaTemplateManager } from '../schema-templates';
|
|||||||
import { useBlockTemplateContext } from '../schema-templates/BlockTemplate';
|
import { useBlockTemplateContext } from '../schema-templates/BlockTemplate';
|
||||||
import { FormLinkageRules } from './LinkageRules';
|
import { FormLinkageRules } from './LinkageRules';
|
||||||
import { useLinkageCollectionFieldOptions } from './LinkageRules/action-hooks';
|
import { useLinkageCollectionFieldOptions } from './LinkageRules/action-hooks';
|
||||||
|
import { FilterBlockType, isSameCollection, useSupportedBlocks } from '../filter-provider/utils';
|
||||||
|
import { findFilterTargets, updateFilterTargets } from '../block-provider/hooks';
|
||||||
import { EnableChildCollections } from './EnableChildCollections';
|
import { EnableChildCollections } from './EnableChildCollections';
|
||||||
|
import { getTargetKey } from '../schema-component/antd/association-filter/utilts';
|
||||||
|
|
||||||
interface SchemaSettingsProps {
|
interface SchemaSettingsProps {
|
||||||
title?: any;
|
title?: any;
|
||||||
@ -448,8 +452,142 @@ SchemaSettings.Remove = (props: any) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SchemaSettings.ConnectDataBlocks = (props: { type: FilterBlockType; emptyDescription?: string }) => {
|
||||||
|
const { type, emptyDescription } = props;
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const collection = useCollection();
|
||||||
|
const dataBlocks = useSupportedBlocks(type);
|
||||||
|
let { targets = [], uid } = findFilterTargets(fieldSchema);
|
||||||
|
const compile = useCompile();
|
||||||
|
|
||||||
|
const Content = dataBlocks.map((block) => {
|
||||||
|
const title = `${compile(block.collection.title)} #${block.uid.slice(0, 4)}`;
|
||||||
|
const onHover = () => {
|
||||||
|
const dom = block.dom;
|
||||||
|
const designer = dom.querySelector('.general-schema-designer') as HTMLElement;
|
||||||
|
if (designer) {
|
||||||
|
designer.style.display = 'block';
|
||||||
|
}
|
||||||
|
dom.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.2)';
|
||||||
|
dom.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'center',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const onLeave = () => {
|
||||||
|
const dom = block.dom;
|
||||||
|
const designer = dom.querySelector('.general-schema-designer') as HTMLElement;
|
||||||
|
if (designer) {
|
||||||
|
designer.style.display = null;
|
||||||
|
}
|
||||||
|
dom.style.boxShadow = 'none';
|
||||||
|
};
|
||||||
|
if (isSameCollection(block.collection, collection)) {
|
||||||
|
return (
|
||||||
|
<SchemaSettings.SwitchItem
|
||||||
|
key={block.uid}
|
||||||
|
title={title}
|
||||||
|
checked={targets.some((target) => target.uid === block.uid)}
|
||||||
|
onChange={(checked) => {
|
||||||
|
if (checked) {
|
||||||
|
targets.push({ uid: block.uid });
|
||||||
|
} else {
|
||||||
|
targets = targets.filter((target) => target.uid !== block.uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFilterTargets(fieldSchema, targets);
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
['x-uid']: uid,
|
||||||
|
'x-filter-targets': targets,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dn.refresh();
|
||||||
|
}}
|
||||||
|
onMouseEnter={onHover}
|
||||||
|
onMouseLeave={onLeave}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = targets.find((target) => target.uid === block.uid);
|
||||||
|
// 与筛选区块的数据表具有关系的表
|
||||||
|
return (
|
||||||
|
<SchemaSettings.SelectItem
|
||||||
|
key={block.uid}
|
||||||
|
title={title}
|
||||||
|
value={target?.field || ''}
|
||||||
|
options={[
|
||||||
|
...block.associatedFields
|
||||||
|
.filter((field) => field.target === collection.name)
|
||||||
|
.map((field) => {
|
||||||
|
return {
|
||||||
|
label: compile(field.uiSchema.title) || field.name,
|
||||||
|
value: `${field.name}.${getTargetKey(field)}`,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
label: t('Unconnected'),
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onChange={(value) => {
|
||||||
|
if (value === '') {
|
||||||
|
targets = targets.filter((target) => target.uid !== block.uid);
|
||||||
|
} else {
|
||||||
|
targets = targets.filter((target) => target.uid !== block.uid);
|
||||||
|
targets.push({ uid: block.uid, field: value });
|
||||||
|
}
|
||||||
|
updateFilterTargets(fieldSchema, targets);
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
['x-uid']: uid,
|
||||||
|
'x-filter-targets': targets,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dn.refresh();
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
onMouseEnter={onHover}
|
||||||
|
onMouseLeave={onLeave}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaSettings.SubMenu title={t('Connect data blocks')}>
|
||||||
|
{Content.length ? (
|
||||||
|
Content
|
||||||
|
) : (
|
||||||
|
<Empty
|
||||||
|
style={{ width: 160, padding: '0 1em' }}
|
||||||
|
description={emptyDescription}
|
||||||
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</SchemaSettings.SubMenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
SchemaSettings.SelectItem = (props) => {
|
SchemaSettings.SelectItem = (props) => {
|
||||||
const { title, options, value, onChange, ...others } = props;
|
const { title, options, value, onChange, openOnHover, onClick: _onClick, ...others } = props;
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const onClick = (...args) => {
|
||||||
|
setOpen(false);
|
||||||
|
_onClick?.(...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 鼠标 hover 时,打开下拉框
|
||||||
|
const moreProps = openOnHover
|
||||||
|
? {
|
||||||
|
onMouseEnter: useCallback(() => setOpen(true), []),
|
||||||
|
open,
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaSettings.Item {...others}>
|
<SchemaSettings.Item {...others}>
|
||||||
<div style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}>
|
||||||
@ -457,9 +595,11 @@ SchemaSettings.SelectItem = (props) => {
|
|||||||
<Select
|
<Select
|
||||||
bordered={false}
|
bordered={false}
|
||||||
defaultValue={value}
|
defaultValue={value}
|
||||||
onChange={onChange}
|
onChange={(...arg) => (setOpen(false), onChange(...arg))}
|
||||||
options={options}
|
options={options}
|
||||||
style={{ textAlign: 'right', minWidth: 100 }}
|
style={{ textAlign: 'right', minWidth: 100 }}
|
||||||
|
onClick={onClick}
|
||||||
|
{...moreProps}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</SchemaSettings.Item>
|
</SchemaSettings.Item>
|
||||||
@ -513,7 +653,6 @@ SchemaSettings.PopupItem = (props) => {
|
|||||||
const { schema, ...others } = props;
|
const { schema, ...others } = props;
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const ctx = useContext(SchemaSettingsContext);
|
const ctx = useContext(SchemaSettingsContext);
|
||||||
const actx = useActionContext();
|
|
||||||
return (
|
return (
|
||||||
<ActionContext.Provider value={{ visible, setVisible }}>
|
<ActionContext.Provider value={{ visible, setVisible }}>
|
||||||
<SchemaSettings.Item
|
<SchemaSettings.Item
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import deepmerge from 'deepmerge';
|
import deepmerge from 'deepmerge';
|
||||||
import lodash from 'lodash';
|
import lodash from 'lodash';
|
||||||
|
import { isPlainObject } from './common';
|
||||||
|
|
||||||
type MergeStrategyType = 'merge' | 'deepMerge' | 'overwrite' | 'andMerge' | 'orMerge' | 'intersect' | 'union';
|
type MergeStrategyType = 'merge' | 'deepMerge' | 'overwrite' | 'andMerge' | 'orMerge' | 'intersect' | 'union';
|
||||||
type MergeStrategyFunc = (x: any, y: any) => any;
|
type MergeStrategyFunc = (x: any, y: any) => any;
|
||||||
@ -10,15 +11,6 @@ export interface MergeStrategies {
|
|||||||
[key: string]: MergeStrategy;
|
[key: string]: MergeStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function isPlainObject(value) {
|
|
||||||
if (Object.prototype.toString.call(value) !== '[object Object]') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const prototype = Object.getPrototypeOf(value);
|
|
||||||
return prototype === null || prototype === Object.prototype;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEnumerableOwnPropertySymbols(target: any): any[] {
|
function getEnumerableOwnPropertySymbols(target: any): any[] {
|
||||||
return Object.getOwnPropertySymbols
|
return Object.getOwnPropertySymbols
|
||||||
? Object.getOwnPropertySymbols(target).filter((symbol) => target.propertyIsEnumerable(symbol))
|
? Object.getOwnPropertySymbols(target).filter((symbol) => target.propertyIsEnumerable(symbol))
|
||||||
@ -71,8 +63,8 @@ mergeStrategies.set('orMerge', (x, y) => {
|
|||||||
mergeStrategies.set('deepMerge', (x, y) => {
|
mergeStrategies.set('deepMerge', (x, y) => {
|
||||||
return isPlainObject(x) && isPlainObject(y)
|
return isPlainObject(x) && isPlainObject(y)
|
||||||
? deepmerge(x, y, {
|
? deepmerge(x, y, {
|
||||||
arrayMerge: (x, y) => y,
|
arrayMerge: (x, y) => y,
|
||||||
})
|
})
|
||||||
: y;
|
: y;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -90,21 +82,23 @@ mergeStrategies.set('union', (x, y) => {
|
|||||||
return lodash.uniq((x || []).concat(y || [])).filter(Boolean);
|
return lodash.uniq((x || []).concat(y || [])).filter(Boolean);
|
||||||
});
|
});
|
||||||
|
|
||||||
mergeStrategies.set('intersect', (x, y) => (() => {
|
mergeStrategies.set('intersect', (x, y) =>
|
||||||
if (typeof x === 'string') {
|
(() => {
|
||||||
x = x.split(',');
|
if (typeof x === 'string') {
|
||||||
}
|
x = x.split(',');
|
||||||
if (typeof y === 'string') {
|
}
|
||||||
y = y.split(',');
|
if (typeof y === 'string') {
|
||||||
}
|
y = y.split(',');
|
||||||
if (!Array.isArray(x) || x.length === 0) {
|
}
|
||||||
return y || [];
|
if (!Array.isArray(x) || x.length === 0) {
|
||||||
}
|
return y || [];
|
||||||
if (!Array.isArray(y) || y.length === 0) {
|
}
|
||||||
return x || [];
|
if (!Array.isArray(y) || y.length === 0) {
|
||||||
}
|
return x || [];
|
||||||
return x.filter((v) => y.includes(v));
|
}
|
||||||
})().filter(Boolean));
|
return x.filter((v) => y.includes(v));
|
||||||
|
})().filter(Boolean),
|
||||||
|
);
|
||||||
|
|
||||||
export function assign(target: any, source: any, strategies: MergeStrategies = {}) {
|
export function assign(target: any, source: any, strategies: MergeStrategies = {}) {
|
||||||
getKeys(source).forEach((sourceKey) => {
|
getKeys(source).forEach((sourceKey) => {
|
||||||
|
@ -5,4 +5,5 @@ export * from './number';
|
|||||||
export * from './registry';
|
export * from './registry';
|
||||||
// export * from './toposort';
|
// export * from './toposort';
|
||||||
export * from './uid';
|
export * from './uid';
|
||||||
|
export * from './common';
|
||||||
|
|
||||||
|
18
packages/core/utils/src/common.ts
Normal file
18
packages/core/utils/src/common.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export const isEmpty = (value: unknown) => {
|
||||||
|
if (isPlainObject(value)) {
|
||||||
|
return Object.keys(value).length === 0;
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.length === 0;
|
||||||
|
}
|
||||||
|
return !value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isPlainObject = (value) => {
|
||||||
|
if (Object.prototype.toString.call(value) !== '[object Object]') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prototype = Object.getPrototypeOf(value);
|
||||||
|
return prototype === null || prototype === Object.prototype;
|
||||||
|
};
|
@ -9,3 +9,4 @@ export * from './toposort';
|
|||||||
export * from './uid';
|
export * from './uid';
|
||||||
export * from './assign';
|
export * from './assign';
|
||||||
export * from './collections-graph';
|
export * from './collections-graph';
|
||||||
|
export * from './common';
|
||||||
|
@ -80,7 +80,7 @@ export const AuditLogsTableColumnInitializers = (props: any) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
items={itemsMerge(fieldItems, items)}
|
items={itemsMerge(fieldItems)}
|
||||||
>
|
>
|
||||||
{t('Configure columns')}
|
{t('Configure columns')}
|
||||||
</SchemaInitializer.Button>
|
</SchemaInitializer.Button>
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"Event": "Event",
|
"Event": "Event",
|
||||||
"None": "None",
|
"None": "None",
|
||||||
|
"Unconnected": "Unconnected",
|
||||||
"System settings": "System settings",
|
"System settings": "System settings",
|
||||||
"System title": "System title",
|
"System title": "System title",
|
||||||
"Logo": "Logo",
|
"Logo": "Logo",
|
||||||
@ -54,8 +55,10 @@
|
|||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
"Set the data scope": "Set the data scope",
|
"Set the data scope": "Set the data scope",
|
||||||
"Data blocks": "Data blocks",
|
"Data blocks": "Data blocks",
|
||||||
|
"Filter blocks": "Filter blocks",
|
||||||
"Table": "Table",
|
"Table": "Table",
|
||||||
"Form": "Form",
|
"Form": "Form",
|
||||||
|
"Collapse": "Collapse",
|
||||||
"Select data source": "Select data source",
|
"Select data source": "Select data source",
|
||||||
"Calendar": "Calendar",
|
"Calendar": "Calendar",
|
||||||
"Delete events": "Delete events",
|
"Delete events": "Delete events",
|
||||||
@ -103,6 +106,7 @@
|
|||||||
"Are you sure you want to delete it?": "Are you sure you want to delete it?",
|
"Are you sure you want to delete it?": "Are you sure you want to delete it?",
|
||||||
"This is a demo text, **supports Markdown syntax**.": "This is a demo text, **supports Markdown syntax**.",
|
"This is a demo text, **supports Markdown syntax**.": "This is a demo text, **supports Markdown syntax**.",
|
||||||
"Filter": "Filter",
|
"Filter": "Filter",
|
||||||
|
"Connect data blocks": "Connect data blocks",
|
||||||
"Action type": "Action type",
|
"Action type": "Action type",
|
||||||
"Actions": "Actions",
|
"Actions": "Actions",
|
||||||
"Insert": "Insert",
|
"Insert": "Insert",
|
||||||
@ -142,6 +146,7 @@
|
|||||||
"Association fields filter": "Association fields filter",
|
"Association fields filter": "Association fields filter",
|
||||||
"PK & FK fields": "PK & FK fields",
|
"PK & FK fields": "PK & FK fields",
|
||||||
"Association fields": "Association fields",
|
"Association fields": "Association fields",
|
||||||
|
"Optional fields": "Optional fields",
|
||||||
"System fields": "System fields",
|
"System fields": "System fields",
|
||||||
"General fields": "General fields",
|
"General fields": "General fields",
|
||||||
"Parent collection fields": "Parent collection fields",
|
"Parent collection fields": "Parent collection fields",
|
||||||
@ -219,6 +224,7 @@
|
|||||||
"Edit block title": "Edit block title",
|
"Edit block title": "Edit block title",
|
||||||
"Block title": "Block title",
|
"Block title": "Block title",
|
||||||
"Pattern": "Pattern",
|
"Pattern": "Pattern",
|
||||||
|
"operater": "operater",
|
||||||
"Editable": "Editable",
|
"Editable": "Editable",
|
||||||
"Readonly": "Readonly",
|
"Readonly": "Readonly",
|
||||||
"Easy-reading": "Easy-reading",
|
"Easy-reading": "Easy-reading",
|
||||||
@ -383,6 +389,7 @@
|
|||||||
"Convert reference to duplicate": "Convert reference to duplicate",
|
"Convert reference to duplicate": "Convert reference to duplicate",
|
||||||
"Template name": "Template name",
|
"Template name": "Template name",
|
||||||
"Block type": "Block type",
|
"Block type": "Block type",
|
||||||
|
"No blocks to connect": "No blocks to connect",
|
||||||
"Action column": "Action column",
|
"Action column": "Action column",
|
||||||
"Records per page": "Records per page",
|
"Records per page": "Records per page",
|
||||||
"(Fields only)": "(Fields only)",
|
"(Fields only)": "(Fields only)",
|
||||||
@ -924,6 +931,7 @@
|
|||||||
"Delete field": "Delete field",
|
"Delete field": "Delete field",
|
||||||
"Required": "Required",
|
"Required": "Required",
|
||||||
"Pattern": "Pattern",
|
"Pattern": "Pattern",
|
||||||
|
"operater": "operater",
|
||||||
"Editable": "Editable",
|
"Editable": "Editable",
|
||||||
"Readonly": "Readonly",
|
"Readonly": "Readonly",
|
||||||
"Easy-reading": "Easy-reading",
|
"Easy-reading": "Easy-reading",
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"Time": "时间",
|
"Time": "时间",
|
||||||
"Event": "事件",
|
"Event": "事件",
|
||||||
"None": "无",
|
"None": "无",
|
||||||
|
"Unconnected": "未连接",
|
||||||
"System settings": "系统设置",
|
"System settings": "系统设置",
|
||||||
"System title": "系统名称",
|
"System title": "系统名称",
|
||||||
"Logo": "Logo",
|
"Logo": "Logo",
|
||||||
@ -54,8 +55,10 @@
|
|||||||
"Close": "关闭",
|
"Close": "关闭",
|
||||||
"Set the data scope": "设置数据范围",
|
"Set the data scope": "设置数据范围",
|
||||||
"Data blocks": "数据区块",
|
"Data blocks": "数据区块",
|
||||||
|
"Filter blocks": "筛选区块",
|
||||||
"Table": "表格",
|
"Table": "表格",
|
||||||
"Form": "表单",
|
"Form": "表单",
|
||||||
|
"Collapse": "折叠面板",
|
||||||
"Select data source": "选择数据源",
|
"Select data source": "选择数据源",
|
||||||
"Calendar": "日历",
|
"Calendar": "日历",
|
||||||
"Delete events": "删除日程",
|
"Delete events": "删除日程",
|
||||||
@ -103,6 +106,7 @@
|
|||||||
"Are you sure you want to delete it?": "你确定要删除吗?",
|
"Are you sure you want to delete it?": "你确定要删除吗?",
|
||||||
"This is a demo text, **supports Markdown syntax**.": "这是一段演示文本,**支持 Markdown 语法**。",
|
"This is a demo text, **supports Markdown syntax**.": "这是一段演示文本,**支持 Markdown 语法**。",
|
||||||
"Filter": "筛选",
|
"Filter": "筛选",
|
||||||
|
"Connect data blocks": "连接数据区块",
|
||||||
"Action type": "操作类型",
|
"Action type": "操作类型",
|
||||||
"Actions": "操作",
|
"Actions": "操作",
|
||||||
"Insert": "新增",
|
"Insert": "新增",
|
||||||
@ -142,6 +146,7 @@
|
|||||||
"Association fields filter": "关系筛选",
|
"Association fields filter": "关系筛选",
|
||||||
"PK & FK fields": "主外键字段",
|
"PK & FK fields": "主外键字段",
|
||||||
"Association fields": "关系字段",
|
"Association fields": "关系字段",
|
||||||
|
"Optional fields": "可选字段",
|
||||||
"System fields": "系统字段",
|
"System fields": "系统字段",
|
||||||
"General fields": "普通字段",
|
"General fields": "普通字段",
|
||||||
"Parent collection fields": "父表字段",
|
"Parent collection fields": "父表字段",
|
||||||
@ -219,6 +224,7 @@
|
|||||||
"Edit block title": "编辑区块标题",
|
"Edit block title": "编辑区块标题",
|
||||||
"Block title": "区块标题",
|
"Block title": "区块标题",
|
||||||
"Pattern": "模式",
|
"Pattern": "模式",
|
||||||
|
"operater": "运算符",
|
||||||
"Editable": "可编辑",
|
"Editable": "可编辑",
|
||||||
"Readonly": "只读(禁止编辑)",
|
"Readonly": "只读(禁止编辑)",
|
||||||
"Easy-reading": "只读(阅读模式)",
|
"Easy-reading": "只读(阅读模式)",
|
||||||
@ -383,6 +389,7 @@
|
|||||||
"Convert reference to duplicate": "模板引用转为复制",
|
"Convert reference to duplicate": "模板引用转为复制",
|
||||||
"Template name": "模板名称",
|
"Template name": "模板名称",
|
||||||
"Block type": "区块类型",
|
"Block type": "区块类型",
|
||||||
|
"No blocks to connect": "没有可连接的区块",
|
||||||
"Action column": "操作列",
|
"Action column": "操作列",
|
||||||
"Records per page": "每页显示数量",
|
"Records per page": "每页显示数量",
|
||||||
"(Fields only)": "(仅字段)",
|
"(Fields only)": "(仅字段)",
|
||||||
@ -918,6 +925,7 @@
|
|||||||
"Delete field": "删除字段",
|
"Delete field": "删除字段",
|
||||||
"Required": "必填",
|
"Required": "必填",
|
||||||
"Pattern": "模式",
|
"Pattern": "模式",
|
||||||
|
"operater": "运算符",
|
||||||
"Editable": "可编辑",
|
"Editable": "可编辑",
|
||||||
"Readonly": "只读(禁止编辑)",
|
"Readonly": "只读(禁止编辑)",
|
||||||
"Easy-reading": "只读(阅读模式)",
|
"Easy-reading": "只读(阅读模式)",
|
||||||
|
@ -6,6 +6,7 @@ export default {
|
|||||||
'Create Collection': 'Create Collection',
|
'Create Collection': 'Create Collection',
|
||||||
'All Fields': 'All Fields',
|
'All Fields': 'All Fields',
|
||||||
'Association Fields': 'Association Fields',
|
'Association Fields': 'Association Fields',
|
||||||
|
'Optional fields': 'Optional fields',
|
||||||
'All relationships': 'All relationships',
|
'All relationships': 'All relationships',
|
||||||
'Entity relationship only': 'Entity relationship only',
|
'Entity relationship only': 'Entity relationship only',
|
||||||
'Inheritance relationship only': 'Inheritance relationship only',
|
'Inheritance relationship only': 'Inheritance relationship only',
|
||||||
|
@ -6,6 +6,7 @@ export default {
|
|||||||
'Create Collection':'创建数据表',
|
'Create Collection':'创建数据表',
|
||||||
'All Fields': '全部',
|
'All Fields': '全部',
|
||||||
'Association Fields': '关系字段',
|
'Association Fields': '关系字段',
|
||||||
|
'Optional fields': '可选字段',
|
||||||
'All relationships': '所有关系',
|
'All relationships': '所有关系',
|
||||||
'Entity relationship only': '实体关系',
|
'Entity relationship only': '实体关系',
|
||||||
'Inheritance relationship only': '继承关系',
|
'Inheritance relationship only': '继承关系',
|
||||||
|
@ -36,6 +36,7 @@ const locale = {
|
|||||||
'Delete field': '删除字段',
|
'Delete field': '删除字段',
|
||||||
Required: '必填',
|
Required: '必填',
|
||||||
Pattern: '模式',
|
Pattern: '模式',
|
||||||
|
"Operator": "运算符",
|
||||||
Editable: '可编辑',
|
Editable: '可编辑',
|
||||||
Readonly: '只读(禁止编辑)',
|
Readonly: '只读(禁止编辑)',
|
||||||
'Easy-reading': '只读(阅读模式)',
|
'Easy-reading': '只读(阅读模式)',
|
||||||
|
Loading…
Reference in New Issue
Block a user