mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-01 18:52:20 +08:00
Merge branch '2.0-i18n' into 2.0
This commit is contained in:
commit
4b79a4ad25
@ -28,7 +28,7 @@ SubModel1.registerFlow({
|
||||
},
|
||||
},
|
||||
handler(ctx, params) {
|
||||
ctx.model.setProps('children', params.title);
|
||||
ctx.model.setProps('children', ctx.globals.flowEngine.translate(params.title));
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -18,7 +18,7 @@ HelloFlowModel.registerFlow('defaultFlow', {
|
||||
uiSchema: {
|
||||
name: {
|
||||
type: 'string',
|
||||
title: 'Name',
|
||||
title: "{{t('Name')}}",
|
||||
'x-component': Input,
|
||||
},
|
||||
},
|
||||
|
@ -1,3 +1,13 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { useGlobalVariable } from '../../application/hooks/useGlobalVariable';
|
||||
import { BlocksSelector } from '../../schema-component/antd/action/Action.Designer';
|
||||
import { useAfterSuccessOptions } from '../../schema-component/antd/action/hooks/useGetAfterSuccessVariablesOptions';
|
||||
@ -16,30 +26,30 @@ const useVariableProps = () => {
|
||||
};
|
||||
|
||||
export const afterSuccessAction = {
|
||||
title: '提交成功后',
|
||||
title: tval('After successful submission'),
|
||||
uiSchema: {
|
||||
successMessage: {
|
||||
title: 'Popup message',
|
||||
title: tval('Popup message'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.TextArea',
|
||||
'x-component-props': {},
|
||||
},
|
||||
manualClose: {
|
||||
title: 'Message popup close method',
|
||||
title: tval('Message popup close method'),
|
||||
enum: [
|
||||
{ label: 'Automatic close', value: false },
|
||||
{ label: 'Manually close', value: true },
|
||||
{ label: tval('Automatic close'), value: false },
|
||||
{ label: tval('Manually close'), value: true },
|
||||
],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
'x-component-props': {},
|
||||
},
|
||||
redirecting: {
|
||||
title: 'Then',
|
||||
title: tval('Then'),
|
||||
'x-hidden': true,
|
||||
enum: [
|
||||
{ label: 'Stay on current page', value: false },
|
||||
{ label: 'Redirect to', value: true },
|
||||
{ label: tval('Stay on current page'), value: false },
|
||||
{ label: tval('Redirect to'), value: true },
|
||||
],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
@ -54,11 +64,11 @@ export const afterSuccessAction = {
|
||||
},
|
||||
},
|
||||
actionAfterSuccess: {
|
||||
title: 'Action after successful submission',
|
||||
title: tval('Action after successful submission'),
|
||||
enum: [
|
||||
{ label: 'Stay on the current popup or page', value: 'stay' },
|
||||
{ label: 'Return to the previous popup or page', value: 'previous' },
|
||||
{ label: 'Redirect to', value: 'redirect' },
|
||||
{ label: tval('Stay on the current popup or page'), value: 'stay' },
|
||||
{ label: tval('Return to the previous popup or page'), value: 'previous' },
|
||||
{ label: tval('Redirect to'), value: 'redirect' },
|
||||
],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
@ -73,7 +83,7 @@ export const afterSuccessAction = {
|
||||
},
|
||||
},
|
||||
redirectTo: {
|
||||
title: 'Link',
|
||||
title: tval('Link'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Variable.TextArea',
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
@ -81,11 +91,11 @@ export const afterSuccessAction = {
|
||||
},
|
||||
blocksToRefresh: {
|
||||
type: 'array',
|
||||
title: 'Refresh data blocks',
|
||||
title: tval('Refresh data blocks'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-use-decorator-props': () => {
|
||||
return {
|
||||
tooltip: 'After successful submission, the selected data blocks will be automatically refreshed.',
|
||||
tooltip: tval('After successful submission, the selected data blocks will be automatically refreshed.'),
|
||||
};
|
||||
},
|
||||
'x-component': BlocksSelector,
|
||||
|
@ -8,42 +8,43 @@
|
||||
*/
|
||||
|
||||
import { defineAction } from '@nocobase/flow-engine';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
|
||||
export const confirm = defineAction({
|
||||
name: 'confirm',
|
||||
title: '二次确认',
|
||||
title: tval('Secondary confirmation'),
|
||||
uiSchema: {
|
||||
enable: {
|
||||
type: 'boolean',
|
||||
title: 'Enable secondary confirmation',
|
||||
title: tval('Enable secondary confirmation'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
title: 'Title',
|
||||
default: 'Delete record',
|
||||
title: tval('Title'),
|
||||
default: tval('Delete record'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.TextArea',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
title: 'Content',
|
||||
default: 'Are you sure you want to delete it?',
|
||||
title: tval('Content'),
|
||||
default: tval('Are you sure you want to delete it?'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.TextArea',
|
||||
},
|
||||
},
|
||||
defaultParams: {
|
||||
enable: true,
|
||||
title: 'Delete record',
|
||||
content: 'Are you sure you want to delete it?',
|
||||
title: tval('Delete record'),
|
||||
content: tval('Are you sure you want to delete it?'),
|
||||
},
|
||||
async handler(ctx, params) {
|
||||
if (params.enable) {
|
||||
const confirmed = await ctx.globals.modal.confirm({
|
||||
title: params.title,
|
||||
content: params.content,
|
||||
title: ctx.model.translate(params.title),
|
||||
content: ctx.model.translate(params.content),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
|
@ -9,11 +9,12 @@
|
||||
|
||||
import { defineAction, MultiRecordResource, useStepSettingContext } from '@nocobase/flow-engine';
|
||||
import React from 'react';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { FilterGroup } from '../components/FilterGroup';
|
||||
|
||||
export const dataScope = defineAction({
|
||||
name: 'dataScope',
|
||||
title: '数据范围',
|
||||
title: tval('Data scope'),
|
||||
uiSchema: {
|
||||
filter: {
|
||||
type: 'object',
|
||||
|
@ -8,22 +8,23 @@
|
||||
*/
|
||||
|
||||
import { css } from '@emotion/css';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { Variable } from '../../schema-component/antd/variable/Variable';
|
||||
|
||||
export const openLinkAction = {
|
||||
title: '编辑链接',
|
||||
title: tval('Edit link'),
|
||||
uiSchema: {
|
||||
url: {
|
||||
title: 'URL',
|
||||
title: tval('URL'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': Variable.TextArea,
|
||||
description: 'Do not concatenate search params in the URL',
|
||||
description: tval('Do not concatenate search params in the URL'),
|
||||
},
|
||||
params: {
|
||||
type: 'array',
|
||||
'x-component': 'ArrayItems',
|
||||
'x-decorator': 'FormItem',
|
||||
title: `Search parameters`,
|
||||
title: tval('Search parameters'),
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@ -48,7 +49,7 @@ export const openLinkAction = {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: `{{t("Name")}}`,
|
||||
placeholder: tval('Name'),
|
||||
},
|
||||
},
|
||||
value: {
|
||||
@ -56,7 +57,7 @@ export const openLinkAction = {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': Variable.TextArea,
|
||||
'x-component-props': {
|
||||
placeholder: `{{t("Value")}}`,
|
||||
placeholder: tval('Value'),
|
||||
useTypedConstant: true,
|
||||
changeOnSelect: true,
|
||||
},
|
||||
@ -73,14 +74,14 @@ export const openLinkAction = {
|
||||
properties: {
|
||||
add: {
|
||||
type: 'void',
|
||||
title: 'Add parameter',
|
||||
title: tval('Add parameter'),
|
||||
'x-component': 'ArrayItems.Addition',
|
||||
},
|
||||
},
|
||||
},
|
||||
openInNewWindow: {
|
||||
type: 'boolean',
|
||||
'x-content': 'Open in new window',
|
||||
'x-content': tval('Open in new window'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
},
|
||||
|
@ -1,26 +1,36 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { FlowPage } from '../FlowPage';
|
||||
|
||||
export const openModeAction = {
|
||||
title: '打开方式',
|
||||
title: tval('Open mode'),
|
||||
uiSchema: {
|
||||
mode: {
|
||||
type: 'string',
|
||||
title: '打开方式',
|
||||
title: tval('Open mode'),
|
||||
enum: [
|
||||
{ label: 'Drawer', value: 'drawer' },
|
||||
{ label: 'Modal', value: 'modal' },
|
||||
{ label: tval('Drawer'), value: 'drawer' },
|
||||
{ label: tval('Modal'), value: 'modal' },
|
||||
],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
},
|
||||
size: {
|
||||
type: 'string',
|
||||
title: '弹窗尺寸',
|
||||
title: tval('Popup size'),
|
||||
enum: [
|
||||
{ label: '小', value: 'small' },
|
||||
{ label: '中', value: 'medium' },
|
||||
{ label: '大', value: 'large' },
|
||||
{ label: tval('Small'), value: 'small' },
|
||||
{ label: tval('Medium'), value: 'medium' },
|
||||
{ label: tval('Large'), value: 'large' },
|
||||
],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
@ -57,7 +67,7 @@ export const openModeAction = {
|
||||
};
|
||||
|
||||
currentDrawer = ctx.globals[params.mode].open({
|
||||
title: '命令式 Drawer',
|
||||
title: tval('Imperative Drawer'),
|
||||
width: sizeToWidthMap[params.size],
|
||||
content: <DrawerContent />,
|
||||
});
|
||||
|
@ -8,31 +8,32 @@
|
||||
*/
|
||||
|
||||
import { defineAction } from '@nocobase/flow-engine';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import React from 'react';
|
||||
import { FlowPage } from '../FlowPage';
|
||||
|
||||
export const openView = defineAction({
|
||||
name: 'openView',
|
||||
title: '打开方式配置',
|
||||
title: tval('Open mode configuration'),
|
||||
uiSchema: {
|
||||
mode: {
|
||||
type: 'string',
|
||||
title: '打开方式',
|
||||
title: tval('Open mode'),
|
||||
enum: [
|
||||
{ label: 'Drawer', value: 'drawer' },
|
||||
{ label: 'Dialog', value: 'dialog' },
|
||||
{ label: 'Page', value: 'page' },
|
||||
{ label: tval('Drawer'), value: 'drawer' },
|
||||
{ label: tval('Dialog'), value: 'dialog' },
|
||||
{ label: tval('Page'), value: 'page' },
|
||||
],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
},
|
||||
size: {
|
||||
type: 'string',
|
||||
title: '弹窗尺寸',
|
||||
title: tval('Popup size'),
|
||||
enum: [
|
||||
{ label: '小', value: 'small' },
|
||||
{ label: '中', value: 'medium' },
|
||||
{ label: '大', value: 'large' },
|
||||
{ label: tval('Small'), value: 'small' },
|
||||
{ label: tval('Medium'), value: 'medium' },
|
||||
{ label: tval('Large'), value: 'large' },
|
||||
],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
|
@ -1,9 +1,20 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
|
||||
export const refreshOnCompleteAction = {
|
||||
title: '执行后刷新数据',
|
||||
title: tval('Refresh data after execution'),
|
||||
uiSchema: {
|
||||
enable: {
|
||||
type: 'boolean',
|
||||
title: 'Enable refresh',
|
||||
title: tval('Enable refresh'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
},
|
||||
@ -16,7 +27,7 @@ export const refreshOnCompleteAction = {
|
||||
async handler(ctx, params) {
|
||||
if (params.enable) {
|
||||
await ctx.extra.currentResource.refresh();
|
||||
ctx.globals.message.success('Data refreshed successfully.');
|
||||
ctx.globals.message.success(ctx.model.translate('Data refreshed successfully'));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -1,23 +1,34 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
|
||||
export const secondaryConfirmationAction = {
|
||||
title: '二次确认',
|
||||
title: tval('Secondary confirmation'),
|
||||
uiSchema: {
|
||||
enable: {
|
||||
type: 'boolean',
|
||||
title: 'Enable secondary confirmation',
|
||||
title: tval('Enable secondary confirmation'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
title: 'Title',
|
||||
default: 'Delete record',
|
||||
title: tval('Title'),
|
||||
default: tval('Delete record'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.TextArea',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
title: 'Content',
|
||||
default: 'Are you sure you want to delete it?',
|
||||
title: tval('Content'),
|
||||
default: tval('Are you sure you want to delete it?'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.TextArea',
|
||||
},
|
||||
@ -25,14 +36,14 @@ export const secondaryConfirmationAction = {
|
||||
defaultParams(ctx) {
|
||||
return {
|
||||
enable: true,
|
||||
title: 'Delete record',
|
||||
content: 'Are you sure you want to delete it?',
|
||||
title: tval('Delete record'),
|
||||
content: tval('Are you sure you want to delete it?'),
|
||||
};
|
||||
},
|
||||
async handler(ctx, params) {
|
||||
if (params.enable) {
|
||||
const confirmed = await ctx.globals.modal.confirm({
|
||||
title: params.title,
|
||||
title: ctx.globals.flowEngine.translate(params.title),
|
||||
content: params.content,
|
||||
});
|
||||
|
||||
|
@ -9,11 +9,11 @@
|
||||
|
||||
import { css } from '@emotion/css';
|
||||
import { defineAction } from '@nocobase/flow-engine';
|
||||
import { getPickerFormat } from '@nocobase/utils/client';
|
||||
import { getPickerFormat, tval } from '@nocobase/utils/client';
|
||||
import { ExpiresRadio, DateFormatCom } from '../components';
|
||||
|
||||
export const dateTimeFormat = defineAction({
|
||||
title: 'Date display format',
|
||||
title: tval('Date display format'),
|
||||
name: 'dateDisplayFormat',
|
||||
uiSchema: {
|
||||
picker: {
|
||||
@ -80,7 +80,7 @@ export const dateTimeFormat = defineAction({
|
||||
value: 'DD/MM/YYYY',
|
||||
},
|
||||
{
|
||||
label: 'custom',
|
||||
label: tval('Custom'),
|
||||
value: 'custom',
|
||||
},
|
||||
],
|
||||
@ -146,7 +146,7 @@ export const dateTimeFormat = defineAction({
|
||||
value: 'HH:mm:ss',
|
||||
},
|
||||
{
|
||||
label: 'custom',
|
||||
label: tval('Custom'),
|
||||
value: 'custom',
|
||||
},
|
||||
],
|
||||
|
@ -10,6 +10,7 @@
|
||||
import { defineAction, useStepSettingContext } from '@nocobase/flow-engine';
|
||||
import { Select } from 'antd';
|
||||
import React from 'react';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { useCompile } from '../../schema-component';
|
||||
import { getUniqueKeyFromCollection } from '../../collection-manager/interfaces/utils';
|
||||
import { isTitleField } from '../../data-source';
|
||||
@ -37,7 +38,7 @@ const SelectOptions = (props) => {
|
||||
|
||||
export const titleField = defineAction({
|
||||
name: 'titleField',
|
||||
title: 'Title field',
|
||||
title: tval('Title field'),
|
||||
uiSchema: {
|
||||
label: {
|
||||
'x-component': SelectOptions,
|
||||
|
@ -8,23 +8,24 @@
|
||||
*/
|
||||
|
||||
import { ButtonProps } from 'antd';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { GlobalActionModel } from '../base/ActionModel';
|
||||
|
||||
export class AddNewActionModel extends GlobalActionModel {
|
||||
defaultProps: ButtonProps = {
|
||||
type: 'primary',
|
||||
title: 'Add new',
|
||||
title: tval('Add new'),
|
||||
icon: 'PlusOutlined',
|
||||
};
|
||||
}
|
||||
|
||||
AddNewActionModel.define({
|
||||
title: 'Add new',
|
||||
title: tval('Add new'),
|
||||
});
|
||||
|
||||
AddNewActionModel.registerFlow({
|
||||
sort: 200,
|
||||
title: '点击事件',
|
||||
title: tval('Click event'),
|
||||
key: 'handleClick',
|
||||
on: {
|
||||
eventName: 'click',
|
||||
|
@ -9,24 +9,23 @@
|
||||
|
||||
import { MultiRecordResource } from '@nocobase/flow-engine';
|
||||
import { ButtonProps } from 'antd';
|
||||
import { refreshOnCompleteAction } from '../../actions/refreshOnCompleteAction';
|
||||
import { secondaryConfirmationAction } from '../../actions/secondaryConfirmationAction';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { GlobalActionModel } from '../base/ActionModel';
|
||||
|
||||
export class BulkDeleteActionModel extends GlobalActionModel {
|
||||
defaultProps: ButtonProps = {
|
||||
title: 'Delete',
|
||||
title: tval('Delete'),
|
||||
icon: 'DeleteOutlined',
|
||||
};
|
||||
}
|
||||
|
||||
BulkDeleteActionModel.define({
|
||||
title: 'Delete',
|
||||
title: tval('Delete'),
|
||||
});
|
||||
|
||||
BulkDeleteActionModel.registerFlow({
|
||||
key: 'handleClick',
|
||||
title: '点击事件',
|
||||
title: tval('Click event'),
|
||||
on: {
|
||||
eventName: 'click',
|
||||
},
|
||||
@ -36,17 +35,18 @@ BulkDeleteActionModel.registerFlow({
|
||||
},
|
||||
delete: {
|
||||
async handler(ctx, params) {
|
||||
const t = ctx.model.translate;
|
||||
if (!ctx.shared?.currentBlockModel?.resource) {
|
||||
ctx.globals.message.error('No resource selected for deletion.');
|
||||
ctx.globals.message.error(t('No resource selected for deletion'));
|
||||
return;
|
||||
}
|
||||
const resource = ctx.shared.currentBlockModel.resource as MultiRecordResource;
|
||||
if (resource.getSelectedRows().length === 0) {
|
||||
ctx.globals.message.warning('No records selected for deletion.');
|
||||
ctx.globals.message.warning(t('No records selected for deletion'));
|
||||
return;
|
||||
}
|
||||
await resource.destroySelectedRows();
|
||||
ctx.globals.message.success('Selected records deleted successfully.');
|
||||
ctx.globals.message.success(t('Selected records deleted successfully'));
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -9,38 +9,39 @@
|
||||
|
||||
import { MultiRecordResource } from '@nocobase/flow-engine';
|
||||
import { ButtonProps } from 'antd';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { openModeAction } from '../../actions/openModeAction';
|
||||
import { GlobalActionModel } from '../base/ActionModel';
|
||||
|
||||
export class BulkEditActionModel extends GlobalActionModel {
|
||||
defaultProps: ButtonProps = {
|
||||
title: 'Bulk edit',
|
||||
title: tval('Bulk edit'),
|
||||
icon: 'EditOutlined',
|
||||
};
|
||||
}
|
||||
|
||||
BulkEditActionModel.define({
|
||||
title: 'Bulk edit',
|
||||
title: tval('Bulk edit'),
|
||||
hide: true,
|
||||
});
|
||||
|
||||
BulkEditActionModel.registerFlow({
|
||||
key: 'handleClick',
|
||||
title: '点击事件',
|
||||
title: tval('Click event'),
|
||||
on: {
|
||||
eventName: 'click',
|
||||
},
|
||||
steps: {
|
||||
openModeAction,
|
||||
bulkEdit: {
|
||||
title: '更新的数据',
|
||||
title: tval('Data will be updated'),
|
||||
uiSchema: {
|
||||
updateMode: {
|
||||
'x-component': 'Radio.Group',
|
||||
'x-component-props': {
|
||||
options: [
|
||||
{ label: '更新选中行', value: 'selected' },
|
||||
{ label: '更新所有行', value: 'all' },
|
||||
{ label: tval('Update selected data?'), value: 'selected' },
|
||||
{ label: tval('Update all data?'), value: 'all' },
|
||||
],
|
||||
},
|
||||
},
|
||||
@ -51,17 +52,18 @@ BulkEditActionModel.registerFlow({
|
||||
};
|
||||
},
|
||||
async handler(ctx, params) {
|
||||
const t = ctx.model.translate;
|
||||
if (!ctx.shared?.currentBlockModel?.resource) {
|
||||
ctx.globals.message.error('No resource selected for bulk edit.');
|
||||
ctx.globals.message.error(t('No resource selected for bulk edit'));
|
||||
return;
|
||||
}
|
||||
const resource = ctx.shared.currentBlockModel.resource as MultiRecordResource;
|
||||
if (resource.getSelectedRows().length === 0) {
|
||||
ctx.globals.message.warning('No records selected for bulk edit.');
|
||||
ctx.globals.message.warning(t('No records selected for bulk edit'));
|
||||
return;
|
||||
}
|
||||
await resource.destroySelectedRows();
|
||||
ctx.globals.message.success('Successfully.');
|
||||
//TODO: await resource.updateSelectedRows(params);
|
||||
ctx.globals.message.success(t('updateSelectedRows not implemented!'));
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import type { ButtonProps } from 'antd/es/button';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { useGlobalVariable } from '../../../application/hooks/useGlobalVariable';
|
||||
import { BlocksSelector } from '../../../schema-component/antd/action/Action.Designer';
|
||||
import { useAfterSuccessOptions } from '../../../schema-component/antd/action/hooks/useGetAfterSuccessVariablesOptions';
|
||||
@ -17,12 +18,12 @@ import { GlobalActionModel } from '../base/ActionModel';
|
||||
|
||||
export class CustomRequestGlobalActionModel extends GlobalActionModel {
|
||||
defaultProps: ButtonProps = {
|
||||
title: 'Custom request',
|
||||
title: tval('Custom request'),
|
||||
};
|
||||
}
|
||||
|
||||
CustomRequestGlobalActionModel.define({
|
||||
title: 'Custom request',
|
||||
title: tval('Custom request'),
|
||||
hide: true,
|
||||
});
|
||||
|
||||
@ -41,22 +42,23 @@ const useVariableProps = () => {
|
||||
|
||||
CustomRequestGlobalActionModel.registerFlow({
|
||||
key: 'handleClick',
|
||||
title: '点击事件',
|
||||
title: tval('Click event'),
|
||||
on: {
|
||||
eventName: 'click',
|
||||
},
|
||||
steps: {
|
||||
secondaryConfirmation: secondaryConfirmationAction,
|
||||
request: {
|
||||
title: '请求设置',
|
||||
title: tval('Request settings'),
|
||||
uiSchema: {
|
||||
method: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: 'HTTP method',
|
||||
title: tval('HTTP method'),
|
||||
'x-decorator-props': {
|
||||
tooltip:
|
||||
tooltip: tval(
|
||||
'When the HTTP method is Post, Put or Patch, and this custom request inside the form, the request body will be automatically filled in with the form data',
|
||||
),
|
||||
},
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
@ -77,20 +79,20 @@ CustomRequestGlobalActionModel.registerFlow({
|
||||
url: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: 'URL',
|
||||
title: tval('URL'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Variable.TextArea',
|
||||
'x-use-component-props': useVariableProps,
|
||||
'x-component-props': {
|
||||
placeholder: 'https://www.nocobase.com',
|
||||
placeholder: tval('https://www.nocobase.com'),
|
||||
},
|
||||
},
|
||||
headers: {
|
||||
type: 'array',
|
||||
'x-component': 'ArrayItems',
|
||||
'x-decorator': 'FormItem',
|
||||
title: 'Headers',
|
||||
description: '"Content-Type" only support "application/json", and no need to specify',
|
||||
title: tval('Headers'),
|
||||
description: tval('"Content-Type" only support "application/json", and no need to specify'),
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@ -103,7 +105,7 @@ CustomRequestGlobalActionModel.registerFlow({
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'Name',
|
||||
placeholder: tval('Name'),
|
||||
},
|
||||
},
|
||||
value: {
|
||||
@ -124,7 +126,7 @@ CustomRequestGlobalActionModel.registerFlow({
|
||||
properties: {
|
||||
add: {
|
||||
type: 'void',
|
||||
title: 'Add request header',
|
||||
title: tval('Add request header'),
|
||||
'x-component': 'ArrayItems.Addition',
|
||||
},
|
||||
},
|
||||
@ -133,7 +135,7 @@ CustomRequestGlobalActionModel.registerFlow({
|
||||
type: 'array',
|
||||
'x-component': 'ArrayItems',
|
||||
'x-decorator': 'FormItem',
|
||||
title: 'Parameters',
|
||||
title: tval('Parameters'),
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@ -146,7 +148,7 @@ CustomRequestGlobalActionModel.registerFlow({
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'Name',
|
||||
placeholder: tval('Name'),
|
||||
},
|
||||
},
|
||||
value: {
|
||||
@ -167,14 +169,14 @@ CustomRequestGlobalActionModel.registerFlow({
|
||||
properties: {
|
||||
add: {
|
||||
type: 'void',
|
||||
title: 'Add parameter',
|
||||
title: tval('Add parameter'),
|
||||
'x-component': 'ArrayItems.Addition',
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
type: 'string',
|
||||
title: 'Body',
|
||||
title: tval('Body'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {},
|
||||
'x-component': 'Variable.JSON',
|
||||
@ -188,13 +190,13 @@ CustomRequestGlobalActionModel.registerFlow({
|
||||
autoSize: {
|
||||
minRows: 10,
|
||||
},
|
||||
placeholder: 'Input request data',
|
||||
placeholder: tval('Input request data'),
|
||||
},
|
||||
description: 'Only support standard JSON data',
|
||||
description: tval('Only support standard JSON data'),
|
||||
},
|
||||
timeout: {
|
||||
type: 'number',
|
||||
title: 'Timeout config',
|
||||
title: tval('Timeout config'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {},
|
||||
'x-component': 'InputNumber',
|
||||
@ -207,7 +209,7 @@ CustomRequestGlobalActionModel.registerFlow({
|
||||
},
|
||||
responseType: {
|
||||
type: 'string',
|
||||
title: 'Response type',
|
||||
title: tval('Response type'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {},
|
||||
'x-component': 'Select',
|
||||
@ -220,35 +222,35 @@ CustomRequestGlobalActionModel.registerFlow({
|
||||
},
|
||||
async handler(ctx, params) {
|
||||
ctx.globals.modal({
|
||||
title: 'TODO: Custom request action handler',
|
||||
title: tval('TODO: Custom request action handler'),
|
||||
});
|
||||
},
|
||||
},
|
||||
afterSuccess: {
|
||||
title: '提交成功后',
|
||||
title: tval('After successful submission'),
|
||||
uiSchema: {
|
||||
successMessage: {
|
||||
title: 'Popup message',
|
||||
title: tval('Popup message'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.TextArea',
|
||||
'x-component-props': {},
|
||||
},
|
||||
manualClose: {
|
||||
title: 'Message popup close method',
|
||||
title: tval('Message popup close method'),
|
||||
enum: [
|
||||
{ label: 'Automatic close', value: false },
|
||||
{ label: 'Manually close', value: true },
|
||||
{ label: tval('Automatic close'), value: false },
|
||||
{ label: tval('Manually close'), value: true },
|
||||
],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
'x-component-props': {},
|
||||
},
|
||||
redirecting: {
|
||||
title: 'Then',
|
||||
title: tval('Then'),
|
||||
'x-hidden': true,
|
||||
enum: [
|
||||
{ label: 'Stay on current page', value: false },
|
||||
{ label: 'Redirect to', value: true },
|
||||
{ label: tval('Stay on current page'), value: false },
|
||||
{ label: tval('Redirect to'), value: true },
|
||||
],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
@ -263,11 +265,11 @@ CustomRequestGlobalActionModel.registerFlow({
|
||||
},
|
||||
},
|
||||
actionAfterSuccess: {
|
||||
title: 'Action after successful submission',
|
||||
title: tval('Action after successful submission'),
|
||||
enum: [
|
||||
{ label: 'Stay on the current popup or page', value: 'stay' },
|
||||
{ label: 'Return to the previous popup or page', value: 'previous' },
|
||||
{ label: 'Redirect to', value: 'redirect' },
|
||||
{ label: tval('Stay on the current popup or page'), value: 'stay' },
|
||||
{ label: tval('Return to the previous popup or page'), value: 'previous' },
|
||||
{ label: tval('Redirect to'), value: 'redirect' },
|
||||
],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
@ -282,7 +284,7 @@ CustomRequestGlobalActionModel.registerFlow({
|
||||
},
|
||||
},
|
||||
redirectTo: {
|
||||
title: 'Link',
|
||||
title: tval('Link'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Variable.TextArea',
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
@ -290,11 +292,11 @@ CustomRequestGlobalActionModel.registerFlow({
|
||||
},
|
||||
blocksToRefresh: {
|
||||
type: 'array',
|
||||
title: 'Refresh data blocks',
|
||||
title: tval('Refresh data blocks'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-use-decorator-props': () => {
|
||||
return {
|
||||
tooltip: 'After successful submission, the selected data blocks will be automatically refreshed.',
|
||||
tooltip: tval('After successful submission, the selected data blocks will be automatically refreshed.'),
|
||||
};
|
||||
},
|
||||
'x-component': BlocksSelector,
|
||||
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import type { ButtonProps } from 'antd/es/button';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { useGlobalVariable } from '../../../application/hooks/useGlobalVariable';
|
||||
import { BlocksSelector } from '../../../schema-component/antd/action/Action.Designer';
|
||||
import { useAfterSuccessOptions } from '../../../schema-component/antd/action/hooks/useGetAfterSuccessVariablesOptions';
|
||||
@ -18,12 +19,12 @@ import { RecordActionModel } from '../base/ActionModel';
|
||||
export class CustomRequestRecordActionModel extends RecordActionModel {
|
||||
defaultProps: ButtonProps = {
|
||||
type: 'link',
|
||||
title: 'Custom request',
|
||||
title: tval('Custom request'),
|
||||
};
|
||||
}
|
||||
|
||||
CustomRequestRecordActionModel.define({
|
||||
title: 'Custom request',
|
||||
title: tval('Custom request'),
|
||||
hide: true,
|
||||
});
|
||||
|
||||
@ -42,22 +43,23 @@ const useVariableProps = () => {
|
||||
|
||||
CustomRequestRecordActionModel.registerFlow({
|
||||
key: 'handleClick',
|
||||
title: '点击事件',
|
||||
title: tval('Click event'),
|
||||
on: {
|
||||
eventName: 'click',
|
||||
},
|
||||
steps: {
|
||||
secondaryConfirmation: secondaryConfirmationAction,
|
||||
request: {
|
||||
title: '请求设置',
|
||||
title: tval('Request settings'),
|
||||
uiSchema: {
|
||||
method: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: 'HTTP method',
|
||||
title: tval('HTTP method'),
|
||||
'x-decorator-props': {
|
||||
tooltip:
|
||||
tooltip: tval(
|
||||
'When the HTTP method is Post, Put or Patch, and this custom request inside the form, the request body will be automatically filled in with the form data',
|
||||
),
|
||||
},
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
@ -78,20 +80,20 @@ CustomRequestRecordActionModel.registerFlow({
|
||||
url: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: 'URL',
|
||||
title: tval('URL'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Variable.TextArea',
|
||||
'x-use-component-props': useVariableProps,
|
||||
'x-component-props': {
|
||||
placeholder: 'https://www.nocobase.com',
|
||||
placeholder: tval('https://www.nocobase.com'),
|
||||
},
|
||||
},
|
||||
headers: {
|
||||
type: 'array',
|
||||
'x-component': 'ArrayItems',
|
||||
'x-decorator': 'FormItem',
|
||||
title: 'Headers',
|
||||
description: '"Content-Type" only support "application/json", and no need to specify',
|
||||
title: tval('Headers'),
|
||||
description: tval('"Content-Type" only support "application/json", and no need to specify'),
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@ -104,7 +106,7 @@ CustomRequestRecordActionModel.registerFlow({
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'Name',
|
||||
placeholder: tval('Name'),
|
||||
},
|
||||
},
|
||||
value: {
|
||||
@ -125,7 +127,7 @@ CustomRequestRecordActionModel.registerFlow({
|
||||
properties: {
|
||||
add: {
|
||||
type: 'void',
|
||||
title: 'Add request header',
|
||||
title: tval('Add request header'),
|
||||
'x-component': 'ArrayItems.Addition',
|
||||
},
|
||||
},
|
||||
@ -134,7 +136,7 @@ CustomRequestRecordActionModel.registerFlow({
|
||||
type: 'array',
|
||||
'x-component': 'ArrayItems',
|
||||
'x-decorator': 'FormItem',
|
||||
title: 'Parameters',
|
||||
title: tval('Parameters'),
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@ -147,7 +149,7 @@ CustomRequestRecordActionModel.registerFlow({
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'Name',
|
||||
placeholder: tval('Name'),
|
||||
},
|
||||
},
|
||||
value: {
|
||||
@ -168,14 +170,14 @@ CustomRequestRecordActionModel.registerFlow({
|
||||
properties: {
|
||||
add: {
|
||||
type: 'void',
|
||||
title: 'Add parameter',
|
||||
title: tval('Add parameter'),
|
||||
'x-component': 'ArrayItems.Addition',
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
type: 'string',
|
||||
title: 'Body',
|
||||
title: tval('Body'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {},
|
||||
'x-component': 'Variable.JSON',
|
||||
@ -183,19 +185,19 @@ CustomRequestRecordActionModel.registerFlow({
|
||||
scope: '{{useCustomRequestVariableOptions}}',
|
||||
fieldNames: {
|
||||
value: 'name',
|
||||
label: 'title',
|
||||
label: tval('title'),
|
||||
},
|
||||
changeOnSelect: true,
|
||||
autoSize: {
|
||||
minRows: 10,
|
||||
},
|
||||
placeholder: 'Input request data',
|
||||
placeholder: tval('Input request data'),
|
||||
},
|
||||
description: 'Only support standard JSON data',
|
||||
description: tval('Only support standard JSON data'),
|
||||
},
|
||||
timeout: {
|
||||
type: 'number',
|
||||
title: 'Timeout config',
|
||||
title: tval('Timeout config'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {},
|
||||
'x-component': 'InputNumber',
|
||||
@ -208,7 +210,7 @@ CustomRequestRecordActionModel.registerFlow({
|
||||
},
|
||||
responseType: {
|
||||
type: 'string',
|
||||
title: 'Response type',
|
||||
title: tval('Response type'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {},
|
||||
'x-component': 'Select',
|
||||
@ -221,35 +223,35 @@ CustomRequestRecordActionModel.registerFlow({
|
||||
},
|
||||
async handler(ctx, params) {
|
||||
ctx.globals.modal({
|
||||
title: 'TODO: Custom request action handler',
|
||||
title: tval('TODO: Custom request action handler'),
|
||||
});
|
||||
},
|
||||
},
|
||||
afterSuccess: {
|
||||
title: '提交成功后',
|
||||
title: tval('After successful submission'),
|
||||
uiSchema: {
|
||||
successMessage: {
|
||||
title: 'Popup message',
|
||||
title: tval('Popup message'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.TextArea',
|
||||
'x-component-props': {},
|
||||
},
|
||||
manualClose: {
|
||||
title: 'Message popup close method',
|
||||
title: tval('Message popup close method'),
|
||||
enum: [
|
||||
{ label: 'Automatic close', value: false },
|
||||
{ label: 'Manually close', value: true },
|
||||
{ label: tval('Automatic close'), value: false },
|
||||
{ label: tval('Manually close'), value: true },
|
||||
],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
'x-component-props': {},
|
||||
},
|
||||
redirecting: {
|
||||
title: 'Then',
|
||||
title: tval('Then'),
|
||||
'x-hidden': true,
|
||||
enum: [
|
||||
{ label: 'Stay on current page', value: false },
|
||||
{ label: 'Redirect to', value: true },
|
||||
{ label: tval('Stay on current page'), value: false },
|
||||
{ label: tval('Redirect to'), value: true },
|
||||
],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
@ -264,11 +266,11 @@ CustomRequestRecordActionModel.registerFlow({
|
||||
},
|
||||
},
|
||||
actionAfterSuccess: {
|
||||
title: 'Action after successful submission',
|
||||
title: tval('Action after successful submission'),
|
||||
enum: [
|
||||
{ label: 'Stay on the current popup or page', value: 'stay' },
|
||||
{ label: 'Return to the previous popup or page', value: 'previous' },
|
||||
{ label: 'Redirect to', value: 'redirect' },
|
||||
{ label: tval('Stay on the current popup or page'), value: 'stay' },
|
||||
{ label: tval('Return to the previous popup or page'), value: 'previous' },
|
||||
{ label: tval('Redirect to'), value: 'redirect' },
|
||||
],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
@ -283,7 +285,7 @@ CustomRequestRecordActionModel.registerFlow({
|
||||
},
|
||||
},
|
||||
redirectTo: {
|
||||
title: 'Link',
|
||||
title: tval('Link'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Variable.TextArea',
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
@ -291,11 +293,11 @@ CustomRequestRecordActionModel.registerFlow({
|
||||
},
|
||||
blocksToRefresh: {
|
||||
type: 'array',
|
||||
title: 'Refresh data blocks',
|
||||
title: tval('Refresh data blocks'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-use-decorator-props': () => {
|
||||
return {
|
||||
tooltip: 'After successful submission, the selected data blocks will be automatically refreshed.',
|
||||
tooltip: tval('After successful submission, the selected data blocks will be automatically refreshed.'),
|
||||
};
|
||||
},
|
||||
'x-component': BlocksSelector,
|
||||
|
@ -9,22 +9,23 @@
|
||||
|
||||
import { MultiRecordResource } from '@nocobase/flow-engine';
|
||||
import type { ButtonProps } from 'antd/es/button';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { RecordActionModel } from '../base/ActionModel';
|
||||
|
||||
export class DeleteActionModel extends RecordActionModel {
|
||||
defaultProps: ButtonProps = {
|
||||
type: 'link',
|
||||
title: 'Delete',
|
||||
title: tval('Delete'),
|
||||
};
|
||||
}
|
||||
|
||||
DeleteActionModel.define({
|
||||
title: 'Delete',
|
||||
title: tval('Delete'),
|
||||
});
|
||||
|
||||
DeleteActionModel.registerFlow({
|
||||
key: 'handleClick',
|
||||
title: '点击事件',
|
||||
title: tval('Click event'),
|
||||
on: {
|
||||
eventName: 'click',
|
||||
},
|
||||
@ -34,17 +35,18 @@ DeleteActionModel.registerFlow({
|
||||
},
|
||||
delete: {
|
||||
async handler(ctx, params) {
|
||||
const t = ctx.model.translate;
|
||||
if (!ctx.shared?.currentBlockModel?.resource) {
|
||||
ctx.globals.message.error('No resource selected for deletion.');
|
||||
ctx.globals.message.error(t('No resource selected for deletion'));
|
||||
return;
|
||||
}
|
||||
if (!ctx.shared.currentRecord) {
|
||||
ctx.globals.message.error('No resource or record selected for deletion.');
|
||||
ctx.globals.message.error(t('No resource or record selected for deletion'));
|
||||
return;
|
||||
}
|
||||
const resource = ctx.shared.currentBlockModel.resource as MultiRecordResource;
|
||||
await resource.destroy(ctx.shared.currentRecord);
|
||||
ctx.globals.message.success('Record deleted successfully.');
|
||||
ctx.globals.message.success(t('Record deleted successfully'));
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -8,31 +8,32 @@
|
||||
*/
|
||||
|
||||
import type { ButtonProps } from 'antd/es/button';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { openModeAction } from '../../actions/openModeAction';
|
||||
import { RecordActionModel } from '../base/ActionModel';
|
||||
|
||||
export class DuplicateActionModel extends RecordActionModel {
|
||||
defaultProps: ButtonProps = {
|
||||
type: 'link',
|
||||
title: 'Duplicate',
|
||||
title: tval('Duplicate'),
|
||||
};
|
||||
}
|
||||
|
||||
DuplicateActionModel.define({
|
||||
title: 'Duplicate',
|
||||
title: tval('Duplicate'),
|
||||
hide: true,
|
||||
});
|
||||
|
||||
DuplicateActionModel.registerFlow({
|
||||
key: 'handleClick',
|
||||
title: '点击事件',
|
||||
title: tval('Click event'),
|
||||
on: {
|
||||
eventName: 'click',
|
||||
},
|
||||
steps: {
|
||||
open: openModeAction,
|
||||
duplicateMode: {
|
||||
title: '复制方式',
|
||||
title: tval('Duplicate mode'),
|
||||
uiSchema: {
|
||||
// TODO
|
||||
duplicateMode: {
|
||||
@ -40,15 +41,15 @@ DuplicateActionModel.registerFlow({
|
||||
'x-component': 'Select',
|
||||
enum: [
|
||||
{
|
||||
label: '快速复制',
|
||||
label: tval('Quick duplicate'),
|
||||
value: 'quickDuplicate',
|
||||
},
|
||||
{
|
||||
label: '表单复制',
|
||||
label: tval('Form duplicate'),
|
||||
value: 'formDuplicate',
|
||||
},
|
||||
],
|
||||
title: '复制方式',
|
||||
title: tval('Duplicate mode'),
|
||||
},
|
||||
},
|
||||
defaultParams(ctx) {
|
||||
|
@ -8,23 +8,24 @@
|
||||
*/
|
||||
|
||||
import type { ButtonProps } from 'antd/es/button';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { openModeAction } from '../../actions/openModeAction';
|
||||
import { RecordActionModel } from '../base/ActionModel';
|
||||
|
||||
export class EditActionModel extends RecordActionModel {
|
||||
defaultProps: ButtonProps = {
|
||||
type: 'link',
|
||||
title: 'Edit',
|
||||
title: tval('Edit'),
|
||||
};
|
||||
}
|
||||
|
||||
EditActionModel.define({
|
||||
title: 'Edit',
|
||||
title: tval('Edit'),
|
||||
});
|
||||
|
||||
EditActionModel.registerFlow({
|
||||
key: 'handleClick',
|
||||
title: '点击事件',
|
||||
title: tval('Click event'),
|
||||
on: {
|
||||
eventName: 'click',
|
||||
},
|
||||
|
@ -10,6 +10,7 @@
|
||||
import { MultiRecordResource, useFlowModel, useStepSettingContext } from '@nocobase/flow-engine';
|
||||
import { Button, ButtonProps, Popover, Select, Space } from 'antd';
|
||||
import React, { FC } from 'react';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { FilterGroup } from '../../components/FilterGroup';
|
||||
import { GlobalActionModel } from '../base/ActionModel';
|
||||
import { DataBlockModel } from '../base/BlockModel';
|
||||
@ -19,14 +20,15 @@ const FilterContent: FC<{ value: any }> = (props) => {
|
||||
const currentBlockModel = modelInstance.ctx.shared.currentBlockModel as DataBlockModel;
|
||||
const fields = currentBlockModel.collection.getFields();
|
||||
const ignoreFieldsNames = modelInstance.props.ignoreFieldsNames || [];
|
||||
const t = modelInstance.translate;
|
||||
|
||||
return (
|
||||
<>
|
||||
<FilterGroup value={props.value} fields={fields} ignoreFieldsNames={ignoreFieldsNames} ctx={modelInstance.ctx} />
|
||||
<Space style={{ width: '100%', display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Button onClick={() => modelInstance.dispatchEvent('reset')}>Reset</Button>
|
||||
<Button onClick={() => modelInstance.dispatchEvent('reset')}>{t('Reset')}</Button>
|
||||
<Button type="primary" onClick={() => modelInstance.dispatchEvent('submit')}>
|
||||
Submit
|
||||
{t('Submit')}
|
||||
</Button>
|
||||
</Space>
|
||||
</>
|
||||
@ -42,7 +44,7 @@ export class FilterActionModel extends GlobalActionModel {
|
||||
|
||||
defaultProps: any = {
|
||||
type: 'default',
|
||||
children: 'Filter',
|
||||
title: tval('Filter'),
|
||||
icon: 'FilterOutlined',
|
||||
filterValue: { $and: [] },
|
||||
ignoreFieldsNames: [],
|
||||
@ -63,16 +65,16 @@ export class FilterActionModel extends GlobalActionModel {
|
||||
}
|
||||
|
||||
FilterActionModel.define({
|
||||
title: 'Filter',
|
||||
title: tval('Filter'),
|
||||
});
|
||||
|
||||
FilterActionModel.registerFlow({
|
||||
key: 'filterSettings',
|
||||
title: '筛选配置',
|
||||
title: tval('Filter configuration'),
|
||||
auto: true,
|
||||
steps: {
|
||||
ignoreFieldsNames: {
|
||||
title: '可筛选字段',
|
||||
title: tval('Filterable fields'),
|
||||
uiSchema: {
|
||||
ignoreFieldsNames: {
|
||||
type: 'array',
|
||||
@ -90,7 +92,7 @@ FilterActionModel.registerFlow({
|
||||
},
|
||||
'x-component-props': {
|
||||
mode: 'multiple',
|
||||
placeholder: '请选择不可筛选的字段',
|
||||
placeholder: tval('Please select non-filterable fields'),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -104,7 +106,7 @@ FilterActionModel.registerFlow({
|
||||
},
|
||||
},
|
||||
defaultValue: {
|
||||
title: '默认筛选条件',
|
||||
title: tval('Default filter conditions'),
|
||||
uiSchema: {
|
||||
filter: {
|
||||
type: 'object',
|
||||
@ -141,7 +143,7 @@ FilterActionModel.registerFlow({
|
||||
|
||||
FilterActionModel.registerFlow({
|
||||
key: 'handleSubmit',
|
||||
title: '提交',
|
||||
title: tval('Submit'),
|
||||
on: {
|
||||
eventName: 'submit',
|
||||
},
|
||||
@ -162,7 +164,7 @@ FilterActionModel.registerFlow({
|
||||
|
||||
FilterActionModel.registerFlow({
|
||||
key: 'handleReset',
|
||||
title: '重置',
|
||||
title: tval('Reset'),
|
||||
on: {
|
||||
eventName: 'reset',
|
||||
},
|
||||
@ -183,7 +185,7 @@ FilterActionModel.registerFlow({
|
||||
|
||||
FilterActionModel.registerFlow({
|
||||
key: 'handleClick',
|
||||
title: '点击事件',
|
||||
title: tval('Click event'),
|
||||
on: {
|
||||
eventName: 'click',
|
||||
},
|
||||
|
@ -8,22 +8,23 @@
|
||||
*/
|
||||
|
||||
import type { ButtonProps } from 'antd';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { GlobalActionModel } from '../base/ActionModel';
|
||||
import { openLinkAction } from '../../actions/openLinkAction';
|
||||
|
||||
export class LinkGlobalActionModel extends GlobalActionModel {
|
||||
defaultProps: ButtonProps = {
|
||||
title: 'Link',
|
||||
title: tval('Link'),
|
||||
};
|
||||
}
|
||||
|
||||
LinkGlobalActionModel.define({
|
||||
title: 'Link',
|
||||
title: tval('Link'),
|
||||
});
|
||||
|
||||
LinkGlobalActionModel.registerFlow({
|
||||
key: 'handleClick',
|
||||
title: '点击事件',
|
||||
title: tval('Click event'),
|
||||
on: {
|
||||
eventName: 'click',
|
||||
},
|
||||
|
@ -8,24 +8,25 @@
|
||||
*/
|
||||
|
||||
import type { ButtonProps } from 'antd';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { openLinkAction } from '../../actions/openLinkAction';
|
||||
import { RecordActionModel } from '../base/ActionModel';
|
||||
|
||||
export class LinkRecordActionModel extends RecordActionModel {
|
||||
defaultProps: ButtonProps = {
|
||||
type: 'link',
|
||||
children: 'Link',
|
||||
children: tval('Link'),
|
||||
};
|
||||
}
|
||||
|
||||
LinkRecordActionModel.define({
|
||||
title: 'Link',
|
||||
title: tval('Link'),
|
||||
hide: true,
|
||||
});
|
||||
|
||||
LinkRecordActionModel.registerFlow({
|
||||
key: 'handleClick',
|
||||
title: '点击事件',
|
||||
title: tval('Click event'),
|
||||
on: {
|
||||
eventName: 'click',
|
||||
},
|
||||
|
@ -8,22 +8,23 @@
|
||||
*/
|
||||
|
||||
import type { ButtonProps } from 'antd/es/button';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { openModeAction } from '../../actions/openModeAction';
|
||||
import { RecordActionModel } from '../base/ActionModel';
|
||||
|
||||
export class PopupRecordActionModel extends RecordActionModel {
|
||||
defaultProps: ButtonProps = {
|
||||
title: 'Popup',
|
||||
title: tval('Popup'),
|
||||
};
|
||||
}
|
||||
|
||||
PopupRecordActionModel.define({
|
||||
title: 'Popup',
|
||||
title: tval('Popup'),
|
||||
});
|
||||
|
||||
PopupRecordActionModel.registerFlow({
|
||||
key: 'handleClick',
|
||||
title: '点击事件',
|
||||
title: tval('Click event'),
|
||||
on: {
|
||||
eventName: 'click',
|
||||
},
|
||||
|
@ -8,31 +8,33 @@
|
||||
*/
|
||||
|
||||
import { ButtonProps } from 'antd';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { GlobalActionModel } from '../base/ActionModel';
|
||||
|
||||
export class RefreshActionModel extends GlobalActionModel {
|
||||
defaultProps: ButtonProps = {
|
||||
title: 'Refresh',
|
||||
title: tval('Refresh'),
|
||||
icon: 'ReloadOutlined',
|
||||
};
|
||||
}
|
||||
|
||||
RefreshActionModel.define({
|
||||
title: 'Refresh',
|
||||
title: tval('Refresh'),
|
||||
});
|
||||
|
||||
RefreshActionModel.registerFlow({
|
||||
key: 'handleClick',
|
||||
title: '点击事件',
|
||||
title: tval('Click event'),
|
||||
on: {
|
||||
eventName: 'click',
|
||||
},
|
||||
steps: {
|
||||
refresh: {
|
||||
async handler(ctx, params) {
|
||||
const t = ctx.model.translate;
|
||||
const currentResource = ctx.shared?.currentBlockModel?.resource;
|
||||
if (!currentResource) {
|
||||
ctx.globals.message.error('No resource selected for refresh.');
|
||||
ctx.globals.message.error(t('No resource selected for refresh'));
|
||||
return;
|
||||
}
|
||||
currentResource.loading = true;
|
||||
|
@ -12,29 +12,30 @@ import { afterSuccessAction } from '../../actions/afterSuccessAction';
|
||||
import { refreshOnCompleteAction } from '../../actions/refreshOnCompleteAction';
|
||||
import { secondaryConfirmationAction } from '../../actions/secondaryConfirmationAction';
|
||||
import { RecordActionModel } from '../base/ActionModel';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
|
||||
export class UpdateRecordActionModel extends RecordActionModel {
|
||||
defaultProps: ButtonProps = {
|
||||
type: 'link',
|
||||
title: 'Update record',
|
||||
title: tval('Update record'),
|
||||
};
|
||||
}
|
||||
|
||||
UpdateRecordActionModel.define({
|
||||
title: 'Update record',
|
||||
title: tval('Update record action'),
|
||||
hide: true,
|
||||
});
|
||||
|
||||
UpdateRecordActionModel.registerFlow({
|
||||
key: 'handleClick',
|
||||
title: '点击事件',
|
||||
title: tval('Click event'),
|
||||
on: {
|
||||
eventName: 'click',
|
||||
},
|
||||
steps: {
|
||||
secondaryConfirmation: secondaryConfirmationAction,
|
||||
update: {
|
||||
title: '字段赋值',
|
||||
title: tval('Assign field values'),
|
||||
handler: async (ctx, params) => {},
|
||||
},
|
||||
afterSuccess: afterSuccessAction,
|
||||
|
@ -8,22 +8,23 @@
|
||||
*/
|
||||
|
||||
import type { ButtonProps } from 'antd/es/button';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { RecordActionModel } from '../base/ActionModel';
|
||||
|
||||
export class ViewActionModel extends RecordActionModel {
|
||||
defaultProps: ButtonProps = {
|
||||
type: 'link',
|
||||
title: 'View',
|
||||
title: tval('View'),
|
||||
};
|
||||
}
|
||||
|
||||
ViewActionModel.define({
|
||||
title: 'View',
|
||||
title: tval('View'),
|
||||
});
|
||||
|
||||
ViewActionModel.registerFlow({
|
||||
key: 'handleClick',
|
||||
title: '点击事件',
|
||||
title: tval('Click event'),
|
||||
on: {
|
||||
eventName: 'click',
|
||||
},
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
import { FlowModel } from '@nocobase/flow-engine';
|
||||
import { Button } from 'antd';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import type { ButtonProps } from 'antd/es/button';
|
||||
import React from 'react';
|
||||
import { Icon } from '../../../icon/Icon';
|
||||
@ -19,7 +20,7 @@ export class ActionModel extends FlowModel {
|
||||
|
||||
defaultProps: ButtonProps = {
|
||||
type: 'default',
|
||||
title: 'Action',
|
||||
title: tval('Action'),
|
||||
};
|
||||
|
||||
render() {
|
||||
@ -37,21 +38,21 @@ export class ActionModel extends FlowModel {
|
||||
|
||||
ActionModel.registerFlow({
|
||||
key: 'default',
|
||||
title: '通用配置',
|
||||
title: tval('General configuration'),
|
||||
auto: true,
|
||||
steps: {
|
||||
buttonProps: {
|
||||
title: '编辑按钮',
|
||||
title: tval('Edit button'),
|
||||
uiSchema: {
|
||||
title: {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
title: 'Button title',
|
||||
title: tval('Button title'),
|
||||
},
|
||||
icon: {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': IconPicker,
|
||||
title: 'Button icon',
|
||||
title: tval('Button icon'),
|
||||
},
|
||||
},
|
||||
defaultParams(ctx) {
|
||||
@ -61,7 +62,7 @@ ActionModel.registerFlow({
|
||||
};
|
||||
},
|
||||
handler(ctx, params) {
|
||||
ctx.model.setProps('title', params.title);
|
||||
ctx.model.setProps('title', ctx.globals.flowEngine.translate(params.title));
|
||||
ctx.model.setProps('icon', params.icon);
|
||||
ctx.model.setProps('onClick', (event) => {
|
||||
ctx.model.dispatchEvent('click', {
|
||||
@ -78,7 +79,7 @@ export class GlobalActionModel extends ActionModel {}
|
||||
export class RecordActionModel extends ActionModel {
|
||||
defaultProps: ButtonProps = {
|
||||
type: 'link',
|
||||
children: 'Action',
|
||||
children: tval('Action'),
|
||||
};
|
||||
|
||||
render() {
|
||||
@ -95,21 +96,21 @@ export class RecordActionModel extends ActionModel {
|
||||
|
||||
RecordActionModel.registerFlow({
|
||||
key: 'default',
|
||||
title: '通用配置',
|
||||
title: tval('General configuration'),
|
||||
auto: true,
|
||||
steps: {
|
||||
buttonProps: {
|
||||
title: '编辑按钮',
|
||||
title: tval('Edit button'),
|
||||
uiSchema: {
|
||||
title: {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
title: 'Button title',
|
||||
title: tval('Button title'),
|
||||
},
|
||||
icon: {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': IconPicker,
|
||||
title: 'Button icon',
|
||||
title: tval('Button icon'),
|
||||
},
|
||||
},
|
||||
defaultParams(ctx) {
|
||||
@ -126,7 +127,7 @@ RecordActionModel.registerFlow({
|
||||
if (!currentBlockModel) {
|
||||
throw new Error('Current block model is not set in shared context');
|
||||
}
|
||||
ctx.model.setProps('title', params.title);
|
||||
ctx.model.setProps('title', ctx.globals.flowEngine.translate(params.title));
|
||||
ctx.model.setProps('icon', params.icon);
|
||||
ctx.model.setProps('onClick', (event) => {
|
||||
ctx.model.dispatchEvent('click', {
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
import { Alert, Space } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { useState } from 'react';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { Grid } from '../../components/Grid';
|
||||
import JsonEditor from '../../components/JsonEditor';
|
||||
import { BlockModel } from './BlockModel';
|
||||
@ -97,6 +98,7 @@ export class GridModel extends FlowModel<GridModelStructure> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.translate;
|
||||
return (
|
||||
<div style={{ padding: 16 }}>
|
||||
<Space direction={'vertical'} style={{ width: '100%' }} size={16}>
|
||||
@ -117,14 +119,14 @@ export class GridModel extends FlowModel<GridModelStructure> {
|
||||
/>
|
||||
<Space>
|
||||
<AddBlockButton model={this} subModelKey="items" subModelBaseClass={this.subModelBaseClass}>
|
||||
<FlowSettingsButton icon={<PlusOutlined />}>{'Add block'}</FlowSettingsButton>
|
||||
<FlowSettingsButton icon={<PlusOutlined />}>{t('Add block')}</FlowSettingsButton>
|
||||
</AddBlockButton>
|
||||
<FlowSettingsButton
|
||||
onClick={() => {
|
||||
this.openStepSettingsDialog('defaultFlow', 'grid');
|
||||
}}
|
||||
>
|
||||
Configure rows
|
||||
{t('Configure rows')}
|
||||
</FlowSettingsButton>
|
||||
</Space>
|
||||
</Space>
|
||||
@ -145,23 +147,24 @@ GridModel.registerFlow({
|
||||
grid: {
|
||||
uiSchema: {
|
||||
rows: {
|
||||
title: 'Rows',
|
||||
title: tval('Rows'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': JsonEditor,
|
||||
'x-component-props': {
|
||||
autoSize: { minRows: 10, maxRows: 20 },
|
||||
description: 'Configure the rows and columns of the grid.',
|
||||
description: tval('Configure the rows and columns of the grid.'),
|
||||
},
|
||||
},
|
||||
sizes: {
|
||||
title: 'Sizes',
|
||||
title: tval('Sizes'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': JsonEditor,
|
||||
'x-component-props': {
|
||||
rows: 5,
|
||||
},
|
||||
description:
|
||||
description: tval(
|
||||
'Configure the sizes of each row. The value is an array of numbers representing the width of each column in the row.',
|
||||
),
|
||||
},
|
||||
},
|
||||
async handler(ctx, params) {
|
||||
|
@ -14,6 +14,7 @@ import { FlowModel, FlowModelRenderer, FlowSettingsButton } from '@nocobase/flow
|
||||
import { Tabs } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
|
||||
type PageModelStructure = {
|
||||
subModels: {
|
||||
@ -63,7 +64,7 @@ export class PageModel extends FlowModel<PageModelStructure> {
|
||||
});
|
||||
}}
|
||||
>
|
||||
Add tab
|
||||
{this.flowEngine.translate('Add tab')}
|
||||
</FlowSettingsButton>
|
||||
}
|
||||
/>
|
||||
@ -82,24 +83,24 @@ export class PageModel extends FlowModel<PageModelStructure> {
|
||||
|
||||
PageModel.registerFlow({
|
||||
key: 'default',
|
||||
title: '基础配置',
|
||||
title: tval('Basic configuration'),
|
||||
auto: true,
|
||||
steps: {
|
||||
settings: {
|
||||
title: '配置页面',
|
||||
title: tval('Configure page'),
|
||||
uiSchema: {
|
||||
title: {
|
||||
type: 'string',
|
||||
title: 'Page Title',
|
||||
title: tval('Page Title'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'Enter page title',
|
||||
placeholder: tval('Enter page title'),
|
||||
},
|
||||
},
|
||||
enableTabs: {
|
||||
type: 'boolean',
|
||||
title: 'Enable tabs',
|
||||
title: tval('Enable tabs'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Switch',
|
||||
},
|
||||
@ -111,7 +112,7 @@ PageModel.registerFlow({
|
||||
};
|
||||
},
|
||||
async handler(ctx, params) {
|
||||
ctx.model.setProps('title', params.title);
|
||||
ctx.model.setProps('title', ctx.globals.flowEngine.translate(params.title));
|
||||
ctx.model.setProps('enableTabs', params.enableTabs);
|
||||
|
||||
if (ctx.shared.currentDrawer) {
|
||||
|
@ -12,6 +12,7 @@ import { Card, Modal } from 'antd';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import { Calendar, momentLocalizer } from 'react-big-calendar';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import 'react-big-calendar/lib/css/react-big-calendar.css';
|
||||
import { DataBlockModel } from '../../base/BlockModel';
|
||||
|
||||
@ -47,7 +48,7 @@ export class CalendarBlockModel extends DataBlockModel {
|
||||
}
|
||||
|
||||
CalendarBlockModel.define({
|
||||
title: 'Calendar',
|
||||
title: tval('Calendar'),
|
||||
group: 'Content',
|
||||
hide: true,
|
||||
defaultOptions: {
|
||||
@ -64,13 +65,20 @@ CalendarBlockModel.registerFlow({
|
||||
step1: {
|
||||
handler(ctx, params) {
|
||||
console.log('ctx.extra.event', ctx.extra.event);
|
||||
const t = ctx.model.translate;
|
||||
Modal.info({
|
||||
title: 'Event Selected',
|
||||
title: t('Event selected'),
|
||||
content: (
|
||||
<div>
|
||||
<p>Title: {ctx.extra.event.nickname}</p>
|
||||
<p>Start: {moment(ctx.extra.event.createdAt).format('YYYY-MM-DD HH:mm:ss')}</p>
|
||||
<p>End: {moment(ctx.extra.event.createdAt).format('YYYY-MM-DD HH:mm:ss')}</p>
|
||||
<p>
|
||||
{t('Title')}: {ctx.extra.event.nickname}
|
||||
</p>
|
||||
<p>
|
||||
{t('Start')}: {moment(ctx.extra.event.createdAt).format('YYYY-MM-DD HH:mm:ss')}
|
||||
</p>
|
||||
<p>
|
||||
{t('End')}: {moment(ctx.extra.event.createdAt).format('YYYY-MM-DD HH:mm:ss')}
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
@ -88,13 +96,20 @@ CalendarBlockModel.registerFlow({
|
||||
step1: {
|
||||
handler(ctx, params) {
|
||||
console.log('ctx.extra.event', ctx.extra.event);
|
||||
const t = ctx.model.translate;
|
||||
Modal.info({
|
||||
title: 'Double Click',
|
||||
title: t('Double click'),
|
||||
content: (
|
||||
<div>
|
||||
<p>Title: {ctx.extra.event.nickname}</p>
|
||||
<p>Start: {moment(ctx.extra.event.createdAt).format('YYYY-MM-DD HH:mm:ss')}</p>
|
||||
<p>End: {moment(ctx.extra.event.createdAt).format('YYYY-MM-DD HH:mm:ss')}</p>
|
||||
<p>
|
||||
{t('Title')}: {ctx.extra.event.nickname}
|
||||
</p>
|
||||
<p>
|
||||
{t('Start')}: {moment(ctx.extra.event.createdAt).format('YYYY-MM-DD HH:mm:ss')}
|
||||
</p>
|
||||
<p>
|
||||
{t('End')}: {moment(ctx.extra.event.createdAt).format('YYYY-MM-DD HH:mm:ss')}
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
@ -113,20 +128,20 @@ CalendarBlockModel.registerFlow({
|
||||
uiSchema: {
|
||||
dataSourceKey: {
|
||||
type: 'string',
|
||||
title: 'Data Source Key',
|
||||
title: tval('Data Source Key'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'Enter data source key',
|
||||
placeholder: tval('Enter data source key'),
|
||||
},
|
||||
},
|
||||
collectionName: {
|
||||
type: 'string',
|
||||
title: 'Collection Name',
|
||||
title: tval('Collection Name'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'Enter collection name',
|
||||
placeholder: tval('Enter collection name'),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -149,29 +164,29 @@ CalendarBlockModel.registerFlow({
|
||||
uiSchema: {
|
||||
titleAccessor: {
|
||||
type: 'string',
|
||||
title: 'Title accessor',
|
||||
title: tval('Title accessor'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'Enter title accessor',
|
||||
placeholder: tval('Enter title accessor'),
|
||||
},
|
||||
},
|
||||
startAccessor: {
|
||||
type: 'string',
|
||||
title: 'Start accessor',
|
||||
title: tval('Start accessor'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'Enter start accessor',
|
||||
placeholder: tval('Enter start accessor'),
|
||||
},
|
||||
},
|
||||
endAccessor: {
|
||||
type: 'string',
|
||||
title: 'End accessor',
|
||||
title: tval('End accessor'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'Enter end accessor',
|
||||
placeholder: tval('Enter end accessor'),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import { ButtonProps } from 'antd';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { ActionModel } from '../../base/ActionModel';
|
||||
import { DataBlockModel } from '../../base/BlockModel';
|
||||
import { FormModel } from './FormModel';
|
||||
@ -16,14 +17,14 @@ export class FormActionModel extends ActionModel {}
|
||||
|
||||
export class FormSubmitActionModel extends FormActionModel {
|
||||
defaultProps: ButtonProps = {
|
||||
children: 'Submit',
|
||||
title: tval('Submit'),
|
||||
type: 'primary',
|
||||
htmlType: 'submit',
|
||||
};
|
||||
}
|
||||
|
||||
FormSubmitActionModel.define({
|
||||
title: 'Submit',
|
||||
title: tval('Submit'),
|
||||
});
|
||||
|
||||
FormSubmitActionModel.registerFlow({
|
||||
@ -35,7 +36,7 @@ FormSubmitActionModel.registerFlow({
|
||||
step1: {
|
||||
async handler(ctx, params) {
|
||||
if (!ctx.shared?.currentBlockModel?.resource) {
|
||||
ctx.globals.message.error('No resource selected for submission.');
|
||||
ctx.globals.message.error(ctx.model.flowEngine.translate('No resource selected for submission.'));
|
||||
return;
|
||||
}
|
||||
const currentBlockModel = ctx.shared.currentBlockModel as FormModel;
|
||||
|
@ -13,6 +13,7 @@ import { FormProvider } from '@formily/react';
|
||||
import { AddActionButton, AddFieldButton, FlowModelRenderer, SingleRecordResource } from '@nocobase/flow-engine';
|
||||
import { Card } from 'antd';
|
||||
import React from 'react';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { DataBlockModel } from '../../base/BlockModel';
|
||||
import { EditableFieldModel } from '../../fields/EditableField/EditableFieldModel';
|
||||
|
||||
@ -80,20 +81,20 @@ FormModel.registerFlow({
|
||||
uiSchema: {
|
||||
dataSourceKey: {
|
||||
type: 'string',
|
||||
title: 'Data Source Key',
|
||||
title: tval('Data Source Key'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'Enter data source key',
|
||||
placeholder: tval('Enter data source key'),
|
||||
},
|
||||
},
|
||||
collectionName: {
|
||||
type: 'string',
|
||||
title: 'Collection Name',
|
||||
title: tval('Collection Name'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'Enter collection name',
|
||||
placeholder: tval('Enter collection name'),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -126,7 +127,7 @@ FormModel.registerFlow({
|
||||
});
|
||||
|
||||
FormModel.define({
|
||||
title: 'Form',
|
||||
title: tval('Form'),
|
||||
group: 'Content',
|
||||
defaultOptions: {
|
||||
use: 'FormModel',
|
||||
|
@ -116,7 +116,7 @@ export class QuickEditForm extends FlowModel {
|
||||
resolve(this.form.values); // 在 close 之后 resolve
|
||||
}}
|
||||
>
|
||||
Submit
|
||||
{this.ctx.globals.flowEngine.translate('Submit')}
|
||||
</Submit>
|
||||
</FormButtonGroup>
|
||||
</FormProvider>
|
||||
|
@ -12,6 +12,7 @@ import { observer } from '@formily/reactive-react';
|
||||
import { AddActionButton, FlowModel, FlowModelRenderer, FlowsFloatContextMenu } from '@nocobase/flow-engine';
|
||||
import { Skeleton, Space } from 'antd';
|
||||
import React from 'react';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { ActionModel } from '../../base/ActionModel';
|
||||
import { SupportedFieldInterfaces } from '../../base/FieldModel';
|
||||
|
||||
@ -63,7 +64,7 @@ export class TableActionsColumnModel extends FlowModel {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Space>{this.props.title || 'Actions'}</Space>
|
||||
<Space>{this.props.title || this.flowEngine.translate('Actions')}</Space>
|
||||
</FlowsFloatContextMenu>
|
||||
),
|
||||
render: this.render(),
|
||||
|
@ -11,6 +11,7 @@ import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import { FlowsFloatContextMenu } from '@nocobase/flow-engine';
|
||||
import { TableColumnProps, Tooltip } from 'antd';
|
||||
import React from 'react';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { FieldModel } from '../../base/FieldModel';
|
||||
import { ReadPrettyFieldModel } from '../../fields/ReadPrettyField/ReadPrettyFieldModel';
|
||||
|
||||
@ -65,7 +66,7 @@ export class TableColumnModel extends FieldModel {
|
||||
}
|
||||
|
||||
TableColumnModel.define({
|
||||
title: 'Table Column',
|
||||
title: tval('Table column'),
|
||||
icon: 'TableColumn',
|
||||
defaultOptions: {
|
||||
use: 'TableColumnModel',
|
||||
@ -103,13 +104,13 @@ TableColumnModel.registerFlow({
|
||||
},
|
||||
},
|
||||
editColumTitle: {
|
||||
title: 'Column title',
|
||||
title: tval('Column title'),
|
||||
uiSchema: {
|
||||
title: {
|
||||
'x-component': 'Input',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {
|
||||
placeholder: 'Column title',
|
||||
placeholder: tval('Column title'),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -119,17 +120,18 @@ TableColumnModel.registerFlow({
|
||||
};
|
||||
},
|
||||
handler(ctx, params) {
|
||||
ctx.model.setProps('title', params.title || ctx.model.collectionField?.title);
|
||||
const title = ctx.globals.flowEngine.translate(params.title || ctx.model.collectionField?.title);
|
||||
ctx.model.setProps('title', title);
|
||||
},
|
||||
},
|
||||
editTooltip: {
|
||||
title: 'Edit tooltip',
|
||||
title: tval('Edit tooltip'),
|
||||
uiSchema: {
|
||||
tooltip: {
|
||||
'x-component': 'Input.TextArea',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {
|
||||
placeholder: 'Edit tooltip',
|
||||
placeholder: tval('Edit tooltip'),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -138,7 +140,7 @@ TableColumnModel.registerFlow({
|
||||
},
|
||||
},
|
||||
editColumnWidth: {
|
||||
title: 'Column width',
|
||||
title: tval('Column width'),
|
||||
uiSchema: {
|
||||
width: {
|
||||
'x-component': 'NumberPicker',
|
||||
@ -153,7 +155,7 @@ TableColumnModel.registerFlow({
|
||||
},
|
||||
},
|
||||
enableEditable: {
|
||||
title: 'Editable',
|
||||
title: tval('Editable'),
|
||||
uiSchema: {
|
||||
editable: {
|
||||
'x-component': 'Switch',
|
||||
|
@ -22,6 +22,7 @@ import { Card, Space, Spin, Table } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
import React, { useRef } from 'react';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { ActionModel } from '../../base/ActionModel';
|
||||
import { DataBlockModel } from '../../base/BlockModel';
|
||||
import { QuickEditForm } from '../form/QuickEditForm';
|
||||
@ -101,7 +102,7 @@ export class TableModel extends DataBlockModel<TableModelStructure> {
|
||||
appendItems={[
|
||||
{
|
||||
key: 'actions',
|
||||
label: 'Actions column',
|
||||
label: tval('Actions column'),
|
||||
createModelOptions: {
|
||||
use: 'TableActionsColumnModel',
|
||||
},
|
||||
@ -263,20 +264,20 @@ TableModel.registerFlow({
|
||||
uiSchema: {
|
||||
dataSourceKey: {
|
||||
type: 'string',
|
||||
title: 'Data Source Key',
|
||||
title: tval('Data Source Key'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'Enter data source key',
|
||||
placeholder: tval('Enter data source key'),
|
||||
},
|
||||
},
|
||||
collectionName: {
|
||||
type: 'string',
|
||||
title: 'Collection Name',
|
||||
title: tval('Collection Name'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'Enter collection name',
|
||||
placeholder: tval('Enter collection name'),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -299,7 +300,7 @@ TableModel.registerFlow({
|
||||
},
|
||||
},
|
||||
editPageSize: {
|
||||
title: 'Edit page size',
|
||||
title: tval('Edit page size'),
|
||||
uiSchema: {
|
||||
pageSize: {
|
||||
'x-component': 'Select',
|
||||
@ -328,14 +329,14 @@ TableModel.registerFlow({
|
||||
},
|
||||
dataScope: {
|
||||
use: 'dataScope',
|
||||
title: '设置数据范围',
|
||||
title: tval('Set data scope'),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
TableModel.define({
|
||||
title: 'Table',
|
||||
group: 'Content',
|
||||
title: tval('Table'),
|
||||
group: tval('Content'),
|
||||
defaultOptions: {
|
||||
use: 'TableModel',
|
||||
subModels: {
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
import { Button, Card, Pagination, Skeleton, Space } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { ColumnDefinition, TabulatorFull as Tabulator } from 'tabulator-tables';
|
||||
import { ActionModel } from '../../base/ActionModel';
|
||||
import { DataBlockModel } from '../../base/BlockModel';
|
||||
@ -41,7 +42,7 @@ export class TabulatorColumnModel extends FlowModel {
|
||||
|
||||
getColumnProps(): ColumnDefinition {
|
||||
return {
|
||||
title: 'abcd',
|
||||
title: tval('abcd'),
|
||||
width: 100,
|
||||
headerSort: false,
|
||||
editable: true,
|
||||
@ -126,7 +127,7 @@ export class TabulatorTableActionsColumnModel extends TabulatorColumnModel {
|
||||
containerStyle={{ display: 'block', padding: '11px 8px', margin: '-11px -8px' }}
|
||||
>
|
||||
<Space>
|
||||
{this.props.title || 'Actions'}
|
||||
{this.props.title || tval('Actions')}
|
||||
<AddActionButton model={this} subModelBaseClass="RecordActionModel" subModelKey="actions">
|
||||
<SettingOutlined />
|
||||
</AddActionButton>
|
||||
@ -194,7 +195,7 @@ export class TabulatorModel extends DataBlockModel<S> {
|
||||
appendItems={[
|
||||
{
|
||||
key: 'actions',
|
||||
label: 'Actions column',
|
||||
label: tval('Actions column'),
|
||||
createModelOptions: {
|
||||
use: 'TabulatorTableActionsColumnModel',
|
||||
},
|
||||
@ -260,7 +261,7 @@ export class TabulatorModel extends DataBlockModel<S> {
|
||||
<FlowModelRenderer model={action} showFlowSettings sharedContext={{ currentBlockModel: this }} />
|
||||
))}
|
||||
<AddActionButton model={this} subModelBaseClass="GlobalActionModel" subModelKey="actions">
|
||||
<Button icon={<SettingOutlined />}>Configure actions</Button>
|
||||
<Button icon={<SettingOutlined />}>{this.translate('Configure actions')}</Button>
|
||||
</AddActionButton>
|
||||
</Space>
|
||||
<div ref={this.tabulatorRef} />
|
||||
@ -292,20 +293,20 @@ TabulatorModel.registerFlow({
|
||||
uiSchema: {
|
||||
dataSourceKey: {
|
||||
type: 'string',
|
||||
title: 'Data Source Key',
|
||||
title: tval('Data Source Key'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'Enter data source key',
|
||||
placeholder: tval('Enter data source key'),
|
||||
},
|
||||
},
|
||||
collectionName: {
|
||||
type: 'string',
|
||||
title: 'Collection Name',
|
||||
title: tval('Collection Name'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'Enter collection name',
|
||||
placeholder: tval('Enter collection name'),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -342,8 +343,8 @@ TabulatorModel.registerFlow({
|
||||
});
|
||||
|
||||
TabulatorModel.define({
|
||||
title: 'Tabulator',
|
||||
group: 'Content',
|
||||
title: tval('Tabulator'),
|
||||
group: tval('Content'),
|
||||
requiresDataSource: true,
|
||||
hide: true,
|
||||
defaultOptions: {
|
||||
|
@ -11,6 +11,7 @@ import { Select } from 'antd';
|
||||
import React from 'react';
|
||||
import { FlowModelRenderer, useFlowEngine, useFlowModel, reactive } from '@nocobase/flow-engine';
|
||||
import { useCompile } from '../../../../../schema-component';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { AssociationFieldEditableFieldModel } from './AssociationFieldEditableFieldModel';
|
||||
|
||||
function toValue(record: any | any[], fieldNames, multiple = false) {
|
||||
@ -41,7 +42,7 @@ function LabelByField(props) {
|
||||
const currentModel: any = useFlowModel();
|
||||
const flowEngine = useFlowEngine();
|
||||
if (modelCache.has(cacheKey)) {
|
||||
return option[fieldNames.label] ? <FlowModelRenderer model={modelCache.get(cacheKey)} /> : 'N/A';
|
||||
return option[fieldNames.label] ? <FlowModelRenderer model={modelCache.get(cacheKey)} /> : tval('N/A');
|
||||
}
|
||||
const collectionManager = currentModel.collectionField.collection.collectionManager;
|
||||
const target = currentModel.collectionField?.options?.target;
|
||||
@ -76,7 +77,7 @@ function LabelByField(props) {
|
||||
|
||||
return (
|
||||
<span key={option[fieldNames.value]}>
|
||||
{option[fieldNames.label] ? <FlowModelRenderer model={model} uid={option[fieldNames.value]} /> : 'N/A'}
|
||||
{option[fieldNames.label] ? <FlowModelRenderer model={model} uid={option[fieldNames.value]} /> : tval('N/A')}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@ -258,7 +259,7 @@ AssociationSelectEditableFieldModel.registerFlow({
|
||||
paginationState.page++;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('滚动分页请求失败:', error);
|
||||
console.error('Scroll pagination request failed:', error);
|
||||
} finally {
|
||||
paginationState.loading = false;
|
||||
}
|
||||
@ -316,13 +317,13 @@ AssociationSelectEditableFieldModel.registerFlow({
|
||||
|
||||
AssociationSelectEditableFieldModel.registerFlow({
|
||||
key: 'fieldNames',
|
||||
title: 'Specific properties',
|
||||
title: tval('Specific properties'),
|
||||
auto: true,
|
||||
sort: 200,
|
||||
steps: {
|
||||
fieldNames: {
|
||||
use: 'titleField',
|
||||
title: 'Title field',
|
||||
title: tval('Title field'),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
import { connect, mapProps } from '@formily/react';
|
||||
import { ColorPicker as AntdColorPicker } from 'antd';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { EditableFieldModel } from './EditableFieldModel';
|
||||
|
||||
const ColorPicker = connect(
|
||||
@ -23,7 +24,7 @@ const ColorPicker = connect(
|
||||
},
|
||||
presets: [
|
||||
{
|
||||
label: 'Recommended',
|
||||
label: tval('Recommended'),
|
||||
colors: [
|
||||
'#8BBB11',
|
||||
'#52C41A',
|
||||
|
@ -7,6 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
import { DatePicker } from '@formily/antd-v5';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { EditableFieldModel } from '../EditableFieldModel';
|
||||
|
||||
export class DateTimeFieldModel extends EditableFieldModel {
|
||||
@ -35,11 +36,11 @@ DateTimeFieldModel.registerFlow({
|
||||
key: 'key3',
|
||||
auto: true,
|
||||
sort: 1000,
|
||||
title: 'Specific properties',
|
||||
title: tval('Specific properties'),
|
||||
steps: {
|
||||
dateFormat: {
|
||||
use: 'dateDisplayFormat',
|
||||
title: 'Date display format',
|
||||
title: tval('Date display format'),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -12,6 +12,7 @@ import type { FieldPatternTypes, FieldValidator } from '@formily/core';
|
||||
import { Field, Form } from '@formily/core';
|
||||
import { FieldContext } from '@formily/react';
|
||||
import React from 'react';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { FieldModel } from '../../base/FieldModel';
|
||||
import { ReactiveField } from '../../../formily/ReactiveField';
|
||||
import { FormModel } from '../..';
|
||||
@ -107,7 +108,7 @@ export class EditableFieldModel extends FieldModel<Structure> {
|
||||
EditableFieldModel.registerFlow({
|
||||
key: 'init',
|
||||
auto: true,
|
||||
title: 'Basic',
|
||||
title: tval('Basic'),
|
||||
sort: 150,
|
||||
steps: {
|
||||
createField: {
|
||||
@ -125,13 +126,13 @@ EditableFieldModel.registerFlow({
|
||||
},
|
||||
},
|
||||
editTitle: {
|
||||
title: 'Edit Title',
|
||||
title: tval('Edit Title'),
|
||||
uiSchema: {
|
||||
title: {
|
||||
'x-component': 'Input',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {
|
||||
placeholder: 'Enter field title',
|
||||
placeholder: tval('Enter field title'),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -145,7 +146,7 @@ EditableFieldModel.registerFlow({
|
||||
},
|
||||
},
|
||||
initialValue: {
|
||||
title: 'Set default value',
|
||||
title: tval('Set default value'),
|
||||
uiSchema: {
|
||||
defaultValue: {
|
||||
'x-component': 'Input',
|
||||
@ -158,14 +159,14 @@ EditableFieldModel.registerFlow({
|
||||
},
|
||||
},
|
||||
required: {
|
||||
title: 'Required',
|
||||
title: tval('Required'),
|
||||
uiSchema: {
|
||||
required: {
|
||||
'x-component': 'Switch',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {
|
||||
checkedChildren: 'Yes',
|
||||
unCheckedChildren: 'No',
|
||||
checkedChildren: tval('Yes'),
|
||||
unCheckedChildren: tval('No'),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -174,14 +175,14 @@ EditableFieldModel.registerFlow({
|
||||
},
|
||||
},
|
||||
displayLabel: {
|
||||
title: 'Display label',
|
||||
title: tval('Display label'),
|
||||
uiSchema: {
|
||||
displayLabel: {
|
||||
'x-component': 'Switch',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {
|
||||
checkedChildren: 'Yes',
|
||||
unCheckedChildren: 'No',
|
||||
checkedChildren: tval('Yes'),
|
||||
unCheckedChildren: tval('No'),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -193,7 +194,7 @@ EditableFieldModel.registerFlow({
|
||||
},
|
||||
},
|
||||
editDescription: {
|
||||
title: 'Edit description',
|
||||
title: tval('Edit description'),
|
||||
uiSchema: {
|
||||
description: {
|
||||
'x-component': 'Input.TextArea',
|
||||
@ -205,7 +206,7 @@ EditableFieldModel.registerFlow({
|
||||
},
|
||||
},
|
||||
editTooltip: {
|
||||
title: 'Edit tooltip',
|
||||
title: tval('Edit tooltip'),
|
||||
uiSchema: {
|
||||
tooltip: {
|
||||
'x-component': 'Input.TextArea',
|
||||
@ -217,7 +218,7 @@ EditableFieldModel.registerFlow({
|
||||
},
|
||||
},
|
||||
pattern: {
|
||||
title: 'Pattern',
|
||||
title: tval('Pattern'),
|
||||
uiSchema: {
|
||||
pattern: {
|
||||
'x-component': 'Select',
|
||||
@ -225,19 +226,19 @@ EditableFieldModel.registerFlow({
|
||||
enum: [
|
||||
{
|
||||
value: 'editable',
|
||||
label: 'Editable',
|
||||
label: tval('Editable'),
|
||||
},
|
||||
{
|
||||
value: 'disabled',
|
||||
label: 'Disabled',
|
||||
label: tval('Disabled'),
|
||||
},
|
||||
{
|
||||
value: 'readOnly',
|
||||
label: 'ReadOnly',
|
||||
label: tval('ReadOnly'),
|
||||
},
|
||||
{
|
||||
value: 'readPretty',
|
||||
label: 'ReadPretty',
|
||||
label: tval('ReadPretty'),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -29,11 +29,11 @@ NanoIDEditableFieldModel.registerFlow({
|
||||
const { size, customAlphabet } = ctx.model.collectionField.options || { size: 21 };
|
||||
function isValidNanoid(value) {
|
||||
if (value?.length !== size) {
|
||||
return 'Field value size is' + ` ${size || 21}`;
|
||||
return ctx.globals.flowEngine.translate('Field value size is') + ` ${size || 21}`;
|
||||
}
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
if (customAlphabet?.indexOf(value[i]) === -1) {
|
||||
return `Field value do not meet the requirements`;
|
||||
return ctx.globals.flowEngine.translate('Field value do not meet the requirements');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import { Password } from '@formily/antd-v5';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { EditableFieldModel } from './EditableFieldModel';
|
||||
|
||||
export class PasswordEditableFieldModel extends EditableFieldModel {
|
||||
@ -21,14 +22,17 @@ PasswordEditableFieldModel.registerFlow({
|
||||
key: 'key3',
|
||||
auto: true,
|
||||
sort: 1000,
|
||||
title: 'Group3',
|
||||
title: tval('Password Options'),
|
||||
steps: {
|
||||
placeholder: {
|
||||
title: 'Placeholder',
|
||||
title: tval('Placeholder'),
|
||||
uiSchema: {
|
||||
checkStrength: {
|
||||
placeholder: {
|
||||
'x-component': 'Input',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {
|
||||
placeholder: tval('Enter placeholder text'),
|
||||
},
|
||||
},
|
||||
},
|
||||
handler(ctx, params) {
|
||||
@ -36,14 +40,14 @@ PasswordEditableFieldModel.registerFlow({
|
||||
},
|
||||
},
|
||||
checkStrength: {
|
||||
title: 'Check strength',
|
||||
title: tval('Check strength'),
|
||||
uiSchema: {
|
||||
checkStrength: {
|
||||
'x-component': 'Switch',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {
|
||||
checkedChildren: 'Yes',
|
||||
unCheckedChildren: 'No',
|
||||
checkedChildren: tval('Yes'),
|
||||
unCheckedChildren: tval('No'),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -11,8 +11,7 @@ import React from 'react';
|
||||
import { AssociationReadPrettyFieldModel } from './AssociationReadPrettyFieldModel';
|
||||
import { FlowEngineProvider, reactive } from '@nocobase/flow-engine';
|
||||
import { getUniqueKeyFromCollection } from '../../../../../collection-manager/interfaces/utils';
|
||||
import { useCompile } from '../../../../../schema-component';
|
||||
import { isTitleField } from '../../../../../data-source';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
|
||||
export class AssociationSelectReadPrettyFieldModel extends AssociationReadPrettyFieldModel {
|
||||
public static readonly supportedFieldInterfaces = [
|
||||
@ -71,7 +70,7 @@ export class AssociationSelectReadPrettyFieldModel extends AssociationReadPretty
|
||||
<React.Fragment key={idx}>
|
||||
{idx > 0 && <span style={{ color: 'rgb(170, 170, 170)' }}>,</span>}
|
||||
<FlowEngineProvider engine={this.flowEngine}>
|
||||
{v?.[fieldNames.label] ? mol.render() : 'N/A'}
|
||||
{v?.[fieldNames.label] ? mol.render() : this.flowEngine.translate('N/A')}
|
||||
</FlowEngineProvider>
|
||||
</React.Fragment>
|
||||
);
|
||||
@ -85,13 +84,13 @@ export class AssociationSelectReadPrettyFieldModel extends AssociationReadPretty
|
||||
|
||||
AssociationSelectReadPrettyFieldModel.registerFlow({
|
||||
key: 'fieldNames',
|
||||
title: 'Specific properties',
|
||||
title: tval('Specific properties'),
|
||||
auto: true,
|
||||
sort: 200,
|
||||
steps: {
|
||||
fieldNames: {
|
||||
use: 'titleField',
|
||||
title: 'Title field',
|
||||
title: tval('Title field'),
|
||||
handler(ctx, params) {
|
||||
const { target } = ctx.model.collectionField.options;
|
||||
const collectionManager = ctx.model.collectionField.collection.collectionManager;
|
||||
|
@ -10,6 +10,7 @@
|
||||
import React from 'react';
|
||||
import dayjs from 'dayjs';
|
||||
import { reactive } from '@nocobase/flow-engine';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { ReadPrettyFieldModel } from './ReadPrettyFieldModel';
|
||||
|
||||
export class DateTimeReadPrettyFieldModel extends ReadPrettyFieldModel {
|
||||
@ -54,11 +55,11 @@ DateTimeReadPrettyFieldModel.registerFlow({
|
||||
key: 'key3',
|
||||
auto: true,
|
||||
sort: 1000,
|
||||
title: 'Specific properties',
|
||||
title: tval('Specific properties'),
|
||||
steps: {
|
||||
dateFormat: {
|
||||
use: 'dateDisplayFormat',
|
||||
title: 'Date display format',
|
||||
title: tval('Date display format'),
|
||||
defaultParams: (ctx) => {
|
||||
const { showTime, dateFormat, timeFormat, picker } = ctx.model.props;
|
||||
return {
|
||||
|
@ -28,7 +28,7 @@ export class JsonReadPrettyFieldModel extends ReadPrettyFieldModel {
|
||||
try {
|
||||
content = JSON.stringify(value, null, space ?? 2);
|
||||
} catch (error) {
|
||||
content = '[Invalid JSON]';
|
||||
content = this.flowEngine.translate('Invalid JSON format');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { reactive } from '@nocobase/flow-engine';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { FieldModel } from '../../base/FieldModel';
|
||||
|
||||
export class ReadPrettyFieldModel extends FieldModel {
|
||||
@ -29,7 +30,7 @@ export class ReadPrettyFieldModel extends FieldModel {
|
||||
ReadPrettyFieldModel.registerFlow({
|
||||
key: 'ReadPrettyFieldDefault',
|
||||
auto: true,
|
||||
title: 'Basic',
|
||||
title: tval('Basic'),
|
||||
sort: 100,
|
||||
steps: {
|
||||
step1: {
|
||||
|
@ -13,6 +13,7 @@ import { FormProvider } from '@formily/react';
|
||||
import { AddActionButton, AddFieldButton, Collection, FlowModelRenderer } from '@nocobase/flow-engine';
|
||||
import { Card } from 'antd';
|
||||
import React from 'react';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { FilterBlockModel } from '../../base/BlockModel';
|
||||
import { FilterFormFieldModel } from './FilterFormFieldModel';
|
||||
|
||||
@ -69,7 +70,7 @@ export class FilterFormModel extends FilterBlockModel {
|
||||
|
||||
FilterFormModel.define({
|
||||
hide: true,
|
||||
title: 'Form',
|
||||
title: tval('Form'),
|
||||
});
|
||||
|
||||
FilterFormModel.registerFlow({
|
||||
@ -82,20 +83,20 @@ FilterFormModel.registerFlow({
|
||||
uiSchema: {
|
||||
dataSourceKey: {
|
||||
type: 'string',
|
||||
title: 'Data Source Key',
|
||||
title: tval('Data Source Key'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'Enter data source key',
|
||||
placeholder: tval('Enter data source key'),
|
||||
},
|
||||
},
|
||||
collectionName: {
|
||||
type: 'string',
|
||||
title: 'Collection Name',
|
||||
title: tval('Collection Name'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'Enter collection name',
|
||||
placeholder: tval('Enter collection name'),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -9,12 +9,13 @@
|
||||
|
||||
import { FlowEngine, MultiRecordResource } from '@nocobase/flow-engine';
|
||||
import { ButtonProps } from 'antd';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { DataBlockModel } from '../../base/BlockModel';
|
||||
import { FilterFormActionModel } from './FilterFormActionModel';
|
||||
|
||||
export class FilterFormResetActionModel extends FilterFormActionModel {
|
||||
defaultProps: ButtonProps = {
|
||||
children: 'Reset',
|
||||
children: tval('Reset'),
|
||||
};
|
||||
}
|
||||
|
||||
@ -27,7 +28,7 @@ FilterFormResetActionModel.registerFlow({
|
||||
step1: {
|
||||
async handler(ctx, params) {
|
||||
if (!ctx.shared?.currentBlockModel?.form) {
|
||||
ctx.globals.message.error('No form available for reset.');
|
||||
ctx.globals.message.error(ctx.globals.flowEngine.translate('No form available for reset.'));
|
||||
return;
|
||||
}
|
||||
const currentBlockModel = ctx.shared.currentBlockModel;
|
||||
|
@ -9,13 +9,14 @@
|
||||
|
||||
import { FlowEngine, MultiRecordResource } from '@nocobase/flow-engine';
|
||||
import type { ButtonProps, ButtonType } from 'antd/es/button';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { ActionModel } from '../../base/ActionModel';
|
||||
import { DataBlockModel } from '../../base/BlockModel';
|
||||
import { FilterFormActionModel } from './FilterFormActionModel';
|
||||
|
||||
export class FilterFormSubmitActionModel extends FilterFormActionModel {
|
||||
defaultProps: ButtonProps = {
|
||||
children: 'Filter',
|
||||
title: tval('Filter'),
|
||||
type: 'primary',
|
||||
};
|
||||
}
|
||||
@ -29,7 +30,7 @@ FilterFormSubmitActionModel.registerFlow({
|
||||
step1: {
|
||||
async handler(ctx, params) {
|
||||
if (!ctx.shared?.currentBlockModel?.form) {
|
||||
ctx.globals.message.error('No form available for submission.');
|
||||
ctx.globals.message.error(ctx.globals.flowEngine.translate('No form available for submission.'));
|
||||
return;
|
||||
}
|
||||
const currentBlockModel = ctx.shared.currentBlockModel;
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
import { Card } from 'antd';
|
||||
import React, { createRef } from 'react';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { BlockModel } from '../../base/BlockModel';
|
||||
|
||||
function waitForRefCallback<T extends HTMLElement>(ref: React.RefObject<T>, cb: (el: T) => void, timeout = 3000) {
|
||||
@ -34,8 +35,8 @@ export class HtmlBlockModel extends BlockModel {
|
||||
}
|
||||
|
||||
HtmlBlockModel.define({
|
||||
title: 'HTML',
|
||||
group: 'Content',
|
||||
title: tval('HTML'),
|
||||
group: tval('Content'),
|
||||
hide: true,
|
||||
defaultOptions: {
|
||||
use: 'HtmlBlockModel',
|
||||
@ -58,7 +59,7 @@ HtmlBlockModel.registerFlow({
|
||||
uiSchema: {
|
||||
html: {
|
||||
type: 'string',
|
||||
title: 'HTML 内容',
|
||||
title: tval('HTML content'),
|
||||
'x-component': 'Input.TextArea',
|
||||
'x-component-props': {
|
||||
autoSize: true,
|
||||
|
@ -895,5 +895,87 @@
|
||||
"Refresh data blocks": "Refresh data blocks",
|
||||
"Select data blocks to refresh": "Select data blocks to refresh",
|
||||
"After successful submission, the selected data blocks will be automatically refreshed.": "After successful submission, the selected data blocks will be automatically refreshed.",
|
||||
"Reset link expiration": "Reset link expiration"
|
||||
"Reset link expiration": "Reset link expiration",
|
||||
"Imperative Drawer": "Imperative Drawer",
|
||||
"Click event": "Click event",
|
||||
"Form duplicate": "Form duplicate",
|
||||
"Filter configuration": "Filter configuration",
|
||||
"Default filter conditions": "Default filter conditions",
|
||||
"No resource selected for deletion": "No resource selected for deletion",
|
||||
"No records selected for deletion": "No records selected for deletion",
|
||||
"Selected records deleted successfully": "Selected records deleted successfully",
|
||||
"No resource selected for bulk edit": "No resource selected for bulk edit",
|
||||
"No records selected for bulk edit": "No records selected for bulk edit",
|
||||
"Successfully": "Successfully",
|
||||
"No resource selected for refresh": "No resource selected for refresh",
|
||||
"No resource or record selected for deletion": "No resource or record selected for deletion",
|
||||
"Record deleted successfully": "Record deleted successfully",
|
||||
"Please select non-filterable fields": "Please select non-filterable fields",
|
||||
"Update record action": "Update record",
|
||||
"Basic configuration": "Basic configuration",
|
||||
"Configure page": "Configure page",
|
||||
"Page Title": "Page Title",
|
||||
"Enter page title": "Enter page title",
|
||||
"Enable tabs": "Enable tabs",
|
||||
"HTML content": "HTML content",
|
||||
"Action": "Action",
|
||||
"General configuration": "General configuration",
|
||||
"Open mode configuration": "Open mode configuration",
|
||||
"Medium": "Medium",
|
||||
"Refresh data after execution": "Refresh data after execution",
|
||||
"Enable refresh": "Enable refresh",
|
||||
"Data refreshed successfully": "Data refreshed successfully",
|
||||
"Secondary confirmation": "Secondary confirmation",
|
||||
"Content": "Content",
|
||||
"Edit link": "Edit link",
|
||||
"URL": "URL",
|
||||
"Do not concatenate search params in the URL": "Do not concatenate search params in the URL",
|
||||
"Search parameters": "Search parameters",
|
||||
"Add parameter": "Add parameter",
|
||||
"When the HTTP method is Post, Put or Patch, and this custom request inside the form, the request body will be automatically filled in with the form data": "When the HTTP method is Post, Put or Patch, and this custom request inside the form, the request body will be automatically filled in with the form data",
|
||||
"\"Content-Type\" only support \"application/json\", and no need to specify": "\"Content-Type\" only support \"application/json\", and no need to specify",
|
||||
"Add request header": "Add request header",
|
||||
"Input request data": "Input request data",
|
||||
"Only support standard JSON data": "Only support standard JSON data",
|
||||
"Timeout config": "Timeout config",
|
||||
"Response type": "Response type",
|
||||
"Action after successful submission": "Action after successful submission",
|
||||
"Stay on the current popup or page": "Stay on the current popup or page",
|
||||
"Return to the previous popup or page": "Return to the previous popup or page",
|
||||
"Actions column": "Actions column",
|
||||
"Data Source Key": "Data Source Key",
|
||||
"Enter data source key": "Enter data source key",
|
||||
"Collection Name": "Collection Name",
|
||||
"Enter collection name": "Enter collection name",
|
||||
"Edit page size": "Edit page size",
|
||||
"Set data scope": "Set data scope",
|
||||
"Edit Title": "Edit Title",
|
||||
"Enter field title": "Enter field title",
|
||||
"Display label": "Display label",
|
||||
"ReadOnly": "ReadOnly",
|
||||
"ReadPretty": "ReadPretty",
|
||||
"Modal": "Modal",
|
||||
"Rows": "Rows",
|
||||
"Configure rows": "Configure rows",
|
||||
"Sizes": "Sizes",
|
||||
"Configure the rows and columns of the grid.": "Configure the rows and columns of the grid.",
|
||||
"Configure the sizes of each row. The value is an array of numbers representing the width of each column in the row.": "Configure the sizes of each row. The value is an array of numbers representing the width of each column in the row.",
|
||||
"Event selected": "Event selected",
|
||||
"Double click": "Double click",
|
||||
"Start": "Start",
|
||||
"End": "End",
|
||||
"Title accessor": "Title accessor",
|
||||
"Enter title accessor": "Enter title accessor",
|
||||
"Start accessor": "Start accessor",
|
||||
"Enter start accessor": "Enter start accessor",
|
||||
"End accessor": "End accessor",
|
||||
"Enter end accessor": "Enter end accessor",
|
||||
"Recommended": "Recommended",
|
||||
"Password Options": "Password Options",
|
||||
"Placeholder": "Placeholder",
|
||||
"Enter placeholder text": "Enter placeholder text",
|
||||
"Check strength": "Check strength",
|
||||
"N/A": "N/A",
|
||||
"No form available for reset.": "No form available for reset.",
|
||||
"No form available for submission.": "No form available for submission."
|
||||
}
|
||||
|
@ -1139,5 +1139,76 @@
|
||||
"Calendar Month":"日历月",
|
||||
"Calendar Year":"日历年",
|
||||
"Scan to input":"扫码录入",
|
||||
"Disable manual input":"禁止手动输入"
|
||||
"Disable manual input":"禁止手动输入",
|
||||
"Imperative Drawer": "命令式抽屉",
|
||||
"Click event": "点击事件",
|
||||
"Form duplicate": "表单复制",
|
||||
"Filter configuration": "筛选配置",
|
||||
"Default filter conditions": "默认筛选条件",
|
||||
"No resource selected for deletion": "未选择要删除的资源",
|
||||
"No records selected for deletion": "未选择要删除的记录",
|
||||
"Selected records deleted successfully": "选中的记录删除成功",
|
||||
"No resource selected for bulk edit": "未选择要批量编辑的资源",
|
||||
"No records selected for bulk edit": "未选择要批量编辑的记录",
|
||||
"Successfully": "成功",
|
||||
"No resource selected for refresh": "未选择要刷新的资源",
|
||||
"No resource or record selected for deletion": "未选择要删除的资源或记录",
|
||||
"Record deleted successfully": "记录删除成功",
|
||||
"Please select non-filterable fields": "请选择不可筛选的字段",
|
||||
"Update record action": "更新记录",
|
||||
"Basic configuration": "基础配置",
|
||||
"Configure page": "配置页面",
|
||||
"Page Title": "页面标题",
|
||||
"Enter page title": "输入页面标题",
|
||||
"Enable tabs": "启用标签页",
|
||||
"HTML content": "HTML内容",
|
||||
"General configuration": "通用配置",
|
||||
"Open mode configuration": "打开方式配置",
|
||||
"Medium": "中",
|
||||
"Refresh data after execution": "执行后刷新数据",
|
||||
"Enable refresh": "启用刷新",
|
||||
"Data refreshed successfully": "数据刷新成功",
|
||||
"When the HTTP method is Post, Put or Patch, and this custom request inside the form, the request body will be automatically filled in with the form data": "当 HTTP 方法为 Post、Put 或 Patch,且此自定义请求在表单内时,请求体将自动填充表单数据",
|
||||
"\"Content-Type\" only support \"application/json\", and no need to specify": "\"Content-Type\" 仅支持 \"application/json\",无需指定",
|
||||
"Add request header": "添加请求头",
|
||||
"Input request data": "输入请求数据",
|
||||
"Only support standard JSON data": "仅支持标准 JSON 数据",
|
||||
"Timeout config": "超时配置",
|
||||
"Response type": "响应类型",
|
||||
"Actions column": "操作列",
|
||||
"Data Source Key": "数据源键",
|
||||
"Enter data source key": "输入数据源键",
|
||||
"Collection Name": "数据表名称",
|
||||
"Enter collection name": "输入数据表名称",
|
||||
"Edit page size": "编辑页面大小",
|
||||
"Set data scope": "设置数据范围",
|
||||
"Edit Title": "编辑标题",
|
||||
"Enter field title": "输入字段标题",
|
||||
"Display label": "显示标签",
|
||||
"ReadOnly": "只读",
|
||||
"ReadPretty": "美观只读",
|
||||
"Modal": "对话框",
|
||||
"Rows": "行",
|
||||
"Configure rows": "配置行",
|
||||
"Sizes": "尺寸",
|
||||
"Configure the rows and columns of the grid.": "配置网格的行和列。",
|
||||
"Configure the sizes of each row. The value is an array of numbers representing the width of each column in the row.": "配置每行的尺寸。该值是一个数字数组,表示行中每列的宽度。",
|
||||
"Event selected": "已选择事件",
|
||||
"Double click": "双击",
|
||||
"Start": "开始",
|
||||
"End": "结束",
|
||||
"Title accessor": "标题访问器",
|
||||
"Enter title accessor": "输入标题访问器",
|
||||
"Start accessor": "开始访问器",
|
||||
"Enter start accessor": "输入开始访问器",
|
||||
"End accessor": "结束访问器",
|
||||
"Enter end accessor": "输入结束访问器",
|
||||
"Recommended": "推荐",
|
||||
"Password Options": "密码选项",
|
||||
"Placeholder": "占位符",
|
||||
"Enter placeholder text": "输入占位符文本",
|
||||
"Check strength": "检查强度",
|
||||
"N/A": "不适用",
|
||||
"No form available for reset.": "没有可用的表单进行重置。",
|
||||
"No form available for submission.": "没有可用的表单进行提交。"
|
||||
}
|
||||
|
@ -1,149 +0,0 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { TranslationUtil } from '../utils';
|
||||
|
||||
describe('TranslationUtil', () => {
|
||||
let translationUtil: TranslationUtil;
|
||||
let mockTranslator: (key: string, options?: any) => string;
|
||||
|
||||
beforeEach(() => {
|
||||
translationUtil = new TranslationUtil();
|
||||
mockTranslator = (key: string, options?: any) => {
|
||||
// 简单的模拟翻译函数
|
||||
if (options?.name) {
|
||||
return key.replace('{name}', options.name);
|
||||
}
|
||||
return `translated_${key}`;
|
||||
};
|
||||
});
|
||||
|
||||
describe('translate', () => {
|
||||
it('should handle simple translation', () => {
|
||||
const result = translationUtil.translate('Hello World', mockTranslator);
|
||||
expect(result).toBe('translated_Hello World');
|
||||
});
|
||||
|
||||
it('should handle translation with options', () => {
|
||||
const result = translationUtil.translate('Hello {name}', mockTranslator, { name: 'John' });
|
||||
expect(result).toBe('Hello John');
|
||||
});
|
||||
|
||||
it('should handle template compilation with single quotes', () => {
|
||||
const template = "{{t('Hello World')}}";
|
||||
const result = translationUtil.translate(template, mockTranslator);
|
||||
expect(result).toBe('translated_Hello World');
|
||||
});
|
||||
|
||||
it('should handle template compilation with double quotes', () => {
|
||||
const template = '{{t("User Name")}}';
|
||||
const result = translationUtil.translate(template, mockTranslator);
|
||||
expect(result).toBe('translated_User Name');
|
||||
});
|
||||
|
||||
it('should handle template compilation with backticks', () => {
|
||||
const template = '{{t(`Email`)}}';
|
||||
const result = translationUtil.translate(template, mockTranslator);
|
||||
expect(result).toBe('translated_Email');
|
||||
});
|
||||
|
||||
it('should handle template with options', () => {
|
||||
const template = '{{t(\'Hello {name}\', {"name": "John"})}}';
|
||||
const result = translationUtil.translate(template, mockTranslator);
|
||||
expect(result).toBe('Hello John');
|
||||
});
|
||||
|
||||
it('should handle template with whitespace', () => {
|
||||
const template = "{{ t ( 'User Name' ) }}";
|
||||
const result = translationUtil.translate(template, mockTranslator);
|
||||
expect(result).toBe('translated_User Name');
|
||||
});
|
||||
|
||||
it('should handle mixed content', () => {
|
||||
const template = "前缀 {{t('User Name')}} 后缀";
|
||||
const result = translationUtil.translate(template, mockTranslator);
|
||||
expect(result).toBe('前缀 translated_User Name 后缀');
|
||||
});
|
||||
|
||||
it('should handle multiple templates', () => {
|
||||
const template = "{{t('Hello')}} {{t('World')}}";
|
||||
const result = translationUtil.translate(template, mockTranslator);
|
||||
expect(result).toBe('translated_Hello translated_World');
|
||||
});
|
||||
|
||||
it('should handle invalid template gracefully', () => {
|
||||
const template = "{{t('unclosed quote)}}";
|
||||
const result = translationUtil.translate(template, mockTranslator);
|
||||
// 由于模板格式不匹配,会被当作普通字符串翻译
|
||||
expect(result).toBe("translated_{{t('unclosed quote)}}");
|
||||
});
|
||||
|
||||
it('should return original value for non-string input', () => {
|
||||
const result = translationUtil.translate(null as any, mockTranslator);
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
|
||||
it('should return empty string for empty input', () => {
|
||||
const result = translationUtil.translate('', mockTranslator);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('caching', () => {
|
||||
it('should cache template results', () => {
|
||||
const template = "{{t('Hello World')}}";
|
||||
|
||||
// 第一次调用
|
||||
const result1 = translationUtil.translate(template, mockTranslator);
|
||||
expect(result1).toBe('translated_Hello World');
|
||||
expect(translationUtil.getCacheSize()).toBe(1);
|
||||
|
||||
// 第二次调用应该使用缓存
|
||||
const result2 = translationUtil.translate(template, mockTranslator);
|
||||
expect(result2).toBe('translated_Hello World');
|
||||
expect(translationUtil.getCacheSize()).toBe(1);
|
||||
});
|
||||
|
||||
it('should not cache simple translations', () => {
|
||||
const result1 = translationUtil.translate('Hello World', mockTranslator);
|
||||
expect(result1).toBe('translated_Hello World');
|
||||
expect(translationUtil.getCacheSize()).toBe(0);
|
||||
});
|
||||
|
||||
it('should clear cache', () => {
|
||||
const template = "{{t('Hello World')}}";
|
||||
translationUtil.translate(template, mockTranslator);
|
||||
expect(translationUtil.getCacheSize()).toBe(1);
|
||||
|
||||
translationUtil.clearCache();
|
||||
expect(translationUtil.getCacheSize()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
it('should handle translator function errors', () => {
|
||||
const errorTranslator = () => {
|
||||
throw new Error('Translation error');
|
||||
};
|
||||
|
||||
const template = "{{t('Hello World')}}";
|
||||
const result = translationUtil.translate(template, errorTranslator);
|
||||
// 应该返回原始模板而不是抛出错误
|
||||
expect(result).toBe("{{t('Hello World')}}");
|
||||
});
|
||||
|
||||
it('should handle invalid JSON options', () => {
|
||||
const template = "{{t('Hello', invalid json)}}";
|
||||
const result = translationUtil.translate(template, mockTranslator);
|
||||
// JSON 解析失败,但仍会调用翻译器,只是没有 options
|
||||
expect(result).toBe('translated_Hello');
|
||||
});
|
||||
});
|
||||
});
|
@ -11,6 +11,7 @@ import { Button, Result, Typography } from 'antd';
|
||||
import React, { FC, useState } from 'react';
|
||||
import { FallbackProps } from 'react-error-boundary';
|
||||
import { useFlowModel } from '../hooks/useFlowModel';
|
||||
import { getT } from '../utils';
|
||||
|
||||
const { Paragraph, Text } = Typography;
|
||||
|
||||
@ -20,6 +21,7 @@ const { Paragraph, Text } = Typography;
|
||||
const FlowErrorFallbackInner: FC<FallbackProps> = ({ error, resetErrorBoundary }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const model = useFlowModel(); // 在这里安全地使用 Hook
|
||||
const t = getT(model);
|
||||
|
||||
const handleCopyError = async () => {
|
||||
setLoading(true);
|
||||
@ -104,9 +106,9 @@ const FlowErrorFallbackInner: FC<FallbackProps> = ({ error, resetErrorBoundary }
|
||||
|
||||
const subTitle = (
|
||||
<span>
|
||||
{'This is likely a NocoBase internals bug. Please open an issue at '}
|
||||
{t('This is likely a NocoBase internals bug. Please open an issue at')}{' '}
|
||||
<a href="https://github.com/nocobase/nocobase/issues" target="_blank" rel="noopener noreferrer">
|
||||
here
|
||||
{t('here')}
|
||||
</a>
|
||||
{model && (
|
||||
<div style={{ marginTop: '8px', fontSize: '12px', color: '#999' }}>
|
||||
@ -121,23 +123,23 @@ const FlowErrorFallbackInner: FC<FallbackProps> = ({ error, resetErrorBoundary }
|
||||
<Result
|
||||
style={{ maxWidth: '60vw', margin: 'auto' }}
|
||||
status="error"
|
||||
title="Render Failed"
|
||||
title={t('Render failed')}
|
||||
subTitle={subTitle}
|
||||
extra={[
|
||||
<Button type="primary" key="feedback" href="https://github.com/nocobase/nocobase/issues" target="_blank">
|
||||
Feedback
|
||||
{t('Feedback')}
|
||||
</Button>,
|
||||
canDownloadLogs && (
|
||||
<Button key="log" loading={loading} onClick={handleDownloadLogs}>
|
||||
Download logs
|
||||
{t('Download logs')}
|
||||
</Button>
|
||||
),
|
||||
<Button key="copy" loading={loading} onClick={handleCopyError}>
|
||||
Copy Error Info
|
||||
{t('Copy error info')}
|
||||
</Button>,
|
||||
resetErrorBoundary && (
|
||||
<Button key="retry" danger onClick={resetErrorBoundary}>
|
||||
Try Again
|
||||
{t('Try again')}
|
||||
</Button>
|
||||
),
|
||||
].filter(Boolean)}
|
||||
@ -197,18 +199,18 @@ export const FlowErrorFallback: FC<FallbackProps> & {
|
||||
<Result
|
||||
style={{ maxWidth: '60vw', margin: 'auto' }}
|
||||
status="error"
|
||||
title="Render Failed"
|
||||
title="Render failed"
|
||||
subTitle={subTitle}
|
||||
extra={[
|
||||
<Button type="primary" key="feedback" href="https://github.com/nocobase/nocobase/issues" target="_blank">
|
||||
Feedback
|
||||
</Button>,
|
||||
<Button key="copy" loading={loading} onClick={handleCopyError}>
|
||||
Copy Error Info
|
||||
Copy error info
|
||||
</Button>,
|
||||
resetErrorBoundary && (
|
||||
<Button key="retry" danger onClick={resetErrorBoundary}>
|
||||
Try Again
|
||||
Try again
|
||||
</Button>
|
||||
),
|
||||
].filter(Boolean)}
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
import { FlowModel } from '../../../../models';
|
||||
import { StepDefinition } from '../../../../types';
|
||||
import { openStepSettings } from './StepSettings';
|
||||
import { getT } from '../../../../utils';
|
||||
|
||||
// Type definitions for better type safety
|
||||
interface StepInfo {
|
||||
@ -110,34 +111,35 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
||||
flattenSubMenus = true,
|
||||
}) => {
|
||||
const { message } = App.useApp();
|
||||
const t = getT(model);
|
||||
|
||||
// 分离处理函数以便更好的代码组织
|
||||
const handleCopyUid = useCallback(async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(model.uid);
|
||||
message.success('UID 已复制到剪贴板');
|
||||
message.success(t('UID copied to clipboard'));
|
||||
} catch (error) {
|
||||
console.error('复制失败:', error);
|
||||
message.error('复制失败,请重试');
|
||||
console.error(t('Copy failed'), ':', error);
|
||||
message.error(t('Copy failed, please try again'));
|
||||
}
|
||||
}, [model.uid, message]);
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
title: t('Confirm delete'),
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
content: '确定要删除此项吗?此操作不可撤销。',
|
||||
okText: '确认删除',
|
||||
content: t('Are you sure you want to delete this item? This action cannot be undone.'),
|
||||
okText: t('Confirm'),
|
||||
okType: 'primary',
|
||||
cancelText: '取消',
|
||||
cancelText: t('Cancel'),
|
||||
async onOk() {
|
||||
try {
|
||||
await model.destroy();
|
||||
} catch (error) {
|
||||
console.error('删除操作失败:', error);
|
||||
console.error(t('Delete operation failed'), ':', error);
|
||||
Modal.error({
|
||||
title: '删除失败',
|
||||
content: '删除操作失败,请检查控制台获取详细信息。',
|
||||
title: t('Delete failed'),
|
||||
content: t('Delete operation failed, please check the console for details.'),
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -181,7 +183,7 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
||||
stepKey,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('配置弹窗已取消或出错:', error);
|
||||
console.log(t('Configuration popup cancelled or error'), ':', error);
|
||||
}
|
||||
},
|
||||
[model],
|
||||
@ -231,12 +233,14 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
||||
|
||||
// 如果step使用了action,检查action是否有uiSchema
|
||||
let hasActionUiSchema = false;
|
||||
let stepTitle = actionStep.title;
|
||||
if (actionStep.use) {
|
||||
try {
|
||||
const action = targetModel.flowEngine?.getAction?.(actionStep.use);
|
||||
hasActionUiSchema = action && action.uiSchema != null;
|
||||
stepTitle = stepTitle || action.title;
|
||||
} catch (error) {
|
||||
console.warn(`获取action '${actionStep.use}' 失败:`, error);
|
||||
console.warn(t('Failed to get action {{action}}', { action: actionStep.use }), ':', error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,7 +257,7 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
||||
stepKey,
|
||||
step: actionStep,
|
||||
uiSchema: mergedUiSchema,
|
||||
title: actionStep.title || stepKey,
|
||||
title: t(stepTitle) || stepKey,
|
||||
modelKey, // 添加模型标识
|
||||
};
|
||||
})
|
||||
@ -263,7 +267,11 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
||||
})
|
||||
.filter(Boolean);
|
||||
} catch (error) {
|
||||
console.error(`获取模型 '${targetModel?.uid || 'unknown'}' 的可配置flows失败:`, error);
|
||||
console.error(
|
||||
t('Failed to get configurable flows for model {{model}}', { model: targetModel?.uid || 'unknown' }),
|
||||
':',
|
||||
error,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}, []);
|
||||
@ -338,7 +346,7 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
||||
// 在平铺模式下始终按流程分组
|
||||
items.push({
|
||||
key: groupKey,
|
||||
label: flow.title || flow.key,
|
||||
label: t(flow.title) || flow.key,
|
||||
type: 'group',
|
||||
});
|
||||
|
||||
@ -353,7 +361,7 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
||||
items.push({
|
||||
key: uniqueKey,
|
||||
icon: <SettingOutlined />,
|
||||
label: stepInfo.title,
|
||||
label: t(stepInfo.title),
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -382,7 +390,7 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
||||
|
||||
items.push({
|
||||
key: groupKey,
|
||||
label: flow.title || flow.key,
|
||||
label: t(flow.title) || flow.key,
|
||||
type: 'group',
|
||||
});
|
||||
|
||||
@ -392,7 +400,7 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
||||
items.push({
|
||||
key: uniqueKey,
|
||||
icon: <SettingOutlined />,
|
||||
label: stepInfo.title,
|
||||
label: t(stepInfo.title),
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -408,7 +416,7 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
||||
subMenuChildren.push({
|
||||
key: uniqueKey,
|
||||
icon: <SettingOutlined />,
|
||||
label: stepInfo.title,
|
||||
label: t(stepInfo.title),
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -443,7 +451,7 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
||||
items.push({
|
||||
key: 'copy-uid',
|
||||
icon: <CopyOutlined />,
|
||||
label: '复制 UID',
|
||||
label: t('Copy UID'),
|
||||
});
|
||||
}
|
||||
|
||||
@ -452,7 +460,7 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
||||
items.push({
|
||||
key: 'delete',
|
||||
icon: <DeleteOutlined />,
|
||||
label: '删除',
|
||||
label: t('Delete'),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -467,7 +475,7 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
||||
|
||||
// 渲染前验证模型
|
||||
if (!model || !model.uid) {
|
||||
console.warn('提供的模型无效');
|
||||
console.warn(t('Invalid model provided'));
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ import { observer } from '@formily/react';
|
||||
import { FlowModel } from '../../../../models';
|
||||
import { useFlowModelById } from '../../../../hooks';
|
||||
import { openStepSettingsDialog } from './StepSettingsDialog';
|
||||
import { getT } from '../../../../utils';
|
||||
|
||||
// 右键菜单组件接口
|
||||
interface ModelProvidedProps {
|
||||
@ -70,25 +71,26 @@ const FlowsContextMenu: React.FC<FlowsContextMenuProps> = (props) => {
|
||||
// 使用传入的model
|
||||
const FlowsContextMenuWithModel: React.FC<ModelProvidedProps> = observer(
|
||||
({ model, children, enabled = true, position = 'right', showDeleteButton = true }) => {
|
||||
const t = getT(model);
|
||||
const handleMenuClick = useCallback(
|
||||
({ key }: { key: string }) => {
|
||||
if (key === 'delete') {
|
||||
// 处理删除操作
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
title: t('Confirm delete'),
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
content: '确定要删除此项吗?此操作不可撤销。',
|
||||
okText: '确认删除',
|
||||
content: t('Are you sure you want to delete this item? This action cannot be undone.'),
|
||||
okText: t('Confirm Delete'),
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
cancelText: t('Cancel'),
|
||||
onOk() {
|
||||
try {
|
||||
model.dispatchEvent('remove');
|
||||
} catch (error) {
|
||||
console.error('删除操作失败:', error);
|
||||
console.error(t('Delete operation failed'), ':', error);
|
||||
Modal.error({
|
||||
title: '删除失败',
|
||||
content: '删除操作失败,请检查控制台获取详细信息。',
|
||||
title: t('Delete failed'),
|
||||
content: t('Delete operation failed, please check the console for details.'),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -16,6 +16,7 @@ import { ToolbarItemConfig } from '../../../../types';
|
||||
import { useFlowModelById } from '../../../../hooks';
|
||||
import { useFlowEngine } from '../../../../provider';
|
||||
import { FlowEngine } from '../../../../flowEngine';
|
||||
import { getT } from '../../../../utils';
|
||||
import { Droppable } from '../../../dnd';
|
||||
|
||||
// 检测DOM中直接子元素是否包含button元素的辅助函数
|
||||
@ -300,7 +301,8 @@ const FlowsFloatContextMenuWithModel: React.FC<ModelProvidedProps> = observer(
|
||||
}, []);
|
||||
|
||||
if (!model) {
|
||||
return <Alert message="提供的模型无效" type="error" />;
|
||||
const t = getT(model || ({} as FlowModel));
|
||||
return <Alert message={t('Invalid model provided')} type="error" />;
|
||||
}
|
||||
|
||||
// 如果未启用或没有children,直接返回children
|
||||
@ -357,9 +359,10 @@ const FlowsFloatContextMenuWithModelById: React.FC<ModelByIdProps> = observer(
|
||||
extraToolbarItems: extraToolbarItems,
|
||||
}) => {
|
||||
const model = useFlowModelById(uid, modelClassName);
|
||||
const flowEngine = useFlowEngine();
|
||||
|
||||
if (!model) {
|
||||
return <Alert message={`未找到ID为 ${uid} 的模型`} type="error" />;
|
||||
return <Alert message={flowEngine.translate('Model with ID {{uid}} not found', { uid })} type="error" />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -12,8 +12,9 @@ import { message, Button } from 'antd';
|
||||
import React from 'react';
|
||||
import { FlowModel } from '../../../../models';
|
||||
import { StepDefinition } from '../../../../types';
|
||||
import { resolveDefaultParams, resolveUiSchema } from '../../../../utils';
|
||||
import { resolveDefaultParams, resolveUiSchema, compileUiSchema, getT } from '../../../../utils';
|
||||
import { StepSettingContextProvider, StepSettingContextType, useStepSettingContext } from './StepSettingContext';
|
||||
import { toJS } from '@formily/reactive';
|
||||
|
||||
/**
|
||||
* 检查步骤是否已经有了所需的配置值
|
||||
@ -116,11 +117,14 @@ export interface StepFormDialogProps {
|
||||
const openRequiredParamsStepFormDialog = async ({
|
||||
model,
|
||||
dialogWidth = 800,
|
||||
dialogTitle = '步骤参数配置',
|
||||
dialogTitle,
|
||||
}: StepFormDialogProps): Promise<any> => {
|
||||
const t = getT(model);
|
||||
const defaultTitle = dialogTitle || t('Step parameter configuration');
|
||||
|
||||
if (!model) {
|
||||
message.error('提供的模型无效');
|
||||
throw new Error('提供的模型无效');
|
||||
message.error(t('Invalid model provided'));
|
||||
throw new Error(t('Invalid model provided'));
|
||||
}
|
||||
|
||||
// 创建一个Promise, 并最终返回,当此弹窗关闭时此promise resolve或者reject
|
||||
@ -171,8 +175,8 @@ const openRequiredParamsStepFormDialog = async ({
|
||||
const resolvedStepUiSchema = await resolveUiSchema(stepUiSchema, paramsContext);
|
||||
|
||||
// 合并uiSchema,确保step的uiSchema优先级更高
|
||||
const mergedUiSchema = { ...resolvedActionUiSchema };
|
||||
Object.entries(resolvedStepUiSchema).forEach(([fieldKey, schema]) => {
|
||||
const mergedUiSchema = { ...toJS(resolvedActionUiSchema) };
|
||||
Object.entries(toJS(resolvedStepUiSchema)).forEach(([fieldKey, schema]) => {
|
||||
if (mergedUiSchema[fieldKey]) {
|
||||
mergedUiSchema[fieldKey] = { ...mergedUiSchema[fieldKey], ...schema };
|
||||
} else {
|
||||
@ -224,7 +228,11 @@ const openRequiredParamsStepFormDialog = async ({
|
||||
// 解析 defaultParams
|
||||
const resolvedActionDefaultParams = await resolveDefaultParams(actionDefaultParams, paramsContext);
|
||||
const resolvedDefaultParams = await resolveDefaultParams(step.defaultParams, paramsContext);
|
||||
const mergedParams = { ...resolvedActionDefaultParams, ...resolvedDefaultParams, ...stepParams };
|
||||
const mergedParams = {
|
||||
...toJS(resolvedActionDefaultParams),
|
||||
...toJS(resolvedDefaultParams),
|
||||
...toJS(stepParams),
|
||||
};
|
||||
|
||||
if (Object.keys(mergedParams).length > 0) {
|
||||
if (!initialValues[flowKey]) {
|
||||
@ -300,17 +308,24 @@ const openRequiredParamsStepFormDialog = async ({
|
||||
// 创建分步表单实例(只有多个步骤时才需要)
|
||||
const formStep = requiredSteps.length > 1 ? FormStep.createFormStep(0) : null;
|
||||
|
||||
const flowEngine = model.flowEngine || {};
|
||||
const scopes = {
|
||||
formStep,
|
||||
totalSteps: requiredSteps.length,
|
||||
requiredSteps,
|
||||
useStepSettingContext,
|
||||
...flowEngine.flowSettings?.scopes,
|
||||
};
|
||||
|
||||
// 创建FormDialog
|
||||
const formDialog = FormDialog(
|
||||
{
|
||||
title: dialogTitle,
|
||||
title: dialogTitle || t('Step parameter configuration'),
|
||||
width: dialogWidth,
|
||||
footer: null, // 移除默认的底部按钮,使用自定义的导航按钮
|
||||
destroyOnClose: true,
|
||||
},
|
||||
(form) => {
|
||||
const flowEngine = model.flowEngine || {};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await form.submit();
|
||||
@ -329,7 +344,7 @@ const openRequiredParamsStepFormDialog = async ({
|
||||
resolve(currentValues);
|
||||
formDialog.close();
|
||||
} catch (error) {
|
||||
console.error('提交表单时出错:', error);
|
||||
console.error(t('Error submitting form'), ':', error);
|
||||
// reject(error);
|
||||
// 这里不需要reject,因为forConfirm会处理
|
||||
}
|
||||
@ -340,37 +355,38 @@ const openRequiredParamsStepFormDialog = async ({
|
||||
resolve({});
|
||||
};
|
||||
|
||||
const dialogScopes = {
|
||||
...scopes,
|
||||
closeDialog: handleClose,
|
||||
handleNext: () => {
|
||||
// 验证当前步骤的表单
|
||||
form
|
||||
.validate()
|
||||
.then(() => {
|
||||
if (formStep) {
|
||||
formStep.next();
|
||||
}
|
||||
})
|
||||
.catch((errors: any) => {
|
||||
console.log(t('Form validation failed'), ':', errors);
|
||||
// 可以在这里添加更详细的错误处理
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// 编译 formSchema 中的表达式
|
||||
const compiledFormSchema = compileUiSchema(dialogScopes, formSchema);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MultiStepContextProvider model={model} requiredSteps={requiredSteps} formStep={formStep}>
|
||||
<SchemaField
|
||||
schema={formSchema}
|
||||
schema={compiledFormSchema}
|
||||
components={{
|
||||
FormStep,
|
||||
...flowEngine.flowSettings?.components,
|
||||
}}
|
||||
scope={{
|
||||
formStep,
|
||||
totalSteps: requiredSteps.length,
|
||||
requiredSteps,
|
||||
useStepSettingContext,
|
||||
closeDialog: handleClose,
|
||||
handleNext: () => {
|
||||
// 验证当前步骤的表单
|
||||
form
|
||||
.validate()
|
||||
.then(() => {
|
||||
if (formStep) {
|
||||
formStep.next();
|
||||
}
|
||||
})
|
||||
.catch((errors: any) => {
|
||||
console.log('表单验证失败:', errors);
|
||||
// 可以在这里添加更详细的错误处理
|
||||
});
|
||||
},
|
||||
...flowEngine.flowSettings?.scopes,
|
||||
}}
|
||||
scope={dialogScopes}
|
||||
/>
|
||||
</MultiStepContextProvider>
|
||||
<FormConsumer>
|
||||
@ -388,7 +404,7 @@ const openRequiredParamsStepFormDialog = async ({
|
||||
{/* 只有一个步骤时,只显示完成按钮 */}
|
||||
{requiredSteps.length === 1 ? (
|
||||
<Button type="primary" onClick={handleSubmit}>
|
||||
完成配置
|
||||
{t('Complete configuration')}
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
@ -400,7 +416,7 @@ const openRequiredParamsStepFormDialog = async ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
上一步
|
||||
{t('Previous step')}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!formStep?.allowNext}
|
||||
@ -415,7 +431,7 @@ const openRequiredParamsStepFormDialog = async ({
|
||||
}
|
||||
})
|
||||
.catch((errors: any) => {
|
||||
console.log('表单验证失败:', errors);
|
||||
console.log(t('Form validation failed'), ':', errors);
|
||||
// 可以在这里添加更详细的错误处理
|
||||
});
|
||||
}}
|
||||
@ -423,7 +439,7 @@ const openRequiredParamsStepFormDialog = async ({
|
||||
display: (formStep?.current ?? 0) < requiredSteps.length - 1 ? 'inline-block' : 'none',
|
||||
}}
|
||||
>
|
||||
下一步
|
||||
{t('Next step')}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={formStep?.allowNext}
|
||||
@ -433,7 +449,7 @@ const openRequiredParamsStepFormDialog = async ({
|
||||
display: (formStep?.current ?? 0) >= requiredSteps.length - 1 ? 'inline-block' : 'none',
|
||||
}}
|
||||
>
|
||||
完成配置
|
||||
{t('Complete configuration')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
@ -447,10 +463,10 @@ const openRequiredParamsStepFormDialog = async ({
|
||||
|
||||
// 打开对话框
|
||||
formDialog.open({
|
||||
initialValues,
|
||||
initialValues: compileUiSchema(scopes, initialValues),
|
||||
});
|
||||
} catch (error) {
|
||||
reject(new Error(`导入 FormDialog 或 FormStep 失败: ${error.message}`));
|
||||
reject(new Error(`${t('Failed to import FormDialog or FormStep')}: ${error.message}`));
|
||||
}
|
||||
})();
|
||||
}).catch((e) => {
|
||||
|
@ -12,6 +12,7 @@ import { StepSettingsProps } from '../../../../types';
|
||||
import { openStepSettingsDialog } from './StepSettingsDialog';
|
||||
import { openStepSettingsDrawer } from './StepSettingsDrawer';
|
||||
import { FlowModel } from '../../../../models';
|
||||
import { getT } from '../../../../utils';
|
||||
|
||||
/**
|
||||
* 统一的步骤设置入口函数
|
||||
@ -24,9 +25,11 @@ import { FlowModel } from '../../../../models';
|
||||
* @returns Promise<any> 返回表单提交的值
|
||||
*/
|
||||
const openStepSettings = async ({ model, flowKey, stepKey, width = 600, title }: StepSettingsProps): Promise<any> => {
|
||||
const t = getT(model);
|
||||
|
||||
if (!model) {
|
||||
message.error('提供的模型无效');
|
||||
throw new Error('提供的模型无效');
|
||||
message.error(t('Invalid model provided'));
|
||||
throw new Error(t('Invalid model provided'));
|
||||
}
|
||||
|
||||
// 获取流程和步骤信息
|
||||
@ -34,13 +37,13 @@ const openStepSettings = async ({ model, flowKey, stepKey, width = 600, title }:
|
||||
const step = flow?.steps?.[stepKey];
|
||||
|
||||
if (!flow) {
|
||||
message.error(`未找到Key为 ${flowKey} 的流程`);
|
||||
throw new Error(`未找到Key为 ${flowKey} 的流程`);
|
||||
message.error(t('Flow with key {{flowKey}} not found', { flowKey }));
|
||||
throw new Error(t('Flow with key {{flowKey}} not found', { flowKey }));
|
||||
}
|
||||
|
||||
if (!step) {
|
||||
message.error(`未找到Key为 ${stepKey} 的步骤`);
|
||||
throw new Error(`未找到Key为 ${stepKey} 的步骤`);
|
||||
message.error(t('Step with key {{stepKey}} not found', { stepKey }));
|
||||
throw new Error(t('Step with key {{stepKey}} not found', { stepKey }));
|
||||
}
|
||||
|
||||
// 检查步骤的 settingMode 配置,默认为 'dialog'
|
||||
@ -84,7 +87,7 @@ const isStepUsingDrawerMode = (model: FlowModel, flowKey: string, stepKey: strin
|
||||
|
||||
return step.settingMode === 'drawer';
|
||||
} catch (error) {
|
||||
console.warn('检查步骤设置模式时出错:', error);
|
||||
console.warn('Error checking step setting mode:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@ -107,7 +110,7 @@ const getStepSettingMode = (model: FlowModel, flowKey: string, stepKey: string):
|
||||
|
||||
return step.settingMode || 'dialog';
|
||||
} catch (error) {
|
||||
console.warn('获取步骤设置模式时出错:', error);
|
||||
console.warn('Error getting step setting mode:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
@ -11,8 +11,9 @@ import { createSchemaField, ISchema } from '@formily/react';
|
||||
import { message } from 'antd';
|
||||
import React from 'react';
|
||||
import { StepSettingsDialogProps } from '../../../../types';
|
||||
import { resolveDefaultParams, resolveUiSchema } from '../../../../utils';
|
||||
import { resolveDefaultParams, resolveUiSchema, compileUiSchema, getT } from '../../../../utils';
|
||||
import { StepSettingContextProvider, StepSettingContextType, useStepSettingContext } from './StepSettingContext';
|
||||
import { toJS } from '@formily/reactive';
|
||||
|
||||
const SchemaField = createSchemaField();
|
||||
|
||||
@ -32,9 +33,11 @@ const openStepSettingsDialog = async ({
|
||||
dialogWidth = 600,
|
||||
dialogTitle,
|
||||
}: StepSettingsDialogProps): Promise<any> => {
|
||||
const t = getT(model);
|
||||
|
||||
if (!model) {
|
||||
message.error('提供的模型无效');
|
||||
throw new Error('提供的模型无效');
|
||||
message.error(t('Invalid model provided'));
|
||||
throw new Error(t('Invalid model provided'));
|
||||
}
|
||||
|
||||
// 获取流程和步骤信息
|
||||
@ -42,16 +45,16 @@ const openStepSettingsDialog = async ({
|
||||
const step = flow?.steps?.[stepKey];
|
||||
|
||||
if (!flow) {
|
||||
message.error(`未找到Key为 ${flowKey} 的流程`);
|
||||
throw new Error(`未找到Key为 ${flowKey} 的流程`);
|
||||
message.error(t('Flow with key {{flowKey}} not found', { flowKey }));
|
||||
throw new Error(t('Flow with key {{flowKey}} not found', { flowKey }));
|
||||
}
|
||||
|
||||
if (!step) {
|
||||
message.error(`未找到Key为 ${stepKey} 的步骤`);
|
||||
throw new Error(`未找到Key为 ${stepKey} 的步骤`);
|
||||
message.error(t('Step with key {{stepKey}} not found', { stepKey }));
|
||||
throw new Error(t('Step with key {{stepKey}} not found', { stepKey }));
|
||||
}
|
||||
|
||||
const title = dialogTitle || (step ? `${step.title || stepKey} - 配置` : `步骤配置 - ${stepKey}`);
|
||||
let title = step.title;
|
||||
|
||||
// 创建参数解析上下文
|
||||
const paramsContext = {
|
||||
@ -72,6 +75,7 @@ const openStepSettingsDialog = async ({
|
||||
actionUiSchema = action.uiSchema;
|
||||
}
|
||||
actionDefaultParams = action.defaultParams || {};
|
||||
title = title || action.title;
|
||||
}
|
||||
|
||||
// 解析动态 uiSchema
|
||||
@ -79,8 +83,8 @@ const openStepSettingsDialog = async ({
|
||||
const resolvedStepUiSchema = await resolveUiSchema(stepUiSchema, paramsContext);
|
||||
|
||||
// 合并uiSchema,确保step的uiSchema优先级更高
|
||||
const mergedUiSchema = { ...resolvedActionUiSchema };
|
||||
Object.entries(resolvedStepUiSchema).forEach(([fieldKey, schema]) => {
|
||||
const mergedUiSchema = { ...toJS(resolvedActionUiSchema) };
|
||||
Object.entries(toJS(resolvedStepUiSchema)).forEach(([fieldKey, schema]) => {
|
||||
if (mergedUiSchema[fieldKey]) {
|
||||
mergedUiSchema[fieldKey] = { ...mergedUiSchema[fieldKey], ...schema };
|
||||
} else {
|
||||
@ -90,17 +94,23 @@ const openStepSettingsDialog = async ({
|
||||
|
||||
// 如果没有可配置的UI Schema,显示提示
|
||||
if (Object.keys(mergedUiSchema).length === 0) {
|
||||
message.info('此步骤没有可配置的参数');
|
||||
message.info(t('This step has no configurable parameters'));
|
||||
return {};
|
||||
}
|
||||
|
||||
const flowEngine = model.flowEngine;
|
||||
const scopes = {
|
||||
useStepSettingContext,
|
||||
...flowEngine.flowSettings?.scopes,
|
||||
};
|
||||
|
||||
// 获取初始值
|
||||
const stepParams = model.getStepParams(flowKey, stepKey) || {};
|
||||
|
||||
// 解析 defaultParams
|
||||
const resolvedDefaultParams = await resolveDefaultParams(step.defaultParams, paramsContext);
|
||||
const resolveActionDefaultParams = await resolveDefaultParams(actionDefaultParams, paramsContext);
|
||||
const initialValues = { ...resolveActionDefaultParams, ...resolvedDefaultParams, ...stepParams };
|
||||
const initialValues = { ...toJS(resolveActionDefaultParams), ...toJS(resolvedDefaultParams), ...toJS(stepParams) };
|
||||
|
||||
// 构建表单Schema
|
||||
const formSchema: ISchema = {
|
||||
@ -122,21 +132,19 @@ const openStepSettingsDialog = async ({
|
||||
try {
|
||||
({ FormDialog } = await import('@formily/antd-v5'));
|
||||
} catch (error) {
|
||||
throw new Error(`导入 FormDialog 失败: ${error.message}`);
|
||||
throw new Error(`${t('Failed to import FormDialog')}: ${error.message}`);
|
||||
}
|
||||
|
||||
// 创建FormDialog
|
||||
const formDialog = FormDialog(
|
||||
{
|
||||
title,
|
||||
title: dialogTitle || `${t(title)} - ${t('Configuration')}`,
|
||||
width: dialogWidth,
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
okText: t('OK'),
|
||||
cancelText: t('Cancel'),
|
||||
destroyOnClose: true,
|
||||
},
|
||||
(form) => {
|
||||
const flowEngine = model.flowEngine;
|
||||
|
||||
// 创建上下文值
|
||||
const contextValue: StepSettingContextType = {
|
||||
model,
|
||||
@ -148,17 +156,17 @@ const openStepSettingsDialog = async ({
|
||||
stepKey,
|
||||
};
|
||||
|
||||
// 编译 formSchema 中的表达式
|
||||
const compiledFormSchema = compileUiSchema(scopes, formSchema);
|
||||
|
||||
return (
|
||||
<StepSettingContextProvider value={contextValue}>
|
||||
<SchemaField
|
||||
schema={formSchema}
|
||||
schema={compiledFormSchema}
|
||||
components={{
|
||||
...flowEngine.flowSettings?.components,
|
||||
}}
|
||||
scope={{
|
||||
useStepSettingContext,
|
||||
...flowEngine.flowSettings?.scopes,
|
||||
}}
|
||||
scope={scopes}
|
||||
/>
|
||||
</StepSettingContextProvider>
|
||||
);
|
||||
@ -172,11 +180,11 @@ const openStepSettingsDialog = async ({
|
||||
const currentValues = payload.values;
|
||||
model.setStepParams(flowKey, stepKey, currentValues);
|
||||
await model.save();
|
||||
message.success('配置已保存');
|
||||
message.success(t('Configuration saved'));
|
||||
next(payload);
|
||||
} catch (error) {
|
||||
console.error('保存配置时出错:', error);
|
||||
message.error('保存配置时出错,请检查控制台');
|
||||
console.error(t('Error saving configuration'), ':', error);
|
||||
message.error(t('Error saving configuration, please check console'));
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
@ -185,7 +193,7 @@ const openStepSettingsDialog = async ({
|
||||
|
||||
// 打开对话框
|
||||
return formDialog.open({
|
||||
initialValues,
|
||||
initialValues: compileUiSchema(scopes, initialValues),
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -10,9 +10,11 @@
|
||||
import { createSchemaField, ISchema } from '@formily/react';
|
||||
import { message, Button, Space } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { StepDefinition, StepSettingsDrawerProps } from '../../../../types';
|
||||
import { resolveDefaultParams, resolveUiSchema } from '../../../../utils';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { StepSettingsDrawerProps } from '../../../../types';
|
||||
import { resolveDefaultParams, resolveUiSchema, compileUiSchema, getT } from '../../../../utils';
|
||||
import { StepSettingContextProvider, StepSettingContextType, useStepSettingContext } from './StepSettingContext';
|
||||
import { toJS } from '@formily/reactive';
|
||||
|
||||
const SchemaField = createSchemaField();
|
||||
|
||||
@ -32,9 +34,11 @@ const openStepSettingsDrawer = async ({
|
||||
drawerWidth = 600,
|
||||
drawerTitle,
|
||||
}: StepSettingsDrawerProps): Promise<any> => {
|
||||
const t = getT(model);
|
||||
|
||||
if (!model) {
|
||||
message.error('提供的模型无效');
|
||||
throw new Error('提供的模型无效');
|
||||
message.error(t('Invalid model provided'));
|
||||
throw new Error(t('Invalid model provided'));
|
||||
}
|
||||
|
||||
// 获取流程和步骤信息
|
||||
@ -42,16 +46,16 @@ const openStepSettingsDrawer = async ({
|
||||
const step = flow?.steps?.[stepKey];
|
||||
|
||||
if (!flow) {
|
||||
message.error(`未找到Key为 ${flowKey} 的流程`);
|
||||
throw new Error(`未找到Key为 ${flowKey} 的流程`);
|
||||
message.error(t('Flow with key {{flowKey}} not found', { flowKey }));
|
||||
throw new Error(t('Flow with key {{flowKey}} not found', { flowKey }));
|
||||
}
|
||||
|
||||
if (!step) {
|
||||
message.error(`未找到Key为 ${stepKey} 的步骤`);
|
||||
throw new Error(`未找到Key为 ${stepKey} 的步骤`);
|
||||
message.error(t('Step with key {{stepKey}} not found', { stepKey }));
|
||||
throw new Error(t('Step with key {{stepKey}} not found', { stepKey }));
|
||||
}
|
||||
|
||||
const title = drawerTitle || (step ? `${step.title || stepKey} - 配置` : `步骤配置 - ${stepKey}`);
|
||||
let title = step.title;
|
||||
|
||||
// 创建参数解析上下文
|
||||
const paramsContext = {
|
||||
@ -71,6 +75,7 @@ const openStepSettingsDrawer = async ({
|
||||
actionUiSchema = action.uiSchema;
|
||||
}
|
||||
actionDefaultParams = action.defaultParams || {};
|
||||
title = title || action.title;
|
||||
}
|
||||
|
||||
// 解析动态 uiSchema
|
||||
@ -78,8 +83,8 @@ const openStepSettingsDrawer = async ({
|
||||
const resolvedStepUiSchema = await resolveUiSchema(stepUiSchema, paramsContext);
|
||||
|
||||
// 合并uiSchema,确保step的uiSchema优先级更高
|
||||
const mergedUiSchema = { ...resolvedActionUiSchema };
|
||||
Object.entries(resolvedStepUiSchema).forEach(([fieldKey, schema]) => {
|
||||
const mergedUiSchema = { ...toJS(resolvedActionUiSchema) };
|
||||
Object.entries(toJS(resolvedStepUiSchema)).forEach(([fieldKey, schema]) => {
|
||||
if (mergedUiSchema[fieldKey]) {
|
||||
mergedUiSchema[fieldKey] = { ...mergedUiSchema[fieldKey], ...schema };
|
||||
} else {
|
||||
@ -89,17 +94,23 @@ const openStepSettingsDrawer = async ({
|
||||
|
||||
// 如果没有可配置的UI Schema,显示提示
|
||||
if (Object.keys(mergedUiSchema).length === 0) {
|
||||
message.info('此步骤没有可配置的参数');
|
||||
message.info(t('This step has no configurable parameters'));
|
||||
return {};
|
||||
}
|
||||
|
||||
// 获取初始值
|
||||
const stepParams = model.getStepParams(flowKey, stepKey) || {};
|
||||
|
||||
const flowEngine = model.flowEngine;
|
||||
const scopes = {
|
||||
useStepSettingContext,
|
||||
...flowEngine.flowSettings?.scopes,
|
||||
};
|
||||
|
||||
// 解析 defaultParams
|
||||
const resolvedDefaultParams = await resolveDefaultParams(step.defaultParams, paramsContext);
|
||||
const resolveActionDefaultParams = await resolveDefaultParams(actionDefaultParams, paramsContext);
|
||||
const initialValues = { ...resolveActionDefaultParams, ...resolvedDefaultParams, ...stepParams };
|
||||
const initialValues = { ...toJS(resolveActionDefaultParams), ...toJS(resolvedDefaultParams), ...toJS(stepParams) };
|
||||
|
||||
// 构建表单Schema
|
||||
const formSchema: ISchema = {
|
||||
@ -122,13 +133,13 @@ const openStepSettingsDrawer = async ({
|
||||
({ Form } = await import('@formily/antd-v5'));
|
||||
({ createForm } = await import('@formily/core'));
|
||||
} catch (error) {
|
||||
throw new Error(`导入 Formily 组件失败: ${error.message}`);
|
||||
throw new Error(`${t('Failed to import Formily components')}: ${error.message}`);
|
||||
}
|
||||
|
||||
// 获取drawer API
|
||||
const drawer = model.flowEngine?.context?.drawer;
|
||||
if (!drawer) {
|
||||
throw new Error('Drawer API 不可用,请确保在 FlowEngineGlobalsContextProvider 内使用');
|
||||
throw new Error(t('Drawer API is not available, please ensure it is used within FlowEngineGlobalsContextProvider'));
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
@ -137,11 +148,12 @@ const openStepSettingsDrawer = async ({
|
||||
|
||||
// 创建表单实例
|
||||
const form = createForm({
|
||||
initialValues,
|
||||
initialValues: compileUiSchema(scopes, initialValues),
|
||||
});
|
||||
|
||||
// 创建抽屉内容组件
|
||||
const DrawerContent: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
@ -156,13 +168,13 @@ const openStepSettingsDrawer = async ({
|
||||
model.setStepParams(flowKey, stepKey, currentValues);
|
||||
await model.save();
|
||||
|
||||
message.success('配置已保存');
|
||||
message.success(t('Configuration saved'));
|
||||
isResolved = true;
|
||||
drawerRef.destroy();
|
||||
resolve(currentValues);
|
||||
} catch (error) {
|
||||
console.error('保存配置时出错:', error);
|
||||
message.error('保存配置时出错,请检查控制台');
|
||||
console.error(t('Error saving configuration'), ':', error);
|
||||
message.error(t('Error saving configuration, please check console'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@ -176,8 +188,6 @@ const openStepSettingsDrawer = async ({
|
||||
drawerRef.destroy();
|
||||
};
|
||||
|
||||
const flowEngine = model.flowEngine;
|
||||
|
||||
// 创建上下文值
|
||||
const contextValue: StepSettingContextType = {
|
||||
model,
|
||||
@ -189,20 +199,20 @@ const openStepSettingsDrawer = async ({
|
||||
stepKey,
|
||||
};
|
||||
|
||||
// 编译 formSchema 中的表达式
|
||||
const compiledFormSchema = compileUiSchema(scopes, formSchema);
|
||||
|
||||
return (
|
||||
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={{ flex: 1, overflow: 'auto', padding: '16px' }}>
|
||||
<Form form={form} layout="vertical">
|
||||
<StepSettingContextProvider value={contextValue}>
|
||||
<SchemaField
|
||||
schema={formSchema}
|
||||
schema={compiledFormSchema}
|
||||
components={{
|
||||
...flowEngine.flowSettings?.components,
|
||||
}}
|
||||
scope={{
|
||||
useStepSettingContext,
|
||||
...flowEngine.flowSettings?.scopes,
|
||||
}}
|
||||
scope={scopes}
|
||||
/>
|
||||
</StepSettingContextProvider>
|
||||
</Form>
|
||||
@ -217,9 +227,9 @@ const openStepSettingsDrawer = async ({
|
||||
}}
|
||||
>
|
||||
<Space>
|
||||
<Button onClick={handleCancel}>取消</Button>
|
||||
<Button onClick={handleCancel}>{t('Cancel')}</Button>
|
||||
<Button type="primary" loading={loading} onClick={handleSubmit}>
|
||||
确认
|
||||
{t('OK')}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
@ -229,7 +239,7 @@ const openStepSettingsDrawer = async ({
|
||||
|
||||
// 打开抽屉
|
||||
const drawerRef = drawer.open({
|
||||
title,
|
||||
title: drawerTitle || `${t(title)} - ${t('Configuration')}`,
|
||||
width: drawerWidth,
|
||||
content: <DrawerContent />,
|
||||
onClose: () => {
|
||||
|
@ -14,6 +14,7 @@ import { ModelConstructor } from '../../types';
|
||||
import { FlowSettingsButton } from '../common/FlowSettingsButton';
|
||||
import { withFlowDesignMode } from '../common/withFlowDesignMode';
|
||||
import { AddSubModelButton, SubModelItemsType } from './AddSubModelButton';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface AddActionButtonProps {
|
||||
/**
|
||||
@ -48,6 +49,11 @@ interface AddActionButtonProps {
|
||||
items?: SubModelItemsType;
|
||||
}
|
||||
|
||||
const DefaultBtn = () => {
|
||||
const { t } = useTranslation();
|
||||
return <FlowSettingsButton icon={<SettingOutlined />}>{t('Configure actions')}</FlowSettingsButton>;
|
||||
};
|
||||
|
||||
/**
|
||||
* 专门用于添加动作模型的按钮组件
|
||||
*
|
||||
@ -63,7 +69,7 @@ const AddActionButtonCore: React.FC<AddActionButtonProps> = ({
|
||||
model,
|
||||
subModelBaseClass = 'ActionFlowModel',
|
||||
subModelKey = 'actions',
|
||||
children = <FlowSettingsButton icon={<SettingOutlined />}>{'Configure actions'}</FlowSettingsButton>,
|
||||
children = <DefaultBtn />,
|
||||
subModelType = 'array',
|
||||
items,
|
||||
filter,
|
||||
|
@ -15,6 +15,7 @@ import { FlowModelOptions, ModelConstructor } from '../../types';
|
||||
import { FlowSettingsButton } from '../common/FlowSettingsButton';
|
||||
import { withFlowDesignMode } from '../common/withFlowDesignMode';
|
||||
import { AddSubModelButton, SubModelItemsType, mergeSubModelItems } from './AddSubModelButton';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export type BuildCreateModelOptionsType = {
|
||||
defaultOptions: FlowModelOptions;
|
||||
@ -65,6 +66,11 @@ function defaultBuildCreateModelOptions({ defaultOptions }: BuildCreateModelOpti
|
||||
return defaultOptions;
|
||||
}
|
||||
|
||||
const DefaultBtn = () => {
|
||||
const { t } = useTranslation();
|
||||
return <FlowSettingsButton icon={<SettingOutlined />}>{t('Configure fields')}</FlowSettingsButton>;
|
||||
};
|
||||
|
||||
/**
|
||||
* 专门用于添加字段模型的按钮组件
|
||||
*
|
||||
@ -80,7 +86,7 @@ const AddFieldButtonCore: React.FC<AddFieldButtonProps> = ({
|
||||
model,
|
||||
subModelBaseClass = 'FieldFlowModel',
|
||||
subModelKey = 'fields',
|
||||
children = <FlowSettingsButton icon={<SettingOutlined />}>{'Configure fields'}</FlowSettingsButton>,
|
||||
children = <DefaultBtn />,
|
||||
subModelType = 'array',
|
||||
collection,
|
||||
buildCreateModelOptions = defaultBuildCreateModelOptions,
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
import { Dropdown, DropdownProps, Input, Menu, Spin, Empty, InputProps } from 'antd';
|
||||
import React, { useEffect, useState, useMemo, useRef, FC } from 'react';
|
||||
import { useFlowModel } from '../../hooks';
|
||||
|
||||
/**
|
||||
* 通过鼠标的位置计算出最佳的 dropdown 的高度,以尽量避免出现滚动条
|
||||
@ -72,6 +73,7 @@ interface LazyDropdownMenuProps extends Omit<DropdownProps['menu'], 'items'> {
|
||||
}
|
||||
|
||||
const LazyDropdown: React.FC<Omit<DropdownProps, 'menu'> & { menu: LazyDropdownMenuProps }> = ({ menu, ...props }) => {
|
||||
const model = useFlowModel();
|
||||
const [loadedChildren, setLoadedChildren] = useState<Record<string, Item[]>>({});
|
||||
const [loadingKeys, setLoadingKeys] = useState<Set<string>>(new Set());
|
||||
const [menuVisible, setMenuVisible] = useState(false);
|
||||
@ -82,6 +84,7 @@ const LazyDropdown: React.FC<Omit<DropdownProps, 'menu'> & { menu: LazyDropdownM
|
||||
const dropdownMaxHeight = useNiceDropdownMaxHeight([menuVisible]);
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const t = model.translate;
|
||||
|
||||
// 清理定时器,避免内存泄露
|
||||
useEffect(() => {
|
||||
@ -223,7 +226,7 @@ const LazyDropdown: React.FC<Omit<DropdownProps, 'menu'> & { menu: LazyDropdownM
|
||||
visible={menuVisible}
|
||||
variant="borderless"
|
||||
allowClear
|
||||
placeholder={item.searchPlaceholder || 'search'}
|
||||
placeholder={t(item.searchPlaceholder || 'Search')}
|
||||
value={currentSearchValue}
|
||||
onChange={(e) => {
|
||||
e.stopPropagation();
|
||||
@ -264,7 +267,7 @@ const LazyDropdown: React.FC<Omit<DropdownProps, 'menu'> & { menu: LazyDropdownM
|
||||
key: `${item.key}-empty`,
|
||||
label: (
|
||||
<div style={{ padding: '16px', textAlign: 'center' as const }}>
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="No Data" style={{ margin: 0 }} />
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('No data')} style={{ margin: 0 }} />
|
||||
</div>
|
||||
),
|
||||
disabled: true,
|
||||
@ -278,7 +281,7 @@ const LazyDropdown: React.FC<Omit<DropdownProps, 'menu'> & { menu: LazyDropdownM
|
||||
return {
|
||||
type: 'group',
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
label: typeof item.label === 'string' ? t(item.label) : item.label,
|
||||
children: groupChildren,
|
||||
};
|
||||
}
|
||||
@ -289,7 +292,7 @@ const LazyDropdown: React.FC<Omit<DropdownProps, 'menu'> & { menu: LazyDropdownM
|
||||
|
||||
return {
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
label: typeof item.label === 'string' ? t(item.label) : item.label,
|
||||
onClick: (info) => {
|
||||
if (children) {
|
||||
return;
|
||||
@ -316,7 +319,7 @@ const LazyDropdown: React.FC<Omit<DropdownProps, 'menu'> & { menu: LazyDropdownM
|
||||
key: `${keyPath}-empty`,
|
||||
label: (
|
||||
<div style={{ padding: '16px', textAlign: 'center' as const }}>
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="No Data" style={{ margin: 0 }} />
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('No data')} style={{ margin: 0 }} />
|
||||
</div>
|
||||
),
|
||||
disabled: true,
|
||||
|
@ -96,7 +96,7 @@ export class DataSource {
|
||||
}
|
||||
|
||||
get displayName() {
|
||||
return this.options.displayName ? this.flowEngine?.t(this.options.displayName) : this.key;
|
||||
return this.options.displayName ? this.flowEngine?.translate(this.options.displayName) : this.key;
|
||||
}
|
||||
|
||||
get key() {
|
||||
@ -272,7 +272,7 @@ export class Collection {
|
||||
}
|
||||
|
||||
get title() {
|
||||
return this.options.title ? this.flowEngine?.t(this.options.title) : this.name;
|
||||
return this.options.title ? this.flowEngine?.translate(this.options.title) : this.name;
|
||||
}
|
||||
|
||||
initInherits() {
|
||||
@ -413,7 +413,7 @@ export class CollectionField {
|
||||
|
||||
get title() {
|
||||
const titleValue = this.options?.title || this.options?.uiSchema?.title || this.options.name;
|
||||
return this.flowEngine?.t(titleValue);
|
||||
return this.flowEngine?.translate(titleValue);
|
||||
}
|
||||
|
||||
set title(value: string) {
|
||||
|
@ -14,11 +14,13 @@ import {
|
||||
ActionDefinition,
|
||||
ActionOptions,
|
||||
CreateModelOptions,
|
||||
FlowContext,
|
||||
FlowDefinition,
|
||||
IFlowModelRepository,
|
||||
ModelConstructor,
|
||||
} from './types';
|
||||
import { isInheritedFrom, TranslationUtil } from './utils';
|
||||
import { isInheritedFrom } from './utils';
|
||||
import { initFlowEngineLocale } from './locale';
|
||||
|
||||
interface ApplyFlowCacheEntry {
|
||||
status: 'pending' | 'resolved' | 'rejected';
|
||||
@ -36,16 +38,15 @@ export class FlowEngine {
|
||||
private modelInstances: Map<string, any> = new Map();
|
||||
/** @public Stores flow settings including components and scopes for formily settings. */
|
||||
public flowSettings: FlowSettings = new FlowSettings();
|
||||
context: Record<string, any> = {};
|
||||
context: FlowContext['globals'] = {} as FlowContext['globals'];
|
||||
private modelRepository: IFlowModelRepository | null = null;
|
||||
private _applyFlowCache = new Map<string, ApplyFlowCacheEntry>();
|
||||
/** @private Translation utility for template compilation and caching */
|
||||
private _translationUtil = new TranslationUtil();
|
||||
|
||||
reactView: ReactView;
|
||||
|
||||
constructor() {
|
||||
this.reactView = new ReactView(this);
|
||||
this.flowSettings.registerScopes({ t: this.translate.bind(this) });
|
||||
}
|
||||
// 注册默认的 FlowModel
|
||||
|
||||
@ -58,6 +59,9 @@ export class FlowEngine {
|
||||
|
||||
setContext(context: any) {
|
||||
this.context = { ...this.context, ...context };
|
||||
if (this.context.i18n) {
|
||||
initFlowEngineLocale(this.context.i18n);
|
||||
}
|
||||
}
|
||||
|
||||
getContext() {
|
||||
@ -82,12 +86,20 @@ export class FlowEngine {
|
||||
* flowEngine.t("前缀 {{ t('User Name') }} 后缀")
|
||||
* flowEngine.t("{{t('Hello {name}', {name: 'John'})}}")
|
||||
*/
|
||||
public t(keyOrTemplate: string, options?: any): string {
|
||||
return this._translationUtil.translate(
|
||||
keyOrTemplate,
|
||||
(key: string, opts?: any) => this.translateKey(key, opts),
|
||||
options,
|
||||
);
|
||||
public translate(keyOrTemplate: string, options?: any): string {
|
||||
if (!keyOrTemplate || typeof keyOrTemplate !== 'string') {
|
||||
return keyOrTemplate;
|
||||
}
|
||||
|
||||
// 先尝试一次翻译
|
||||
let result = this.translateKey(keyOrTemplate, options);
|
||||
|
||||
// 检查翻译结果是否包含模板语法,如果有则进行模板编译
|
||||
if (this.isTemplate(result)) {
|
||||
result = this.compileTemplate(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,6 +114,44 @@ export class FlowEngine {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查字符串是否包含模板语法
|
||||
* @private
|
||||
*/
|
||||
private isTemplate(str: string): boolean {
|
||||
return /\{\{\s*t\s*\(\s*["'`].*?["'`]\s*(?:,\s*.*?)?\s*\)\s*\}\}/g.test(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译模板字符串
|
||||
* @private
|
||||
*/
|
||||
private compileTemplate(template: string): string {
|
||||
return template.replace(
|
||||
/\{\{\s*t\s*\(\s*["'`](.*?)["'`]\s*(?:,\s*((?:[^{}]|\{[^}]*\})*?))?\s*\)\s*\}\}/g,
|
||||
(match, key, optionsStr) => {
|
||||
try {
|
||||
let templateOptions = {};
|
||||
if (optionsStr) {
|
||||
optionsStr = optionsStr.trim();
|
||||
if (optionsStr.startsWith('{') && optionsStr.endsWith('}')) {
|
||||
// 使用受限的 Function 构造器解析
|
||||
try {
|
||||
templateOptions = new Function('$root', `with($root) { return (${optionsStr}); }`)({});
|
||||
} catch (parseError) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.translateKey(key, templateOptions);
|
||||
} catch (error) {
|
||||
console.warn(`FlowEngine: Failed to compile template "${match}":`, error);
|
||||
return match;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
get applyFlowCache() {
|
||||
return this._applyFlowCache;
|
||||
}
|
||||
|
59
packages/core/flow-engine/src/locale/en-US.json
Normal file
59
packages/core/flow-engine/src/locale/en-US.json
Normal file
@ -0,0 +1,59 @@
|
||||
{
|
||||
"Invalid model provided": "Invalid model provided",
|
||||
"Flow with key {{flowKey}} not found": "Flow with key {{flowKey}} not found",
|
||||
"Step with key {{stepKey}} not found": "Step with key {{stepKey}} not found",
|
||||
"Configuration": "Configuration",
|
||||
"Step configuration": "Step configuration",
|
||||
"This step has no configurable parameters": "This step has no configurable parameters",
|
||||
"Failed to import Formily components": "Failed to import Formily components",
|
||||
"Drawer API is not available, please ensure it is used within FlowEngineGlobalsContextProvider": "Drawer API is not available, please ensure it is used within FlowEngineGlobalsContextProvider",
|
||||
"Configuration saved": "Configuration saved",
|
||||
"Error saving configuration": "Error saving configuration",
|
||||
"Error saving configuration, please check console": "Error saving configuration, please check console",
|
||||
"Failed to import FormDialog": "Failed to import FormDialog",
|
||||
"OK": "OK",
|
||||
"Cancel": "Cancel",
|
||||
"Step parameter configuration": "Step parameter configuration",
|
||||
"Error submitting form": "Error submitting form",
|
||||
"Complete configuration": "Complete configuration",
|
||||
"Previous step": "Previous step",
|
||||
"Form validation failed": "Form validation failed",
|
||||
"Next step": "Next step",
|
||||
"Failed to import FormDialog or FormStep": "Failed to import FormDialog or FormStep",
|
||||
"UID copied to clipboard": "UID copied to clipboard",
|
||||
"Copy failed": "Copy failed",
|
||||
"Copy failed, please try again": "Copy failed, please try again",
|
||||
"Confirm delete": "Confirm delete",
|
||||
"Are you sure you want to delete this item? This action cannot be undone.": "Are you sure you want to delete this item? This action cannot be undone.",
|
||||
"Delete operation failed": "Delete operation failed",
|
||||
"Delete failed": "Delete failed",
|
||||
"Delete operation failed, please check the console for details.": "Delete operation failed, please check the console for details.",
|
||||
"Configuration popup cancelled or error": "Configuration popup cancelled or error",
|
||||
"Failed to get action {{action}}": "Failed to get action {{action}}",
|
||||
"Failed to get configurable flows for model {{model}}": "Failed to get configurable flows for model {{model}}",
|
||||
"Copy UID": "Copy UID",
|
||||
"Delete": "Delete",
|
||||
"This is likely a NocoBase internals bug. Please open an issue at": "This is likely a NocoBase internals bug. Please open an issue at",
|
||||
"here": "here",
|
||||
"Render failed": "Render failed",
|
||||
"Feedback": "Feedback",
|
||||
"Download logs": "Download logs",
|
||||
"Copy error info": "Copy error info",
|
||||
"Try again": "Try again",
|
||||
"Data blocks": "Data blocks",
|
||||
"Filter blocks": "Filter blocks",
|
||||
"Other blocks": "Other blocks",
|
||||
"Invalid input parameters": "Invalid input parameters",
|
||||
"Invalid subModelKey format": "Invalid subModelKey format: {{subModelKey}}",
|
||||
"Submodel not found": "Submodel '{{subKey}}' not found",
|
||||
"Expected array for subModel": "Expected array for '{{subKey}}', got {{type}}",
|
||||
"Array index out of bounds": "Array index {{index}} out of bounds for '{{subKey}}'",
|
||||
"Expected object for subModel": "Expected object for '{{subKey}}', got array",
|
||||
"No createModelOptions found for item": "No createModelOptions found for item",
|
||||
"createModelOptions must specify use property": "createModelOptions must specify \"use\" property",
|
||||
"Failed to add sub model": "Failed to add sub model",
|
||||
"Failed to destroy model after creation error": "Failed to destroy model after creation error",
|
||||
"Add": "Add",
|
||||
"Name": "Name",
|
||||
"Model with ID {{uid}} not found": "Model with ID {{uid}} not found"
|
||||
}
|
38
packages/core/flow-engine/src/locale/index.ts
Normal file
38
packages/core/flow-engine/src/locale/index.ts
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import enUS from './en-US.json';
|
||||
import zhCN from './zh-CN.json';
|
||||
import { FLOW_ENGINE_NAMESPACE } from '../utils';
|
||||
|
||||
export const locales = {
|
||||
'en-US': enUS,
|
||||
'zh-CN': zhCN,
|
||||
};
|
||||
|
||||
/**
|
||||
* Get translation for a key with fallback
|
||||
*/
|
||||
export function getFlowEngineTranslation(key: string, locale = 'en-US'): string {
|
||||
const translations = locales[locale] || locales['en-US'];
|
||||
return translations[key] || key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize flow-engine locale resources
|
||||
* This should be called when the flow-engine is initialized
|
||||
*/
|
||||
export function initFlowEngineLocale(i18nInstance?: any) {
|
||||
if (i18nInstance && typeof i18nInstance.addResourceBundle === 'function') {
|
||||
// Register flow-engine translations to the flow-engine namespace
|
||||
Object.entries(locales).forEach(([locale, resources]) => {
|
||||
i18nInstance.addResourceBundle(locale, FLOW_ENGINE_NAMESPACE, resources, true, false);
|
||||
});
|
||||
}
|
||||
}
|
59
packages/core/flow-engine/src/locale/zh-CN.json
Normal file
59
packages/core/flow-engine/src/locale/zh-CN.json
Normal file
@ -0,0 +1,59 @@
|
||||
{
|
||||
"Invalid model provided": "提供的模型无效",
|
||||
"Flow with key {{flowKey}} not found": "未找到key为 {{flowKey}} 的流程",
|
||||
"Step with key {{stepKey}} not found": "未找到key为 {{stepKey}} 的步骤",
|
||||
"Configuration": "配置",
|
||||
"Step configuration": "步骤配置",
|
||||
"This step has no configurable parameters": "此步骤没有可配置的参数",
|
||||
"Failed to import Formily components": "导入 Formily 组件失败",
|
||||
"Drawer API is not available, please ensure it is used within FlowEngineGlobalsContextProvider": "抽屉 API 不可用,请确保在 FlowEngineGlobalsContextProvider 中使用",
|
||||
"Configuration saved": "配置已保存",
|
||||
"Error saving configuration": "保存配置时出错",
|
||||
"Error saving configuration, please check console": "保存配置时出错,请检查控制台",
|
||||
"Failed to import FormDialog": "导入 FormDialog 失败",
|
||||
"OK": "确定",
|
||||
"Cancel": "取消",
|
||||
"Step parameter configuration": "步骤参数配置",
|
||||
"Error submitting form": "提交表单时出错",
|
||||
"Complete configuration": "完成配置",
|
||||
"Previous step": "上一步",
|
||||
"Form validation failed": "表单验证失败",
|
||||
"Next step": "下一步",
|
||||
"Failed to import FormDialog or FormStep": "导入 FormDialog 或 FormStep 失败",
|
||||
"UID copied to clipboard": "UID 已复制到剪贴板",
|
||||
"Copy failed": "复制失败",
|
||||
"Copy failed, please try again": "复制失败,请重试",
|
||||
"Confirm delete": "确认删除",
|
||||
"Are you sure you want to delete this item? This action cannot be undone.": "确定要删除此项吗?此操作不可撤销。",
|
||||
"Delete operation failed": "删除操作失败",
|
||||
"Delete failed": "删除失败",
|
||||
"Delete operation failed, please check the console for details.": "删除操作失败,请检查控制台获取详细信息。",
|
||||
"Configuration popup cancelled or error": "配置弹窗已取消或出错",
|
||||
"Failed to get action {{action}}": "获取 action '{{action}}' 失败",
|
||||
"Failed to get configurable flows for model {{model}}": "获取模型 '{{model}}' 的可配置 flows 失败",
|
||||
"Copy UID": "复制 UID",
|
||||
"Delete": "删除",
|
||||
"This is likely a NocoBase internals bug. Please open an issue at": "这可能是 NocoBase 内部错误。请在以下地址提交问题",
|
||||
"here": "这里",
|
||||
"Render failed": "渲染失败",
|
||||
"Feedback": "反馈",
|
||||
"Download logs": "下载日志",
|
||||
"Copy error info": "复制错误信息",
|
||||
"Try again": "重试",
|
||||
"Data blocks": "数据区块",
|
||||
"Filter blocks": "筛选区块",
|
||||
"Other blocks": "其他区块",
|
||||
"Invalid input parameters": "输入参数无效",
|
||||
"Invalid subModelKey format": "无效的 subModelKey 格式: {{subModelKey}}",
|
||||
"Submodel not found": "未找到 Submodel '{{subKey}}'",
|
||||
"Expected array for subModel": "期望 '{{subKey}}' 为数组,实际为 {{type}}",
|
||||
"Array index out of bounds": "数组索引 {{index}} 超出 '{{subKey}}' 的边界",
|
||||
"Expected object for subModel": "期望 '{{subKey}}' 为对象,实际为数组",
|
||||
"No createModelOptions found for item": "未找到该项的 createModelOptions",
|
||||
"createModelOptions must specify use property": "createModelOptions 必须指定 \"use\" 属性",
|
||||
"Failed to add sub model": "添加子模型失败",
|
||||
"Failed to destroy model after creation error": "创建错误后销毁模型失败",
|
||||
"Add": "添加",
|
||||
"Name": "名称",
|
||||
"Model with ID {{uid}} not found": "未找到 ID 为 {{uid}} 的模型"
|
||||
}
|
@ -401,7 +401,7 @@ export class FlowModel<Structure extends { parent?: any; subModels?: any } = Def
|
||||
console[level.toLowerCase()](logMessage, logMeta);
|
||||
};
|
||||
|
||||
const globalContexts = currentFlowEngine.getContext() || {};
|
||||
const globalContexts = currentFlowEngine.getContext();
|
||||
const flowContext: FlowContext<this> = {
|
||||
exit: () => {
|
||||
throw new FlowExitException(flowKey, this.uid);
|
||||
@ -875,6 +875,10 @@ export class FlowModel<Structure extends { parent?: any; subModels?: any } = Def
|
||||
};
|
||||
}
|
||||
|
||||
get translate() {
|
||||
return this.flowEngine.translate.bind(this.flowEngine);
|
||||
}
|
||||
|
||||
public setSharedContext(ctx: Record<string, any>) {
|
||||
this._sharedContext = { ...this._sharedContext, ...ctx };
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import { ISchema } from '@formily/json-schema';
|
||||
import type { FlowEngine } from './flowEngine';
|
||||
import type { FlowModel } from './models';
|
||||
import { ReactView } from './ReactView';
|
||||
import { APIClient } from '@nocobase/sdk';
|
||||
|
||||
/**
|
||||
* 工具类型:如果 T 是数组类型,则提取数组元素类型;否则返回 T 本身
|
||||
@ -99,7 +100,11 @@ export interface FlowContext<TModel extends FlowModel = FlowModel> {
|
||||
reactView: ReactView;
|
||||
stepResults: Record<string, any>; // Results from previous steps
|
||||
shared: Record<string, any>; // Shared data within the flow (read/write)
|
||||
globals: Record<string, any>; // Global context data (read-only)
|
||||
globals: Record<string, any> & {
|
||||
flowEngine: FlowEngine;
|
||||
app: any;
|
||||
api: APIClient;
|
||||
};
|
||||
extra: Record<string, any>; // Extra context passed to applyFlow (read-only)
|
||||
model: TModel; // Current model instance with specific type
|
||||
app: any; // Application instance (required)
|
||||
|
@ -9,9 +9,29 @@
|
||||
|
||||
import _ from 'lodash';
|
||||
import type { ISchema } from '@formily/json-schema';
|
||||
import { Schema } from '@formily/json-schema';
|
||||
import type { FlowModel } from './models';
|
||||
import { ActionDefinition, DeepPartial, FlowContext, FlowDefinition, ModelConstructor, ParamsContext } from './types';
|
||||
|
||||
// Flow Engine 命名空间常量
|
||||
export const FLOW_ENGINE_NAMESPACE = 'flow-engine';
|
||||
|
||||
/**
|
||||
* 获取带有 flow-engine 命名空间的翻译函数
|
||||
* @param model FlowModel 实例
|
||||
* @returns 翻译函数,自动使用 flow-engine 命名空间
|
||||
*/
|
||||
export function getT(model: FlowModel): (key: string, options?: any) => string {
|
||||
if (model.flowEngine?.translate) {
|
||||
return (key: string, options?: any) => {
|
||||
// 自动添加 flow-engine 命名空间
|
||||
return model.flowEngine.translate(key, { ns: [FLOW_ENGINE_NAMESPACE, 'client'], nsMode: 'fallback', ...options });
|
||||
};
|
||||
}
|
||||
// 回退到原始键值
|
||||
return (key: string) => key;
|
||||
}
|
||||
|
||||
export function generateUid(): string {
|
||||
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
||||
}
|
||||
@ -155,95 +175,87 @@ export function defineAction(options: ActionDefinition) {
|
||||
return options;
|
||||
}
|
||||
|
||||
// 模块级全局缓存,与 useCompile 保持一致
|
||||
const compileCache = {};
|
||||
|
||||
/**
|
||||
* 翻译工具类,负责模板编译和缓存管理
|
||||
* 编译 UI Schema 中的表达式
|
||||
*
|
||||
* @param scope 编译作用域,包含可用的变量和函数(如 t, randomString 等)
|
||||
* @param uiSchema 待编译的 UI Schema
|
||||
* @param options 编译选项
|
||||
* @returns 编译后的 UI Schema
|
||||
*/
|
||||
export class TranslationUtil {
|
||||
/** @private Simple template cache - template string -> compiled result */
|
||||
private _templateCache = new Map<string, string>();
|
||||
export function compileUiSchema(scope: Record<string, any>, uiSchema: any, options: { noCache?: boolean } = {}): any {
|
||||
const { noCache = false } = options;
|
||||
|
||||
/**
|
||||
* 翻译函数,支持简单翻译和模板编译
|
||||
* @param keyOrTemplate 翻译键或包含 {{t('key', options)}} 的模板字符串
|
||||
* @param translator 翻译函数,通常是 i18n.t
|
||||
* @param options 翻译选项(如命名空间、参数等)
|
||||
* @returns 翻译后的文本
|
||||
*/
|
||||
public translate(keyOrTemplate: string, translator: (key: string, options?: any) => string, options?: any): string {
|
||||
if (!keyOrTemplate || typeof keyOrTemplate !== 'string') {
|
||||
return keyOrTemplate;
|
||||
const hasVariable = (source: string): boolean => {
|
||||
const reg = /\{\{.*?\}\}/g;
|
||||
return reg.test(source);
|
||||
};
|
||||
|
||||
const compile = (source: any): any => {
|
||||
let shouldCompile = false;
|
||||
let cacheKey: string;
|
||||
|
||||
// source is expression, for example: {{ t('Add new') }}
|
||||
if (typeof source === 'string' && source.startsWith('{{')) {
|
||||
shouldCompile = true;
|
||||
cacheKey = source;
|
||||
}
|
||||
|
||||
// 检查是否包含模板语法 {{t('...')}}
|
||||
const hasTemplate = this.isTemplate(keyOrTemplate);
|
||||
|
||||
if (hasTemplate) {
|
||||
// 模板编译模式 - 简单缓存
|
||||
if (this._templateCache.has(keyOrTemplate)) {
|
||||
return this._templateCache.get(keyOrTemplate)!;
|
||||
// source is Component Object, for example: { 'x-component': "Cascader", type: "array", title: "所属地区(行政区划)" }
|
||||
if (source && typeof source === 'object' && !Array.isArray(source)) {
|
||||
try {
|
||||
cacheKey = JSON.stringify(source);
|
||||
} catch (e) {
|
||||
console.warn('Failed to stringify:', e);
|
||||
return source;
|
||||
}
|
||||
|
||||
// 解析模板
|
||||
const result = this.compileTemplate(keyOrTemplate, translator);
|
||||
|
||||
// 简单缓存:直接存储
|
||||
this._templateCache.set(keyOrTemplate, result);
|
||||
return result;
|
||||
} else {
|
||||
// 简单翻译模式 - 直接调用翻译函数
|
||||
return translator(keyOrTemplate, options);
|
||||
if (compileCache[cacheKey]) return compileCache[cacheKey];
|
||||
shouldCompile = hasVariable(cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查字符串是否包含模板语法
|
||||
* @private
|
||||
*/
|
||||
private isTemplate(str: string): boolean {
|
||||
return /\{\{\s*t\s*\(\s*["'`].*?["'`]\s*(?:,\s*.*?)?\s*\)\s*\}\}/g.test(str);
|
||||
}
|
||||
// source is Array, for example: [{ 'title': "{{ t('Admin') }}", name: 'admin' }, { 'title': "{{ t('Root') }}", name: 'root' }]
|
||||
if (Array.isArray(source)) {
|
||||
try {
|
||||
cacheKey = JSON.stringify(source);
|
||||
} catch (e) {
|
||||
console.warn('Failed to stringify:', e);
|
||||
return source;
|
||||
}
|
||||
if (compileCache[cacheKey]) return compileCache[cacheKey];
|
||||
shouldCompile = hasVariable(cacheKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译模板字符串
|
||||
* @private
|
||||
*/
|
||||
private compileTemplate(template: string, translator: (key: string, options?: any) => string): string {
|
||||
return template.replace(
|
||||
/\{\{\s*t\s*\(\s*["'`](.*?)["'`]\s*(?:,\s*((?:[^{}]|\{[^}]*\})*))?\s*\)\s*\}\}/g,
|
||||
(match, key, optionsStr) => {
|
||||
if (shouldCompile) {
|
||||
if (!cacheKey) {
|
||||
try {
|
||||
let templateOptions = {};
|
||||
if (optionsStr) {
|
||||
optionsStr = optionsStr.trim();
|
||||
if (optionsStr.startsWith('{') && optionsStr.endsWith('}')) {
|
||||
try {
|
||||
templateOptions = JSON.parse(optionsStr);
|
||||
} catch (jsonError) {
|
||||
// JSON 解析失败,返回原始匹配字符串
|
||||
return match;
|
||||
}
|
||||
}
|
||||
}
|
||||
return translator(key, templateOptions);
|
||||
return Schema.compile(source, scope);
|
||||
} catch (error) {
|
||||
console.warn(`TranslationUtil: Failed to compile template "${match}":`, error);
|
||||
return match;
|
||||
console.warn('Failed to compile with Formily Schema.compile:', error);
|
||||
return source;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (noCache) {
|
||||
return Schema.compile(source, scope);
|
||||
}
|
||||
compileCache[cacheKey] = compileCache[cacheKey] || Schema.compile(source, scope);
|
||||
return compileCache[cacheKey];
|
||||
} catch (e) {
|
||||
console.log('compileUiSchema error', source, e);
|
||||
try {
|
||||
return Schema.compile(source, scope);
|
||||
} catch (error) {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空模板缓存
|
||||
*/
|
||||
public clearCache(): void {
|
||||
this._templateCache.clear();
|
||||
}
|
||||
// source is: plain object、string、number、boolean、undefined、null
|
||||
return source;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取缓存大小
|
||||
*/
|
||||
public getCacheSize(): number {
|
||||
return this._templateCache.size;
|
||||
}
|
||||
return compile(uiSchema);
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import * as antd from 'antd';
|
||||
import { Card, Spin } from 'antd';
|
||||
import React, { createRef } from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { NAMESPACE } from './locale';
|
||||
import { NAMESPACE, tStr } from './locale';
|
||||
export class LowcodeBlockModel extends BlockModel {
|
||||
ref = createRef<HTMLDivElement>();
|
||||
declare resource: APIResource;
|
||||
@ -125,15 +125,15 @@ ctx.element.innerHTML = \`
|
||||
|
||||
LowcodeBlockModel.registerFlow({
|
||||
key: 'default',
|
||||
title: 'Configuration',
|
||||
title: tStr('Configuration'),
|
||||
auto: true,
|
||||
steps: {
|
||||
executionStep: {
|
||||
title: 'Code',
|
||||
title: tStr('Code'),
|
||||
uiSchema: {
|
||||
code: {
|
||||
type: 'string',
|
||||
title: 'Execution Code',
|
||||
title: tStr('Execution Code'),
|
||||
'x-component': 'CodeEditor',
|
||||
'x-component-props': {
|
||||
minHeight: '400px',
|
||||
|
Loading…
x
Reference in New Issue
Block a user