feat: kanban& gantt&bulk edit& bulk update& duplicate& print action pluggable (#3019)
* fix: bug * test: kanban test * test: gantt block test * test: gantt test * test: gantt test * test: gantt test * refactor: convert parameters to destructured object * test: add tests choices * test: add tests media * test: add tests for datetime in creating form * feat(plugin-mock-collection): generate faker time * test: add tests for datetime in editing form * test: add tests for datetime in details form * test: gantt block test * test: gantt test * test: gantt test * test: gantt test * fix: bug * feat: improve code * test: add tests for relation fields * refactor: style improve * fix: rename SchemaSettings * fix: type bug * refactor: useDesinger() * fix: bug * fix: bug * fix: build tip * refactor: kanban setting * refactor: gantt setting * refactor: bulk update setting * refactor: bulk update setting * refactor: bulk edit settings * refactor: duplicate settings * refactor: print action setting * refactor: duplicate settings * refactor: bulk update refactor * refactor: bulk update refactor * fix: designableState * fix: bug * fix: designable * refactor: bulk update * fix: designable * test: add tests for relation fields * test: add tests for relation fields * test: add tests for relation fields * feat: client api doc * test: add tests for relation fields * test: avoid errors * test: make tests passing * fix: bug * test: gantt test * test: make tests passing * test: add tests for advanced fields * test: increase e2e timeout-minutes to 60 * fix: bug * fix: improve code * feat: add schema initailizer component demos * test: make tests passing * fix: schema settings demos * feat: shallowMerge & deepMerge * test: reduce number of tests * test: make tests passing * feat: updates * fix: add Initializer Internal * demos: useSchemaSettingsRender * test: make tests passing * test: make tests passing * test: bulk update test * refactor: useSchemaToolbar * fix: improve docs * fix: bug * test: duplicate action test * test: duplicate action test * test: duplicate action test * test: duplicate action test * chore: upgrade dumi theme * test: make tests passing * test: add tests for linkage rules * test: add test for form data templates * test: add tests for default value * test: reduce number of tests * refactor: bulk edit setting * test: bulk edit action test * test: bulk edit action test * fix: dn.deepMerge * fix: bug * test: bulk edit action test * test: kanban test * refactor: bulk update version * refactor: print action version * fix: bug * fix: toolbar * fix: docs ssr * test: add tests for system fields * test: add tests for actions * fix: bug * test: add tests for lazy loading of variables * test: make testing more stable * test: bulk edit test * test: bulk update action test * fix: update docs * test: print test * test: kanban test * test: kanban test * refactor: duplicate version * fix: merge bug * refactor: bulk update * fix: merge bug * refactor: bulk edit * refactor: import * refactor: export action * refactor: code improve * refactor: kanban code improve * fix: merge bug * refactor: readPrettyFormActionInitializers * test: print action test * test: print action test * refactor: print action * test: action test * refactor: gantt date change * style: gantt style improve * test: gantt &bulkUpdate test * test: bulk update test * test: duplicate action test * test: print action test * test: bulk edit action test * test: gantt block test * test: kanban block test * test: test file name * test: test file name * test: bulk update action tst * refactor: rename test file name * refactor: bulk update action * refactor: gantt test * fix: block template * fix: block template * fix: useFieldModeOptions * refactor: code improve * fix: merge bug --------- Co-authored-by: dream2023 <1098626505@qq.com> Co-authored-by: Rain <958414905@qq.com> Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
823a112435
commit
b1610e6994
@ -7,9 +7,7 @@ import { DetailsBlockProvider, useDetailsBlockProps } from './DetailsBlockProvid
|
|||||||
import { FilterFormBlockProvider } from './FilterFormBlockProvider';
|
import { FilterFormBlockProvider } from './FilterFormBlockProvider';
|
||||||
import { FormBlockProvider, useFormBlockProps } from './FormBlockProvider';
|
import { FormBlockProvider, useFormBlockProps } from './FormBlockProvider';
|
||||||
import { FormFieldProvider, useFormFieldProps } from './FormFieldProvider';
|
import { FormFieldProvider, useFormFieldProps } from './FormFieldProvider';
|
||||||
import { GanttBlockProvider, useGanttBlockProps } from './GanttBlockProvider';
|
|
||||||
import * as bp from './hooks';
|
import * as bp from './hooks';
|
||||||
import { KanbanBlockProvider, useKanbanBlockProps } from './KanbanBlockProvider';
|
|
||||||
import { TableBlockProvider, useTableBlockProps } from './TableBlockProvider';
|
import { TableBlockProvider, useTableBlockProps } from './TableBlockProvider';
|
||||||
import { TableFieldProvider, useTableFieldProps } from './TableFieldProvider';
|
import { TableFieldProvider, useTableFieldProps } from './TableFieldProvider';
|
||||||
import { TableSelectorProvider, useTableSelectorProps } from './TableSelectorProvider';
|
import { TableSelectorProvider, useTableSelectorProps } from './TableSelectorProvider';
|
||||||
@ -19,7 +17,6 @@ export const BlockSchemaComponentProvider: React.FC = (props) => {
|
|||||||
return (
|
return (
|
||||||
<SchemaComponentOptions
|
<SchemaComponentOptions
|
||||||
components={{
|
components={{
|
||||||
GanttBlockProvider,
|
|
||||||
CalendarBlockProvider,
|
CalendarBlockProvider,
|
||||||
TableFieldProvider,
|
TableFieldProvider,
|
||||||
TableBlockProvider,
|
TableBlockProvider,
|
||||||
@ -28,7 +25,6 @@ export const BlockSchemaComponentProvider: React.FC = (props) => {
|
|||||||
FilterFormBlockProvider,
|
FilterFormBlockProvider,
|
||||||
FormFieldProvider,
|
FormFieldProvider,
|
||||||
DetailsBlockProvider,
|
DetailsBlockProvider,
|
||||||
KanbanBlockProvider,
|
|
||||||
RecordLink,
|
RecordLink,
|
||||||
}}
|
}}
|
||||||
scope={{
|
scope={{
|
||||||
@ -43,8 +39,6 @@ export const BlockSchemaComponentProvider: React.FC = (props) => {
|
|||||||
useTableFieldProps,
|
useTableFieldProps,
|
||||||
useTableBlockProps,
|
useTableBlockProps,
|
||||||
useTableSelectorProps,
|
useTableSelectorProps,
|
||||||
useKanbanBlockProps,
|
|
||||||
useGanttBlockProps,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
@ -60,7 +54,6 @@ export class BlockSchemaComponentPlugin extends Plugin {
|
|||||||
|
|
||||||
addComponents() {
|
addComponents() {
|
||||||
this.app.addComponents({
|
this.app.addComponents({
|
||||||
GanttBlockProvider,
|
|
||||||
CalendarBlockProvider,
|
CalendarBlockProvider,
|
||||||
TableFieldProvider,
|
TableFieldProvider,
|
||||||
TableBlockProvider,
|
TableBlockProvider,
|
||||||
@ -69,7 +62,6 @@ export class BlockSchemaComponentPlugin extends Plugin {
|
|||||||
FilterFormBlockProvider,
|
FilterFormBlockProvider,
|
||||||
FormFieldProvider,
|
FormFieldProvider,
|
||||||
DetailsBlockProvider,
|
DetailsBlockProvider,
|
||||||
KanbanBlockProvider,
|
|
||||||
RecordLink,
|
RecordLink,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -87,8 +79,6 @@ export class BlockSchemaComponentPlugin extends Plugin {
|
|||||||
useTableFieldProps,
|
useTableFieldProps,
|
||||||
useTableBlockProps,
|
useTableBlockProps,
|
||||||
useTableSelectorProps,
|
useTableSelectorProps,
|
||||||
useKanbanBlockProps,
|
|
||||||
useGanttBlockProps,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import { TemplateBlockProvider } from './TemplateBlockProvider';
|
|||||||
export const FormBlockContext = createContext<any>({});
|
export const FormBlockContext = createContext<any>({});
|
||||||
|
|
||||||
const InternalFormBlockProvider = (props) => {
|
const InternalFormBlockProvider = (props) => {
|
||||||
|
const ctx = useFormBlockContext();
|
||||||
const { action, readPretty, params, association } = props;
|
const { action, readPretty, params, association } = props;
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const form = useMemo(
|
const form = useMemo(
|
||||||
@ -28,6 +29,7 @@ const InternalFormBlockProvider = (props) => {
|
|||||||
const record = useRecord();
|
const record = useRecord();
|
||||||
const formBlockValue = useMemo(() => {
|
const formBlockValue = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
|
...ctx,
|
||||||
params,
|
params,
|
||||||
action,
|
action,
|
||||||
form,
|
form,
|
||||||
|
@ -3,12 +3,10 @@ export * from './BlockSchemaComponentProvider';
|
|||||||
export * from './CalendarBlockProvider';
|
export * from './CalendarBlockProvider';
|
||||||
export * from './FilterFormBlockProvider';
|
export * from './FilterFormBlockProvider';
|
||||||
export * from './FormBlockProvider';
|
export * from './FormBlockProvider';
|
||||||
export * from './KanbanBlockProvider';
|
|
||||||
export * from './SharedFilterProvider';
|
export * from './SharedFilterProvider';
|
||||||
export * from './TableBlockProvider';
|
export * from './TableBlockProvider';
|
||||||
export * from './TableFieldProvider';
|
export * from './TableFieldProvider';
|
||||||
export * from './TableSelectorProvider';
|
export * from './TableSelectorProvider';
|
||||||
export * from './FormFieldProvider';
|
export * from './FormFieldProvider';
|
||||||
export * from './GanttBlockProvider';
|
|
||||||
export * from './SharedFilterProvider';
|
export * from './SharedFilterProvider';
|
||||||
export * from './hooks';
|
export * from './hooks';
|
||||||
|
@ -1,157 +0,0 @@
|
|||||||
import { createStyles } from 'antd-style';
|
|
||||||
|
|
||||||
export const useStyles = createStyles(({ css, token }) => {
|
|
||||||
return css`
|
|
||||||
.react-kanban-board {
|
|
||||||
height: 100%;
|
|
||||||
// padding: 5px;
|
|
||||||
// margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-kanban-card {
|
|
||||||
box-sizing: border-box;
|
|
||||||
max-width: 250px;
|
|
||||||
min-width: 250px;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 10px;
|
|
||||||
margin-bottom: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-kanban-card-skeleton {
|
|
||||||
box-sizing: border-box;
|
|
||||||
max-width: 300px;
|
|
||||||
min-width: 300px;
|
|
||||||
height: 70vh;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 0 12px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
> div {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
+ div {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-kanban-card--dragging {
|
|
||||||
box-shadow: 2px 2px grey;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-kanban-card__description {
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-kanban-card__title {
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
font-weight: bold;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-kanban-column {
|
|
||||||
background-color: ${token.colorFillQuaternary};
|
|
||||||
margin-right: 15px;
|
|
||||||
padding-bottom: 15px;
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-kanban-column-header {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-kanban-card-adder-form {
|
|
||||||
box-sizing: border-box;
|
|
||||||
max-width: 250px;
|
|
||||||
min-width: 250px;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 10px;
|
|
||||||
margin-bottom: 7px;
|
|
||||||
input {
|
|
||||||
border: 0px;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-kanban-card-adder-button {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 5px;
|
|
||||||
background-color: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
transition: 0.3s;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-size: 20px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-weight: bold;
|
|
||||||
&:hover {
|
|
||||||
background-color: #ccc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-kanban-card-adder-form__title {
|
|
||||||
font-weight: bold;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
font-weight: bold;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0px;
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-kanban-card-adder-form__description {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 10px;
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-kanban-card-adder-form__button {
|
|
||||||
background-color: #eee;
|
|
||||||
border: none;
|
|
||||||
padding: 5px;
|
|
||||||
width: 45%;
|
|
||||||
margin-top: 5px;
|
|
||||||
border-radius: 3px;
|
|
||||||
&:hover {
|
|
||||||
transition: 0.3s;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #ccc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-kanban-column-header {
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-kanban-column-header__button {
|
|
||||||
color: #333333;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-color: #cccccc;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus,
|
|
||||||
&:active {
|
|
||||||
background-color: #e6e6e6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-kanban-column-adder-button {
|
|
||||||
border: 2px dashed #eee;
|
|
||||||
height: 132px;
|
|
||||||
margin: 5px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
});
|
|
@ -20,7 +20,6 @@ export * from './application';
|
|||||||
export * from './async-data-provider';
|
export * from './async-data-provider';
|
||||||
export * from './auth';
|
export * from './auth';
|
||||||
export * from './block-provider';
|
export * from './block-provider';
|
||||||
export * from './board';
|
|
||||||
export * from './china-region';
|
export * from './china-region';
|
||||||
export * from './collection-manager';
|
export * from './collection-manager';
|
||||||
export * from './css-variable';
|
export * from './css-variable';
|
||||||
@ -47,3 +46,5 @@ export * from './style';
|
|||||||
export * from './system-settings';
|
export * from './system-settings';
|
||||||
export * from './user';
|
export * from './user';
|
||||||
export * from './variables';
|
export * from './variables';
|
||||||
|
export * from './flag-provider';
|
||||||
|
export * from './testUtils';
|
||||||
|
@ -675,7 +675,6 @@
|
|||||||
"Changed to": "Mudou para",
|
"Changed to": "Mudou para",
|
||||||
"Clear": "Limpar",
|
"Clear": "Limpar",
|
||||||
"Add attach": "Adicionar anexo",
|
"Add attach": "Adicionar anexo",
|
||||||
"Please select the records to be updated": "Por favor, selecione os registros a serem atualizados",
|
|
||||||
"Selector": "Seletor",
|
"Selector": "Seletor",
|
||||||
"Inner": "Interno",
|
"Inner": "Interno",
|
||||||
"Search and select collection": "Pesquisar e selecionar coleção",
|
"Search and select collection": "Pesquisar e selecionar coleção",
|
||||||
|
@ -15,12 +15,7 @@ import { RemoteDocumentTitlePlugin } from '../document-title';
|
|||||||
import { PinnedListPlugin } from '../plugin-manager';
|
import { PinnedListPlugin } from '../plugin-manager';
|
||||||
import { PMPlugin } from '../pm';
|
import { PMPlugin } from '../pm';
|
||||||
import { AdminLayoutPlugin, AuthLayout, RouteSchemaComponent } from '../route-switch';
|
import { AdminLayoutPlugin, AuthLayout, RouteSchemaComponent } from '../route-switch';
|
||||||
import {
|
import { AntdSchemaComponentPlugin, SchemaComponentPlugin, menuItemInitializer } from '../schema-component';
|
||||||
AntdSchemaComponentPlugin,
|
|
||||||
KanbanPlugin,
|
|
||||||
SchemaComponentPlugin,
|
|
||||||
menuItemInitializer,
|
|
||||||
} from '../schema-component';
|
|
||||||
import { ErrorFallback } from '../schema-component/antd/error-fallback';
|
import { ErrorFallback } from '../schema-component/antd/error-fallback';
|
||||||
import { AssociationFilterPlugin, SchemaInitializerPlugin } from '../schema-initializer';
|
import { AssociationFilterPlugin, SchemaInitializerPlugin } from '../schema-initializer';
|
||||||
import { BlockTemplateDetails, BlockTemplatePage } from '../schema-templates';
|
import { BlockTemplateDetails, BlockTemplatePage } from '../schema-templates';
|
||||||
@ -317,7 +312,6 @@ export class NocoBaseBuildInPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
async addPlugins() {
|
async addPlugins() {
|
||||||
await this.app.pm.add(AssociationFilterPlugin);
|
await this.app.pm.add(AssociationFilterPlugin);
|
||||||
await this.app.pm.add(KanbanPlugin);
|
|
||||||
await this.app.pm.add(LocalePlugin, { name: 'builtin-locale' });
|
await this.app.pm.add(LocalePlugin, { name: 'builtin-locale' });
|
||||||
await this.app.pm.add(AdminLayoutPlugin, { name: 'admin-layout' });
|
await this.app.pm.add(AdminLayoutPlugin, { name: 'admin-layout' });
|
||||||
await this.app.pm.add(SystemSettingsPlugin, { name: 'system-setting' });
|
await this.app.pm.add(SystemSettingsPlugin, { name: 'system-setting' });
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { ArrayTable } from '@formily/antd-v5';
|
import { ArrayTable } from '@formily/antd-v5';
|
||||||
import { onFieldInputValueChange, onFieldValueChange } from '@formily/core';
|
import { onFieldValueChange } from '@formily/core';
|
||||||
import { ISchema, connect, mapProps, useField, useFieldSchema, useForm, useFormEffects } from '@formily/react';
|
import { ISchema, connect, mapProps, useField, useFieldSchema, useForm, useFormEffects } from '@formily/react';
|
||||||
import { isValid, uid } from '@formily/shared';
|
import { isValid, uid } from '@formily/shared';
|
||||||
import { Alert, Tree as AntdTree, ModalProps } from 'antd';
|
import { Alert, Tree as AntdTree, ModalProps } from 'antd';
|
||||||
import { cloneDeep } from 'lodash';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { RemoteSelect, useCompile, useDesignable } from '../..';
|
import { RemoteSelect, useCompile, useDesignable } from '../..';
|
||||||
import { useApp } from '../../../application';
|
import { useApp } from '../../../application';
|
||||||
@ -13,10 +12,8 @@ import { SchemaSettingOptions, SchemaSettings } from '../../../application/schem
|
|||||||
import { useSchemaToolbar } from '../../../application/schema-toolbar';
|
import { useSchemaToolbar } from '../../../application/schema-toolbar';
|
||||||
import { CollectionOptions, useCollection, useCollectionManager } from '../../../collection-manager';
|
import { CollectionOptions, useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
import { FlagProvider } from '../../../flag-provider';
|
import { FlagProvider } from '../../../flag-provider';
|
||||||
import { useRecord } from '../../../record-provider';
|
|
||||||
import { SchemaSettingOpenModeSchemaItems } from '../../../schema-items';
|
import { SchemaSettingOpenModeSchemaItems } from '../../../schema-items';
|
||||||
import { useCollectionState } from '../../../schema-settings/DataTemplates/hooks/useCollectionState';
|
import { useCollectionState } from '../../../schema-settings/DataTemplates/hooks/useCollectionState';
|
||||||
import { useSyncFromForm } from '../../../schema-settings/DataTemplates/utils';
|
|
||||||
import { GeneralSchemaDesigner } from '../../../schema-settings/GeneralSchemaDesigner';
|
import { GeneralSchemaDesigner } from '../../../schema-settings/GeneralSchemaDesigner';
|
||||||
import {
|
import {
|
||||||
SchemaSettingsActionModalItem,
|
SchemaSettingsActionModalItem,
|
||||||
@ -44,27 +41,10 @@ const Tree = connect(
|
|||||||
field.value = checkedKeys;
|
field.value = checkedKeys;
|
||||||
};
|
};
|
||||||
field.onCheck = onCheck;
|
field.onCheck = onCheck;
|
||||||
const form = useForm();
|
|
||||||
return {
|
return {
|
||||||
...props,
|
...props,
|
||||||
checkedKeys,
|
checkedKeys,
|
||||||
onCheck,
|
onCheck,
|
||||||
treeData: props?.treeData.map((v: any) => {
|
|
||||||
if (form.values.duplicateMode === 'quickDulicate') {
|
|
||||||
const children = v?.children?.map((k) => {
|
|
||||||
return {
|
|
||||||
...k,
|
|
||||||
disabled: false,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
...v,
|
|
||||||
disabled: false,
|
|
||||||
children,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -269,264 +249,6 @@ const getAllkeys = (data, result) => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
function DuplicationMode() {
|
|
||||||
const { dn } = useDesignable();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const field = useField();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
const { name } = useCollection();
|
|
||||||
const { collectionList, getEnableFieldTree, getOnLoadData, getOnCheck } = useCollectionState(name);
|
|
||||||
const duplicateValues = cloneDeep(fieldSchema['x-component-props'].duplicateFields || []);
|
|
||||||
const record = useRecord();
|
|
||||||
const syncCallBack = useCallback((treeData, selectFields, form) => {
|
|
||||||
form.query('duplicateFields').take((f) => {
|
|
||||||
f.componentProps.treeData = treeData;
|
|
||||||
f.componentProps.defaultCheckedKeys = selectFields;
|
|
||||||
f.setInitialValue(selectFields);
|
|
||||||
f?.onCheck(selectFields);
|
|
||||||
form.setValues({ ...form.values, treeData });
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
const useSelectAllFields = (form) => {
|
|
||||||
return {
|
|
||||||
async run() {
|
|
||||||
form.query('duplicateFields').take((f) => {
|
|
||||||
const selectFields = getAllkeys(f.componentProps.treeData, []);
|
|
||||||
f.componentProps.defaultCheckedKeys = selectFields;
|
|
||||||
f.setInitialValue(selectFields);
|
|
||||||
f?.onCheck(selectFields);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const useUnSelectAllFields = (form) => {
|
|
||||||
return {
|
|
||||||
async run() {
|
|
||||||
form.query('duplicateFields').take((f) => {
|
|
||||||
f.componentProps.defaultCheckedKeys = [];
|
|
||||||
f.setInitialValue([]);
|
|
||||||
f?.onCheck([]);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<SchemaSettingsModalItem
|
|
||||||
title={t('Duplicate mode')}
|
|
||||||
components={{ Tree }}
|
|
||||||
scope={{
|
|
||||||
getEnableFieldTree,
|
|
||||||
collectionName: fieldSchema['x-component-props']?.duplicateCollection || record?.__collection || name,
|
|
||||||
currentCollection: record?.__collection || name,
|
|
||||||
getOnLoadData,
|
|
||||||
getOnCheck,
|
|
||||||
treeData: fieldSchema['x-component-props']?.treeData,
|
|
||||||
duplicateValues,
|
|
||||||
onFieldInputValueChange,
|
|
||||||
}}
|
|
||||||
schema={
|
|
||||||
{
|
|
||||||
type: 'object',
|
|
||||||
title: t('Duplicate mode'),
|
|
||||||
properties: {
|
|
||||||
duplicateMode: {
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Radio.Group',
|
|
||||||
title: t('Duplicate mode'),
|
|
||||||
default: fieldSchema['x-component-props']?.duplicateMode || 'quickDulicate',
|
|
||||||
enum: [
|
|
||||||
{ value: 'quickDulicate', label: '{{t("Direct duplicate")}}' },
|
|
||||||
{ value: 'continueduplicate', label: '{{t("Copy into the form and continue to fill in")}}' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
collection: {
|
|
||||||
type: 'string',
|
|
||||||
title: '{{ t("Target collection") }}',
|
|
||||||
required: true,
|
|
||||||
description: t('If collection inherits, choose inherited collections as templates'),
|
|
||||||
default: '{{ collectionName }}',
|
|
||||||
'x-display': collectionList.length > 1 ? 'visible' : 'hidden',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Select',
|
|
||||||
'x-component-props': {
|
|
||||||
options: collectionList,
|
|
||||||
},
|
|
||||||
'x-reactions': [
|
|
||||||
{
|
|
||||||
dependencies: ['.duplicateMode'],
|
|
||||||
fulfill: {
|
|
||||||
state: {
|
|
||||||
disabled: `{{ $deps[0]==="quickDulicate" }}`,
|
|
||||||
value: `{{ $deps[0]==="quickDulicate"? currentCollection:collectionName }}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
syncFromForm: {
|
|
||||||
type: 'void',
|
|
||||||
title: '{{ t("Sync from form fields") }}',
|
|
||||||
'x-component': 'Action.Link',
|
|
||||||
'x-component-props': {
|
|
||||||
type: 'primary',
|
|
||||||
style: { float: 'right', position: 'relative', zIndex: 1200 },
|
|
||||||
useAction: () => {
|
|
||||||
const formSchema = useMemo(() => findFormBlock(fieldSchema), [fieldSchema]);
|
|
||||||
return useSyncFromForm(
|
|
||||||
formSchema,
|
|
||||||
fieldSchema['x-component-props']?.duplicateCollection || record?.__collection || name,
|
|
||||||
syncCallBack,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'x-reactions': [
|
|
||||||
{
|
|
||||||
dependencies: ['.duplicateMode'],
|
|
||||||
fulfill: {
|
|
||||||
state: {
|
|
||||||
visible: `{{ $deps[0]!=="quickDulicate" }}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
selectAll: {
|
|
||||||
type: 'void',
|
|
||||||
title: '{{ t("Select all") }}',
|
|
||||||
'x-component': 'Action.Link',
|
|
||||||
'x-reactions': [
|
|
||||||
{
|
|
||||||
dependencies: ['.duplicateMode'],
|
|
||||||
fulfill: {
|
|
||||||
state: {
|
|
||||||
visible: `{{ $deps[0]==="quickDulicate"}}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'x-component-props': {
|
|
||||||
type: 'primary',
|
|
||||||
style: { float: 'right', position: 'relative', zIndex: 1200 },
|
|
||||||
useAction: () => {
|
|
||||||
const from = useForm();
|
|
||||||
return useSelectAllFields(from);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
unselectAll: {
|
|
||||||
type: 'void',
|
|
||||||
title: '{{ t("UnSelect all") }}',
|
|
||||||
'x-component': 'Action.Link',
|
|
||||||
'x-reactions': [
|
|
||||||
{
|
|
||||||
dependencies: ['.duplicateMode', '.duplicateFields'],
|
|
||||||
fulfill: {
|
|
||||||
state: {
|
|
||||||
visible: `{{ $deps[0]==="quickDulicate"&&$form.getValuesIn('duplicateFields').length>0 }}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'x-component-props': {
|
|
||||||
type: 'primary',
|
|
||||||
style: { float: 'right', position: 'relative', zIndex: 1200, marginRight: '10px' },
|
|
||||||
useAction: () => {
|
|
||||||
const from = useForm();
|
|
||||||
return useUnSelectAllFields(from);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
duplicateFields: {
|
|
||||||
type: 'array',
|
|
||||||
title: '{{ t("Data fields") }}',
|
|
||||||
required: true,
|
|
||||||
description: t('Only the selected fields will be used as the initialization data for the form'),
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': Tree,
|
|
||||||
'x-component-props': {
|
|
||||||
defaultCheckedKeys: duplicateValues,
|
|
||||||
treeData: [],
|
|
||||||
checkable: true,
|
|
||||||
checkStrictly: true,
|
|
||||||
selectable: false,
|
|
||||||
loadData: '{{ getOnLoadData($self) }}',
|
|
||||||
onCheck: '{{ getOnCheck($self) }}',
|
|
||||||
rootStyle: {
|
|
||||||
padding: '8px 0',
|
|
||||||
border: '1px solid #d9d9d9',
|
|
||||||
borderRadius: '2px',
|
|
||||||
maxHeight: '30vh',
|
|
||||||
overflow: 'auto',
|
|
||||||
margin: '2px 0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'x-reactions': [
|
|
||||||
{
|
|
||||||
dependencies: ['.collection', '.duplicateMode'],
|
|
||||||
fulfill: {
|
|
||||||
state: {
|
|
||||||
disabled: '{{ !$deps[0] }}',
|
|
||||||
componentProps: {
|
|
||||||
treeData: '{{ getEnableFieldTree($deps[0], $self,treeData) }}',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ISchema
|
|
||||||
}
|
|
||||||
onSubmit={({ duplicateMode, collection, duplicateFields, treeData }) => {
|
|
||||||
const fields = Array.isArray(duplicateFields) ? duplicateFields : duplicateFields.checked || [];
|
|
||||||
field.componentProps.duplicateMode = duplicateMode;
|
|
||||||
field.componentProps.duplicateFields = fields;
|
|
||||||
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
|
||||||
fieldSchema['x-component-props'].duplicateMode = duplicateMode;
|
|
||||||
fieldSchema['x-component-props'].duplicateFields = fields;
|
|
||||||
fieldSchema['x-component-props'].duplicateCollection = collection;
|
|
||||||
fieldSchema['x-component-props'].treeData = treeData || field.componentProps?.treeData;
|
|
||||||
dn.emit('patch', {
|
|
||||||
schema: {
|
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
|
||||||
'x-component-props': {
|
|
||||||
...fieldSchema['x-component-props'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
dn.refresh();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function UpdateMode() {
|
|
||||||
const { dn } = useDesignable();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SchemaSettingsSelectItem
|
|
||||||
title={t('Data will be updated')}
|
|
||||||
options={[
|
|
||||||
{ label: t('Selected'), value: 'selected' },
|
|
||||||
{ label: t('All'), value: 'all' },
|
|
||||||
]}
|
|
||||||
value={fieldSchema?.['x-action-settings']?.['updateMode']}
|
|
||||||
onChange={(value) => {
|
|
||||||
fieldSchema['x-action-settings']['updateMode'] = value;
|
|
||||||
dn.emit('patch', {
|
|
||||||
schema: {
|
|
||||||
'x-uid': fieldSchema['x-uid'],
|
|
||||||
'x-action-settings': fieldSchema['x-action-settings'],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
dn.refresh();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function AssignedFieldValues() {
|
function AssignedFieldValues() {
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -630,7 +352,6 @@ function SkipValidation() {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AfterSuccess() {
|
function AfterSuccess() {
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -699,7 +420,6 @@ function AfterSuccess() {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function RemoveButton(
|
function RemoveButton(
|
||||||
props: {
|
props: {
|
||||||
onConfirmOk?: ModalProps['onOk'];
|
onConfirmOk?: ModalProps['onOk'];
|
||||||
@ -955,15 +675,6 @@ export const actionSettingsItems: SchemaSettingOptions['items'] = [
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'duplicationMode',
|
|
||||||
Component: DuplicationMode,
|
|
||||||
useVisible() {
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
const isDuplicateAction = fieldSchema['x-action'] === 'duplicate';
|
|
||||||
return isDuplicateAction;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'openMode',
|
name: 'openMode',
|
||||||
Component: SchemaSettingOpenModeSchemaItems,
|
Component: SchemaSettingOpenModeSchemaItems,
|
||||||
@ -984,17 +695,6 @@ export const actionSettingsItems: SchemaSettingOptions['items'] = [
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'updateMode',
|
|
||||||
Component: UpdateMode,
|
|
||||||
useVisible() {
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
const isUpdateModePopupAction = ['customize:bulkUpdate', 'customize:bulkEdit'].includes(
|
|
||||||
fieldSchema['x-action'],
|
|
||||||
);
|
|
||||||
return isUpdateModePopupAction;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'assignFieldValues',
|
name: 'assignFieldValues',
|
||||||
Component: AssignedFieldValues,
|
Component: AssignedFieldValues,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { EditOutlined } from '@ant-design/icons';
|
import { EditOutlined } from '@ant-design/icons';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { observer } from '@formily/react';
|
import { observer, useFieldSchema } from '@formily/react';
|
||||||
import React, { useContext, useRef, useState } from 'react';
|
import React, { useContext, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ActionContext, ActionContextProvider } from '../action/context';
|
import { ActionContext, ActionContextProvider } from '../action/context';
|
||||||
@ -16,6 +16,8 @@ export const InternaPopoverNester = observer(
|
|||||||
const { options } = useAssociationFieldContext();
|
const { options } = useAssociationFieldContext();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const schema = useFieldSchema();
|
||||||
|
schema['x-component-props'].enableLink = false;
|
||||||
const ref = useRef();
|
const ref = useRef();
|
||||||
const nesterProps = {
|
const nesterProps = {
|
||||||
...props,
|
...props,
|
||||||
@ -37,10 +39,6 @@ export const InternaPopoverNester = observer(
|
|||||||
<InternalNester {...nesterProps} />
|
<InternalNester {...nesterProps} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
const titleProps = {
|
|
||||||
...props,
|
|
||||||
enableLink: true,
|
|
||||||
};
|
|
||||||
const getContainer = () => ref.current;
|
const getContainer = () => ref.current;
|
||||||
const ctx = useContext(ActionContext);
|
const ctx = useContext(ActionContext);
|
||||||
const modalProps = {
|
const modalProps = {
|
||||||
@ -69,7 +67,7 @@ export const InternaPopoverNester = observer(
|
|||||||
max-width: 95%;
|
max-width: 95%;
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<ReadPrettyInternalViewer {...titleProps} />
|
<ReadPrettyInternalViewer {...props} />
|
||||||
</div>
|
</div>
|
||||||
<EditOutlined style={{ display: 'inline-flex', margin: '5px' }} />
|
<EditOutlined style={{ display: 'inline-flex', margin: '5px' }} />
|
||||||
</span>
|
</span>
|
||||||
|
@ -67,7 +67,7 @@ export const ReadPrettyInternalViewer: React.FC = observer(
|
|||||||
<span>
|
<span>
|
||||||
{snapshot ? (
|
{snapshot ? (
|
||||||
text
|
text
|
||||||
) : enableLink !== false && !props.enableLink ? (
|
) : enableLink !== false ? (
|
||||||
<a
|
<a
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -7,10 +7,10 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useAPIClient } from '../../../api-client';
|
import { useAPIClient } from '../../../api-client';
|
||||||
import { findFormBlock } from '../../../block-provider';
|
import { findFormBlock } from '../../../block-provider';
|
||||||
import { useCollectionManager } from '../../../collection-manager';
|
import { useCollectionManager } from '../../../collection-manager';
|
||||||
import { useDuplicatefieldsContext } from '../../../schema-initializer/components';
|
|
||||||
import { compatibleDataId } from '../../../schema-settings/DataTemplates/FormDataTemplates';
|
import { compatibleDataId } from '../../../schema-settings/DataTemplates/FormDataTemplates';
|
||||||
import { useToken } from '../__builtins__';
|
import { useToken } from '../__builtins__';
|
||||||
import { RemoteSelect } from '../remote-select';
|
import { RemoteSelect } from '../remote-select';
|
||||||
|
import { useFormBlockContext } from '../../../block-provider/FormBlockProvider';
|
||||||
|
|
||||||
export interface ITemplate {
|
export interface ITemplate {
|
||||||
config?: {
|
config?: {
|
||||||
@ -38,10 +38,11 @@ export interface ITemplate {
|
|||||||
const useDataTemplates = () => {
|
const useDataTemplates = () => {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const data = useDuplicatefieldsContext();
|
const { duplicateData } = useFormBlockContext();
|
||||||
const { getCollectionJoinField } = useCollectionManager();
|
const { getCollectionJoinField } = useCollectionManager();
|
||||||
if (data) {
|
console.log(useFormBlockContext());
|
||||||
return data;
|
if (duplicateData) {
|
||||||
|
return duplicateData;
|
||||||
}
|
}
|
||||||
const { items = [], display = true } = findDataTemplates(fieldSchema);
|
const { items = [], display = true } = findDataTemplates(fieldSchema);
|
||||||
// 过滤掉已经被删除的字段
|
// 过滤掉已经被删除的字段
|
||||||
|
@ -11,4 +11,6 @@ FormV2.ReadPrettyDesigner = ReadPrettyFormDesigner;
|
|||||||
FormV2.Templates = Templates;
|
FormV2.Templates = Templates;
|
||||||
|
|
||||||
export * from './FormField';
|
export * from './FormField';
|
||||||
|
export * from './Templates';
|
||||||
|
|
||||||
export { FormV2, DetailsDesigner };
|
export { FormV2, DetailsDesigner };
|
||||||
|
@ -1,184 +0,0 @@
|
|||||||
import { useField, useFieldSchema } from '@formily/react';
|
|
||||||
import React from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useCompile, useDesignable } from '../..';
|
|
||||||
import { useFormBlockContext, useGanttBlockContext } from '../../../block-provider';
|
|
||||||
import { useCollection } from '../../../collection-manager';
|
|
||||||
import {
|
|
||||||
GeneralSchemaDesigner,
|
|
||||||
SchemaSettingsBlockTitleItem,
|
|
||||||
SchemaSettingsDataScope,
|
|
||||||
SchemaSettingsDivider,
|
|
||||||
SchemaSettingsRemove,
|
|
||||||
SchemaSettingsSelectItem,
|
|
||||||
SchemaSettingsTemplate,
|
|
||||||
} from '../../../schema-settings';
|
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
|
||||||
|
|
||||||
const useOptions = (type = 'string') => {
|
|
||||||
const compile = useCompile();
|
|
||||||
const { fields } = useCollection();
|
|
||||||
const options = fields
|
|
||||||
?.filter((field) => field.type === type)
|
|
||||||
?.map((field) => {
|
|
||||||
return {
|
|
||||||
value: field.name,
|
|
||||||
label: compile(field?.uiSchema?.title),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return options;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GanttDesigner = () => {
|
|
||||||
const field = useField();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
const { form } = useFormBlockContext();
|
|
||||||
const { name, title } = useCollection();
|
|
||||||
const { service } = useGanttBlockContext();
|
|
||||||
const { dn } = useDesignable();
|
|
||||||
const compile = useCompile();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const template = useSchemaTemplate();
|
|
||||||
const fieldNames = fieldSchema?.['x-decorator-props']?.['fieldNames'] || {};
|
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
|
||||||
return (
|
|
||||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
|
||||||
<SchemaSettingsBlockTitleItem />
|
|
||||||
<SchemaSettingsSelectItem
|
|
||||||
title={t('Title field')}
|
|
||||||
value={fieldNames.title}
|
|
||||||
options={useOptions('string')}
|
|
||||||
onChange={(title) => {
|
|
||||||
const fieldNames = field.decoratorProps.fieldNames || {};
|
|
||||||
fieldNames['title'] = title;
|
|
||||||
field.decoratorProps.params = fieldNames;
|
|
||||||
fieldSchema['x-decorator-props']['params'] = fieldNames;
|
|
||||||
// Select切换option后value未按照预期切换,固增加以下代码
|
|
||||||
fieldSchema['x-decorator-props']['fieldNames'] = fieldNames;
|
|
||||||
service.refresh();
|
|
||||||
dn.emit('patch', {
|
|
||||||
schema: {
|
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
|
||||||
'x-decorator-props': field.decoratorProps,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
dn.refresh();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SchemaSettingsSelectItem
|
|
||||||
title={t('Time scale')}
|
|
||||||
value={fieldNames.range || 'day'}
|
|
||||||
options={[
|
|
||||||
{ label: compile('{{t("Hour")}}'), value: 'hour', color: 'orange' },
|
|
||||||
{ label: compile('{{t("Quarter of day")}}'), value: 'quarterDay', color: 'default' },
|
|
||||||
{ label: compile('{{t("Half of day")}}'), value: 'halfDay', color: 'blue' },
|
|
||||||
{ label: compile('{{t("Day")}}'), value: 'day', color: 'yellow' },
|
|
||||||
{ label: compile('{{t("Week")}}'), value: 'week', color: 'pule' },
|
|
||||||
{ label: compile('{{t("Month")}}'), value: 'month', color: 'green' },
|
|
||||||
{ label: compile('{{t("QuarterYear")}}'), value: 'quarterYear', color: 'red' },
|
|
||||||
{ label: compile('{{t("Year")}}'), value: 'year', color: 'green' },
|
|
||||||
]}
|
|
||||||
onChange={(range) => {
|
|
||||||
const fieldNames = field.decoratorProps.fieldNames || {};
|
|
||||||
fieldNames['range'] = range;
|
|
||||||
field.decoratorProps.params = fieldNames;
|
|
||||||
fieldSchema['x-decorator-props']['params'] = fieldNames;
|
|
||||||
// Select切换option后value未按照预期切换,固增加以下代码
|
|
||||||
fieldSchema['x-decorator-props']['fieldNames'] = fieldNames;
|
|
||||||
service.refresh();
|
|
||||||
dn.emit('patch', {
|
|
||||||
schema: {
|
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
|
||||||
'x-decorator-props': field.decoratorProps,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
dn.refresh();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SchemaSettingsSelectItem
|
|
||||||
title={t('Start date field')}
|
|
||||||
value={fieldNames.start}
|
|
||||||
options={useOptions('date')}
|
|
||||||
onChange={(start) => {
|
|
||||||
const fieldNames = field.decoratorProps.fieldNames || {};
|
|
||||||
fieldNames['start'] = start;
|
|
||||||
field.decoratorProps.fieldNames = fieldNames;
|
|
||||||
fieldSchema['x-decorator-props']['fieldNames'] = fieldNames;
|
|
||||||
service.refresh();
|
|
||||||
dn.emit('patch', {
|
|
||||||
schema: {
|
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
|
||||||
'x-decorator-props': field.decoratorProps,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
dn.refresh();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SchemaSettingsSelectItem
|
|
||||||
title={t('End date field')}
|
|
||||||
value={fieldNames.end}
|
|
||||||
options={useOptions('date')}
|
|
||||||
onChange={(end) => {
|
|
||||||
const fieldNames = field.decoratorProps.fieldNames || {};
|
|
||||||
fieldNames['end'] = end;
|
|
||||||
field.decoratorProps.fieldNames = fieldNames;
|
|
||||||
fieldSchema['x-decorator-props']['fieldNames'] = fieldNames;
|
|
||||||
service.refresh();
|
|
||||||
dn.emit('patch', {
|
|
||||||
schema: {
|
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
|
||||||
'x-decorator-props': field.decoratorProps,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
dn.refresh();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SchemaSettingsSelectItem
|
|
||||||
title={t('Progress field')}
|
|
||||||
value={fieldNames.progress}
|
|
||||||
options={useOptions('float')}
|
|
||||||
onChange={(progress) => {
|
|
||||||
const fieldNames = field.decoratorProps.fieldNames || {};
|
|
||||||
fieldNames['progress'] = progress;
|
|
||||||
field.decoratorProps.fieldNames = fieldNames;
|
|
||||||
fieldSchema['x-decorator-props']['fieldNames'] = fieldNames;
|
|
||||||
service.refresh();
|
|
||||||
dn.emit('patch', {
|
|
||||||
schema: {
|
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
|
||||||
'x-decorator-props': field.decoratorProps,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
dn.refresh();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SchemaSettingsDataScope
|
|
||||||
collectionName={name}
|
|
||||||
defaultFilter={fieldSchema?.['x-decorator-props']?.params?.filter || {}}
|
|
||||||
form={form}
|
|
||||||
onSubmit={({ filter }) => {
|
|
||||||
const params = field.decoratorProps.params || {};
|
|
||||||
params.filter = filter;
|
|
||||||
field.decoratorProps.params = params;
|
|
||||||
fieldSchema['x-decorator-props']['params'] = params;
|
|
||||||
service.run({ ...service?.params?.[0], filter });
|
|
||||||
dn.emit('patch', {
|
|
||||||
schema: {
|
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
|
||||||
'x-decorator-props': field.decoratorProps,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SchemaSettingsDivider />
|
|
||||||
<SchemaSettingsTemplate componentName={'Gantt'} collectionName={name} resourceName={defaultResource} />
|
|
||||||
<SchemaSettingsDivider />
|
|
||||||
<SchemaSettingsRemove
|
|
||||||
removeParentsIfNoChildren
|
|
||||||
breakRemoveOn={{
|
|
||||||
'x-component': 'Grid',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</GeneralSchemaDesigner>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,9 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { render } from 'testUtils';
|
|
||||||
import App1 from '../demos/demo1';
|
|
||||||
|
|
||||||
describe('gantt', () => {
|
|
||||||
it('renders without crashing', () => {
|
|
||||||
render(<App1 />);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,52 +0,0 @@
|
|||||||
import { TinyColor } from '@ctrl/tinycolor';
|
|
||||||
import { genStyleHook } from '../../../__builtins__';
|
|
||||||
|
|
||||||
const useStyles = genStyleHook('nb-gantt-calendar', (token) => {
|
|
||||||
const { componentCls } = token;
|
|
||||||
const colorFillAlterSolid = new TinyColor(token.colorFillAlter)
|
|
||||||
.onBackground(token.colorBgContainer)
|
|
||||||
.toHexShortString();
|
|
||||||
|
|
||||||
return {
|
|
||||||
[componentCls]: {
|
|
||||||
'.calendarBottomText': {
|
|
||||||
textAnchor: 'middle',
|
|
||||||
fill: token.colorText,
|
|
||||||
WebkitTouchCallout: 'none',
|
|
||||||
WebkitUserSelect: 'none',
|
|
||||||
MozUserSelect: 'none',
|
|
||||||
msUserSelect: 'none',
|
|
||||||
userSelect: 'none',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
fontWeight: 500,
|
|
||||||
},
|
|
||||||
|
|
||||||
'.calendarTopTick': {
|
|
||||||
stroke: token.colorBorderSecondary,
|
|
||||||
strokeWidth: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
'.calendarTopText': {
|
|
||||||
textAnchor: 'middle',
|
|
||||||
WebkitTouchCallout: 'none',
|
|
||||||
WebkitUserSelect: 'none',
|
|
||||||
MozUserSelect: 'none',
|
|
||||||
msUserSelect: 'none',
|
|
||||||
userSelect: 'none',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
fontWeight: 500,
|
|
||||||
fill: token.colorText,
|
|
||||||
},
|
|
||||||
|
|
||||||
'.calendarHeader': {
|
|
||||||
color: token.colorText,
|
|
||||||
fill: colorFillAlterSolid,
|
|
||||||
strokeWidth: 1.4,
|
|
||||||
background: colorFillAlterSolid,
|
|
||||||
borderBottom: `1px solid ${token.colorBorderSecondary}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export default useStyles;
|
|
@ -1,40 +0,0 @@
|
|||||||
import { genStyleHook } from '../../../__builtins__';
|
|
||||||
|
|
||||||
const useStyles = genStyleHook('nb-gantt', (token) => {
|
|
||||||
const { componentCls } = token;
|
|
||||||
|
|
||||||
return {
|
|
||||||
[componentCls]: {
|
|
||||||
'.ganttVerticalContainer': {
|
|
||||||
overflow: 'hidden',
|
|
||||||
fontSize: '0',
|
|
||||||
margin: '0',
|
|
||||||
padding: '0',
|
|
||||||
width: '100%',
|
|
||||||
borderLeft: `2px solid ${token.colorBorderSecondary}`,
|
|
||||||
|
|
||||||
'.ganttHeader': { borderBottom: `1px solid ${token.colorBorderSecondary}`, fontWeight: 700 },
|
|
||||||
'.ganttBody': { borderBottom: `1px solid ${token.colorBorderSecondary}` },
|
|
||||||
},
|
|
||||||
|
|
||||||
'.horizontalContainer': {
|
|
||||||
margin: '0',
|
|
||||||
padding: '0',
|
|
||||||
overflow: 'hidden',
|
|
||||||
},
|
|
||||||
|
|
||||||
'.wrapper': {
|
|
||||||
display: 'flex',
|
|
||||||
padding: '0',
|
|
||||||
margin: '0',
|
|
||||||
listStyle: 'none',
|
|
||||||
outline: 'none',
|
|
||||||
position: 'relative',
|
|
||||||
'.gantt-horizontal-scoll': { display: 'none' },
|
|
||||||
'&:hover': { '.gantt-horizontal-scoll': { display: 'block' } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export default useStyles;
|
|
@ -1,30 +0,0 @@
|
|||||||
import { genStyleHook } from '../../../__builtins__';
|
|
||||||
|
|
||||||
const useStyles = genStyleHook('nb-grid-body', (token) => {
|
|
||||||
const { componentCls } = token;
|
|
||||||
|
|
||||||
return {
|
|
||||||
[componentCls]: {
|
|
||||||
'.gridRow': {
|
|
||||||
fill: token.colorBgContainer,
|
|
||||||
},
|
|
||||||
|
|
||||||
'.gridHeightRow': {
|
|
||||||
fill: '#e6f7ff',
|
|
||||||
borderColor: token.colorBorder,
|
|
||||||
},
|
|
||||||
|
|
||||||
'.gridRowLine': {
|
|
||||||
stroke: token.colorBorderSecondary,
|
|
||||||
strokeWidth: 0,
|
|
||||||
borderBottom: `1px solid ${token.colorBorderSecondary}`,
|
|
||||||
},
|
|
||||||
|
|
||||||
'.gridTick': {
|
|
||||||
stroke: token.colorBorderSecondary,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export default useStyles;
|
|
@ -1,91 +0,0 @@
|
|||||||
import { genStyleHook } from '../../../__builtins__';
|
|
||||||
|
|
||||||
const useStyles = genStyleHook('nb-grid-other', (token) => {
|
|
||||||
const { componentCls } = token;
|
|
||||||
|
|
||||||
return {
|
|
||||||
[componentCls]: {
|
|
||||||
'&.scrollWrapper': {
|
|
||||||
overflow: 'auto',
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: '-4px',
|
|
||||||
maxWidth: '100%',
|
|
||||||
scrollbarWidth: 'thin',
|
|
||||||
height: '1.2rem',
|
|
||||||
'&::-webkit-scrollbar': { width: 8, height: 8 },
|
|
||||||
'&::-webkit-scrollbar-corner': { background: 'transparent' },
|
|
||||||
'&::-webkit-scrollbar-track': { background: 'var(--colorBgScrollTrack)' },
|
|
||||||
'&::-webkit-scrollbar-thumb': {
|
|
||||||
background: 'var(--colorBgScrollBar)',
|
|
||||||
borderRadius: 4,
|
|
||||||
},
|
|
||||||
'&::-webkit-scrollbar-thumb:hover': {
|
|
||||||
background: 'var(--colorBgScrollBarHover)',
|
|
||||||
},
|
|
||||||
'&::-webkit-scrollbar-thumb:active': {
|
|
||||||
background: 'var(--colorBgScrollBarActive)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
'.horizontalScroll': {
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
|
|
||||||
'&.tooltipDefaultContainer': {
|
|
||||||
padding: '12px',
|
|
||||||
backgroundColor: token.colorBgElevated,
|
|
||||||
backgroundClip: 'padding-box',
|
|
||||||
borderRadius: token.borderRadius,
|
|
||||||
boxShadow: token.boxShadow,
|
|
||||||
b: { display: 'block', marginBottom: token.marginXS },
|
|
||||||
|
|
||||||
'.tooltipDefaultContainerParagraph': {
|
|
||||||
fontSize: token.fontSizeSM,
|
|
||||||
marginBottom: token.marginXXS + (token.marginXS - token.marginXXS),
|
|
||||||
color: token.colorText,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
'&.tooltipDetailsContainer': {
|
|
||||||
position: 'absolute',
|
|
||||||
display: 'flex',
|
|
||||||
flexShrink: 0,
|
|
||||||
pointerEvents: 'none',
|
|
||||||
WebkitTouchCallout: 'none',
|
|
||||||
WebkitUserSelect: 'none',
|
|
||||||
MozUserSelect: 'none',
|
|
||||||
msUserSelect: 'none',
|
|
||||||
userSelect: 'none',
|
|
||||||
},
|
|
||||||
|
|
||||||
'&.tooltipDetailsContainerHidden': {
|
|
||||||
visibility: 'hidden',
|
|
||||||
position: 'absolute',
|
|
||||||
display: 'flex',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
},
|
|
||||||
|
|
||||||
'&.verticalScroll': {
|
|
||||||
overflow: 'hidden auto',
|
|
||||||
width: '1rem',
|
|
||||||
flexShrink: 0,
|
|
||||||
scrollbarWidth: 'thin',
|
|
||||||
'&::-webkit-scrollbar': { width: 8, height: 8 },
|
|
||||||
'&::-webkit-scrollbar-corner': { background: 'transparent' },
|
|
||||||
'&::-webkit-scrollbar-track': { background: 'var(--colorBgScrollTrack)' },
|
|
||||||
'&::-webkit-scrollbar-thumb': {
|
|
||||||
background: 'var(--colorBgScrollBar)',
|
|
||||||
borderRadius: 4,
|
|
||||||
},
|
|
||||||
'&::-webkit-scrollbar-thumb:hover': {
|
|
||||||
background: 'var(--colorBgScrollBarHover)',
|
|
||||||
},
|
|
||||||
'&::-webkit-scrollbar-thumb:active': {
|
|
||||||
background: 'var(--colorBgScrollBarActive)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export default useStyles;
|
|
@ -1,48 +0,0 @@
|
|||||||
import { genStyleHook } from '../../../__builtins__';
|
|
||||||
|
|
||||||
const useStyles = genStyleHook('nb-gantt-task-item', (token) => {
|
|
||||||
const { componentCls } = token;
|
|
||||||
|
|
||||||
return {
|
|
||||||
[componentCls]: {
|
|
||||||
'.barLabel': {
|
|
||||||
fill: token.colorTextLightSolid,
|
|
||||||
textAnchor: 'middle',
|
|
||||||
fontWeight: 400,
|
|
||||||
dominantBaseline: 'central',
|
|
||||||
WebkitTouchCallout: 'none',
|
|
||||||
WebkitUserSelect: 'none',
|
|
||||||
MozUserSelect: 'none',
|
|
||||||
msUserSelect: 'none',
|
|
||||||
userSelect: 'none',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
},
|
|
||||||
|
|
||||||
'.projectLabel': {
|
|
||||||
fill: '#130d0d',
|
|
||||||
fontWeight: 500,
|
|
||||||
fontSize: '0.9em',
|
|
||||||
dominantBaseline: 'central',
|
|
||||||
WebkitTouchCallout: 'none',
|
|
||||||
WebkitUserSelect: 'none',
|
|
||||||
MozUserSelect: 'none',
|
|
||||||
msUserSelect: 'none',
|
|
||||||
userSelect: 'none',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
},
|
|
||||||
|
|
||||||
'.barLabelOutside': {
|
|
||||||
fill: token.colorTextLabel,
|
|
||||||
textAnchor: 'start',
|
|
||||||
WebkitTouchCallout: 'none',
|
|
||||||
WebkitUserSelect: 'none',
|
|
||||||
MozUserSelect: 'none',
|
|
||||||
msUserSelect: 'none',
|
|
||||||
userSelect: 'none',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export default useStyles;
|
|
@ -1,15 +0,0 @@
|
|||||||
import { ActionBar } from '../action';
|
|
||||||
import { Gantt } from './components/gantt/gantt';
|
|
||||||
import { GanttDesigner } from './Gantt.Designer';
|
|
||||||
import { ViewMode } from './types/public-types';
|
|
||||||
import { Event } from './components/gantt/Event';
|
|
||||||
|
|
||||||
Gantt.ActionBar = ActionBar;
|
|
||||||
|
|
||||||
Gantt.ViewMode = ViewMode;
|
|
||||||
Gantt.Designer = GanttDesigner;
|
|
||||||
Gantt.Event = Event;
|
|
||||||
|
|
||||||
// const GanttV2 = Gantt;
|
|
||||||
|
|
||||||
export { Gantt };
|
|
@ -21,13 +21,11 @@ export * from './form-dialog';
|
|||||||
export * from './form-item';
|
export * from './form-item';
|
||||||
export * from './form-v2';
|
export * from './form-v2';
|
||||||
export * from './g2plot';
|
export * from './g2plot';
|
||||||
export * from './gantt';
|
|
||||||
export * from './grid';
|
export * from './grid';
|
||||||
export * from './grid-card';
|
export * from './grid-card';
|
||||||
export * from './icon-picker';
|
export * from './icon-picker';
|
||||||
export * from './input';
|
export * from './input';
|
||||||
export * from './input-number';
|
export * from './input-number';
|
||||||
export * from './kanban';
|
|
||||||
export * from './list';
|
export * from './list';
|
||||||
export * from './markdown';
|
export * from './markdown';
|
||||||
export * from './menu';
|
export * from './menu';
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
import { useField, useFieldSchema } from '@formily/react';
|
|
||||||
import React from 'react';
|
|
||||||
import { useFormBlockContext, useKanbanBlockContext } from '../../../block-provider';
|
|
||||||
import { useCollection } from '../../../collection-manager';
|
|
||||||
import {
|
|
||||||
GeneralSchemaDesigner,
|
|
||||||
SchemaSettingsBlockTitleItem,
|
|
||||||
SchemaSettingsDataScope,
|
|
||||||
SchemaSettingsDivider,
|
|
||||||
SchemaSettingsRemove,
|
|
||||||
SchemaSettingsTemplate,
|
|
||||||
} from '../../../schema-settings';
|
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
|
||||||
import { useDesignable } from '../../hooks';
|
|
||||||
import { removeNullCondition } from '../filter';
|
|
||||||
import { FixedBlockDesignerItem } from '../page';
|
|
||||||
|
|
||||||
export const KanbanDesigner = () => {
|
|
||||||
const { name, title } = useCollection();
|
|
||||||
const field = useField();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
const { form } = useFormBlockContext();
|
|
||||||
const { service } = useKanbanBlockContext();
|
|
||||||
const { dn } = useDesignable();
|
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
|
||||||
const template = useSchemaTemplate();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
|
||||||
<SchemaSettingsBlockTitleItem />
|
|
||||||
<SchemaSettingsDataScope
|
|
||||||
collectionName={name}
|
|
||||||
defaultFilter={fieldSchema?.['x-decorator-props']?.params?.filter || {}}
|
|
||||||
form={form}
|
|
||||||
onSubmit={({ filter }) => {
|
|
||||||
filter = removeNullCondition(filter);
|
|
||||||
const params = field.decoratorProps.params || {};
|
|
||||||
params.filter = filter;
|
|
||||||
field.decoratorProps.params = params;
|
|
||||||
fieldSchema['x-decorator-props']['params'] = params;
|
|
||||||
service.run({ ...service.params?.[0], filter });
|
|
||||||
dn.emit('patch', {
|
|
||||||
schema: {
|
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
|
||||||
'x-decorator-props': fieldSchema['x-decorator-props'],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<FixedBlockDesignerItem />
|
|
||||||
<SchemaSettingsDivider />
|
|
||||||
<SchemaSettingsTemplate componentName={'Kanban'} collectionName={name} resourceName={defaultResource} />
|
|
||||||
<SchemaSettingsDivider />
|
|
||||||
<SchemaSettingsRemove
|
|
||||||
removeParentsIfNoChildren
|
|
||||||
breakRemoveOn={{
|
|
||||||
'x-component': 'Grid',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</GeneralSchemaDesigner>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,24 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { render, screen, waitFor } from 'testUtils';
|
|
||||||
import App1 from '../demos/demo1';
|
|
||||||
|
|
||||||
describe('Kanban', () => {
|
|
||||||
it('should render correctly', async () => {
|
|
||||||
render(<App1 />);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
// 每列卡片的状态
|
|
||||||
expect(screen.getByText(/未开始/i)).toBeInTheDocument();
|
|
||||||
expect(screen.getByText(/进行中/i)).toBeInTheDocument();
|
|
||||||
expect(screen.getByText(/测试中/i)).toBeInTheDocument();
|
|
||||||
expect(screen.getByText(/已完成/i)).toBeInTheDocument();
|
|
||||||
|
|
||||||
// 第一个卡片的内容
|
|
||||||
expect(screen.getByText(/子表格字段缺少标题/i)).toBeInTheDocument();
|
|
||||||
expect(screen.getByText(/见附件,在此位置显示标题,包括表单和详情/i)).toBeInTheDocument();
|
|
||||||
|
|
||||||
// 最后一个卡片的内容
|
|
||||||
expect(screen.getByText(/新增 uischema:clearancestor action api/i)).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,89 +0,0 @@
|
|||||||
import { ISchema } from '@formily/react';
|
|
||||||
import {
|
|
||||||
AntdSchemaComponentProvider,
|
|
||||||
APIClientProvider,
|
|
||||||
BlockSchemaComponentProvider,
|
|
||||||
CollectionManagerProvider,
|
|
||||||
CurrentUserProvider,
|
|
||||||
SchemaComponent,
|
|
||||||
SchemaComponentProvider,
|
|
||||||
} from '@nocobase/client';
|
|
||||||
import React from 'react';
|
|
||||||
import { mockAPIClient } from '../../../../testUtils';
|
|
||||||
import collections from './collections';
|
|
||||||
import data from './data';
|
|
||||||
|
|
||||||
const { apiClient, mockRequest } = mockAPIClient();
|
|
||||||
const sleep = (value: number) => new Promise((resolve) => setTimeout(resolve, value));
|
|
||||||
|
|
||||||
mockRequest.onGet('/t_j6omof6tza8:list').reply(async (config) => {
|
|
||||||
await sleep(200);
|
|
||||||
return [200, data];
|
|
||||||
});
|
|
||||||
mockRequest.onGet('/auth:check').reply(() => {
|
|
||||||
return [200, { data: {} }];
|
|
||||||
});
|
|
||||||
|
|
||||||
const schema: ISchema = {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
block: {
|
|
||||||
type: 'void',
|
|
||||||
'x-decorator': 'KanbanBlockProvider',
|
|
||||||
'x-decorator-props': {
|
|
||||||
collection: 't_j6omof6tza8',
|
|
||||||
resource: 't_j6omof6tza8',
|
|
||||||
action: 'list',
|
|
||||||
groupField: 'f_hpmvdltzs6m',
|
|
||||||
params: {
|
|
||||||
paginate: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
kanban: {
|
|
||||||
type: 'array',
|
|
||||||
'x-component': 'Kanban',
|
|
||||||
'x-component-props': {
|
|
||||||
useProps: '{{ useKanbanBlockProps }}',
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
card: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Kanban.Card',
|
|
||||||
properties: {
|
|
||||||
f_g8j5jvalqh0: {
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'CollectionField',
|
|
||||||
'x-read-pretty': true,
|
|
||||||
},
|
|
||||||
f_tegyd222bcc: {
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'CollectionField',
|
|
||||||
'x-read-pretty': true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
return (
|
|
||||||
<APIClientProvider apiClient={apiClient}>
|
|
||||||
<CurrentUserProvider>
|
|
||||||
<SchemaComponentProvider>
|
|
||||||
<CollectionManagerProvider collections={collections}>
|
|
||||||
<AntdSchemaComponentProvider>
|
|
||||||
<BlockSchemaComponentProvider>
|
|
||||||
<SchemaComponent schema={schema} />
|
|
||||||
</BlockSchemaComponentProvider>
|
|
||||||
</AntdSchemaComponentProvider>
|
|
||||||
</CollectionManagerProvider>
|
|
||||||
</SchemaComponentProvider>
|
|
||||||
</CurrentUserProvider>
|
|
||||||
</APIClientProvider>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,25 +0,0 @@
|
|||||||
import { Plugin } from '../../../application/Plugin';
|
|
||||||
import { Action } from '../action';
|
|
||||||
import { Kanban } from './Kanban';
|
|
||||||
import { KanbanCard } from './Kanban.Card';
|
|
||||||
import { KanbanCardDesigner, kanbanCardInitializers } from './Kanban.Card.Designer';
|
|
||||||
import { KanbanCardDesignerTitleSwitch } from './Kanban.Card.Designer.TitleSwitch';
|
|
||||||
import { KanbanCardViewer } from './Kanban.CardViewer';
|
|
||||||
import { KanbanDesigner } from './Kanban.Designer';
|
|
||||||
|
|
||||||
Kanban.Card = KanbanCard;
|
|
||||||
Kanban.CardAdder = Action;
|
|
||||||
Kanban.CardViewer = KanbanCardViewer;
|
|
||||||
Kanban.Card.Designer = KanbanCardDesigner;
|
|
||||||
Kanban.Card.Designer.TitleSwitch = KanbanCardDesignerTitleSwitch;
|
|
||||||
Kanban.Designer = KanbanDesigner;
|
|
||||||
|
|
||||||
const KanbanV2 = Kanban;
|
|
||||||
|
|
||||||
class KanbanPlugin extends Plugin {
|
|
||||||
async load() {
|
|
||||||
this.app.schemaInitializerManager.add(kanbanCardInitializers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Kanban, KanbanV2, KanbanPlugin };
|
|
@ -1,25 +0,0 @@
|
|||||||
import { genStyleHook } from './../__builtins__';
|
|
||||||
|
|
||||||
export const useStyles = genStyleHook('nb-kanban', (token) => {
|
|
||||||
const { componentCls } = token;
|
|
||||||
|
|
||||||
return {
|
|
||||||
[componentCls]: {
|
|
||||||
overflow: 'hidden',
|
|
||||||
height: '100%',
|
|
||||||
'> .ant-spin-container': { height: '100%' },
|
|
||||||
|
|
||||||
'.react-kanban-board': {
|
|
||||||
'.nb-block-item': {
|
|
||||||
'.ant-formily-item-control .ant-space-item': {
|
|
||||||
whiteSpace: 'normal',
|
|
||||||
wordBreak: 'break-all',
|
|
||||||
wordWrap: 'break-word',
|
|
||||||
},
|
|
||||||
'& .ant-formily-item-label': { color: '#8c8c8c', fontWeight: 'normal' },
|
|
||||||
},
|
|
||||||
'.kanban-no-label': { '.ant-formily-item-label': { display: 'none' } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
@ -9,7 +9,7 @@ export const useFieldModeOptions = (props?) => {
|
|||||||
const fieldSchema = props?.fieldSchema || currentFieldSchema;
|
const fieldSchema = props?.fieldSchema || currentFieldSchema;
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const form = useForm();
|
const form = useForm();
|
||||||
const isReadPretty = field.readPretty && form.readPretty;
|
const isReadPretty = field.readPretty || form.readPretty;
|
||||||
const isSubTableField = props?.fieldSchema;
|
const isSubTableField = props?.fieldSchema;
|
||||||
const { getField } = useCollection();
|
const { getField } = useCollection();
|
||||||
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
||||||
|
@ -42,16 +42,6 @@ export const blockInitializers = new SchemaInitializer({
|
|||||||
title: '{{t("Calendar")}}',
|
title: '{{t("Calendar")}}',
|
||||||
Component: 'CalendarBlockInitializer',
|
Component: 'CalendarBlockInitializer',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'kanban',
|
|
||||||
title: '{{t("Kanban")}}',
|
|
||||||
Component: 'KanbanBlockInitializer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'gantt',
|
|
||||||
title: '{{t("Gantt")}}',
|
|
||||||
Component: 'GanttBlockInitializer',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -57,14 +57,6 @@ export const calendarFormActionInitializers = new SchemaInitializer({
|
|||||||
return (collection.template !== 'view' || collection?.writableView) && collection.template !== 'sql';
|
return (collection.template !== 'view' || collection?.writableView) && collection.template !== 'sql';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'print',
|
|
||||||
title: '{{t("Print")}}',
|
|
||||||
Component: 'PrintActionInitializer',
|
|
||||||
schema: {
|
|
||||||
'x-component': 'Action',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -35,19 +35,6 @@ export const detailsActionInitializers = new SchemaInitializer({
|
|||||||
'x-decorator': 'ACLActionProvider',
|
'x-decorator': 'ACLActionProvider',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'duplicate',
|
|
||||||
title: '{{t("Duplicate")}}',
|
|
||||||
Component: 'DuplicateActionInitializer',
|
|
||||||
schema: {
|
|
||||||
'x-component': 'Action',
|
|
||||||
'x-action': 'duplicate',
|
|
||||||
'x-decorator': 'ACLActionProvider',
|
|
||||||
'x-component-props': {
|
|
||||||
type: 'primary',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -42,28 +42,6 @@ export const readPrettyFormActionInitializers = new SchemaInitializer({
|
|||||||
},
|
},
|
||||||
useVisible: useVisibleCollection,
|
useVisible: useVisibleCollection,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '{{t("Duplicate")}}',
|
|
||||||
name: 'duplicate',
|
|
||||||
Component: 'DuplicateActionInitializer',
|
|
||||||
schema: {
|
|
||||||
'x-component': 'Action',
|
|
||||||
'x-action': 'duplicate',
|
|
||||||
'x-decorator': 'ACLActionProvider',
|
|
||||||
'x-component-props': {
|
|
||||||
type: 'primary',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
useVisible: useVisibleCollection,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '{{t("Print")}}',
|
|
||||||
name: 'print',
|
|
||||||
Component: 'PrintActionInitializer',
|
|
||||||
schema: {
|
|
||||||
'x-component': 'Action',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -168,21 +168,6 @@ export const tableActionColumnInitializers = new SchemaInitializer({
|
|||||||
return collection.tree && treeTable !== false;
|
return collection.tree && treeTable !== false;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: 'item',
|
|
||||||
title: '{{t("Duplicate")}}',
|
|
||||||
name: 'duplicate',
|
|
||||||
Component: 'DuplicateActionInitializer',
|
|
||||||
schema: {
|
|
||||||
'x-component': 'Action.Link',
|
|
||||||
'x-action': 'duplicate',
|
|
||||||
'x-decorator': 'ACLActionProvider',
|
|
||||||
},
|
|
||||||
useVisible() {
|
|
||||||
const collection = useCollection();
|
|
||||||
return (collection.template !== 'view' || collection?.writableView) && collection.template !== 'sql';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -94,52 +94,6 @@ export const tableActionInitializers = new SchemaInitializer({
|
|||||||
name: 'customize',
|
name: 'customize',
|
||||||
title: '{{t("Customize")}}',
|
title: '{{t("Customize")}}',
|
||||||
children: [
|
children: [
|
||||||
{
|
|
||||||
type: 'item',
|
|
||||||
title: '{{t("Bulk update")}}',
|
|
||||||
Component: 'CustomizeActionInitializer',
|
|
||||||
name: 'bulkUpdate',
|
|
||||||
schema: {
|
|
||||||
type: 'void',
|
|
||||||
title: '{{ t("Bulk update") }}',
|
|
||||||
'x-component': 'Action',
|
|
||||||
'x-align': 'right',
|
|
||||||
'x-acl-action': 'update',
|
|
||||||
'x-decorator': 'ACLActionProvider',
|
|
||||||
'x-acl-action-props': {
|
|
||||||
skipScopeCheck: true,
|
|
||||||
},
|
|
||||||
'x-action': 'customize:bulkUpdate',
|
|
||||||
'x-designer': 'Action.Designer',
|
|
||||||
'x-action-settings': {
|
|
||||||
assignedValues: {},
|
|
||||||
updateMode: 'selected',
|
|
||||||
onSuccess: {
|
|
||||||
manualClose: true,
|
|
||||||
redirecting: false,
|
|
||||||
successMessage: '{{t("Updated successfully")}}',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'x-component-props': {
|
|
||||||
icon: 'EditOutlined',
|
|
||||||
useProps: '{{ useCustomizeBulkUpdateActionProps }}',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'item',
|
|
||||||
title: '{{t("Bulk edit")}}',
|
|
||||||
name: 'bulkEdit',
|
|
||||||
Component: 'CustomizeBulkEditActionInitializer',
|
|
||||||
schema: {
|
|
||||||
'x-align': 'right',
|
|
||||||
'x-decorator': 'ACLActionProvider',
|
|
||||||
'x-acl-action': 'update',
|
|
||||||
'x-acl-action-props': {
|
|
||||||
skipScopeCheck: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: 'item',
|
type: 'item',
|
||||||
title: '{{t("Add record")}}',
|
title: '{{t("Add record")}}',
|
||||||
|
@ -1,18 +1,14 @@
|
|||||||
export * from './BlockInitializers';
|
export * from './BlockInitializers';
|
||||||
export * from './BulkEditFormItemInitializers';
|
|
||||||
export * from './CalendarActionInitializers';
|
export * from './CalendarActionInitializers';
|
||||||
export * from './CalendarFormActionInitializers';
|
export * from './CalendarFormActionInitializers';
|
||||||
export * from './CreateFormBlockInitializers';
|
export * from './CreateFormBlockInitializers';
|
||||||
export * from './CreateFormBulkEditBlockInitializers';
|
|
||||||
export * from './CusomeizeCreateFormBlockInitializers';
|
export * from './CusomeizeCreateFormBlockInitializers';
|
||||||
export * from './CustomFormItemInitializers';
|
export * from './CustomFormItemInitializers';
|
||||||
export * from './DetailsActionInitializers';
|
export * from './DetailsActionInitializers';
|
||||||
export * from './FilterFormActionInitializers';
|
export * from './FilterFormActionInitializers';
|
||||||
export * from './FormActionInitializers';
|
export * from './FormActionInitializers';
|
||||||
export * from './FormItemInitializers';
|
export * from './FormItemInitializers';
|
||||||
export * from './GanttActionInitializers';
|
|
||||||
export * from './GridCardActionInitializers';
|
export * from './GridCardActionInitializers';
|
||||||
export * from './KanbanActionInitializers';
|
|
||||||
export * from './ListActionInitializers';
|
export * from './ListActionInitializers';
|
||||||
export * from './ReadPrettyFormActionInitializers';
|
export * from './ReadPrettyFormActionInitializers';
|
||||||
export * from './ReadPrettyFormItemInitializers';
|
export * from './ReadPrettyFormItemInitializers';
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
export * from './assigned-field';
|
export * from './assigned-field';
|
||||||
export * from './BulkEditField';
|
export * from './BulkEditField';
|
||||||
export * from './CreateRecordAction';
|
export * from './CreateRecordAction';
|
||||||
export * from './DuplicateAction'
|
|
||||||
|
@ -16,26 +16,25 @@ export {
|
|||||||
useInheritsTableColumnInitializerFields,
|
useInheritsTableColumnInitializerFields,
|
||||||
useRecordCollectionDataSourceItems,
|
useRecordCollectionDataSourceItems,
|
||||||
useTableColumnInitializerFields,
|
useTableColumnInitializerFields,
|
||||||
|
useRemoveGridFormItem,
|
||||||
|
useAssociatedFormItemInitializerFields,
|
||||||
|
useFormItemInitializerFields,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
import { Plugin } from '../application/Plugin';
|
import { Plugin } from '../application/Plugin';
|
||||||
import {
|
import {
|
||||||
bulkEditFormItemInitializers,
|
|
||||||
calendarActionInitializers,
|
calendarActionInitializers,
|
||||||
calendarFormActionInitializers,
|
calendarFormActionInitializers,
|
||||||
createFormBlockInitializers,
|
createFormBlockInitializers,
|
||||||
createFormBulkEditBlockInitializers,
|
|
||||||
cusomeizeCreateFormBlockInitializers,
|
cusomeizeCreateFormBlockInitializers,
|
||||||
customFormItemInitializers,
|
customFormItemInitializers,
|
||||||
filterFormActionInitializers,
|
filterFormActionInitializers,
|
||||||
createFormActionInitializers,
|
createFormActionInitializers,
|
||||||
updateFormActionInitializers,
|
updateFormActionInitializers,
|
||||||
bulkEditFormActionInitializers,
|
bulkEditFormActionInitializers,
|
||||||
ganttActionInitializers,
|
|
||||||
filterFormItemInitializers,
|
filterFormItemInitializers,
|
||||||
gridCardActionInitializers,
|
gridCardActionInitializers,
|
||||||
gridCardItemActionInitializers,
|
gridCardItemActionInitializers,
|
||||||
kanbanActionInitializers,
|
|
||||||
listActionInitializers,
|
listActionInitializers,
|
||||||
listItemActionInitializers,
|
listItemActionInitializers,
|
||||||
recordBlockInitializers,
|
recordBlockInitializers,
|
||||||
@ -72,22 +71,19 @@ export class SchemaInitializerPlugin extends Plugin {
|
|||||||
this.app.schemaInitializerManager.add(detailsActionInitializers);
|
this.app.schemaInitializerManager.add(detailsActionInitializers);
|
||||||
this.app.schemaInitializerManager.add(readPrettyFormItemInitializers);
|
this.app.schemaInitializerManager.add(readPrettyFormItemInitializers);
|
||||||
this.app.schemaInitializerManager.add(readPrettyFormActionInitializers);
|
this.app.schemaInitializerManager.add(readPrettyFormActionInitializers);
|
||||||
this.app.schemaInitializerManager.add(bulkEditFormItemInitializers);
|
|
||||||
this.app.schemaInitializerManager.add(calendarActionInitializers);
|
this.app.schemaInitializerManager.add(calendarActionInitializers);
|
||||||
this.app.schemaInitializerManager.add(calendarFormActionInitializers);
|
this.app.schemaInitializerManager.add(calendarFormActionInitializers);
|
||||||
this.app.schemaInitializerManager.add(createFormBlockInitializers);
|
this.app.schemaInitializerManager.add(createFormBlockInitializers);
|
||||||
this.app.schemaInitializerManager.add(createFormBulkEditBlockInitializers);
|
|
||||||
this.app.schemaInitializerManager.add(cusomeizeCreateFormBlockInitializers);
|
this.app.schemaInitializerManager.add(cusomeizeCreateFormBlockInitializers);
|
||||||
this.app.schemaInitializerManager.add(customFormItemInitializers);
|
this.app.schemaInitializerManager.add(customFormItemInitializers);
|
||||||
this.app.schemaInitializerManager.add(filterFormActionInitializers);
|
this.app.schemaInitializerManager.add(filterFormActionInitializers);
|
||||||
this.app.schemaInitializerManager.add(createFormActionInitializers);
|
this.app.schemaInitializerManager.add(createFormActionInitializers);
|
||||||
this.app.schemaInitializerManager.add(updateFormActionInitializers);
|
this.app.schemaInitializerManager.add(updateFormActionInitializers);
|
||||||
this.app.schemaInitializerManager.add(bulkEditFormActionInitializers);
|
this.app.schemaInitializerManager.add(bulkEditFormActionInitializers);
|
||||||
this.app.schemaInitializerManager.add(ganttActionInitializers);
|
|
||||||
this.app.schemaInitializerManager.add(filterFormItemInitializers);
|
this.app.schemaInitializerManager.add(filterFormItemInitializers);
|
||||||
this.app.schemaInitializerManager.add(gridCardActionInitializers);
|
this.app.schemaInitializerManager.add(gridCardActionInitializers);
|
||||||
this.app.schemaInitializerManager.add(gridCardItemActionInitializers);
|
this.app.schemaInitializerManager.add(gridCardItemActionInitializers);
|
||||||
this.app.schemaInitializerManager.add(kanbanActionInitializers);
|
|
||||||
this.app.schemaInitializerManager.add(listActionInitializers);
|
this.app.schemaInitializerManager.add(listActionInitializers);
|
||||||
this.app.schemaInitializerManager.add(listItemActionInitializers);
|
this.app.schemaInitializerManager.add(listItemActionInitializers);
|
||||||
this.app.schemaInitializerManager.add(recordBlockInitializers);
|
this.app.schemaInitializerManager.add(recordBlockInitializers);
|
||||||
|
@ -13,30 +13,24 @@ export * from './CreateActionInitializer';
|
|||||||
export * from './CreateChildInitializer';
|
export * from './CreateChildInitializer';
|
||||||
export * from './CreateFilterActionInitializer';
|
export * from './CreateFilterActionInitializer';
|
||||||
export * from './CreateFormBlockInitializer';
|
export * from './CreateFormBlockInitializer';
|
||||||
export * from './CreateFormBulkEditBlockInitializer';
|
|
||||||
export * from './CreateResetActionInitializer';
|
export * from './CreateResetActionInitializer';
|
||||||
export * from './CreateSubmitActionInitializer';
|
export * from './CreateSubmitActionInitializer';
|
||||||
export * from './CustomizeActionInitializer';
|
export * from './CustomizeActionInitializer';
|
||||||
export * from './CustomizeAddRecordActionInitializer';
|
export * from './CustomizeAddRecordActionInitializer';
|
||||||
export * from './CustomizeBulkEditActionInitializer';
|
|
||||||
export * from './DataBlockInitializer';
|
export * from './DataBlockInitializer';
|
||||||
export * from './DeleteEventActionInitializer';
|
export * from './DeleteEventActionInitializer';
|
||||||
export * from './DestroyActionInitializer';
|
export * from './DestroyActionInitializer';
|
||||||
export * from './DetailsBlockInitializer';
|
export * from './DetailsBlockInitializer';
|
||||||
export * from './DuplicateActionInitializer';
|
|
||||||
export * from './ExpandActionInitializer';
|
export * from './ExpandActionInitializer';
|
||||||
export * from './FilterActionInitializer';
|
export * from './FilterActionInitializer';
|
||||||
export * from './FilterCollapseBlockInitializer';
|
export * from './FilterCollapseBlockInitializer';
|
||||||
export * from './FilterFormBlockInitializer';
|
export * from './FilterFormBlockInitializer';
|
||||||
export * from './FormBlockInitializer';
|
export * from './FormBlockInitializer';
|
||||||
export * from './G2PlotInitializer';
|
export * from './G2PlotInitializer';
|
||||||
export * from './GanttBlockInitializer';
|
|
||||||
export * from './GridCardBlockInitializer';
|
export * from './GridCardBlockInitializer';
|
||||||
export * from './InitializerWithSwitch';
|
export * from './InitializerWithSwitch';
|
||||||
export * from './KanbanBlockInitializer';
|
|
||||||
export * from './ListBlockInitializer';
|
export * from './ListBlockInitializer';
|
||||||
export * from './MarkdownBlockInitializer';
|
export * from './MarkdownBlockInitializer';
|
||||||
export * from './PrintActionInitializer';
|
|
||||||
export * from './RecordAssociationBlockInitializer';
|
export * from './RecordAssociationBlockInitializer';
|
||||||
export * from './RecordAssociationGridCardBlockInitializer';
|
export * from './RecordAssociationGridCardBlockInitializer';
|
||||||
export * from './RecordAssociationListBlockInitializer';
|
export * from './RecordAssociationListBlockInitializer';
|
||||||
|
@ -1592,242 +1592,6 @@ export const createCalendarBlockSchema = (options) => {
|
|||||||
return schema;
|
return schema;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createGanttBlockSchema = (options) => {
|
|
||||||
const { collection, resource, fieldNames, ...others } = options;
|
|
||||||
const schema: ISchema = {
|
|
||||||
type: 'void',
|
|
||||||
'x-acl-action': `${resource || collection}:list`,
|
|
||||||
'x-decorator': 'GanttBlockProvider',
|
|
||||||
'x-decorator-props': {
|
|
||||||
collection: collection,
|
|
||||||
resource: resource || collection,
|
|
||||||
action: 'list',
|
|
||||||
fieldNames: {
|
|
||||||
id: 'id',
|
|
||||||
...fieldNames,
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
paginate: false,
|
|
||||||
},
|
|
||||||
...others,
|
|
||||||
},
|
|
||||||
'x-designer': 'Gantt.Designer',
|
|
||||||
'x-component': 'CardItem',
|
|
||||||
properties: {
|
|
||||||
[uid()]: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Gantt',
|
|
||||||
'x-component-props': {
|
|
||||||
useProps: '{{ useGanttBlockProps }}',
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
toolBar: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'ActionBar',
|
|
||||||
'x-component-props': {
|
|
||||||
style: {
|
|
||||||
marginBottom: 24,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'x-initializer': 'GanttActionInitializers',
|
|
||||||
properties: {},
|
|
||||||
},
|
|
||||||
table: {
|
|
||||||
type: 'array',
|
|
||||||
'x-decorator': 'div',
|
|
||||||
'x-decorator-props': {
|
|
||||||
style: {
|
|
||||||
float: 'left',
|
|
||||||
maxWidth: '35%',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
'x-initializer': 'TableColumnInitializers',
|
|
||||||
'x-component': 'TableV2',
|
|
||||||
'x-component-props': {
|
|
||||||
rowKey: 'id',
|
|
||||||
rowSelection: {
|
|
||||||
type: 'checkbox',
|
|
||||||
},
|
|
||||||
useProps: '{{ useTableBlockProps }}',
|
|
||||||
pagination: false,
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
actions: {
|
|
||||||
type: 'void',
|
|
||||||
title: '{{ t("Actions") }}',
|
|
||||||
'x-action-column': 'actions',
|
|
||||||
'x-decorator': 'TableV2.Column.ActionBar',
|
|
||||||
'x-component': 'TableV2.Column',
|
|
||||||
'x-designer': 'TableV2.ActionColumnDesigner',
|
|
||||||
'x-initializer': 'TableActionColumnInitializers',
|
|
||||||
properties: {
|
|
||||||
actions: {
|
|
||||||
type: 'void',
|
|
||||||
'x-decorator': 'DndContext',
|
|
||||||
'x-component': 'Space',
|
|
||||||
'x-component-props': {
|
|
||||||
split: '|',
|
|
||||||
},
|
|
||||||
properties: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
detail: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Gantt.Event',
|
|
||||||
properties: {
|
|
||||||
drawer: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Action.Drawer',
|
|
||||||
'x-component-props': {
|
|
||||||
className: 'nb-action-popup',
|
|
||||||
},
|
|
||||||
title: '{{ t("View record") }}',
|
|
||||||
properties: {
|
|
||||||
tabs: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Tabs',
|
|
||||||
'x-component-props': {},
|
|
||||||
'x-initializer': 'TabPaneInitializers',
|
|
||||||
properties: {
|
|
||||||
tab1: {
|
|
||||||
type: 'void',
|
|
||||||
title: '{{t("Details")}}',
|
|
||||||
'x-component': 'Tabs.TabPane',
|
|
||||||
'x-designer': 'Tabs.Designer',
|
|
||||||
'x-component-props': {},
|
|
||||||
properties: {
|
|
||||||
grid: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Grid',
|
|
||||||
'x-initializer': 'RecordBlockInitializers',
|
|
||||||
properties: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return schema;
|
|
||||||
};
|
|
||||||
export const createKanbanBlockSchema = (options) => {
|
|
||||||
const { collection, resource, groupField, ...others } = options;
|
|
||||||
const schema: ISchema = {
|
|
||||||
type: 'void',
|
|
||||||
'x-acl-action': `${resource || collection}:list`,
|
|
||||||
'x-decorator': 'KanbanBlockProvider',
|
|
||||||
'x-decorator-props': {
|
|
||||||
collection: collection,
|
|
||||||
resource: resource || collection,
|
|
||||||
action: 'list',
|
|
||||||
groupField,
|
|
||||||
params: {
|
|
||||||
paginate: false,
|
|
||||||
},
|
|
||||||
...others,
|
|
||||||
},
|
|
||||||
'x-designer': 'Kanban.Designer',
|
|
||||||
'x-component': 'CardItem',
|
|
||||||
properties: {
|
|
||||||
actions: {
|
|
||||||
type: 'void',
|
|
||||||
'x-initializer': 'KanbanActionInitializers',
|
|
||||||
'x-component': 'ActionBar',
|
|
||||||
'x-component-props': {
|
|
||||||
style: {
|
|
||||||
marginBottom: 'var(--nb-spacing)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
properties: {},
|
|
||||||
},
|
|
||||||
[uid()]: {
|
|
||||||
type: 'array',
|
|
||||||
'x-component': 'Kanban',
|
|
||||||
'x-component-props': {
|
|
||||||
useProps: '{{ useKanbanBlockProps }}',
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
card: {
|
|
||||||
type: 'void',
|
|
||||||
'x-read-pretty': true,
|
|
||||||
'x-label-disabled': true,
|
|
||||||
'x-decorator': 'BlockItem',
|
|
||||||
'x-component': 'Kanban.Card',
|
|
||||||
'x-component-props': {
|
|
||||||
openMode: 'drawer',
|
|
||||||
},
|
|
||||||
'x-designer': 'Kanban.Card.Designer',
|
|
||||||
properties: {
|
|
||||||
grid: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Grid',
|
|
||||||
'x-component-props': { dndContext: false },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
cardViewer: {
|
|
||||||
type: 'void',
|
|
||||||
title: '{{ t("View") }}',
|
|
||||||
'x-designer': 'Action.Designer',
|
|
||||||
'x-component': 'Kanban.CardViewer',
|
|
||||||
'x-action': 'view',
|
|
||||||
'x-component-props': {
|
|
||||||
openMode: 'drawer',
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
drawer: {
|
|
||||||
type: 'void',
|
|
||||||
title: '{{ t("View record") }}',
|
|
||||||
'x-component': 'Action.Container',
|
|
||||||
'x-component-props': {
|
|
||||||
className: 'nb-action-popup',
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
tabs: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Tabs',
|
|
||||||
'x-component-props': {},
|
|
||||||
'x-initializer': 'TabPaneInitializers',
|
|
||||||
properties: {
|
|
||||||
tab1: {
|
|
||||||
type: 'void',
|
|
||||||
title: '{{t("Details")}}',
|
|
||||||
'x-component': 'Tabs.TabPane',
|
|
||||||
'x-designer': 'Tabs.Designer',
|
|
||||||
'x-component-props': {},
|
|
||||||
properties: {
|
|
||||||
grid: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Grid',
|
|
||||||
'x-initializer': 'RecordBlockInitializers',
|
|
||||||
properties: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return schema;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getChildren = ({
|
const getChildren = ({
|
||||||
collections,
|
collections,
|
||||||
getCollectionFields,
|
getCollectionFields,
|
||||||
|
@ -166,7 +166,7 @@ export const useSyncFromForm = (fieldSchema, collection?, callBack?) => {
|
|||||||
const formData = new Set([]);
|
const formData = new Set([]);
|
||||||
const selectFields = new Set([]);
|
const selectFields = new Set([]);
|
||||||
const getAssociationAppends = (schema, str) => {
|
const getAssociationAppends = (schema, str) => {
|
||||||
schema.reduceProperties((pre, s) => {
|
schema?.reduceProperties?.((pre, s) => {
|
||||||
const prefix = pre || str;
|
const prefix = pre || str;
|
||||||
const collectionfield = s['x-collection-field'] && getCollectionJoinField(s['x-collection-field']);
|
const collectionfield = s['x-collection-field'] && getCollectionJoinField(s['x-collection-field']);
|
||||||
const isAssociationSubfield = s.name.includes('.');
|
const isAssociationSubfield = s.name.includes('.');
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
export * from './GeneralSchemaDesigner';
|
export * from './GeneralSchemaDesigner';
|
||||||
export * from './SchemaSettings';
|
export * from './SchemaSettings';
|
||||||
export * from './hooks/useIsAllowToSetDefaultValue';
|
export * from './hooks/useIsAllowToSetDefaultValue';
|
||||||
|
export * from './hooks/useGetAriaLabelOfDesigner';
|
||||||
|
export * from './DataTemplates/hooks/useCollectionState';
|
||||||
|
export * from './DataTemplates/utils';
|
||||||
|
|
||||||
export { default as useParseDataScopeFilter } from './hooks/useParseDataScopeFilter';
|
export { default as useParseDataScopeFilter } from './hooks/useParseDataScopeFilter';
|
||||||
|
|
||||||
|
@ -3,5 +3,7 @@ export { default as useBuiltinVariables } from './hooks/useBuiltinVariables';
|
|||||||
export { default as useContextVariable } from './hooks/useContextVariable';
|
export { default as useContextVariable } from './hooks/useContextVariable';
|
||||||
export { default as useLocalVariables } from './hooks/useLocalVariables';
|
export { default as useLocalVariables } from './hooks/useLocalVariables';
|
||||||
export { default as useVariables } from './hooks/useVariables';
|
export { default as useVariables } from './hooks/useVariables';
|
||||||
|
export * from './utils/isVariable';
|
||||||
|
export * from './utils/transformVariableValue';
|
||||||
|
|
||||||
export * from './constants';
|
export * from './constants';
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
/node_modules
|
||||||
|
/src
|
@ -0,0 +1 @@
|
|||||||
|
# @nocobase/plugin-action-bulk-edit
|
2
packages/plugins/@nocobase/plugin-action-bulk-edit/client.d.ts
vendored
Normal file
2
packages/plugins/@nocobase/plugin-action-bulk-edit/client.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './dist/client';
|
||||||
|
export { default } from './dist/client';
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./dist/client/index.js');
|
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "@nocobase/plugin-action-bulk-edit",
|
||||||
|
"version": "0.17.0-alpha.7",
|
||||||
|
"main": "dist/server/index.js",
|
||||||
|
"displayName": "Bulk edit action",
|
||||||
|
"displayName.zh-CN": "批量编辑操作",
|
||||||
|
"description": "Provides bulk edit action",
|
||||||
|
"description.zh-CN": "提供批量编辑操作",
|
||||||
|
"license": "AGPL-3.0",
|
||||||
|
"dependencies": {},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nocobase/client": "0.x",
|
||||||
|
"@nocobase/server": "0.x",
|
||||||
|
"@nocobase/test": "0.x"
|
||||||
|
}
|
||||||
|
}
|
2
packages/plugins/@nocobase/plugin-action-bulk-edit/server.d.ts
vendored
Normal file
2
packages/plugins/@nocobase/plugin-action-bulk-edit/server.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './dist/server';
|
||||||
|
export { default } from './dist/server';
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./dist/server/index.js');
|
@ -0,0 +1,134 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
SchemaSettings,
|
||||||
|
useCompile,
|
||||||
|
ActionDesigner,
|
||||||
|
useSchemaToolbar,
|
||||||
|
useDesignable,
|
||||||
|
SchemaInitializerOpenModeSchemaItems,
|
||||||
|
SchemaSettingsSelectItem,
|
||||||
|
SchemaSettingsDivider,
|
||||||
|
SchemaSettingsRemove,
|
||||||
|
SchemaSettingsItemGroup,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
import { ModalProps } from 'antd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useFieldSchema } from '@formily/react';
|
||||||
|
|
||||||
|
const MenuGroup = (props) => {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const compile = useCompile();
|
||||||
|
const actionTitle = fieldSchema.title ? compile(fieldSchema.title) : '';
|
||||||
|
return (
|
||||||
|
<SchemaSettingsItemGroup title={`${t('Customize')} > ${actionTitle}`}>{props.children}</SchemaSettingsItemGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function UpdateMode() {
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaSettingsSelectItem
|
||||||
|
title={t('Data will be updated')}
|
||||||
|
options={[
|
||||||
|
{ label: t('Selected'), value: 'selected' },
|
||||||
|
{ label: t('All'), value: 'all' },
|
||||||
|
]}
|
||||||
|
value={fieldSchema?.['x-action-settings']?.['updateMode']}
|
||||||
|
onChange={(value) => {
|
||||||
|
fieldSchema['x-action-settings']['updateMode'] = value;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
'x-uid': fieldSchema['x-uid'],
|
||||||
|
'x-action-settings': fieldSchema['x-action-settings'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dn.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function RemoveButton(
|
||||||
|
props: {
|
||||||
|
onConfirmOk?: ModalProps['onOk'];
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const isDeletable = fieldSchema?.parent['x-component'] === 'CollectionField';
|
||||||
|
return (
|
||||||
|
!isDeletable && (
|
||||||
|
<>
|
||||||
|
<SchemaSettingsDivider />
|
||||||
|
<SchemaSettingsRemove
|
||||||
|
removeParentsIfNoChildren
|
||||||
|
breakRemoveOn={(s) => {
|
||||||
|
return s['x-component'] === 'Space' || s['x-component'].endsWith('ActionBar');
|
||||||
|
}}
|
||||||
|
confirm={{
|
||||||
|
title: t('Delete action'),
|
||||||
|
onOk: props.onConfirmOk,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const bulkEditactionSettings = new SchemaSettings({
|
||||||
|
name: 'ActionSettings:customize:bulkEdit',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'Customize',
|
||||||
|
Component: MenuGroup,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'editButton',
|
||||||
|
Component: ActionDesigner.ButtonEditor,
|
||||||
|
useComponentProps() {
|
||||||
|
const { buttonEditorProps } = useSchemaToolbar();
|
||||||
|
return buttonEditorProps;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'openMode',
|
||||||
|
Component: SchemaInitializerOpenModeSchemaItems,
|
||||||
|
useComponentProps() {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const isPopupAction = [
|
||||||
|
'create',
|
||||||
|
'update',
|
||||||
|
'view',
|
||||||
|
'customize:popup',
|
||||||
|
'duplicate',
|
||||||
|
'customize:create',
|
||||||
|
].includes(fieldSchema['x-action'] || '');
|
||||||
|
|
||||||
|
return {
|
||||||
|
openMode: isPopupAction,
|
||||||
|
openSize: isPopupAction,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updateMode',
|
||||||
|
Component: UpdateMode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'remove',
|
||||||
|
sort: 100,
|
||||||
|
Component: RemoveButton as any,
|
||||||
|
useComponentProps() {
|
||||||
|
const { removeButtonProps } = useSchemaToolbar();
|
||||||
|
return removeButtonProps;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export { bulkEditactionSettings };
|
@ -1,7 +1,7 @@
|
|||||||
import { SchemaInitializer } from '../../application/schema-initializer/SchemaInitializer';
|
import { SchemaInitializer, gridRowColWrap } from '@nocobase/client';
|
||||||
import { gridRowColWrap, useCustomBulkEditFormItemInitializerFields } from '../utils';
|
import { useCustomBulkEditFormItemInitializerFields } from './utils';
|
||||||
|
|
||||||
export const bulkEditFormItemInitializers = new SchemaInitializer({
|
export const BulkEditFormItemInitializers = new SchemaInitializer({
|
||||||
name: 'BulkEditFormItemInitializers',
|
name: 'BulkEditFormItemInitializers',
|
||||||
wrap: gridRowColWrap,
|
wrap: gridRowColWrap,
|
||||||
icon: 'SettingOutlined',
|
icon: 'SettingOutlined',
|
@ -0,0 +1,17 @@
|
|||||||
|
import { SchemaComponentOptions } from '@nocobase/client';
|
||||||
|
import React from 'react';
|
||||||
|
import { CustomizeBulkEditActionInitializer } from './CustomizeBulkEditActionInitializer';
|
||||||
|
import { CreateFormBulkEditBlockInitializer } from './CreateFormBulkEditBlockInitializer';
|
||||||
|
|
||||||
|
export const BulkEditPluginProvider = (props: any) => {
|
||||||
|
return (
|
||||||
|
<SchemaComponentOptions
|
||||||
|
components={{
|
||||||
|
CustomizeBulkEditActionInitializer,
|
||||||
|
CreateFormBulkEditBlockInitializer,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</SchemaComponentOptions>
|
||||||
|
);
|
||||||
|
};
|
@ -1,10 +1,15 @@
|
|||||||
import { FormOutlined } from '@ant-design/icons';
|
import { FormOutlined } from '@ant-design/icons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useBlockAssociationContext } from '../../block-provider';
|
import {
|
||||||
import { useCollection } from '../../collection-manager';
|
useBlockAssociationContext,
|
||||||
import { useSchemaTemplateManager } from '../../schema-templates';
|
useCollection,
|
||||||
import { createFormBlockSchema, useRecordCollectionDataSourceItems } from '../utils';
|
useSchemaTemplateManager,
|
||||||
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../application';
|
createFormBlockSchema,
|
||||||
|
useRecordCollectionDataSourceItems,
|
||||||
|
SchemaInitializerItem,
|
||||||
|
useSchemaInitializer,
|
||||||
|
useSchemaInitializerItem,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
|
||||||
export const CreateFormBulkEditBlockInitializer = () => {
|
export const CreateFormBulkEditBlockInitializer = () => {
|
||||||
const itemConfig = useSchemaInitializerItem();
|
const itemConfig = useSchemaInitializerItem();
|
@ -1,7 +1,6 @@
|
|||||||
import { SchemaInitializer } from '../../application/schema-initializer/SchemaInitializer';
|
import { SchemaInitializer, gridRowColWrap } from '@nocobase/client';
|
||||||
import { gridRowColWrap } from '../utils';
|
|
||||||
|
|
||||||
export const createFormBulkEditBlockInitializers = new SchemaInitializer({
|
export const CreateFormBulkEditBlockInitializers = new SchemaInitializer({
|
||||||
name: 'CreateFormBulkEditBlockInitializers',
|
name: 'CreateFormBulkEditBlockInitializers',
|
||||||
wrap: gridRowColWrap,
|
wrap: gridRowColWrap,
|
||||||
title: '{{t("Add block")}}',
|
title: '{{t("Add block")}}',
|
@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BlockInitializer } from './BlockInitializer';
|
import { BlockInitializer, useSchemaInitializerItem } from '@nocobase/client';
|
||||||
import { useSchemaInitializerItem } from '../../application';
|
|
||||||
|
|
||||||
export const CustomizeBulkEditActionInitializer = () => {
|
export const CustomizeBulkEditActionInitializer = () => {
|
||||||
const schema = {
|
const schema = {
|
@ -0,0 +1,22 @@
|
|||||||
|
import { expect, test, oneEmptyTableBlockWithActions } from '@nocobase/test/client';
|
||||||
|
import { oneEmptyGantt } from './utils';
|
||||||
|
|
||||||
|
test.describe('TableActionInitializers & GanttActionInitializers & MapActionInitializers should add bulk edit action', () => {
|
||||||
|
test('bulk edit in TableActionInitializers', async ({ page, mockPage }) => {
|
||||||
|
await mockPage(oneEmptyTableBlockWithActions).goto();
|
||||||
|
await page.getByLabel('schema-initializer-ActionBar-TableActionInitializers-general').hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Customize right' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Bulk edit' }).click();
|
||||||
|
await page.mouse.move(300, 0);
|
||||||
|
await expect(await page.getByLabel('Bulk edit')).toBeVisible();
|
||||||
|
});
|
||||||
|
test('bulk edit in GanttActionInitializers', async ({ page, mockPage, mockRecords }) => {
|
||||||
|
const nocoPage = await mockPage(oneEmptyGantt).waitForInit();
|
||||||
|
await mockRecords('general', 3);
|
||||||
|
await nocoPage.goto();
|
||||||
|
await page.getByLabel('schema-initializer-ActionBar-GanttActionInitializers-general').hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Customize right' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Bulk edit' }).click();
|
||||||
|
await expect(await page.getByLabel('Bulk edit')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,45 @@
|
|||||||
|
import { expect, test } from '@nocobase/test/client';
|
||||||
|
import { oneEmptyTableBlockWithCustomizeActions } from './utils';
|
||||||
|
|
||||||
|
test.describe('bulk edit action setting', () => {
|
||||||
|
test('data will be updated && edit form configure', async ({ page, mockPage, mockRecords }) => {
|
||||||
|
const nocoPage = await mockPage(oneEmptyTableBlockWithCustomizeActions).waitForInit();
|
||||||
|
await mockRecords('general', 3);
|
||||||
|
await nocoPage.goto();
|
||||||
|
await page.getByLabel('Bulk edit').hover();
|
||||||
|
await page.getByLabel('designer-schema-settings-Action-Action.Designer-general').hover();
|
||||||
|
//默认是选中的数据
|
||||||
|
await expect(await page.getByTitle('Data will be updated').getByText('Selected')).toBeVisible();
|
||||||
|
await page.getByRole('menuitem', { name: 'Data will be updated' }).click();
|
||||||
|
//切换为全部数据
|
||||||
|
await page.getByRole('option', { name: 'All' }).click();
|
||||||
|
//配置更新规则
|
||||||
|
await page.getByLabel('Bulk edit').click();
|
||||||
|
await page.getByLabel('schema-initializer-Grid-CreateFormBulkEditBlockInitializers-general').hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'form Form' }).click();
|
||||||
|
await page.mouse.move(300, 0);
|
||||||
|
await page.getByLabel('schema-initializer-Grid-BulkEditFormItemInitializers-general').hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Single line text' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Start date time' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Percent' }).click();
|
||||||
|
await page.getByLabel('block-item-BulkEditField-general-form-general.singleLineText').click();
|
||||||
|
await page.getByText('Clear').click();
|
||||||
|
await page.getByLabel('block-item-BulkEditField-general-form-general.startDatetime').click();
|
||||||
|
await page.getByRole('option', { name: 'Remains the same' }).locator('div').click();
|
||||||
|
await page.getByLabel('block-item-BulkEditField-general-form-general.percent').click();
|
||||||
|
await page.getByRole('option', { name: 'Changed to' }).locator('div').click();
|
||||||
|
await page.getByRole('spinbutton').click();
|
||||||
|
await page.getByRole('spinbutton').fill('0');
|
||||||
|
await page.getByLabel('schema-initializer-ActionBar-BulkEditFormActionInitializers-general').click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Submit' }).click();
|
||||||
|
//更新提交的数据符合预期
|
||||||
|
const [request] = await Promise.all([
|
||||||
|
page.waitForRequest((request) => request.url().includes('api/general:update')),
|
||||||
|
page.getByLabel('action-Action-Submit-submit-general-form').click(),
|
||||||
|
]);
|
||||||
|
const postData = request.postDataJSON();
|
||||||
|
//更新的数据符合预期
|
||||||
|
expect(postData.singleLineText).toEqual(null);
|
||||||
|
expect(postData.percent).toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,534 @@
|
|||||||
|
import { CollectionSetting, PageConfig } from '@nocobase/test/client';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. 创建一个名为 general 的 collection,其包含 时间、Percent 类型的字段
|
||||||
|
*/
|
||||||
|
export const generalWithDatetimeFields: CollectionSetting[] = [
|
||||||
|
{
|
||||||
|
name: 'general',
|
||||||
|
title: 'General',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'singleLineText',
|
||||||
|
interface: 'input',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Input',
|
||||||
|
title: 'Single line text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'startDatetime',
|
||||||
|
interface: 'datetime',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
title: 'Start date time',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'endDatetime',
|
||||||
|
interface: 'datetime',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
title: 'End date time',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'f_t22o7loai3j',
|
||||||
|
interface: 'integer',
|
||||||
|
isForeignKey: true,
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
title: 'f_t22o7loai3j',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'manyToOne',
|
||||||
|
interface: 'm2o',
|
||||||
|
foreignKey: 'f_t22o7loai3j',
|
||||||
|
uiSchema: {
|
||||||
|
'x-component': 'AssociationField',
|
||||||
|
'x-component-props': {
|
||||||
|
multiple: false,
|
||||||
|
fieldNames: {
|
||||||
|
label: 'id',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: 'Many to one',
|
||||||
|
},
|
||||||
|
target: 'users',
|
||||||
|
targetKey: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'percent',
|
||||||
|
type: 'float',
|
||||||
|
interface: 'percent',
|
||||||
|
uiSchema: {
|
||||||
|
'x-component-props': {
|
||||||
|
step: '0.01',
|
||||||
|
stringMode: true,
|
||||||
|
addonAfter: '%',
|
||||||
|
},
|
||||||
|
'x-component': 'Percent',
|
||||||
|
title: 'Percent',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const oneEmptyGantt: PageConfig = {
|
||||||
|
collections: generalWithDatetimeFields,
|
||||||
|
pageSchema: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Page',
|
||||||
|
properties: {
|
||||||
|
jebhzap4dzi: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer': 'BlockInitializers',
|
||||||
|
properties: {
|
||||||
|
gl57m4hyewf: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Row',
|
||||||
|
properties: {
|
||||||
|
jm7n5dybw6t: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Col',
|
||||||
|
properties: {
|
||||||
|
nsq0rdemz4i: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-acl-action': 'general:list',
|
||||||
|
'x-decorator': 'GanttBlockProvider',
|
||||||
|
'x-decorator-props': {
|
||||||
|
collection: 'general',
|
||||||
|
resource: 'general',
|
||||||
|
action: 'list',
|
||||||
|
fieldNames: {
|
||||||
|
id: 'id',
|
||||||
|
start: 'startDatetime',
|
||||||
|
range: 'day',
|
||||||
|
title: 'singleLineText',
|
||||||
|
end: 'endDatetime',
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
paginate: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-designer': 'Gantt.Designer',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
properties: {
|
||||||
|
zf07g7relim: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Gantt',
|
||||||
|
'x-component-props': {
|
||||||
|
useProps: '{{ useGanttBlockProps }}',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
toolBar: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-initializer': 'GanttActionInitializers',
|
||||||
|
'x-uid': 'guwovmwt4c0',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'array',
|
||||||
|
'x-decorator': 'div',
|
||||||
|
'x-decorator-props': {
|
||||||
|
style: {
|
||||||
|
float: 'left',
|
||||||
|
maxWidth: '35%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-initializer': 'TableColumnInitializers',
|
||||||
|
'x-component': 'TableV2',
|
||||||
|
'x-component-props': {
|
||||||
|
rowKey: 'id',
|
||||||
|
rowSelection: {
|
||||||
|
type: 'checkbox',
|
||||||
|
},
|
||||||
|
useProps: '{{ useTableBlockProps }}',
|
||||||
|
pagination: false,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Actions") }}',
|
||||||
|
'x-action-column': 'actions',
|
||||||
|
'x-decorator': 'TableV2.Column.ActionBar',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-designer': 'TableV2.ActionColumnDesigner',
|
||||||
|
'x-initializer': 'TableActionColumnInitializers',
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'DndContext',
|
||||||
|
'x-component': 'Space',
|
||||||
|
'x-component-props': {
|
||||||
|
split: '|',
|
||||||
|
},
|
||||||
|
'x-uid': '9in7s3pymsd',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'rgpbsjwvq2h',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'g7qwompxaeo',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 2,
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Gantt.Event',
|
||||||
|
properties: {
|
||||||
|
drawer: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
'x-component-props': {
|
||||||
|
className: 'nb-action-popup',
|
||||||
|
},
|
||||||
|
title: '{{ t("View record") }}',
|
||||||
|
properties: {
|
||||||
|
tabs: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Tabs',
|
||||||
|
'x-component-props': {},
|
||||||
|
'x-initializer': 'TabPaneInitializers',
|
||||||
|
properties: {
|
||||||
|
tab1: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
title: '{{t("Details")}}',
|
||||||
|
'x-component': 'Tabs.TabPane',
|
||||||
|
'x-designer': 'Tabs.Designer',
|
||||||
|
'x-component-props': {},
|
||||||
|
properties: {
|
||||||
|
grid: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer': 'RecordBlockInitializers',
|
||||||
|
'x-uid': 'gxtfjqzxbfu',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'zxwwx4358p1',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '1p0tnmzpsim',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'hcue8v3fwti',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'fg7cjxu4hxp',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'rf47sf7k16z',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'tritqukkd86',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '3am2ctuhyka',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'p8pesbc7fiv',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'ddm9fhkvrbw',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'vpk8sp3eap1',
|
||||||
|
'x-async': true,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一个空的 Table 区块,并且有这些按钮:Bulk edit
|
||||||
|
*/
|
||||||
|
export const oneEmptyTableBlockWithCustomizeActions: PageConfig = {
|
||||||
|
collections: generalWithDatetimeFields,
|
||||||
|
pageSchema: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Page',
|
||||||
|
'x-index': 1,
|
||||||
|
properties: {
|
||||||
|
'1lqiou007g2': {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer': 'BlockInitializers',
|
||||||
|
'x-index': 1,
|
||||||
|
properties: {
|
||||||
|
'1m4gz110aaw': {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Row',
|
||||||
|
properties: {
|
||||||
|
'695oy51236d': {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Col',
|
||||||
|
properties: {
|
||||||
|
twtgsvrdmn1: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableBlockProvider',
|
||||||
|
'x-acl-action': 'general:list',
|
||||||
|
'x-decorator-props': {
|
||||||
|
collection: 'general',
|
||||||
|
resource: 'general',
|
||||||
|
action: 'list',
|
||||||
|
params: {
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
rowKey: 'id',
|
||||||
|
showIndex: true,
|
||||||
|
dragSort: false,
|
||||||
|
disableTemplate: false,
|
||||||
|
},
|
||||||
|
'x-designer': 'TableBlockDesigner',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-filter-targets': [],
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-initializer': 'TableActionInitializers',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
marginBottom: 'var(--nb-spacing)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
yt5ua92rqvb: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
title: '{{t("Bulk edit")}}',
|
||||||
|
'x-designer': 'Action.Designer',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-action': 'customize:bulkEdit',
|
||||||
|
'x-action-settings': {
|
||||||
|
updateMode: 'selected',
|
||||||
|
},
|
||||||
|
'x-component-props': {
|
||||||
|
openMode: 'drawer',
|
||||||
|
icon: 'EditOutlined',
|
||||||
|
},
|
||||||
|
'x-align': 'right',
|
||||||
|
'x-decorator': 'ACLActionProvider',
|
||||||
|
'x-acl-action': 'update',
|
||||||
|
'x-acl-action-props': {
|
||||||
|
skipScopeCheck: true,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
drawer: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
title: '{{t("Bulk edit")}}',
|
||||||
|
'x-component': 'Action.Container',
|
||||||
|
'x-component-props': {
|
||||||
|
className: 'nb-action-popup',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
tabs: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Tabs',
|
||||||
|
'x-component-props': {},
|
||||||
|
'x-initializer': 'TabPaneInitializersForBulkEditFormBlock',
|
||||||
|
properties: {
|
||||||
|
tab1: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
title: '{{t("Bulk edit")}}',
|
||||||
|
'x-component': 'Tabs.TabPane',
|
||||||
|
'x-designer': 'Tabs.Designer',
|
||||||
|
'x-component-props': {},
|
||||||
|
properties: {
|
||||||
|
grid: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer': 'CreateFormBulkEditBlockInitializers',
|
||||||
|
'x-uid': '3gjmo60w4de',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '2q284n9l6w2',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 's07s8zva7id',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '0n86l5rtgpb',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'k8sk9wa8sv6',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'znrsshrlsna',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
'1xnl1d9j48o': {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'array',
|
||||||
|
'x-initializer': 'TableColumnInitializers',
|
||||||
|
'x-component': 'TableV2',
|
||||||
|
'x-component-props': {
|
||||||
|
rowKey: 'id',
|
||||||
|
rowSelection: {
|
||||||
|
type: 'checkbox',
|
||||||
|
},
|
||||||
|
useProps: '{{ useTableBlockProps }}',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Actions") }}',
|
||||||
|
'x-action-column': 'actions',
|
||||||
|
'x-decorator': 'TableV2.Column.ActionBar',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-designer': 'TableV2.ActionColumnDesigner',
|
||||||
|
'x-initializer': 'TableActionColumnInitializers',
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'DndContext',
|
||||||
|
'x-component': 'Space',
|
||||||
|
'x-component-props': {
|
||||||
|
split: '|',
|
||||||
|
},
|
||||||
|
'x-uid': 'ijgo5usyzbp',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '5tnwpodzirq',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'k8t01z9qna3',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'c29q4s49svw',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '9pe6fpnq33f',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'j6g551r7tbp',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'mbw5vw7y3ea',
|
||||||
|
'x-async': false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '4mbt7m7in1l',
|
||||||
|
'x-async': true,
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,46 @@
|
|||||||
|
import { Plugin, useCollection } from '@nocobase/client';
|
||||||
|
import { BulkEditPluginProvider } from './BulkEditPluginProvider';
|
||||||
|
import { BulkEditFormItemInitializers } from './BulkEditFormItemInitializers';
|
||||||
|
import { CreateFormBulkEditBlockInitializers } from './CreateFormBulkEditBlockInitializers';
|
||||||
|
import { bulkEditactionSettings } from './BulkEditAction.Settings';
|
||||||
|
export class BulkEditPlugin extends Plugin {
|
||||||
|
async load() {
|
||||||
|
this.app.use(BulkEditPluginProvider);
|
||||||
|
this.app.schemaSettingsManager.add(bulkEditactionSettings);
|
||||||
|
this.app.schemaInitializerManager.add(BulkEditFormItemInitializers);
|
||||||
|
this.app.schemaInitializerManager.add(CreateFormBulkEditBlockInitializers);
|
||||||
|
|
||||||
|
const initializerData = {
|
||||||
|
type: 'item',
|
||||||
|
title: '{{t("Bulk edit")}}',
|
||||||
|
name: 'bulkEdit',
|
||||||
|
Component: 'CustomizeBulkEditActionInitializer',
|
||||||
|
schema: {
|
||||||
|
'x-align': 'right',
|
||||||
|
'x-decorator': 'ACLActionProvider',
|
||||||
|
'x-designer': 'Action.Designer',
|
||||||
|
'x-action': 'customize:bulkEdit',
|
||||||
|
'x-settings': 'ActionSettings:customize:bulkEdit',
|
||||||
|
'x-acl-action': 'update',
|
||||||
|
'x-acl-action-props': {
|
||||||
|
skipScopeCheck: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useVisible() {
|
||||||
|
const collection = useCollection();
|
||||||
|
return (
|
||||||
|
(collection.template !== 'view' || collection?.writableView) &&
|
||||||
|
collection.template !== 'file' &&
|
||||||
|
collection.template !== 'sql'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const tableActionInitializers = this.app.schemaInitializerManager.get('TableActionInitializers');
|
||||||
|
tableActionInitializers?.add('customize.bulkEdit', initializerData);
|
||||||
|
this.app.schemaInitializerManager.addItem('GanttActionInitializers', 'customize.bulkEdit', initializerData);
|
||||||
|
this.app.schemaInitializerManager.addItem('MapActionInitializers', 'customize.bulkEdit', initializerData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BulkEditPlugin;
|
@ -0,0 +1,21 @@
|
|||||||
|
import { i18n } from '@nocobase/client';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export const NAMESPACE = 'bulk-edit';
|
||||||
|
|
||||||
|
// i18n.addResources('zh-CN', NAMESPACE, zhCN);
|
||||||
|
// i18n.addResources('en-US', NAMESPACE, enUS);
|
||||||
|
|
||||||
|
export function lang(key: string) {
|
||||||
|
return i18n.t(key, { ns: NAMESPACE });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateNTemplate(key: string) {
|
||||||
|
return `{{t('${key}', { ns: '${NAMESPACE}', nsMode: 'fallback' })}}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useBulkEditTranslation() {
|
||||||
|
return useTranslation(NAMESPACE, {
|
||||||
|
nsMode: 'fallback',
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
import {
|
||||||
|
useCollection,
|
||||||
|
useCollectionManager,
|
||||||
|
useRemoveGridFormItem,
|
||||||
|
SchemaInitializerItemType,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
import { useForm } from '@formily/react';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
export const useCustomBulkEditFormItemInitializerFields = (options?: any) => {
|
||||||
|
const { name, fields } = useCollection();
|
||||||
|
const { getInterface, getCollection } = useCollectionManager();
|
||||||
|
const form = useForm();
|
||||||
|
const { readPretty = form.readPretty, block = 'Form' } = options || {};
|
||||||
|
const remove = useRemoveGridFormItem();
|
||||||
|
const filterFields = useMemo(
|
||||||
|
() =>
|
||||||
|
fields
|
||||||
|
?.filter((field) => {
|
||||||
|
return (
|
||||||
|
field?.interface &&
|
||||||
|
!field?.uiSchema?.['x-read-pretty'] &&
|
||||||
|
field.interface !== 'snapshot' &&
|
||||||
|
field.type !== 'sequence'
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.map((field) => {
|
||||||
|
const interfaceConfig = getInterface(field.interface);
|
||||||
|
const schema = {
|
||||||
|
type: 'string',
|
||||||
|
name: field.name,
|
||||||
|
title: field?.uiSchema?.title || field.name,
|
||||||
|
'x-designer': 'FormItem.Designer',
|
||||||
|
'x-component': 'BulkEditField',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-collection-field': `${name}.${field.name}`,
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
name: field?.uiSchema?.title || field.name,
|
||||||
|
type: 'item',
|
||||||
|
title: field?.uiSchema?.title || field.name,
|
||||||
|
Component: 'CollectionFieldInitializer',
|
||||||
|
remove: remove,
|
||||||
|
schemaInitialize: (s) => {
|
||||||
|
interfaceConfig?.schemaInitialize?.(s, {
|
||||||
|
field,
|
||||||
|
block,
|
||||||
|
readPretty,
|
||||||
|
targetCollection: getCollection(field.target),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
schema,
|
||||||
|
} as SchemaInitializerItemType;
|
||||||
|
}),
|
||||||
|
[fields],
|
||||||
|
);
|
||||||
|
|
||||||
|
return filterFields;
|
||||||
|
};
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './server';
|
||||||
|
export { default } from './server';
|
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"Bulk edit": "Bulk edit",
|
||||||
|
"Data will be updated": "Data will be updated"
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"Bulk edit": "Edición en masa",
|
||||||
|
"Data will be updated": "Se actualizarán los datos"
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"Bulk edit": "Edição em massa",
|
||||||
|
"Data will be updated": "Os dados serão atualizados"
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"Bulk edit": "批量编辑",
|
||||||
|
"Data will be updated": "更新的数据"
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from './plugin';
|
@ -0,0 +1,19 @@
|
|||||||
|
import { InstallOptions, Plugin } from '@nocobase/server';
|
||||||
|
|
||||||
|
export class PluginActionBulkEditServer extends Plugin {
|
||||||
|
afterAdd() {}
|
||||||
|
|
||||||
|
beforeLoad() {}
|
||||||
|
|
||||||
|
async load() {}
|
||||||
|
|
||||||
|
async install(options?: InstallOptions) {}
|
||||||
|
|
||||||
|
async afterEnable() {}
|
||||||
|
|
||||||
|
async afterDisable() {}
|
||||||
|
|
||||||
|
async remove() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PluginActionBulkEditServer;
|
@ -0,0 +1,2 @@
|
|||||||
|
/node_modules
|
||||||
|
/src
|
@ -0,0 +1 @@
|
|||||||
|
# @nocobase/plugin-action-bulk-update
|
2
packages/plugins/@nocobase/plugin-action-bulk-update/client.d.ts
vendored
Normal file
2
packages/plugins/@nocobase/plugin-action-bulk-update/client.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './dist/client';
|
||||||
|
export { default } from './dist/client';
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./dist/client/index.js');
|
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "@nocobase/plugin-action-bulk-update",
|
||||||
|
"version":"0.17.0-alpha.7",
|
||||||
|
"main": "dist/server/index.js",
|
||||||
|
"displayName": "Bulk update action",
|
||||||
|
"displayName.zh-CN": "批量更新操作",
|
||||||
|
"description": "Provides bulk update action",
|
||||||
|
"description.zh-CN": "提供批量更新操作",
|
||||||
|
"license": "AGPL-3.0",
|
||||||
|
"dependencies": {},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nocobase/client": "0.x",
|
||||||
|
"@nocobase/server": "0.x",
|
||||||
|
"@nocobase/test": "0.x"
|
||||||
|
}
|
||||||
|
}
|
2
packages/plugins/@nocobase/plugin-action-bulk-update/server.d.ts
vendored
Normal file
2
packages/plugins/@nocobase/plugin-action-bulk-update/server.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './dist/server';
|
||||||
|
export { default } from './dist/server';
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./dist/server/index.js');
|
@ -0,0 +1,237 @@
|
|||||||
|
import React, { useCallback, useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
SchemaSettings,
|
||||||
|
ActionDesigner,
|
||||||
|
useSchemaToolbar,
|
||||||
|
useDesignable,
|
||||||
|
useCompile,
|
||||||
|
DefaultValueProvider,
|
||||||
|
SchemaSettingsItemGroup,
|
||||||
|
SchemaSettingsModalItem,
|
||||||
|
SchemaSettingsActionModalItem,
|
||||||
|
SchemaSettingsSelectItem,
|
||||||
|
FlagProvider,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
import { isValid, uid } from '@formily/shared';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useField, useFieldSchema, ISchema } from '@formily/react';
|
||||||
|
|
||||||
|
const MenuGroup = (props) => {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const compile = useCompile();
|
||||||
|
const actionTitle = fieldSchema.title ? compile(fieldSchema.title) : '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaSettingsItemGroup title={`${t('Customize')} > ${actionTitle}`}>{props.children}</SchemaSettingsItemGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function UpdateMode() {
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaSettingsSelectItem
|
||||||
|
title={t('Data will be updated')}
|
||||||
|
options={[
|
||||||
|
{ label: t('Selected'), value: 'selected' },
|
||||||
|
{ label: t('All'), value: 'all' },
|
||||||
|
]}
|
||||||
|
value={fieldSchema?.['x-action-settings']?.['updateMode']}
|
||||||
|
onChange={(value) => {
|
||||||
|
fieldSchema['x-action-settings']['updateMode'] = value;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
'x-uid': fieldSchema['x-uid'],
|
||||||
|
'x-action-settings': fieldSchema['x-action-settings'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dn.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AfterSuccess() {
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
return (
|
||||||
|
<SchemaSettingsModalItem
|
||||||
|
title={t('After successful submission')}
|
||||||
|
initialValues={fieldSchema?.['x-action-settings']?.['onSuccess']}
|
||||||
|
schema={
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
title: t('After successful submission'),
|
||||||
|
properties: {
|
||||||
|
successMessage: {
|
||||||
|
title: t('Popup message'),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
manualClose: {
|
||||||
|
title: t('Popup close method'),
|
||||||
|
enum: [
|
||||||
|
{ label: t('Automatic close'), value: false },
|
||||||
|
{ label: t('Manually close'), value: true },
|
||||||
|
],
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Radio.Group',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
redirecting: {
|
||||||
|
title: t('Then'),
|
||||||
|
enum: [
|
||||||
|
{ label: t('Stay on current page'), value: false },
|
||||||
|
{ label: t('Redirect to'), value: true },
|
||||||
|
],
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Radio.Group',
|
||||||
|
'x-component-props': {},
|
||||||
|
'x-reactions': {
|
||||||
|
target: 'redirectTo',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
visible: '{{!!$self.value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
redirectTo: {
|
||||||
|
title: t('Link'),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ISchema
|
||||||
|
}
|
||||||
|
onSubmit={(onSuccess) => {
|
||||||
|
fieldSchema['x-action-settings']['onSuccess'] = onSuccess;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
'x-action-settings': fieldSchema['x-action-settings'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function AssignedFieldValues() {
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const field = useField();
|
||||||
|
const [initialSchema, setInitialSchema] = useState<ISchema>();
|
||||||
|
useEffect(() => {
|
||||||
|
const schemaUid = uid();
|
||||||
|
const schema: ISchema = {
|
||||||
|
type: 'void',
|
||||||
|
'x-uid': schemaUid,
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer': 'CustomFormItemInitializers',
|
||||||
|
};
|
||||||
|
setInitialSchema(schema);
|
||||||
|
}, [field.address]);
|
||||||
|
|
||||||
|
const tips = {
|
||||||
|
'customize:update': t(
|
||||||
|
'After clicking the custom button, the following fields of the current record will be saved according to the following form.',
|
||||||
|
),
|
||||||
|
'customize:save': t(
|
||||||
|
'After clicking the custom button, the following fields of the current record will be saved according to the following form.',
|
||||||
|
),
|
||||||
|
};
|
||||||
|
const actionType = fieldSchema['x-action'] ?? '';
|
||||||
|
const onSubmit = useCallback(
|
||||||
|
(assignedValues) => {
|
||||||
|
fieldSchema['x-action-settings']['assignedValues'] = assignedValues;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
'x-action-settings': fieldSchema['x-action-settings'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[dn, fieldSchema],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FlagProvider isInAssignFieldValues={true}>
|
||||||
|
<DefaultValueProvider isAllowToSetDefaultValue={() => false}>
|
||||||
|
<SchemaSettingsActionModalItem
|
||||||
|
title={t('Assign field values')}
|
||||||
|
maskClosable={false}
|
||||||
|
initialSchema={initialSchema}
|
||||||
|
initialValues={fieldSchema?.['x-action-settings']?.assignedValues}
|
||||||
|
modalTip={tips[actionType]}
|
||||||
|
uid={fieldSchema?.['x-action-settings']?.schemaUid}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
/>
|
||||||
|
</DefaultValueProvider>
|
||||||
|
</FlagProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bulkUpdateActionSettings = new SchemaSettings({
|
||||||
|
name: 'ActionSettings:customize:bulkUpdate',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'Customize',
|
||||||
|
Component: MenuGroup,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'editButton',
|
||||||
|
Component: ActionDesigner.ButtonEditor,
|
||||||
|
useComponentProps() {
|
||||||
|
const { buttonEditorProps } = useSchemaToolbar();
|
||||||
|
return buttonEditorProps;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updateMode',
|
||||||
|
Component: UpdateMode,
|
||||||
|
useVisible() {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const isUpdateModePopupAction = ['customize:bulkUpdate', 'customize:bulkEdit'].includes(
|
||||||
|
fieldSchema['x-action'],
|
||||||
|
);
|
||||||
|
return isUpdateModePopupAction;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'assignFieldValues',
|
||||||
|
Component: AssignedFieldValues,
|
||||||
|
useVisible() {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
return isValid(fieldSchema?.['x-action-settings']?.assignedValues);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'afterSuccess',
|
||||||
|
Component: AfterSuccess,
|
||||||
|
useVisible() {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
return isValid(fieldSchema?.['x-action-settings']?.onSuccess);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'remove',
|
||||||
|
sort: 100,
|
||||||
|
Component: ActionDesigner.RemoveButton as any,
|
||||||
|
useComponentProps() {
|
||||||
|
const { removeButtonProps } = useSchemaToolbar();
|
||||||
|
return removeButtonProps;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export { bulkUpdateActionSettings };
|
@ -0,0 +1,17 @@
|
|||||||
|
import { SchemaComponentOptions } from '@nocobase/client';
|
||||||
|
import React from 'react';
|
||||||
|
import { CustomizeActionInitializer } from './CustomizeActionInitializer';
|
||||||
|
import { useCustomizeBulkUpdateActionProps } from './utils';
|
||||||
|
|
||||||
|
export const BulkUpdatePluginProvider = (props: any) => {
|
||||||
|
return (
|
||||||
|
<SchemaComponentOptions
|
||||||
|
components={{ CustomizeActionInitializer }}
|
||||||
|
scope={{
|
||||||
|
useCustomizeBulkUpdateActionProps,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</SchemaComponentOptions>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { BlockInitializer, useCollection, useSchemaInitializerItem } from '@nocobase/client';
|
||||||
|
|
||||||
|
export const CustomizeActionInitializer = () => {
|
||||||
|
const collection = useCollection();
|
||||||
|
const itemConfig = useSchemaInitializerItem();
|
||||||
|
|
||||||
|
const schema = {};
|
||||||
|
if (collection && schema['x-acl-action']) {
|
||||||
|
schema['x-acl-action'] = `${collection.name}:${schema['x-acl-action']}`;
|
||||||
|
schema['x-decorator'] = 'ACLActionProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
return <BlockInitializer {...itemConfig} schema={schema} item={itemConfig} />;
|
||||||
|
};
|
@ -0,0 +1,22 @@
|
|||||||
|
import { expect, test, oneEmptyTableBlockWithCustomizeActions } from '@nocobase/test/client';
|
||||||
|
import { oneEmptyGantt } from './utils';
|
||||||
|
|
||||||
|
test.describe('TableActionInitializers & GanttActionInitializers & MapActionInitializers ', () => {
|
||||||
|
test('TableActionInitializers should add bulk update', async ({ page, mockPage }) => {
|
||||||
|
await mockPage(oneEmptyTableBlockWithCustomizeActions).goto();
|
||||||
|
await page.getByLabel('schema-initializer-ActionBar-TableActionInitializers-general').hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Customize right' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Bulk update' }).click();
|
||||||
|
await expect(page.getByLabel('action-Action-Bulk update-customize:bulkUpdate-general-table')).toBeVisible();
|
||||||
|
});
|
||||||
|
test('GanttActionInitializers should add bulk update', async ({ page, mockPage, mockRecords }) => {
|
||||||
|
const nocoPage = await mockPage(oneEmptyGantt).waitForInit();
|
||||||
|
await mockRecords('general', 3);
|
||||||
|
await nocoPage.goto();
|
||||||
|
await page.getByLabel('schema-initializer-ActionBar-GanttActionInitializers-general').hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Customize right' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Bulk update' }).click();
|
||||||
|
await page.mouse.move(300, 0);
|
||||||
|
await expect(page.getByRole('button', { name: 'Bulk update' })).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,46 @@
|
|||||||
|
import { expect, test } from '@nocobase/test/client';
|
||||||
|
import { oneEmptyTableBlockWithCustomizeUpdate } from './utils';
|
||||||
|
|
||||||
|
test.describe('data will be updated && Assign field values && after successful submission', () => {
|
||||||
|
test('data will be updated && Assign field values && after successful submission', async ({
|
||||||
|
page,
|
||||||
|
mockPage,
|
||||||
|
mockRecords,
|
||||||
|
}) => {
|
||||||
|
await mockPage(oneEmptyTableBlockWithCustomizeUpdate).goto();
|
||||||
|
await mockRecords('general', 2);
|
||||||
|
await page.getByLabel('action-Action-Bulk update-customize:bulkUpdate-general-table').hover();
|
||||||
|
await page.getByLabel('designer-schema-settings-Action-Action.Designer-general').hover();
|
||||||
|
//默认是选中的数据
|
||||||
|
await expect(await page.getByTitle('Data will be updated').getByText('Selected')).toBeVisible();
|
||||||
|
await page.getByRole('menuitem', { name: 'Data will be updated' }).click();
|
||||||
|
//切换为全部数据
|
||||||
|
await page.getByRole('option', { name: 'All' }).click();
|
||||||
|
//字段赋值
|
||||||
|
await page.getByRole('menuitem', { name: 'Assign field values' }).click();
|
||||||
|
await page.getByLabel('schema-initializer-Grid-CustomFormItemInitializers-general').click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Single select' }).click();
|
||||||
|
await page.getByTestId('select-single').click();
|
||||||
|
await page.getByText('option3').click();
|
||||||
|
await page.getByRole('button', { name: 'Submit' }).click();
|
||||||
|
await page.getByLabel('action-Action-Bulk update-customize:bulkUpdate-general-table').hover();
|
||||||
|
await page.getByLabel('designer-schema-settings-Action-Action.Designer-general').hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'After successful submission' }).click();
|
||||||
|
await page.getByLabel('Manually close').check();
|
||||||
|
await page.getByLabel('Redirect to').check();
|
||||||
|
await page.locator('input[type="text"]').click();
|
||||||
|
await page.locator('input[type="text"]').fill('/admin/pm/list/local/');
|
||||||
|
await page.getByRole('button', { name: 'OK' }).click();
|
||||||
|
await page.getByLabel('action-Action-Bulk update-customize:bulkUpdate-general-table').click();
|
||||||
|
const [request] = await Promise.all([
|
||||||
|
page.waitForRequest((request) => request.url().includes('api/general:update')),
|
||||||
|
page.getByRole('button', { name: 'OK' }).click(),
|
||||||
|
]);
|
||||||
|
const postData = request.postDataJSON();
|
||||||
|
//更新的数据符合预期
|
||||||
|
expect(postData.singleSelect).toEqual('option3');
|
||||||
|
await page.getByRole('button', { name: 'OK' }).click();
|
||||||
|
//成功后跳转路由
|
||||||
|
expect(page.url()).toContain('/admin/pm/list/local/');
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,529 @@
|
|||||||
|
import { CollectionSetting, PageConfig, generalWithM2oSingleSelect } from '@nocobase/test/client';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. 创建一个名为 general 的 collection,其包含 时间、Percent 类型的字段
|
||||||
|
*/
|
||||||
|
export const generalWithDatetimeFields: CollectionSetting[] = [
|
||||||
|
{
|
||||||
|
name: 'general',
|
||||||
|
title: 'General',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'singleLineText',
|
||||||
|
interface: 'input',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Input',
|
||||||
|
title: 'Single line text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'singleLineText2',
|
||||||
|
interface: 'input',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Input',
|
||||||
|
title: 'Single line text2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'startDatetime',
|
||||||
|
interface: 'datetime',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
title: 'Start date time',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'startDatetime2',
|
||||||
|
interface: 'datetime',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
title: 'Start date time2',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'endDatetime',
|
||||||
|
interface: 'datetime',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
title: 'End date time',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'endDatetime2',
|
||||||
|
interface: 'datetime',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
title: 'End date time2',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'f_t22o7loai3j',
|
||||||
|
interface: 'integer',
|
||||||
|
isForeignKey: true,
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
title: 'f_t22o7loai3j',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'manyToOne',
|
||||||
|
interface: 'm2o',
|
||||||
|
foreignKey: 'f_t22o7loai3j',
|
||||||
|
uiSchema: {
|
||||||
|
'x-component': 'AssociationField',
|
||||||
|
'x-component-props': {
|
||||||
|
multiple: false,
|
||||||
|
fieldNames: {
|
||||||
|
label: 'id',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: 'Many to one',
|
||||||
|
},
|
||||||
|
target: 'users',
|
||||||
|
targetKey: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'percent',
|
||||||
|
type: 'float',
|
||||||
|
interface: 'percent',
|
||||||
|
uiSchema: {
|
||||||
|
'x-component-props': {
|
||||||
|
step: '0.01',
|
||||||
|
stringMode: true,
|
||||||
|
addonAfter: '%',
|
||||||
|
},
|
||||||
|
'x-component': 'Percent',
|
||||||
|
title: 'Percent',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'percent2',
|
||||||
|
type: 'float',
|
||||||
|
interface: 'percent',
|
||||||
|
uiSchema: {
|
||||||
|
'x-component-props': {
|
||||||
|
step: '0.01',
|
||||||
|
stringMode: true,
|
||||||
|
addonAfter: '%',
|
||||||
|
},
|
||||||
|
'x-component': 'Percent',
|
||||||
|
title: 'Percent2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const oneEmptyGantt: PageConfig = {
|
||||||
|
collections: generalWithDatetimeFields,
|
||||||
|
pageSchema: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Page',
|
||||||
|
properties: {
|
||||||
|
jebhzap4dzi: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer': 'BlockInitializers',
|
||||||
|
properties: {
|
||||||
|
gl57m4hyewf: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Row',
|
||||||
|
properties: {
|
||||||
|
jm7n5dybw6t: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Col',
|
||||||
|
properties: {
|
||||||
|
nsq0rdemz4i: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-acl-action': 'general:list',
|
||||||
|
'x-decorator': 'GanttBlockProvider',
|
||||||
|
'x-decorator-props': {
|
||||||
|
collection: 'general',
|
||||||
|
resource: 'general',
|
||||||
|
action: 'list',
|
||||||
|
fieldNames: {
|
||||||
|
id: 'id',
|
||||||
|
start: 'startDatetime',
|
||||||
|
range: 'day',
|
||||||
|
title: 'singleLineText',
|
||||||
|
end: 'endDatetime',
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
paginate: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-designer': 'Gantt.Designer',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
properties: {
|
||||||
|
zf07g7relim: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Gantt',
|
||||||
|
'x-component-props': {
|
||||||
|
useProps: '{{ useGanttBlockProps }}',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
toolBar: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-initializer': 'GanttActionInitializers',
|
||||||
|
'x-uid': 'guwovmwt4c0',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'array',
|
||||||
|
'x-decorator': 'div',
|
||||||
|
'x-decorator-props': {
|
||||||
|
style: {
|
||||||
|
float: 'left',
|
||||||
|
maxWidth: '35%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-initializer': 'TableColumnInitializers',
|
||||||
|
'x-component': 'TableV2',
|
||||||
|
'x-component-props': {
|
||||||
|
rowKey: 'id',
|
||||||
|
rowSelection: {
|
||||||
|
type: 'checkbox',
|
||||||
|
},
|
||||||
|
useProps: '{{ useTableBlockProps }}',
|
||||||
|
pagination: false,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Actions") }}',
|
||||||
|
'x-action-column': 'actions',
|
||||||
|
'x-decorator': 'TableV2.Column.ActionBar',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-designer': 'TableV2.ActionColumnDesigner',
|
||||||
|
'x-initializer': 'TableActionColumnInitializers',
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'DndContext',
|
||||||
|
'x-component': 'Space',
|
||||||
|
'x-component-props': {
|
||||||
|
split: '|',
|
||||||
|
},
|
||||||
|
'x-uid': '9in7s3pymsd',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'rgpbsjwvq2h',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'g7qwompxaeo',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 2,
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Gantt.Event',
|
||||||
|
properties: {
|
||||||
|
drawer: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
'x-component-props': {
|
||||||
|
className: 'nb-action-popup',
|
||||||
|
},
|
||||||
|
title: '{{ t("View record") }}',
|
||||||
|
properties: {
|
||||||
|
tabs: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Tabs',
|
||||||
|
'x-component-props': {},
|
||||||
|
'x-initializer': 'TabPaneInitializers',
|
||||||
|
properties: {
|
||||||
|
tab1: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
title: '{{t("Details")}}',
|
||||||
|
'x-component': 'Tabs.TabPane',
|
||||||
|
'x-designer': 'Tabs.Designer',
|
||||||
|
'x-component-props': {},
|
||||||
|
properties: {
|
||||||
|
grid: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer': 'RecordBlockInitializers',
|
||||||
|
'x-uid': 'gxtfjqzxbfu',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'zxwwx4358p1',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '1p0tnmzpsim',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'hcue8v3fwti',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'fg7cjxu4hxp',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'rf47sf7k16z',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'tritqukkd86',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '3am2ctuhyka',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'p8pesbc7fiv',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'ddm9fhkvrbw',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'vpk8sp3eap1',
|
||||||
|
'x-async': true,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 一个空的 Table 区块,并且有这些按钮:Bulk edit
|
||||||
|
*/
|
||||||
|
export const oneEmptyTableBlockWithCustomizeUpdate: PageConfig = {
|
||||||
|
collections: generalWithM2oSingleSelect,
|
||||||
|
pageSchema: {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'Page',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
s409jhc9wzp: {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer': 'BlockInitializers',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
btrf53i4lpi: {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'Grid.Row',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
'6u5d4dju1zw': {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'Grid.Col',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
yxyx102howk: {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-designer': 'TableBlockDesigner',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-decorator': 'TableBlockProvider',
|
||||||
|
'x-acl-action': 'general:list',
|
||||||
|
'x-filter-targets': [],
|
||||||
|
'x-decorator-props': {
|
||||||
|
action: 'list',
|
||||||
|
params: {
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
rowKey: 'id',
|
||||||
|
dragSort: false,
|
||||||
|
resource: 'general',
|
||||||
|
showIndex: true,
|
||||||
|
collection: 'general',
|
||||||
|
disableTemplate: false,
|
||||||
|
},
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-initializer': 'TableActionInitializers',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
marginBottom: 'var(--nb-spacing)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
hsb2p0q5ftw: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Bulk update") }}',
|
||||||
|
version: '2.0',
|
||||||
|
'x-align': 'right',
|
||||||
|
'x-action': 'customize:bulkUpdate',
|
||||||
|
'x-designer': 'Action.Designer',
|
||||||
|
'x-settings': 'ActionSettings:customize:bulkUpdate',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-decorator': 'ACLActionProvider',
|
||||||
|
'x-acl-action': 'update',
|
||||||
|
'x-action-settings': {
|
||||||
|
onSuccess: {
|
||||||
|
manualClose: true,
|
||||||
|
redirecting: false,
|
||||||
|
successMessage: '{{t("Updated successfully")}}',
|
||||||
|
},
|
||||||
|
updateMode: 'selected',
|
||||||
|
assignedValues: {},
|
||||||
|
},
|
||||||
|
'x-component-props': {
|
||||||
|
icon: 'EditOutlined',
|
||||||
|
useProps: '{{ useCustomizeBulkUpdateActionProps }}',
|
||||||
|
},
|
||||||
|
'x-acl-action-props': {
|
||||||
|
skipScopeCheck: true,
|
||||||
|
},
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
'x-uid': '5nghpuc3f3x',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '8lq9zqwwkqd',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
dcblkvx1jj1: {
|
||||||
|
type: 'array',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'TableV2',
|
||||||
|
'x-initializer': 'TableColumnInitializers',
|
||||||
|
'x-component-props': {
|
||||||
|
rowKey: 'id',
|
||||||
|
useProps: '{{ useTableBlockProps }}',
|
||||||
|
rowSelection: {
|
||||||
|
type: 'checkbox',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Actions") }}',
|
||||||
|
version: '2.0',
|
||||||
|
'x-designer': 'TableV2.ActionColumnDesigner',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-decorator': 'TableV2.Column.ActionBar',
|
||||||
|
'x-initializer': 'TableActionColumnInitializers',
|
||||||
|
'x-action-column': 'actions',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'Space',
|
||||||
|
'x-decorator': 'DndContext',
|
||||||
|
'x-component-props': {
|
||||||
|
split: '|',
|
||||||
|
},
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
'x-uid': 'dqt7mgaazmh',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'qp1nqbd1ccv',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '32v8q0beyo2',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 't785fa9uwcc',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'pebalqz6iw2',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'drll4zh4jq1',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '5fdz5g6qr6y',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'z47gi9qo5jn',
|
||||||
|
'x-async': true,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,57 @@
|
|||||||
|
import { Plugin, useCollection } from '@nocobase/client';
|
||||||
|
import { BulkUpdatePluginProvider } from './BulkUpdatePluginProvider';
|
||||||
|
import { bulkUpdateActionSettings } from './BulkUpdateAction.Settings';
|
||||||
|
export class BulkUpdatePlugin extends Plugin {
|
||||||
|
async load() {
|
||||||
|
this.app.use(BulkUpdatePluginProvider);
|
||||||
|
this.app.schemaSettingsManager.add(bulkUpdateActionSettings);
|
||||||
|
|
||||||
|
const initializerData = {
|
||||||
|
title: '{{t("Bulk update")}}',
|
||||||
|
Component: 'CustomizeActionInitializer',
|
||||||
|
name: 'bulkUpdate',
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Bulk update") }}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-align': 'right',
|
||||||
|
'x-acl-action': 'update',
|
||||||
|
'x-decorator': 'ACLActionProvider',
|
||||||
|
'x-designer': 'Action.Designer',
|
||||||
|
'x-acl-action-props': {
|
||||||
|
skipScopeCheck: true,
|
||||||
|
},
|
||||||
|
'x-action': 'customize:bulkUpdate',
|
||||||
|
'x-settings': 'ActionSettings:customize:bulkUpdate',
|
||||||
|
'x-action-settings': {
|
||||||
|
assignedValues: {},
|
||||||
|
updateMode: 'selected',
|
||||||
|
onSuccess: {
|
||||||
|
manualClose: true,
|
||||||
|
redirecting: false,
|
||||||
|
successMessage: '{{t("Updated successfully")}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-component-props': {
|
||||||
|
icon: 'EditOutlined',
|
||||||
|
useProps: '{{ useCustomizeBulkUpdateActionProps }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useVisible() {
|
||||||
|
const collection = useCollection();
|
||||||
|
return (
|
||||||
|
(collection.template !== 'view' || collection?.writableView) &&
|
||||||
|
collection.template !== 'file' &&
|
||||||
|
collection.template !== 'sql'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const tableActionInitializers = this.app.schemaInitializerManager.get('TableActionInitializers');
|
||||||
|
tableActionInitializers?.add('customize.bulkUpdate', initializerData);
|
||||||
|
this.app.schemaInitializerManager.addItem('GanttActionInitializers', 'customize.bulkUpdate', initializerData);
|
||||||
|
this.app.schemaInitializerManager.addItem('MapActionInitializers', 'customize.bulkUpdate', initializerData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BulkUpdatePlugin;
|
@ -0,0 +1,21 @@
|
|||||||
|
import { i18n } from '@nocobase/client';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export const NAMESPACE = 'bulk-update';
|
||||||
|
|
||||||
|
// i18n.addResources('zh-CN', NAMESPACE, zhCN);
|
||||||
|
// i18n.addResources('en-US', NAMESPACE, enUS);
|
||||||
|
|
||||||
|
export function lang(key: string) {
|
||||||
|
return i18n.t(key, { ns: NAMESPACE });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateNTemplate(key: string) {
|
||||||
|
return `{{t('${key}', { ns: '${NAMESPACE}', nsMode: 'fallback' })}}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useBulkUpdateTranslation() {
|
||||||
|
return useTranslation(NAMESPACE, {
|
||||||
|
nsMode: 'fallback',
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,135 @@
|
|||||||
|
import {
|
||||||
|
useBlockRequestContext,
|
||||||
|
useTableBlockContext,
|
||||||
|
useCompile,
|
||||||
|
useRecord,
|
||||||
|
useVariables,
|
||||||
|
useCollection,
|
||||||
|
useLocalVariables,
|
||||||
|
isVariable,
|
||||||
|
transformVariableValue,
|
||||||
|
TableFieldResource,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
import { isURL } from '@nocobase/utils/client';
|
||||||
|
import { SchemaExpressionScopeContext, useFieldSchema, useField } from '@formily/react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { App, message } from 'antd';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { useBulkUpdateTranslation } from './locale';
|
||||||
|
|
||||||
|
export const useCustomizeBulkUpdateActionProps = () => {
|
||||||
|
const { field, resource, __parent, service } = useBlockRequestContext();
|
||||||
|
const expressionScope = useContext(SchemaExpressionScopeContext);
|
||||||
|
const actionSchema = useFieldSchema();
|
||||||
|
const tableBlockContext = useTableBlockContext();
|
||||||
|
const { rowKey } = tableBlockContext;
|
||||||
|
const selectedRecordKeys =
|
||||||
|
tableBlockContext.field?.data?.selectedRowKeys ?? expressionScope?.selectedRecordKeys ?? {};
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const compile = useCompile();
|
||||||
|
const { t } = useBulkUpdateTranslation();
|
||||||
|
const actionField: any = useField();
|
||||||
|
const { modal } = App.useApp();
|
||||||
|
const variables = useVariables();
|
||||||
|
const record = useRecord();
|
||||||
|
const { name, getField } = useCollection();
|
||||||
|
const localVariables = useLocalVariables({ currentRecord: { __parent: record, __collectionName: name } });
|
||||||
|
|
||||||
|
return {
|
||||||
|
async onClick() {
|
||||||
|
const {
|
||||||
|
assignedValues: originalAssignedValues = {},
|
||||||
|
onSuccess,
|
||||||
|
updateMode,
|
||||||
|
} = actionSchema?.['x-action-settings'] ?? {};
|
||||||
|
actionField.data = field.data || {};
|
||||||
|
actionField.data.loading = true;
|
||||||
|
|
||||||
|
const assignedValues = {};
|
||||||
|
const waitList = Object.keys(originalAssignedValues).map(async (key) => {
|
||||||
|
const value = originalAssignedValues[key];
|
||||||
|
const collectionField = getField(key);
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
if (!collectionField) {
|
||||||
|
throw new Error(`useCustomizeBulkUpdateActionProps: field "${key}" not found in collection "${name}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isVariable(value)) {
|
||||||
|
const result = await variables?.parseVariable(value, localVariables);
|
||||||
|
if (result) {
|
||||||
|
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
|
||||||
|
}
|
||||||
|
} else if (value != null && value !== '') {
|
||||||
|
assignedValues[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await Promise.all(waitList);
|
||||||
|
|
||||||
|
modal.confirm({
|
||||||
|
title: t('Bulk update'),
|
||||||
|
content: updateMode === 'selected' ? t('Update selected data?') : t('Update all data?'),
|
||||||
|
async onOk() {
|
||||||
|
const { filter } = service.params?.[0] ?? {};
|
||||||
|
const updateData: { filter?: any; values: any; forceUpdate: boolean } = {
|
||||||
|
values: { ...assignedValues },
|
||||||
|
filter,
|
||||||
|
forceUpdate: false,
|
||||||
|
};
|
||||||
|
if (updateMode === 'selected') {
|
||||||
|
if (!selectedRecordKeys?.length) {
|
||||||
|
message.error(t('Please select the records to be updated'));
|
||||||
|
actionField.data.loading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateData.filter = { $and: [{ [rowKey || 'id']: { $in: selectedRecordKeys } }] };
|
||||||
|
}
|
||||||
|
if (!updateData.filter) {
|
||||||
|
updateData.forceUpdate = true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await resource.update(updateData);
|
||||||
|
} catch (error) {
|
||||||
|
/* empty */
|
||||||
|
} finally {
|
||||||
|
actionField.data.loading = false;
|
||||||
|
}
|
||||||
|
service?.refresh?.();
|
||||||
|
if (!(resource instanceof TableFieldResource)) {
|
||||||
|
__parent?.service?.refresh?.();
|
||||||
|
}
|
||||||
|
if (!onSuccess?.successMessage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (onSuccess?.manualClose) {
|
||||||
|
modal.success({
|
||||||
|
title: compile(onSuccess?.successMessage),
|
||||||
|
onOk: async () => {
|
||||||
|
if (onSuccess?.redirecting && onSuccess?.redirectTo) {
|
||||||
|
if (isURL(onSuccess.redirectTo)) {
|
||||||
|
window.location.href = onSuccess.redirectTo;
|
||||||
|
} else {
|
||||||
|
navigate(onSuccess.redirectTo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
message.success(compile(onSuccess?.successMessage));
|
||||||
|
if (onSuccess?.redirecting && onSuccess?.redirectTo) {
|
||||||
|
if (isURL(onSuccess.redirectTo)) {
|
||||||
|
window.location.href = onSuccess.redirectTo;
|
||||||
|
} else {
|
||||||
|
navigate(onSuccess.redirectTo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async onCancel() {
|
||||||
|
actionField.data.loading = false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './server';
|
||||||
|
export { default } from './server';
|
@ -0,0 +1,5 @@
|
|||||||
|
export default {
|
||||||
|
"Bulk update": "Bulk update",
|
||||||
|
"After successful bulk update": "After successful bulk update",
|
||||||
|
"Please select the records to be updated": "Please select the records to be updated",
|
||||||
|
};
|
@ -0,0 +1,5 @@
|
|||||||
|
export default {
|
||||||
|
"Bulk update": "Actualización en masa",
|
||||||
|
"After successful bulk update": "Tras una actualización en masa correcta",
|
||||||
|
"Please select the records to be updated": "Seleccione los registros que desea actualizar",
|
||||||
|
};
|
@ -0,0 +1,5 @@
|
|||||||
|
export default {
|
||||||
|
"Bulk update": "Atualização em massa",
|
||||||
|
"After successful bulk update": "Após a atualização em massa bem sucedida",
|
||||||
|
"Please select the records to be updated": "Por favor, selecione os registros a serem atualizados",
|
||||||
|
};
|
@ -0,0 +1,5 @@
|
|||||||
|
export default {
|
||||||
|
'Bulk update': '批量更新',
|
||||||
|
'After successful bulk update': '批量成功更新后',
|
||||||
|
'Please select the records to be updated': '请选择要更新的记录',
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from './plugin';
|
@ -0,0 +1,19 @@
|
|||||||
|
import { InstallOptions, Plugin } from '@nocobase/server';
|
||||||
|
|
||||||
|
export class PluginActionBulkUpdateServer extends Plugin {
|
||||||
|
afterAdd() {}
|
||||||
|
|
||||||
|
beforeLoad() {}
|
||||||
|
|
||||||
|
async load() {}
|
||||||
|
|
||||||
|
async install(options?: InstallOptions) {}
|
||||||
|
|
||||||
|
async afterEnable() {}
|
||||||
|
|
||||||
|
async afterDisable() {}
|
||||||
|
|
||||||
|
async remove() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PluginActionBulkUpdateServer;
|
@ -0,0 +1,2 @@
|
|||||||
|
/node_modules
|
||||||
|
/src
|
@ -0,0 +1 @@
|
|||||||
|
# @nocobase/plugin-action-duplicate
|
2
packages/plugins/@nocobase/plugin-action-duplicate/client.d.ts
vendored
Normal file
2
packages/plugins/@nocobase/plugin-action-duplicate/client.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './dist/client';
|
||||||
|
export { default } from './dist/client';
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./dist/client/index.js');
|
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "@nocobase/plugin-action-duplicate",
|
||||||
|
"version": "0.17.0-alpha.7",
|
||||||
|
"main": "dist/server/index.js",
|
||||||
|
"displayName": "Data duplicate",
|
||||||
|
"displayName.zh-CN": "数据复制",
|
||||||
|
"description": "Provides deplicate action",
|
||||||
|
"description.zh-CN": "提供复制数据操作",
|
||||||
|
"license": "AGPL-3.0",
|
||||||
|
"dependencies": {},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nocobase/client": "0.x",
|
||||||
|
"@nocobase/server": "0.x",
|
||||||
|
"@nocobase/test": "0.x"
|
||||||
|
}
|
||||||
|
}
|
2
packages/plugins/@nocobase/plugin-action-duplicate/server.d.ts
vendored
Normal file
2
packages/plugins/@nocobase/plugin-action-duplicate/server.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './dist/server';
|
||||||
|
export { default } from './dist/server';
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user