mirror of
https://github.com/nocobase/nocobase.git
synced 2026-08-28 17:43:07 +08:00
Merge branch 'next' into develop
This commit is contained in:
@@ -168,8 +168,6 @@ const external = [
|
||||
'ahooks',
|
||||
'lodash',
|
||||
'china-division',
|
||||
'file-saver',
|
||||
|
||||
// langChain
|
||||
'langchain',
|
||||
'@langchain/core',
|
||||
@@ -597,12 +595,13 @@ export async function buildPluginClient(
|
||||
}
|
||||
const sourcePackages = getPluginBrowserSourcePackages(sourceCwds, globExcludeFiles);
|
||||
const excludePackages = getExcludePackages(sourcePackages, external, pluginPrefix);
|
||||
const browserExternalPackages = excludePackages.filter((packageName) => packageName !== 'file-saver');
|
||||
|
||||
checkRequire(clientFiles, log);
|
||||
buildCheck({ cwd, packageJson, entry: lane, files: clientFiles, log });
|
||||
const outDir = path.join(cwd, target_dir, laneConfig.distDir);
|
||||
|
||||
const globals = excludePackages.reduce<Record<string, string>>((prev, curr) => {
|
||||
const globals = browserExternalPackages.reduce<Record<string, string>>((prev, curr) => {
|
||||
if (curr.startsWith('@nocobase')) {
|
||||
laneConfig.externalSubpaths.forEach((subpath) => {
|
||||
prev[`${curr}/${subpath}`] = `${curr}/${subpath}`;
|
||||
|
||||
@@ -29,9 +29,11 @@
|
||||
"@nocobase/shared": "2.1.0-alpha.34",
|
||||
"ahooks": "^3.7.2",
|
||||
"antd": "5.24.2",
|
||||
"antd-style": "3.7.1",
|
||||
"axios": "^1.7.0",
|
||||
"classnames": "^2.3.1",
|
||||
"dayjs": "^1.11.10",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18next": "^22.4.9",
|
||||
"json5": "^2.2.3",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -59,7 +59,7 @@ export const titleField = defineAction({
|
||||
};
|
||||
},
|
||||
defaultParams: (ctx: any) => {
|
||||
const titleField = ctx.model.context.collectionField.targetCollectionTitleFieldName;
|
||||
const titleField = ctx.model?.context?.collectionField?.targetCollectionTitleFieldName;
|
||||
return {
|
||||
label: ctx.model.parent?.props?.titleField || ctx.model.props.titleField || titleField,
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ export const validation = defineAction({
|
||||
return;
|
||||
}
|
||||
const targetInterface = ctx.model.collectionField.getInterfaceOptions();
|
||||
if (!targetInterface.validationType) {
|
||||
if (!targetInterface?.validationType) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -431,7 +431,7 @@ FormItemModel.registerFlow({
|
||||
},
|
||||
defaultParams: (ctx: any) => {
|
||||
const titleField =
|
||||
ctx.model.props.titleField || ctx.model.context.collectionField.targetCollectionTitleFieldName;
|
||||
ctx.model.props.titleField || ctx.model?.context?.collectionField?.targetCollectionTitleFieldName;
|
||||
return {
|
||||
titleField: titleField,
|
||||
};
|
||||
|
||||
@@ -541,7 +541,7 @@ TableColumnModel.registerFlow({
|
||||
ctx.model.setProps(targetCollectionField.getComponentProps());
|
||||
},
|
||||
defaultParams: (ctx: any) => {
|
||||
const titleField = ctx.model.context.collectionField.targetCollectionTitleFieldName;
|
||||
const titleField = ctx.model?.context?.collectionField?.targetCollectionTitleFieldName;
|
||||
return {
|
||||
label: getSavedAssociationTitleField(ctx.model) || titleField,
|
||||
};
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
|
||||
import * as antdCssinjs from '@ant-design/cssinjs';
|
||||
import * as antdIcons from '@ant-design/icons';
|
||||
import * as antdStyle from 'antd-style';
|
||||
import * as emotionCss from '@emotion/css';
|
||||
import * as formilyAntdV5 from '@formily/antd-v5';
|
||||
import * as formilyCore from '@formily/core';
|
||||
import * as formilyReact from '@formily/react';
|
||||
import * as formilyReactive from '@formily/reactive';
|
||||
@@ -19,6 +21,7 @@ import { dayjs } from '@nocobase/utils/client';
|
||||
import * as nocobaseFlowEngine from '@nocobase/flow-engine';
|
||||
import * as ahooks from 'ahooks';
|
||||
import * as antd from 'antd';
|
||||
import * as FileSaver from 'file-saver';
|
||||
import axios from 'axios';
|
||||
import * as i18next from 'i18next';
|
||||
import lodash from 'lodash';
|
||||
@@ -49,12 +52,14 @@ export function defineGlobalDeps(requirejs: RequireJS) {
|
||||
requirejs.define('antd', () => antd);
|
||||
requirejs.define('@ant-design/icons', () => antdIcons);
|
||||
requirejs.define('@ant-design/cssinjs', () => antdCssinjs);
|
||||
requirejs.define('antd-style', () => antdStyle);
|
||||
|
||||
// i18next
|
||||
requirejs.define('i18next', () => i18next);
|
||||
requirejs.define('react-i18next', () => reactI18next);
|
||||
|
||||
// formily
|
||||
requirejs.define('@formily/antd-v5', () => formilyAntdV5);
|
||||
requirejs.define('@formily/core', () => formilyCore);
|
||||
requirejs.define('@formily/react', () => formilyReact);
|
||||
requirejs.define('@formily/reactive', () => formilyReactive);
|
||||
@@ -73,4 +78,5 @@ export function defineGlobalDeps(requirejs: RequireJS) {
|
||||
requirejs.define('dayjs', () => dayjs);
|
||||
requirejs.define('lodash', () => lodash);
|
||||
requirejs.define('@emotion/css', () => emotionCss);
|
||||
requirejs.define('file-saver', () => FileSaver);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './dist/client-v2';
|
||||
export { default } from './dist/client-v2';
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('./dist/client-v2/index.js');
|
||||
@@ -14,6 +14,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"@nocobase/client": "2.x",
|
||||
"@nocobase/client-v2": "2.x",
|
||||
"@nocobase/server": "2.x",
|
||||
"@nocobase/test": "2.x"
|
||||
},
|
||||
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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 { Select, Space } from 'antd';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { BulkEditFormItemValueType } from '../types';
|
||||
import { lang } from '../locale';
|
||||
|
||||
function toFormFieldValue(value: any) {
|
||||
if (BulkEditFormItemValueType.Clear in value) {
|
||||
return null;
|
||||
} else if (BulkEditFormItemValueType.ChangedTo in value) {
|
||||
return value[BulkEditFormItemValueType.ChangedTo];
|
||||
} else if (BulkEditFormItemValueType.RemainsTheSame in value) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export const BulkEditFieldV2 = (props) => {
|
||||
const { formItemModel, onChange, field, ...rest } = props;
|
||||
const [type, setType] = useState<number>(BulkEditFormItemValueType.RemainsTheSame);
|
||||
const [value, setValue] = useState(null);
|
||||
const form = formItemModel.context.blockModel.form;
|
||||
// 保存原始的校验规则
|
||||
const [originalRules] = useState(() => formItemModel?.props?.rules || []);
|
||||
|
||||
const typeChangeHandler = (val) => {
|
||||
setType(val);
|
||||
const fieldValue = toFormFieldValue({ [val]: value });
|
||||
form.setFieldValue(formItemModel.props.name, fieldValue);
|
||||
if (val === BulkEditFormItemValueType.ChangedTo) {
|
||||
const fieldUse = formItemModel?.subModels?.field?.use;
|
||||
if (fieldUse === 'CheckboxFieldModel') {
|
||||
// checkbox 默认值为 false
|
||||
setTimeout(() => {
|
||||
valueChangeHandler(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const required = type === BulkEditFormItemValueType.ChangedTo;
|
||||
// 设置必填状态
|
||||
if (required) {
|
||||
// 移除原始规则中的必填规则
|
||||
const otherRules = originalRules.filter((rule) => !rule.required);
|
||||
// 添加新的必填规则,保留其他原始规则
|
||||
const rules = [
|
||||
{
|
||||
required: true,
|
||||
message: formItemModel.context.t('The field value is required'),
|
||||
},
|
||||
...otherRules,
|
||||
];
|
||||
formItemModel?.setProps({ required: true, rules });
|
||||
} else {
|
||||
// 移除所有规则
|
||||
formItemModel?.setProps({ required: false, rules: [] });
|
||||
}
|
||||
}, [type]);
|
||||
|
||||
const valueChangeHandler = (val) => {
|
||||
const v = val?.target?.value ?? val?.target?.checked ?? val;
|
||||
setValue(v);
|
||||
onChange?.(v);
|
||||
};
|
||||
|
||||
const child = React.cloneElement(field, {
|
||||
...rest,
|
||||
onChange: valueChangeHandler,
|
||||
});
|
||||
|
||||
return (
|
||||
<Space
|
||||
className={css`
|
||||
display: flex;
|
||||
> .ant-space-item {
|
||||
width: 100%;
|
||||
}
|
||||
`}
|
||||
direction="vertical"
|
||||
>
|
||||
<Select defaultValue={type} value={type} onChange={typeChangeHandler} disabled={props.aclDisabled}>
|
||||
<Select.Option value={BulkEditFormItemValueType.RemainsTheSame}>{lang('Remains the same')}</Select.Option>
|
||||
<Select.Option value={BulkEditFormItemValueType.ChangedTo}>{lang('Changed to')}</Select.Option>
|
||||
<Select.Option value={BulkEditFormItemValueType.Clear}>{lang('Clear')}</Select.Option>
|
||||
</Select>
|
||||
{[BulkEditFormItemValueType.ChangedTo, BulkEditFormItemValueType.AddAttach].includes(type) && child}
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
+3
-3
@@ -8,9 +8,9 @@
|
||||
*/
|
||||
|
||||
import { defineAction, tExpr } from '@nocobase/flow-engine';
|
||||
import { DetailsItemModel } from '@nocobase/client';
|
||||
import type { FieldModel } from '@nocobase/client';
|
||||
import { getFieldBindingUse, rebuildFieldSubModel } from './rebuildFieldSubModel';
|
||||
import { DetailsItemModel } from '@nocobase/client-v2';
|
||||
import type { FieldModel } from '@nocobase/client-v2';
|
||||
import { getFieldBindingUse, rebuildFieldSubModel } from '../rebuildFieldSubModel';
|
||||
|
||||
const bulkEditExcludedBindingModels = new Set([
|
||||
'SubFormFieldModel',
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import { defineAction, DisplayItemModel } from '@nocobase/flow-engine';
|
||||
import { titleField } from '@nocobase/client';
|
||||
import { titleField } from '@nocobase/client-v2';
|
||||
|
||||
// 重新实现 RecordSelectFieldModel 中 titleField 的 bulkEditTitleField 版本
|
||||
export const bulkEditTitleField = defineAction({
|
||||
+3
-3
@@ -7,11 +7,11 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { ActionSceneEnum, PopupActionModel } from '@nocobase/client';
|
||||
import { ActionSceneEnum, PopupActionModel } from '@nocobase/client-v2';
|
||||
import { tExpr } from '@nocobase/flow-engine';
|
||||
import type { ButtonProps } from 'antd/es/button';
|
||||
import { NAMESPACE, lang } from '../locale';
|
||||
import { createTagPageOptions } from './utils';
|
||||
import { NAMESPACE, lang } from '../../locale';
|
||||
import { createTagPageOptions } from '../utils';
|
||||
|
||||
const SETTINGS_FLOW_KEY = 'bulkEditSettings';
|
||||
|
||||
+2
-2
@@ -7,12 +7,12 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { ChildPageTabModel, BlockGridModel, SkeletonFallback } from '@nocobase/client';
|
||||
import { ChildPageTabModel, BlockGridModel, SkeletonFallback } from '@nocobase/client-v2';
|
||||
|
||||
import { FlowModel, FlowModelRenderer } from '@nocobase/flow-engine';
|
||||
import { useRequest } from 'ahooks';
|
||||
import React from 'react';
|
||||
import { BlockSceneEnum } from '@nocobase/client';
|
||||
import { BlockSceneEnum } from '@nocobase/client-v2';
|
||||
|
||||
function PageTabChildrenRenderer({ ctx, options }) {
|
||||
const { data, loading } = useRequest(
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
import { buildSubModelItems, DefaultStructure, tExpr } from '@nocobase/flow-engine';
|
||||
import _ from 'lodash';
|
||||
import { DataBlockModel, BlockModel } from '@nocobase/client';
|
||||
import { DataBlockModel, BlockModel } from '@nocobase/client-v2';
|
||||
|
||||
export class BulkEditDataBlockModel<T = DefaultStructure> extends DataBlockModel<T> {}
|
||||
|
||||
+4
-4
@@ -7,14 +7,14 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { FieldModelRenderer, FieldModel, RecordSelectFieldModel, titleField } from '@nocobase/client';
|
||||
import { FieldModel, RecordSelectFieldModel, titleField } from '@nocobase/client-v2';
|
||||
import { ParamObject, StepParams, FieldModelRenderer } from '@nocobase/flow-engine';
|
||||
import { Select, Space } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import _ from 'lodash';
|
||||
import { BulkEditFormItemValueType } from './types';
|
||||
import { lang } from '../locale';
|
||||
import { ParamObject, StepParams } from '@nocobase/flow-engine';
|
||||
import { BulkEditFormItemValueType } from '../../types';
|
||||
import { lang } from '../../locale';
|
||||
import { BulkEditFormItemModel } from './BulkEditFormItemModel';
|
||||
|
||||
function toFormFieldValue(value: any) {
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { ActionGroupModel } from '@nocobase/client';
|
||||
import { ActionGroupModel } from '@nocobase/client-v2';
|
||||
import { BulkEditFormSubmitActionModel } from './BulkEditFormSubmitActionModel';
|
||||
|
||||
export class BulkEditFormActionGroupModel extends ActionGroupModel {}
|
||||
+2
-3
@@ -7,11 +7,10 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { FormGridModel } from '@nocobase/client';
|
||||
import { FormGridModel } from '@nocobase/client-v2';
|
||||
import { SettingOutlined } from '@ant-design/icons';
|
||||
import { AddSubModelButton, FlowSettingsButton } from '@nocobase/flow-engine';
|
||||
import { AddSubModelButton, FlowSettingsButton, tExpr } from '@nocobase/flow-engine';
|
||||
import React from 'react';
|
||||
import { tExpr } from '@nocobase/flow-engine';
|
||||
|
||||
export class BulkEditFormGridModel extends FormGridModel {
|
||||
// 显示字段的设置项
|
||||
+2
-3
@@ -8,10 +8,9 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FormItemModel, FieldModel } from '@nocobase/client';
|
||||
import { FormItemModel, FieldModel } from '@nocobase/client-v2';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { BulkEditFieldV2 } from '../component/BulkEditFieldV2';
|
||||
import { BulkEditFieldV2 } from '../../component/BulkEditFieldV2';
|
||||
import { tExpr, FieldModelRenderer, FormItem } from '@nocobase/flow-engine';
|
||||
|
||||
function buildDynamicName(nameParts: string[], fieldIndex: string[]) {
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { CreateFormModel, BlockSceneEnum } from '@nocobase/client';
|
||||
import { CreateFormModel, BlockSceneEnum } from '@nocobase/client-v2';
|
||||
|
||||
import { tExpr } from '@nocobase/flow-engine';
|
||||
|
||||
+3
-3
@@ -7,12 +7,12 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { FormSubmitActionModel } from '@nocobase/client';
|
||||
import { FormSubmitActionModel } from '@nocobase/client-v2';
|
||||
import { ButtonProps } from 'antd';
|
||||
import { tExpr } from '@nocobase/flow-engine';
|
||||
import { submitHandler } from './submitHandler';
|
||||
import { submitHandler } from '../../submitHandler';
|
||||
import { BulkEditFormModel } from './BulkEditFormModel';
|
||||
import { lang } from '../locale';
|
||||
import { lang } from '../../locale';
|
||||
|
||||
export class BulkEditFormSubmitActionModel extends FormSubmitActionModel {
|
||||
defaultProps: ButtonProps & { withScheduleSend?: boolean } = {
|
||||
-9
@@ -1,12 +1,3 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './BulkEditActionModel';
|
||||
export * from './BulkEditFormModel';
|
||||
export * from './BulkEditFormItemModel';
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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 { Plugin } from '@nocobase/client-v2';
|
||||
import { bulkEditTitleField } from './flow/bulkEditTitleField';
|
||||
import { bulkEditFieldComponent } from './flow/bulkEditFieldComponent';
|
||||
|
||||
import * as models from './flow/models';
|
||||
|
||||
export class PluginActionBulkEditClient extends Plugin {
|
||||
async load() {
|
||||
// 先注册 bulkEditTitleField/bulkEditFieldComponent action,再导入 models
|
||||
this.app.flowEngine.registerActions({ bulkEditTitleField, bulkEditFieldComponent });
|
||||
this.app.flowEngine.registerModels(models);
|
||||
}
|
||||
}
|
||||
|
||||
export default PluginActionBulkEditClient;
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 { useTranslation } from 'react-i18next';
|
||||
|
||||
export const NAMESPACE = 'action-bulk-edit';
|
||||
|
||||
export function lang(key: string) {
|
||||
const runtimeI18n = (globalThis as any)?.window?.__nocobase_i18n__;
|
||||
return runtimeI18n?.t?.(key, { ns: NAMESPACE }) ?? key;
|
||||
}
|
||||
|
||||
export function generateNTemplate(key: string) {
|
||||
return `{{t('${key}', { ns: '${NAMESPACE}', nsMode: 'fallback' })}}`;
|
||||
}
|
||||
|
||||
export function useBulkEditTranslation() {
|
||||
return useTranslation(NAMESPACE, {
|
||||
nsMode: 'fallback',
|
||||
});
|
||||
}
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
* - 支持同步父项模式(pattern)
|
||||
* - 重建后触发 beforeRender(useCache: false)
|
||||
*/
|
||||
import { FieldModel } from '@nocobase/client';
|
||||
import { FieldModel } from '@nocobase/client-v2';
|
||||
import { FlowEngine, FlowModel, StepParams } from '@nocobase/flow-engine';
|
||||
|
||||
type FieldBindingParams = {
|
||||
+2
-2
@@ -6,8 +6,8 @@
|
||||
* 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 type { FormBlockModel } from '@nocobase/client';
|
||||
import { lang } from '../locale';
|
||||
import type { FormBlockModel } from '@nocobase/client-v2';
|
||||
import { lang } from './locale';
|
||||
|
||||
export async function submitHandler(ctx, params) {
|
||||
const blockModel = ctx.blockModel as FormBlockModel;
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
import { Select, Space } from 'antd';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { BulkEditFormItemValueType } from '../models/types';
|
||||
import { BulkEditFormItemValueType } from '../../client-v2/types';
|
||||
import { lang } from '../locale';
|
||||
|
||||
function toFormFieldValue(value: any) {
|
||||
|
||||
@@ -8,37 +8,38 @@
|
||||
*/
|
||||
|
||||
import { Plugin, useActionAvailable } from '@nocobase/client';
|
||||
import {
|
||||
bulkEditActionSettings,
|
||||
deprecatedBulkEditActionSettings,
|
||||
bulkEditFormSubmitActionSettings,
|
||||
} from './BulkEditAction.Settings';
|
||||
import * as BulkEditActionSettings from './BulkEditAction.Settings';
|
||||
import { BulkEditActionDecorator } from './BulkEditActionDecorator';
|
||||
import { BulkEditActionInitializer } from './BulkEditActionInitializer';
|
||||
import {
|
||||
BulkEditBlockInitializers_deprecated,
|
||||
CreateFormBulkEditBlockInitializers,
|
||||
bulkEditBlockInitializers,
|
||||
} from './BulkEditBlockInitializers';
|
||||
import {
|
||||
BulkEditFormActionInitializers_deprecated,
|
||||
bulkEditFormActionInitializers,
|
||||
} from './BulkEditFormActionInitializers';
|
||||
import { BulkEditFormItemInitializers_deprecated, bulkEditFormItemInitializers } from './BulkEditFormItemInitializers';
|
||||
import * as BulkEditBlockInitializers from './BulkEditBlockInitializers';
|
||||
import * as BulkEditFormActionInitializers from './BulkEditFormActionInitializers';
|
||||
import * as BulkEditFormItemInitializers from './BulkEditFormItemInitializers';
|
||||
import { bulkEditFormItemSettings } from './bulkEditFormItemSettings';
|
||||
import { bulkEditFormBlockSettings } from './BulkEditFormBlockSettings';
|
||||
import { BulkEditField } from './component/BulkEditField';
|
||||
import { useCustomizeBulkEditActionProps } from './utils';
|
||||
import { bulkEditTitleField } from './models/bulkEditTitleField';
|
||||
import { bulkEditFieldComponent } from './models/bulkEditFieldComponent';
|
||||
import * as models from './models';
|
||||
import { bulkEditTitleField } from '../client-v2/flow/bulkEditTitleField';
|
||||
import { bulkEditFieldComponent } from '../client-v2/flow/bulkEditFieldComponent';
|
||||
import * as models from '../client-v2/flow/models';
|
||||
|
||||
const deprecatedBulkEditActionSettings = (BulkEditActionSettings as any).deprecatedBulkEditActionSettings;
|
||||
const BulkEditBlockInitializers_deprecated = (BulkEditBlockInitializers as any).BulkEditBlockInitializers_deprecated;
|
||||
const CreateFormBulkEditBlockInitializers = (BulkEditBlockInitializers as any).CreateFormBulkEditBlockInitializers;
|
||||
const BulkEditFormActionInitializers_deprecated = (BulkEditFormActionInitializers as any)
|
||||
.BulkEditFormActionInitializers_deprecated;
|
||||
const BulkEditFormItemInitializers_deprecated = (BulkEditFormItemInitializers as any)
|
||||
.BulkEditFormItemInitializers_deprecated;
|
||||
|
||||
const { bulkEditActionSettings, bulkEditFormSubmitActionSettings } = BulkEditActionSettings;
|
||||
const { bulkEditBlockInitializers } = BulkEditBlockInitializers;
|
||||
const { bulkEditFormActionInitializers } = BulkEditFormActionInitializers;
|
||||
const { bulkEditFormItemInitializers } = BulkEditFormItemInitializers;
|
||||
|
||||
export class PluginActionBulkEditClient extends Plugin {
|
||||
async load() {
|
||||
// 先注册 bulkEditTitleField/bulkEditFieldComponent action,再导入 models
|
||||
this.app.flowEngine.registerActions({ bulkEditTitleField, bulkEditFieldComponent });
|
||||
this.app.flowEngine.registerModels(models);
|
||||
|
||||
this.app.addComponents({ BulkEditField, BulkEditActionDecorator });
|
||||
this.app.addScopes({ useCustomizeBulkEditActionProps });
|
||||
this.app.schemaSettingsManager.add(bulkEditFormBlockSettings);
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './dist/client-v2';
|
||||
export { default } from './dist/client-v2';
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('./dist/client-v2/index.js');
|
||||
@@ -21,6 +21,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nocobase/client": "2.x",
|
||||
"@nocobase/client-v2": "2.x",
|
||||
"@nocobase/server": "2.x",
|
||||
"@nocobase/test": "2.x"
|
||||
},
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { ActionModel, ActionSceneEnum, AssignFormModel } from '@nocobase/client';
|
||||
import { ActionModel, ActionSceneEnum, AssignFormModel } from '@nocobase/client-v2';
|
||||
import {
|
||||
FlowModelRenderer,
|
||||
resolveRunJSObjectValues,
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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 { Plugin } from '@nocobase/client-v2';
|
||||
import { BulkUpdateActionModel } from './BulkUpdateActionModel';
|
||||
|
||||
export class PluginActionBulkUpdateClient extends Plugin {
|
||||
async load() {
|
||||
// 注册 Flow 模型以支持新版流程引擎按钮动作
|
||||
this.app.flowEngine.registerModels({
|
||||
BulkUpdateActionModel,
|
||||
});
|
||||
}
|
||||
}
|
||||
export { BulkUpdateActionModel } from './BulkUpdateActionModel';
|
||||
export default PluginActionBulkUpdateClient;
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const NAMESPACE = 'action-bulk-update';
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import { Plugin, useActionAvailable } from '@nocobase/client';
|
||||
import { bulkUpdateActionSettings, deprecatedBulkUpdateActionSettings } from './BulkUpdateAction.Settings';
|
||||
import { BulkUpdateActionModel } from './BulkUpdateActionModel';
|
||||
import { BulkUpdateActionModel } from '../client-v2/BulkUpdateActionModel';
|
||||
import { BulkUpdateActionInitializer } from './BulkUpdateActionInitializer';
|
||||
import { CustomizeActionInitializer } from './CustomizeActionInitializer';
|
||||
import { useCustomizeBulkUpdateActionProps } from './utils';
|
||||
@@ -36,5 +36,5 @@ export class PluginActionBulkUpdateClient extends Plugin {
|
||||
this.app.schemaInitializerManager.addItem('map:configureActions', 'customize.bulkUpdate', initializerData);
|
||||
}
|
||||
}
|
||||
export { BulkUpdateActionModel } from './BulkUpdateActionModel';
|
||||
export { BulkUpdateActionModel } from '../client-v2/BulkUpdateActionModel';
|
||||
export default PluginActionBulkUpdateClient;
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
|
||||
import { i18n } from '@nocobase/client';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const NAMESPACE = 'action-bulk-update';
|
||||
import { NAMESPACE } from '../../client-v2/locale';
|
||||
|
||||
export function lang(key: string) {
|
||||
return i18n.t(key, { ns: NAMESPACE });
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './dist/client-v2';
|
||||
export { default } from './dist/client-v2';
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('./dist/client-v2/index.js');
|
||||
@@ -16,18 +16,21 @@
|
||||
"@formily/react": "2.x",
|
||||
"@formily/shared": "2.x",
|
||||
"antd": "5.x",
|
||||
"file-saver": "^2.0.5",
|
||||
"lodash": "4.x",
|
||||
"react-i18next": "^11.15.1",
|
||||
"react-router-dom": "6.x"
|
||||
},
|
||||
"nocobase": {
|
||||
"supportedVersions": [
|
||||
"1.x"
|
||||
"1.x",
|
||||
"2.x"
|
||||
],
|
||||
"editionLevel": 0
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nocobase/client": "2.x",
|
||||
"@nocobase/client-v2": "2.x",
|
||||
"@nocobase/server": "2.x",
|
||||
"@nocobase/test": "2.x"
|
||||
},
|
||||
|
||||
+15
-24
@@ -7,21 +7,15 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import {
|
||||
ActionSceneEnum,
|
||||
CollectionActionGroupModel,
|
||||
FormActionGroupModel,
|
||||
RecordActionGroupModel,
|
||||
ActionModel,
|
||||
PopupSubTableFormActionGroupModel,
|
||||
} from '@nocobase/client';
|
||||
import { ActionModel, ActionSceneEnum } from '@nocobase/client-v2';
|
||||
import { tExpr } from '@nocobase/flow-engine';
|
||||
import type { ButtonProps } from 'antd/es/button';
|
||||
import { NAMESPACE } from '../locale';
|
||||
import { NAMESPACE } from './locale';
|
||||
import { CUSTOM_REQUEST_ACTION_NAME } from './customRequestFlowAction';
|
||||
|
||||
export class CustomRequestActionModel extends ActionModel {
|
||||
static scene = ActionSceneEnum.all;
|
||||
|
||||
defaultProps: ButtonProps = {
|
||||
title: tExpr('Custom request', { ns: NAMESPACE }),
|
||||
};
|
||||
@@ -54,18 +48,15 @@ CustomRequestActionModel.registerFlow({
|
||||
},
|
||||
});
|
||||
|
||||
CollectionActionGroupModel.registerActionModels({
|
||||
CustomRequestActionModel,
|
||||
});
|
||||
|
||||
RecordActionGroupModel.registerActionModels({
|
||||
CustomRequestActionModel,
|
||||
});
|
||||
|
||||
FormActionGroupModel.registerActionModels({
|
||||
CustomRequestActionModel,
|
||||
});
|
||||
|
||||
PopupSubTableFormActionGroupModel.registerActionModels({
|
||||
CustomRequestActionModel,
|
||||
});
|
||||
export function registerCustomRequestActionGroups(flowEngine: any) {
|
||||
[
|
||||
'CollectionActionGroupModel',
|
||||
'RecordActionGroupModel',
|
||||
'FormActionGroupModel',
|
||||
'PopupSubTableFormActionGroupModel',
|
||||
].forEach((modelName) => {
|
||||
flowEngine.getModelClass(modelName)?.registerActionModels?.({
|
||||
CustomRequestActionModel,
|
||||
});
|
||||
});
|
||||
}
|
||||
+6
-2
@@ -11,7 +11,11 @@ import { useForm } from '@formily/react';
|
||||
import { useFlowContext } from '@nocobase/flow-engine';
|
||||
import { Input } from 'antd';
|
||||
import React from 'react';
|
||||
import { loadCustomRequestRecord, makeRequestKey, toCustomRequestConfigurationInitialValues } from '../utils';
|
||||
import {
|
||||
loadCustomRequestRecord,
|
||||
makeRequestKey,
|
||||
toCustomRequestConfigurationInitialValues,
|
||||
} from '../customRequestUtils';
|
||||
|
||||
const requestConfigInFlight = new Map<string, Promise<any>>();
|
||||
|
||||
@@ -62,7 +66,7 @@ export const RequestKeyField = (props: { value?: string; onChange?: (value: stri
|
||||
});
|
||||
loadedKeyRef.current = value;
|
||||
} catch (error) {
|
||||
// ignore - this key may not have existing config yet
|
||||
console.log(error);
|
||||
}
|
||||
})();
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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 { useFlowContext } from '@nocobase/flow-engine';
|
||||
import { Select } from 'antd';
|
||||
import React from 'react';
|
||||
import { normalizeRoleNames } from '../customRequestUtils';
|
||||
|
||||
type RoleOption = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export const RolesSelect = (props: { value?: string[]; onChange?: (value: string[]) => void }) => {
|
||||
const { value = [], onChange } = props;
|
||||
const { api } = useFlowContext();
|
||||
const [options, setOptions] = React.useState<RoleOption[]>([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
let mounted = true;
|
||||
(async () => {
|
||||
try {
|
||||
const res = await api.resource('roles').list({
|
||||
pageSize: 200,
|
||||
sort: ['createdAt'],
|
||||
});
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
const data = Array.isArray(res?.data?.data) ? res.data.data : [];
|
||||
setOptions(
|
||||
data
|
||||
.map((item: any) => ({
|
||||
label: item?.title || item?.name,
|
||||
value: item?.name,
|
||||
}))
|
||||
.filter((item: RoleOption) => !!item.value),
|
||||
);
|
||||
} catch (error) {
|
||||
if (mounted) {
|
||||
setOptions([]);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}, [api]);
|
||||
|
||||
return (
|
||||
<Select
|
||||
mode="multiple"
|
||||
allowClear
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={(next) => onChange?.(normalizeRoleNames(next))}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+3
-3
@@ -8,15 +8,15 @@
|
||||
*/
|
||||
|
||||
import { ActionScene, defineAction, tExpr } from '@nocobase/flow-engine';
|
||||
import { NAMESPACE } from '../locale';
|
||||
import { executeCustomRequest } from './utils';
|
||||
import { NAMESPACE } from './locale';
|
||||
import { CustomRequestStepParams } from './customRequestFlowActionTypes';
|
||||
import {
|
||||
DEFAULT_CUSTOM_REQUEST_SETTINGS,
|
||||
executeCustomRequest,
|
||||
extractVariablePaths,
|
||||
makeRequestKey,
|
||||
saveCustomRequestConfig,
|
||||
} from './utils';
|
||||
} from './customRequestUtils';
|
||||
import { customRequestUiSchema } from './customRequestUiSchema';
|
||||
|
||||
const buildSanitizedParams = (key: string, params: CustomRequestStepParams): CustomRequestStepParams => {
|
||||
+7
-7
@@ -7,11 +7,10 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { RemoteSelect, TextAreaWithContextSelector } from '@nocobase/client';
|
||||
import { TextAreaWithContextSelector } from '@nocobase/client-v2';
|
||||
import { css } from '@emotion/css';
|
||||
import { InputNumber } from 'antd';
|
||||
import { generateNTemplate } from '../locale';
|
||||
import { FlowJsonWithContextSelector } from './components/FlowJsonWithContextSelector';
|
||||
import { generateNTemplate } from './locale';
|
||||
import { RequestKeyField } from './components/RequestKeyField';
|
||||
import { RolesSelect } from './components/RolesSelect';
|
||||
|
||||
@@ -108,6 +107,7 @@ export const customRequestUiSchema = {
|
||||
'x-component': TextAreaWithContextSelector,
|
||||
'x-component-props': {
|
||||
rows: 1,
|
||||
maxRows: 4,
|
||||
},
|
||||
},
|
||||
remove: {
|
||||
@@ -173,6 +173,7 @@ export const customRequestUiSchema = {
|
||||
'x-component': TextAreaWithContextSelector,
|
||||
'x-component-props': {
|
||||
rows: 1,
|
||||
maxRows: 4,
|
||||
},
|
||||
},
|
||||
remove: {
|
||||
@@ -196,11 +197,10 @@ export const customRequestUiSchema = {
|
||||
type: 'string',
|
||||
title: generateNTemplate('Body'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': FlowJsonWithContextSelector,
|
||||
'x-component': TextAreaWithContextSelector,
|
||||
'x-component-props': {
|
||||
autoSize: {
|
||||
minRows: 10,
|
||||
},
|
||||
rows: 10,
|
||||
maxRows: 20,
|
||||
placeholder: generateNTemplate('Input request data'),
|
||||
},
|
||||
description: generateNTemplate('Only support standard JSON data'),
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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 { Plugin } from '@nocobase/client-v2';
|
||||
import { CustomRequestActionModel, registerCustomRequestActionGroups } from './CustomRequestActionModel';
|
||||
import { customRequestFlowAction } from './customRequestFlowAction';
|
||||
|
||||
export class PluginActionCustomRequestClient extends Plugin {
|
||||
async load() {
|
||||
this.app.flowEngine.registerActions({ customRequestFlowAction });
|
||||
this.app.flowEngine.registerModels({
|
||||
CustomRequestActionModel,
|
||||
});
|
||||
registerCustomRequestActionGroups(this.app.flowEngine);
|
||||
}
|
||||
}
|
||||
|
||||
export { CustomRequestActionModel } from './CustomRequestActionModel';
|
||||
export { customRequestFlowAction } from './customRequestFlowAction';
|
||||
export default PluginActionCustomRequestClient;
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 { useTranslation as useT } from 'react-i18next';
|
||||
|
||||
export const NAMESPACE = 'action-custom-request';
|
||||
|
||||
export function lang(key: string) {
|
||||
const runtimeI18n = (globalThis as any)?.window?.__nocobase_i18n__;
|
||||
return runtimeI18n?.t?.(key, { ns: NAMESPACE }) ?? key;
|
||||
}
|
||||
|
||||
export function generateNTemplate(key: string) {
|
||||
return `{{t('${key}', { ns: '${NAMESPACE}', nsMode: 'fallback' })}}`;
|
||||
}
|
||||
|
||||
export function useTranslation() {
|
||||
return useT(NAMESPACE, {
|
||||
nsMode: 'fallback',
|
||||
});
|
||||
}
|
||||
@@ -9,13 +9,13 @@
|
||||
|
||||
import { Plugin, SchemaComponentOptions } from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { CustomRequestActionModel, registerCustomRequestActionGroups } from '../client-v2/CustomRequestActionModel';
|
||||
import { customRequestFlowAction } from '../client-v2/customRequestFlowAction';
|
||||
import { CustomRequestAction } from './components';
|
||||
import { customRequestActionSettings } from './components/CustomRequestActionDesigner';
|
||||
import { CustomRequestInitializer } from './initializer';
|
||||
import { customRequestFlowAction } from './models/customRequestFlowAction';
|
||||
import { customizeCustomRequestActionSettings } from './schemaSettings';
|
||||
import { CustomRequestConfigurationFieldsSchema } from './schemas';
|
||||
import { CustomRequestActionModel } from './models/CustomRequestActionModel';
|
||||
|
||||
const CustomRequestProvider: React.FC = (props) => {
|
||||
return (
|
||||
@@ -37,6 +37,7 @@ export class PluginActionCustomRequestClient extends Plugin {
|
||||
this.app.flowEngine.registerModels({
|
||||
CustomRequestActionModel,
|
||||
});
|
||||
registerCustomRequestActionGroups(this.app.flowEngine);
|
||||
this.app.schemaSettingsManager.add(customizeCustomRequestActionSettings);
|
||||
|
||||
// @deprecated
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
|
||||
import { i18n } from '@nocobase/client';
|
||||
import { useTranslation as useT } from 'react-i18next';
|
||||
import { NAMESPACE } from '../client-v2/locale';
|
||||
|
||||
export const NAMESPACE = 'action-custom-request';
|
||||
export { NAMESPACE };
|
||||
|
||||
export function lang(key: string) {
|
||||
return i18n.t(key, { ns: NAMESPACE });
|
||||
|
||||
-106
@@ -1,106 +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 { css } from '@emotion/css';
|
||||
import { FlowContextSelector, useFlowContext } from '@nocobase/flow-engine';
|
||||
import { Input } from '@nocobase/client';
|
||||
import { Button } from 'antd';
|
||||
import type { TextAreaProps } from 'antd/es/input';
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
|
||||
const JsonInput = Input.JSON as any;
|
||||
|
||||
export type FlowJsonWithContextSelectorProps = TextAreaProps & {
|
||||
value?: any;
|
||||
onChange?: (value: any) => void;
|
||||
};
|
||||
|
||||
function setNativeInputValue(input: HTMLTextAreaElement, value: string) {
|
||||
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(input.constructor.prototype, 'value')?.set;
|
||||
nativeInputValueSetter?.call(input, value);
|
||||
input.dispatchEvent(
|
||||
new Event('input', {
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export const FlowJsonWithContextSelector: React.FC<FlowJsonWithContextSelectorProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
...props
|
||||
}) => {
|
||||
const flowCtx = useFlowContext();
|
||||
const inputRef = useRef<any>(null);
|
||||
|
||||
const insertAtCaret = useCallback((toInsert: string) => {
|
||||
const textArea = inputRef.current?.resizableTextArea?.textArea as HTMLTextAreaElement | undefined;
|
||||
if (!textArea) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentValue = textArea.value || '';
|
||||
const start = textArea.selectionStart ?? currentValue.length;
|
||||
const end = textArea.selectionEnd ?? start;
|
||||
const nextValue = currentValue.slice(0, start) + toInsert + currentValue.slice(end);
|
||||
|
||||
setNativeInputValue(textArea, nextValue);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const pos = start + toInsert.length;
|
||||
textArea.setSelectionRange(pos, pos);
|
||||
textArea.focus();
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleVariableSelected = useCallback(
|
||||
(varValue: string) => {
|
||||
if (!varValue) {
|
||||
return;
|
||||
}
|
||||
insertAtCaret(varValue);
|
||||
},
|
||||
[insertAtCaret],
|
||||
);
|
||||
|
||||
const metaTree = useMemo(() => () => flowCtx.getPropertyMetaTree?.(), [flowCtx]);
|
||||
|
||||
return (
|
||||
<div style={{ position: 'relative', width: '100%' }}>
|
||||
<JsonInput ref={inputRef} value={value} onChange={onChange} {...props} />
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
zIndex: 1,
|
||||
lineHeight: 0,
|
||||
}}
|
||||
>
|
||||
<FlowContextSelector metaTree={metaTree} onChange={(val) => handleVariableSelected(val)}>
|
||||
<Button
|
||||
type="default"
|
||||
style={{ fontStyle: 'italic', fontFamily: 'New York, Times New Roman, Times, serif' }}
|
||||
className={css`
|
||||
&:not(:hover) {
|
||||
border-right-color: transparent;
|
||||
border-top-color: transparent;
|
||||
background-color: transparent;
|
||||
}
|
||||
`}
|
||||
>
|
||||
x
|
||||
</Button>
|
||||
</FlowContextSelector>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FlowJsonWithContextSelector;
|
||||
-33
@@ -1,33 +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 { RemoteSelect } from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { normalizeRoleNames } from '../utils';
|
||||
|
||||
export const RolesSelect = (props: { value?: string[]; onChange?: (value: string[]) => void }) => {
|
||||
const { value = [], onChange } = props;
|
||||
|
||||
return (
|
||||
<RemoteSelect
|
||||
mode="multiple"
|
||||
manual={false}
|
||||
value={value}
|
||||
service={{
|
||||
resource: 'roles',
|
||||
}}
|
||||
fieldNames={{
|
||||
label: 'title',
|
||||
value: 'name',
|
||||
}}
|
||||
allowClear
|
||||
onChange={(next) => onChange?.(normalizeRoleNames(next))}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './dist/client-v2';
|
||||
export { default } from './dist/client-v2';
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('./dist/client-v2/index.js');
|
||||
@@ -24,6 +24,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nocobase/client": "2.x",
|
||||
"@nocobase/client-v2": "2.x",
|
||||
"@nocobase/server": "2.x",
|
||||
"@nocobase/test": "2.x"
|
||||
},
|
||||
|
||||
+27
-17
@@ -8,20 +8,21 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
FlowModelRenderer,
|
||||
MultiRecordResource,
|
||||
tExpr,
|
||||
useFlowContext,
|
||||
useFlowViewContext,
|
||||
FlowModelRenderer,
|
||||
MultiRecordResource,
|
||||
} from '@nocobase/flow-engine';
|
||||
import { onFieldInputValueChange } from '@formily/core';
|
||||
import { connect, mapProps, useForm } from '@formily/react';
|
||||
import { ActionModel, ActionSceneEnum, useRequest, SkeletonFallback } from '@nocobase/client';
|
||||
import { ActionModel, ActionSceneEnum, RecordActionGroupModel, SkeletonFallback } from '@nocobase/client-v2';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { Tree as AntdTree } from 'antd';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { NAMESPACE } from './locale';
|
||||
import { getCollectionState, getSyncFromForm } from './utils';
|
||||
import { NAMESPACE } from '../locale';
|
||||
|
||||
const Tree = connect(
|
||||
AntdTree,
|
||||
@@ -77,7 +78,14 @@ function RemoteModelRenderer({ options }) {
|
||||
const ctx = useFlowViewContext();
|
||||
const { data, loading }: any = useRequest(
|
||||
async () => {
|
||||
const model: any = await ctx.engine.loadOrCreateModel(options, { delegateToParent: false, delegate: ctx });
|
||||
const model: any = await ctx.engine.loadOrCreateModel(options, {
|
||||
delegateToParent: false,
|
||||
delegate: ctx,
|
||||
skipSave: !ctx.flowSettingsEnabled,
|
||||
});
|
||||
if (options?.scene) {
|
||||
model.actionName = options.scene;
|
||||
}
|
||||
return model;
|
||||
},
|
||||
{
|
||||
@@ -89,8 +97,10 @@ function RemoteModelRenderer({ options }) {
|
||||
}
|
||||
return <FlowModelRenderer model={data} fallback={<SkeletonFallback style={{ margin: 16 }} />} />;
|
||||
}
|
||||
|
||||
const EditDuplicateSubKey = 'deplicate.edit-form-grid-block';
|
||||
function EditFormContent({ model }) {
|
||||
|
||||
function EditFormContent({ model, scene = 'create' }) {
|
||||
const ctx = useFlowContext();
|
||||
const { Header, type } = ctx.view;
|
||||
model._closeView = ctx.view.close;
|
||||
@@ -122,6 +132,7 @@ function EditFormContent({ model }) {
|
||||
delegateToParent: false,
|
||||
subType: 'object',
|
||||
use: 'BlockGridModel',
|
||||
scene,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -142,6 +153,7 @@ export class DuplicateActionModel extends ActionModel {
|
||||
getAclActionName() {
|
||||
return 'create';
|
||||
}
|
||||
|
||||
onClick(event) {
|
||||
if (this.props.duplicateMode === 'quickDulicate') {
|
||||
this.dispatchEvent(
|
||||
@@ -167,6 +179,7 @@ export class DuplicateActionModel extends ActionModel {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getTitle() {
|
||||
return this.duplicateLoading ? this.context.t('Duplicating') : this.props.title || this.context.t('Duplicate');
|
||||
}
|
||||
@@ -177,7 +190,6 @@ DuplicateActionModel.define({
|
||||
sort: 60,
|
||||
});
|
||||
|
||||
//复制模式
|
||||
DuplicateActionModel.registerFlow({
|
||||
key: 'duplicateModeSettings',
|
||||
steps: {
|
||||
@@ -306,7 +318,6 @@ DuplicateActionModel.registerFlow({
|
||||
</a>
|
||||
);
|
||||
},
|
||||
|
||||
'x-reactions': [
|
||||
{
|
||||
dependencies: ['.duplicateMode'],
|
||||
@@ -408,7 +419,6 @@ DuplicateActionModel.registerFlow({
|
||||
margin: '2px 0',
|
||||
},
|
||||
},
|
||||
|
||||
'x-reactions': [
|
||||
{
|
||||
dependencies: ['.collection'],
|
||||
@@ -458,7 +468,6 @@ async function fetchTemplateData(resource: any, template: { dataId: number; fiel
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
//快捷创建
|
||||
DuplicateActionModel.registerFlow({
|
||||
key: 'duplicateSettings',
|
||||
title: tExpr('Duplicate mode settings'),
|
||||
@@ -504,7 +513,7 @@ DuplicateActionModel.registerFlow({
|
||||
default: true,
|
||||
fields:
|
||||
duplicateFields?.filter((v) => {
|
||||
return [...ctx.collection.fields.values()].find((k) => v.includes(k.name));
|
||||
return typeof v === 'string' && [...ctx.collection.fields.values()].find((k) => v.includes(k.name));
|
||||
}) || [],
|
||||
};
|
||||
const resource = ctx.createResource(MultiRecordResource);
|
||||
@@ -527,7 +536,6 @@ DuplicateActionModel.registerFlow({
|
||||
},
|
||||
});
|
||||
|
||||
// 复制并继续填写
|
||||
DuplicateActionModel.registerFlow({
|
||||
key: 'popupSettings',
|
||||
on: {
|
||||
@@ -559,7 +567,7 @@ DuplicateActionModel.registerFlow({
|
||||
size: 'medium',
|
||||
},
|
||||
async handler(ctx, params) {
|
||||
await ctx.model.dispatchEvent('beforeRender'); // 确保 duplicateFields 已经分析过了
|
||||
await ctx.model.dispatchEvent('beforeRender');
|
||||
const { duplicateFields = [] } = ctx.model.props;
|
||||
if (!duplicateFields?.length) {
|
||||
ctx.message.error(
|
||||
@@ -596,7 +604,7 @@ DuplicateActionModel.registerFlow({
|
||||
default: true,
|
||||
fields:
|
||||
duplicateFields?.filter((v) => {
|
||||
return [...ctx.collection.fields.values()].find((k) => v.includes(k.name));
|
||||
return typeof v === 'string' && [...ctx.collection.fields.values()].find((k) => v.includes(k.name));
|
||||
}) || [],
|
||||
};
|
||||
const resource = ctx.createResource(MultiRecordResource);
|
||||
@@ -614,8 +622,6 @@ DuplicateActionModel.registerFlow({
|
||||
if (shouldDelegateToOpenView) {
|
||||
const delegatedParams: any = {
|
||||
...(params || {}),
|
||||
// DuplicateAction 的弹窗是自定义打开逻辑(openDuplicatePopup),
|
||||
// 不能走 openView 动作默认的路由导航(否则路由会触发 model.click,而该模型并不监听 click)。
|
||||
navigation: false,
|
||||
scene: 'new',
|
||||
formData,
|
||||
@@ -644,7 +650,7 @@ DuplicateActionModel.registerFlow({
|
||||
formData,
|
||||
viewUid: runtimeViewUid,
|
||||
},
|
||||
content: () => <EditFormContent model={ctx.model} />,
|
||||
content: () => <EditFormContent model={ctx.model} scene="create" />,
|
||||
styles: {
|
||||
content: {
|
||||
padding: 0,
|
||||
@@ -660,3 +666,7 @@ DuplicateActionModel.registerFlow({
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
RecordActionGroupModel.registerActionModels({
|
||||
DuplicateActionModel,
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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 { Plugin } from '@nocobase/client-v2';
|
||||
import { DuplicateActionModel } from './DuplicateActionModel';
|
||||
|
||||
export class PluginActionDuplicateClient extends Plugin {
|
||||
async load() {
|
||||
this.app.flowEngine.registerModels({
|
||||
DuplicateActionModel,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { DuplicateActionModel } from './DuplicateActionModel';
|
||||
export default PluginActionDuplicateClient;
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const NAMESPACE = 'action-duplicate';
|
||||
+50
-87
@@ -9,7 +9,6 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Tag } from 'antd';
|
||||
import { ArrayBase } from '@formily/antd-v5';
|
||||
import { LRUCache } from 'lru-cache';
|
||||
|
||||
const TreeNode = (props) => {
|
||||
@@ -33,9 +32,7 @@ const TreeNode = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
// 过滤掉系统字段
|
||||
const systemKeys = [
|
||||
// 'id',
|
||||
'sort',
|
||||
'createdById',
|
||||
'createdBy',
|
||||
@@ -47,8 +44,6 @@ const systemKeys = [
|
||||
'sequence',
|
||||
];
|
||||
|
||||
// 广度优先遍历查找一个 key 相等的节点并返回
|
||||
// 注意:返回的 node 是一个响应式对象,修改它的属性会触发页面更新
|
||||
function findNode(treeData, item) {
|
||||
const queue = [...treeData];
|
||||
while (queue.length) {
|
||||
@@ -65,7 +60,6 @@ function findNode(treeData, item) {
|
||||
function loadChildren({ node, traverseAssociations, traverseFields, systemKeys, dataSource, setDataSource }) {
|
||||
const activeNode = findNode(dataSource, node);
|
||||
let children = [];
|
||||
// 多对多和多对一只展示关系字段
|
||||
if (['belongsTo', 'belongsToMany'].includes(node.field.type) && node?.type === 'reference') {
|
||||
children = traverseAssociations(node.field.target, {
|
||||
exclude: systemKeys,
|
||||
@@ -90,9 +84,6 @@ function loadChildren({ node, traverseAssociations, traverseFields, systemKeys,
|
||||
}
|
||||
|
||||
export const getCollectionState = (dm, t, dataSourceKey, displayType = true) => {
|
||||
/**
|
||||
* maxDepth: 从 0 开始,0 表示一层,1 表示两层,以此类推
|
||||
*/
|
||||
const traverseFields = (collectionName, { exclude = [], depth = 0, maxDepth, prefix = '' }) => {
|
||||
if (depth > maxDepth) {
|
||||
return [];
|
||||
@@ -120,7 +111,6 @@ export const getCollectionState = (dm, t, dataSourceKey, displayType = true) =>
|
||||
isLeaf: true,
|
||||
field,
|
||||
};
|
||||
// 多对多和多对一只展示关系字段
|
||||
if (['belongsTo', 'belongsToMany'].includes(field.type)) {
|
||||
node['type'] = 'reference';
|
||||
option['type'] = 'reference';
|
||||
@@ -150,8 +140,7 @@ export const getCollectionState = (dm, t, dataSourceKey, displayType = true) =>
|
||||
if (depth > maxDepth || !dm.getCollection(dataSourceKey, collectionName)) {
|
||||
return [];
|
||||
}
|
||||
// eslint-disable-next-line no-unsafe-optional-chaining
|
||||
return [...dm.getCollection(dataSourceKey, collectionName)?.fields.values()]
|
||||
return [...(dm.getCollection(dataSourceKey, collectionName)?.fields.values() || [])]
|
||||
.map((field) => {
|
||||
if (!field.target || !field.interface) {
|
||||
return;
|
||||
@@ -224,6 +213,7 @@ export const getSyncFromForm = (dm, t, dataSourceKey, collectionName, callBack)
|
||||
if (depth > maxDepth) {
|
||||
return [];
|
||||
}
|
||||
const safeFormData = Array.isArray(formData) ? formData : [];
|
||||
const result = (
|
||||
dm.getCollection(dataSourceKey, collectionName)
|
||||
? [...dm.getCollection(dataSourceKey, collectionName).fields.values()]
|
||||
@@ -252,7 +242,7 @@ export const getSyncFromForm = (dm, t, dataSourceKey, collectionName, callBack)
|
||||
field,
|
||||
disabled,
|
||||
};
|
||||
const targetFormField = formData.find((v) => v.name === option.key);
|
||||
const targetFormField = safeFormData.find((v) => v?.name === option.key);
|
||||
if (
|
||||
['belongsTo', 'belongsToMany'].includes(field.type) &&
|
||||
(!targetFormField || !targetFormField?.updateAssociation)
|
||||
@@ -280,90 +270,66 @@ export const getSyncFromForm = (dm, t, dataSourceKey, collectionName, callBack)
|
||||
depth: depth + 1,
|
||||
maxDepth,
|
||||
prefix: option.key,
|
||||
exclude: ['id', ...systemKeys],
|
||||
exclude: ['id', ...systemKeys.filter((v) => v !== 'sort')],
|
||||
disabled: childrenDisabled,
|
||||
},
|
||||
formData,
|
||||
safeFormData,
|
||||
);
|
||||
}
|
||||
return option;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
cache.set(collectionName + JSON.stringify(safeFormData), result);
|
||||
return result;
|
||||
};
|
||||
})(new LRUCache<string, any>({ max: 100 }));
|
||||
})(new LRUCache({ max: 50 }));
|
||||
|
||||
const traverseAssociations = ((cache) => {
|
||||
return (collectionName, { prefix, maxDepth, depth = 0, exclude = [] }) => {
|
||||
const cacheKey = `${collectionName}-${exclude.join(',')}-${depth}-${maxDepth}-${prefix}`;
|
||||
const cachedResult = cache.get(cacheKey);
|
||||
if (cachedResult) {
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
if (depth > maxDepth) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result = (
|
||||
dm.getCollection(dataSourceKey, collectionName)
|
||||
? [...dm.getCollection(dataSourceKey, collectionName).fields.values()]
|
||||
: []
|
||||
)
|
||||
.map((field) => {
|
||||
if (!field.target || !field.interface) {
|
||||
return;
|
||||
}
|
||||
if (exclude.includes(field.name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const option = {
|
||||
type: 'preloading',
|
||||
tag: t(field.uiSchema?.title) || field.name,
|
||||
};
|
||||
const value = prefix ? `${prefix}.${field.name}` : field.name;
|
||||
return {
|
||||
type: 'preloading',
|
||||
tag: t(field.uiSchema?.title) || field.name,
|
||||
title: React.createElement(TreeNode, option),
|
||||
key: value,
|
||||
isLeaf: false,
|
||||
field,
|
||||
children: traverseAssociations(field.target, {
|
||||
prefix: value,
|
||||
depth: depth + 1,
|
||||
maxDepth,
|
||||
exclude,
|
||||
}),
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
cache.set(cacheKey, result);
|
||||
return result;
|
||||
};
|
||||
})(new LRUCache<string, any>({ max: 100 }));
|
||||
const getEnableFieldTree = (collectionName: string, formData) => {
|
||||
if (!collectionName) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
return traverseFields(collectionName, { exclude: ['id', ...systemKeys], maxDepth: 1, disabled: false }, formData);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
const traverseAssociations = (collectionName, { prefix, maxDepth, depth = 0, exclude = [] }) => {
|
||||
if (depth > maxDepth || !dm.getCollection(dataSourceKey, collectionName)) {
|
||||
return [];
|
||||
}
|
||||
return [...(dm.getCollection(dataSourceKey, collectionName)?.fields.values() || [])]
|
||||
.map((field) => {
|
||||
if (!field.target || !field.interface) {
|
||||
return;
|
||||
}
|
||||
if (exclude.includes(field.name)) {
|
||||
return;
|
||||
}
|
||||
const option = {
|
||||
type: 'preloading',
|
||||
tag: t(field.uiSchema?.title) || field.name,
|
||||
};
|
||||
const value = prefix ? `${prefix}.${field.name}` : field.name;
|
||||
return {
|
||||
title: React.createElement(TreeNode, option),
|
||||
key: value,
|
||||
disabled: true,
|
||||
isLeaf: false,
|
||||
field,
|
||||
children: traverseAssociations(field.target, {
|
||||
prefix: value,
|
||||
depth: depth + 1,
|
||||
maxDepth,
|
||||
exclude,
|
||||
}),
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
return {
|
||||
async run(model) {
|
||||
const formData = new Set([]);
|
||||
const selectFields = new Set([]);
|
||||
if (!model?.mapSubModels) {
|
||||
callBack?.([], []);
|
||||
return;
|
||||
}
|
||||
|
||||
const getAssociationAppends = (model, subKey) => {
|
||||
model.mapSubModels(subKey, (item) => {
|
||||
const formData = new Set<any>();
|
||||
const selectFields = new Set<string>();
|
||||
|
||||
const getAssociationAppends = (currentModel, subKey) => {
|
||||
currentModel?.mapSubModels?.(subKey, (item) => {
|
||||
if (
|
||||
item.collectionField &&
|
||||
!(['hasOne', 'hasMany'].includes(item.collectionField.type) || item.subModels.field?.updateAssociation)
|
||||
@@ -376,26 +342,23 @@ export const getSyncFromForm = (dm, t, dataSourceKey, collectionName, callBack)
|
||||
name: item.fieldPath,
|
||||
updateAssociation: item.subModels.field?.updateAssociation,
|
||||
});
|
||||
|
||||
if (item.subModels.field?.updateAssociation) {
|
||||
if (item.subModels.field.subModels.grid) {
|
||||
//子表单
|
||||
getAssociationAppends(item.subModels.field.subModels.grid, 'items');
|
||||
} else if (item.subModels.field.subModels.subTableColumns) {
|
||||
//子表格(弹窗编辑)subTableColumns
|
||||
getAssociationAppends(item.subModels.field, 'subTableColumns');
|
||||
} else if (item.subModels.field.subModels.columns) {
|
||||
//子表格(行内编辑)subTableColumns
|
||||
getAssociationAppends(item.subModels.field, 'columns');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
getAssociationAppends(model, 'items');
|
||||
const treeData = getEnableFieldTree(collectionName, [...formData]);
|
||||
if (callBack) {
|
||||
callBack(treeData, [...selectFields]);
|
||||
}
|
||||
const fieldTree = traverseFields(collectionName, { exclude: ['id', ...systemKeys], maxDepth: 1 }, [...formData]);
|
||||
callBack?.(fieldTree, [...selectFields]);
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -13,7 +13,7 @@ import { deprecatedDuplicateActionSettings, duplicateActionSettings } from './Du
|
||||
import { DuplicateActionDecorator } from './DuplicateActionDecorator';
|
||||
import { DuplicateActionInitializer } from './DuplicateActionInitializer';
|
||||
import { DuplicatePluginProvider } from './DuplicatePluginProvider';
|
||||
import { DuplicateActionModel } from './models/DuplicateActionModel';
|
||||
import { DuplicateActionModel } from '../client-v2/DuplicateActionModel';
|
||||
|
||||
export class PluginActionDuplicateClient extends Plugin {
|
||||
async load() {
|
||||
@@ -51,3 +51,4 @@ export class PluginActionDuplicateClient extends Plugin {
|
||||
|
||||
export default PluginActionDuplicateClient;
|
||||
export * from './DuplicateAction';
|
||||
export { DuplicateActionModel } from '../client-v2/DuplicateActionModel';
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
|
||||
import { i18n } from '@nocobase/client';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { NAMESPACE } from '../../client-v2/locale';
|
||||
|
||||
export const NAMESPACE = 'action-duplicate';
|
||||
export { NAMESPACE };
|
||||
|
||||
export function lang(key: string) {
|
||||
return i18n.t(key, { ns: NAMESPACE });
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './dist/client-v2';
|
||||
export { default } from './dist/client-v2';
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('./dist/client-v2/index.js');
|
||||
@@ -34,6 +34,7 @@
|
||||
"peerDependencies": {
|
||||
"@nocobase/actions": "2.x",
|
||||
"@nocobase/client": "2.x",
|
||||
"@nocobase/client-v2": "2.x",
|
||||
"@nocobase/database": "2.x",
|
||||
"@nocobase/server": "2.x",
|
||||
"@nocobase/test": "2.x",
|
||||
|
||||
+19
-20
@@ -8,19 +8,21 @@
|
||||
*/
|
||||
|
||||
import { escapeT } from '@nocobase/flow-engine';
|
||||
import type { ButtonProps } from 'antd/es/button';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { ActionModel, Cascader, ActionSceneEnum } from '@nocobase/client';
|
||||
import { ActionModel, ActionSceneEnum } from '@nocobase/client-v2';
|
||||
import { css } from '@emotion/css';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { Cascader } from 'antd';
|
||||
import type { ButtonProps } from 'antd/es/button';
|
||||
import { getOptionFields } from './getOptionFields';
|
||||
import { NAMESPACE } from '../locale';
|
||||
import { NAMESPACE } from './locale';
|
||||
|
||||
const initExportSettings = (fields) => {
|
||||
const exportSettings = fields
|
||||
?.filter((f) => !f.isAssociationField() && f.options.interface)
|
||||
.map((f) => ({ dataIndex: [f.name] }));
|
||||
?.filter((field) => !field.isAssociationField() && field.options.interface)
|
||||
.map((field) => ({ dataIndex: [field.name] }));
|
||||
return exportSettings;
|
||||
};
|
||||
|
||||
export class ExportActionModel extends ActionModel {
|
||||
static scene = ActionSceneEnum.collection;
|
||||
|
||||
@@ -60,30 +62,28 @@ ExportActionModel.registerFlow({
|
||||
const { title, fields, filterTargetKey } = currentBlock.collection;
|
||||
const selectedRows = resource.getSelectedRows();
|
||||
let filter;
|
||||
// If there are selected rows, use the filter for selected rows, otherwise use the default filter
|
||||
|
||||
if (selectedRows.length > 0) {
|
||||
filter = { [filterTargetKey]: ctx.blockModel.collection.getFilterByTK(selectedRows) };
|
||||
} else {
|
||||
filter = resource.getFilter(); // Default filter if no rows are selected
|
||||
filter = resource.getFilter();
|
||||
}
|
||||
|
||||
// If filterTargetKey indicates a complex filter (e.g., multiple keys), apply `$or`
|
||||
if (Array.isArray(filterTargetKey) && filterTargetKey.length > 1) {
|
||||
// Assuming getFilterByTK returns an array of conditions, apply $or
|
||||
filter = { $or: ctx.blockModel.collection.getFilterByTK(selectedRows) };
|
||||
}
|
||||
|
||||
exportSettings.forEach((es) => {
|
||||
const { uiSchema, interface: fieldInterface } = fields.get(es.dataIndex[0]) ?? {};
|
||||
// @ts-ignore
|
||||
es.enum = uiSchema?.enum?.map((e) => ({ value: e.value, label: e.label }));
|
||||
if (!es.enum && uiSchema?.type === 'boolean') {
|
||||
es.enum = [
|
||||
exportSettings.forEach((exportSetting) => {
|
||||
const { uiSchema, interface: fieldInterface } = fields.get(exportSetting.dataIndex[0]) ?? {};
|
||||
void fieldInterface;
|
||||
exportSetting.enum = uiSchema?.enum?.map((item) => ({ value: item.value, label: item.label }));
|
||||
if (!exportSetting.enum && uiSchema?.type === 'boolean') {
|
||||
exportSetting.enum = [
|
||||
{ value: true, label: ctx.t('Yes') },
|
||||
{ value: false, label: ctx.t('No') },
|
||||
];
|
||||
}
|
||||
es.defaultTitle = ctx.t(uiSchema?.title);
|
||||
exportSetting.defaultTitle = ctx.t(uiSchema?.title);
|
||||
});
|
||||
|
||||
const data = await resource.runAction('export', {
|
||||
@@ -95,7 +95,7 @@ ExportActionModel.registerFlow({
|
||||
title: ctx.t(title),
|
||||
appends: resource.getAppends(),
|
||||
sort: resource.getSort(),
|
||||
filter: filter,
|
||||
filter,
|
||||
},
|
||||
});
|
||||
const blob = new Blob([data], { type: 'application/x-xls' });
|
||||
@@ -151,7 +151,6 @@ ExportActionModel.registerFlow({
|
||||
value: 'name',
|
||||
children: 'children',
|
||||
},
|
||||
// labelInValue: true,
|
||||
changeOnSelect: false,
|
||||
options: data,
|
||||
},
|
||||
@@ -205,7 +204,7 @@ ExportActionModel.registerFlow({
|
||||
const columns = exportSettings
|
||||
?.filter((fieldItem) => fieldItem?.dataIndex?.length)
|
||||
.map((item) => ({
|
||||
dataIndex: item.dataIndex.map((di) => di.name ?? di),
|
||||
dataIndex: item.dataIndex.map((dataIndex) => dataIndex.name ?? dataIndex),
|
||||
title: item.title,
|
||||
}));
|
||||
ctx.model.setProps('exportSettings', columns);
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const TO_ONE_RELATION_TYPES = ['hasOne', 'belongsTo'];
|
||||
const TO_MANY_RELATION_TYPES = ['hasMany', 'belongsToMany', 'belongsToArray'];
|
||||
const RELATION_TYPES = [...TO_ONE_RELATION_TYPES, 'hasMany', 'belongsToMany', 'belongsToArray'];
|
||||
|
||||
const isRelationField = (field) => field?.target && RELATION_TYPES.includes(field.type);
|
||||
const isToManyRelationField = (field) => field?.target && TO_MANY_RELATION_TYPES.includes(field.type);
|
||||
|
||||
const createOption = (field, getTitle, disabled = false) => ({
|
||||
name: field.name,
|
||||
title: getTitle(field),
|
||||
schema: field?.uiSchema,
|
||||
disabled,
|
||||
});
|
||||
|
||||
const canNestRelationField = (parentField, childField, relationDepth) => {
|
||||
if (relationDepth >= 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isToManyRelationField(parentField) && isToManyRelationField(childField)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const buildFieldOption = (field, getTitle, getTargetFields, relationDepth = 0) => {
|
||||
if (!field?.interface) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isRelationField(field)) {
|
||||
return createOption(field, getTitle);
|
||||
}
|
||||
|
||||
const targetFields = getTargetFields(field) || [];
|
||||
const children = targetFields
|
||||
.map((targetField) => {
|
||||
if (!isRelationField(targetField)) {
|
||||
return buildFieldOption(targetField, getTitle, getTargetFields);
|
||||
}
|
||||
|
||||
if (!canNestRelationField(field, targetField, relationDepth)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return buildFieldOption(targetField, getTitle, getTargetFields, relationDepth + 1);
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
if (!children.length) {
|
||||
return createOption(field, getTitle, true);
|
||||
}
|
||||
|
||||
return {
|
||||
...createOption(field, getTitle),
|
||||
children,
|
||||
};
|
||||
};
|
||||
|
||||
export const buildExportFieldOptions = (fields, getTitle, getTargetFields) => {
|
||||
return (fields || [])
|
||||
.map((field) => buildFieldOption(field, getTitle, getTargetFields, isRelationField(field) ? 1 : 0))
|
||||
.filter(Boolean);
|
||||
};
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { buildExportFieldOptions } from '../buildExportFieldOptions';
|
||||
import { buildExportFieldOptions } from './buildExportFieldOptions';
|
||||
|
||||
export const getOptionFields = (fields, t) => {
|
||||
return buildExportFieldOptions(
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 { Plugin } from '@nocobase/client-v2';
|
||||
import { ExportActionModel } from './ExportActionModel';
|
||||
|
||||
export class PluginActionExportClient extends Plugin {
|
||||
async load() {
|
||||
this.app.flowEngine.registerModels({
|
||||
ExportActionModel,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { ExportActionModel } from './ExportActionModel';
|
||||
export { buildExportFieldOptions } from './buildExportFieldOptions';
|
||||
export default PluginActionExportClient;
|
||||
+1
-1
@@ -7,4 +7,4 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
export * from './CustomRequestActionModel';
|
||||
export const NAMESPACE = 'action-export';
|
||||
+1
-66
@@ -7,69 +7,4 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
const TO_ONE_RELATION_TYPES = ['hasOne', 'belongsTo'];
|
||||
const TO_MANY_RELATION_TYPES = ['hasMany', 'belongsToMany', 'belongsToArray'];
|
||||
const RELATION_TYPES = [...TO_ONE_RELATION_TYPES, 'hasMany', 'belongsToMany', 'belongsToArray'];
|
||||
|
||||
const isRelationField = (field) => field?.target && RELATION_TYPES.includes(field.type);
|
||||
const isToManyRelationField = (field) => field?.target && TO_MANY_RELATION_TYPES.includes(field.type);
|
||||
|
||||
const createOption = (field, getTitle, disabled = false) => ({
|
||||
name: field.name,
|
||||
title: getTitle(field),
|
||||
schema: field?.uiSchema,
|
||||
disabled,
|
||||
});
|
||||
|
||||
const canNestRelationField = (parentField, childField, relationDepth) => {
|
||||
if (relationDepth >= 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isToManyRelationField(parentField) && isToManyRelationField(childField)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const buildFieldOption = (field, getTitle, getTargetFields, relationDepth = 0) => {
|
||||
if (!field?.interface) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isRelationField(field)) {
|
||||
return createOption(field, getTitle);
|
||||
}
|
||||
|
||||
const targetFields = getTargetFields(field) || [];
|
||||
const children = targetFields
|
||||
.map((targetField) => {
|
||||
if (!isRelationField(targetField)) {
|
||||
return buildFieldOption(targetField, getTitle, getTargetFields);
|
||||
}
|
||||
|
||||
if (!canNestRelationField(field, targetField, relationDepth)) {
|
||||
// return createOption(targetField, getTitle, true);
|
||||
return null;
|
||||
}
|
||||
|
||||
return buildFieldOption(targetField, getTitle, getTargetFields, relationDepth + 1);
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
if (!children.length) {
|
||||
return createOption(field, getTitle, true);
|
||||
}
|
||||
|
||||
return {
|
||||
...createOption(field, getTitle),
|
||||
children,
|
||||
};
|
||||
};
|
||||
|
||||
export const buildExportFieldOptions = (fields, getTitle, getTargetFields) => {
|
||||
return (fields || [])
|
||||
.map((field) => buildFieldOption(field, getTitle, getTargetFields, isRelationField(field) ? 1 : 0))
|
||||
.filter(Boolean);
|
||||
};
|
||||
export { buildExportFieldOptions } from '../client-v2/buildExportFieldOptions';
|
||||
|
||||
@@ -11,12 +11,12 @@ export * from './ExportActionInitializer';
|
||||
export * from './ExportDesigner';
|
||||
export * from './ExportPluginProvider';
|
||||
export * from './useExportAction';
|
||||
import { Plugin, useCollection, useActionAvailable } from '@nocobase/client';
|
||||
import { Plugin, useActionAvailable } from '@nocobase/client';
|
||||
import { ExportActionModel } from '../client-v2/ExportActionModel';
|
||||
import { ExportPluginProvider } from './ExportPluginProvider';
|
||||
import { exportActionSchemaSettings } from './schemaSettings';
|
||||
import { ExportActionModel } from './models/ExportActionModel';
|
||||
|
||||
export { buildExportFieldOptions } from './buildExportFieldOptions';
|
||||
export { buildExportFieldOptions } from '../client-v2/buildExportFieldOptions';
|
||||
|
||||
export class PluginActionExportClient extends Plugin {
|
||||
async load() {
|
||||
@@ -45,4 +45,5 @@ export class PluginActionExportClient extends Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
export { ExportActionModel } from '../client-v2/ExportActionModel';
|
||||
export default PluginActionExportClient;
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
*/
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { NAMESPACE } from '../../client-v2/locale';
|
||||
|
||||
export const NAMESPACE = 'action-export';
|
||||
export { NAMESPACE };
|
||||
|
||||
export function useExportTranslation() {
|
||||
return useTranslation([NAMESPACE, 'client'], { nsMode: 'fallback' });
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './dist/client-v2';
|
||||
export { default } from './dist/client-v2';
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('./dist/client-v2/index.js');
|
||||
@@ -41,6 +41,7 @@
|
||||
"peerDependencies": {
|
||||
"@nocobase/actions": "2.x",
|
||||
"@nocobase/client": "2.x",
|
||||
"@nocobase/client-v2": "2.x",
|
||||
"@nocobase/database": "2.x",
|
||||
"@nocobase/server": "2.x",
|
||||
"@nocobase/test": "2.x",
|
||||
|
||||
+20
-29
@@ -13,14 +13,14 @@ import { FormButtonGroup, FormItem, FormLayout } from '@formily/antd-v5';
|
||||
import { createForm } from '@formily/core';
|
||||
import { createSchemaField, FormProvider } from '@formily/react';
|
||||
import { observable } from '@formily/reactive';
|
||||
import { Cascader, ActionModel, ActionSceneEnum } from '@nocobase/client';
|
||||
import { ActionModel, ActionSceneEnum } from '@nocobase/client-v2';
|
||||
import { escapeT, observer } from '@nocobase/flow-engine';
|
||||
import { Button, Space, Spin, Upload } from 'antd';
|
||||
import { Button, Cascader, Space, Spin, Upload } from 'antd';
|
||||
import type { ButtonProps } from 'antd/es/button';
|
||||
import { saveAs } from 'file-saver';
|
||||
import React from 'react';
|
||||
import { DownloadTips, ImportWarning } from '../ImportActionInitializer';
|
||||
import { NAMESPACE } from '../constants';
|
||||
import { DownloadTips, ImportWarning, initImportSettings } from './importSupport';
|
||||
import { NAMESPACE } from './locale';
|
||||
import { getOptionFields } from './getOptionFields';
|
||||
|
||||
const EXCLUDE_INTERFACES = ['id', 'createdAt', 'createdBy', 'updatedAt', 'updatedBy'];
|
||||
@@ -30,13 +30,6 @@ const INCLUDE_FILE_TYPE = [
|
||||
'application/wps-office.xlsx',
|
||||
];
|
||||
|
||||
const initImportSettings = (fields) => {
|
||||
const importColumns = fields
|
||||
?.filter((f) => !f.isAssociationField() && !(EXCLUDE_INTERFACES.includes(f.interface) || !f.options.interface))
|
||||
.map((f) => ({ dataIndex: [f.name] }));
|
||||
return { importColumns, explain: '' };
|
||||
};
|
||||
|
||||
const importFormSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@@ -73,7 +66,7 @@ const importFormSchema = {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-acl-ignore': true,
|
||||
'x-component': 'Upload.Dragger',
|
||||
'x-validator': (value, rule) => {
|
||||
'x-validator': (value) => {
|
||||
const { fileList } = value;
|
||||
if (fileList.length > 1) {
|
||||
return {
|
||||
@@ -95,9 +88,7 @@ const importFormSchema = {
|
||||
height: '150px',
|
||||
maxCount: 1,
|
||||
children: escapeT('Upload placeholder', { ns: `${NAMESPACE}` }),
|
||||
beforeUpload: () => {
|
||||
return false;
|
||||
},
|
||||
beforeUpload: () => false,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -121,6 +112,7 @@ export class ImportActionModel extends ActionModel {
|
||||
type: 'default',
|
||||
icon: 'uploadoutlined',
|
||||
};
|
||||
|
||||
getAclActionName() {
|
||||
return 'importXlsx';
|
||||
}
|
||||
@@ -129,21 +121,23 @@ export class ImportActionModel extends ActionModel {
|
||||
ImportActionModel.define({
|
||||
label: escapeT('Import'),
|
||||
});
|
||||
const toArr = (v: any) => {
|
||||
if (!v || !Array.isArray(v)) {
|
||||
|
||||
const toArr = (value: any) => {
|
||||
if (!value || !Array.isArray(value)) {
|
||||
return [];
|
||||
}
|
||||
return v;
|
||||
return value;
|
||||
};
|
||||
|
||||
ImportActionModel.registerFlow({
|
||||
key: 'importSettings',
|
||||
title: escapeT('Import settings'),
|
||||
on: 'click',
|
||||
steps: {
|
||||
import: {
|
||||
handler: async (ctx, params) => {
|
||||
handler: async (ctx) => {
|
||||
const state = observable({
|
||||
status: '', // '', 'importing', 'imported'
|
||||
status: '',
|
||||
result: null as any,
|
||||
});
|
||||
const form = createForm();
|
||||
@@ -173,6 +167,7 @@ ImportActionModel.registerFlow({
|
||||
return column;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
const handelDownloadXlsxTemplateAction = async () => {
|
||||
const data = await resource.runAction('downloadXlsxTemplate', {
|
||||
data: {
|
||||
@@ -191,7 +186,7 @@ ImportActionModel.registerFlow({
|
||||
const { upload } = form.values;
|
||||
const { fileList } = upload;
|
||||
const formData = new FormData();
|
||||
const uploadFiles = fileList.map((f) => f.originFileObj);
|
||||
const uploadFiles = fileList.map((file) => file.originFileObj);
|
||||
formData.append('file', uploadFiles[0]);
|
||||
formData.append('columns', JSON.stringify(columns));
|
||||
formData.append('explain', explain);
|
||||
@@ -213,7 +208,7 @@ ImportActionModel.registerFlow({
|
||||
await resource?.refresh?.();
|
||||
form.reset();
|
||||
} else {
|
||||
popover.close(); // 后台任务直接关闭弹窗
|
||||
popover.close();
|
||||
form.reset();
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -226,7 +221,7 @@ ImportActionModel.registerFlow({
|
||||
const { t } = ctx;
|
||||
const renderResult = (result) => {
|
||||
if (!result) return null;
|
||||
const { successCount, meta } = result;
|
||||
const { successCount } = result;
|
||||
if (successCount) {
|
||||
return t('{{successCount}} records have been successfully imported', { successCount, ns: NAMESPACE });
|
||||
}
|
||||
@@ -270,7 +265,6 @@ ImportActionModel.registerFlow({
|
||||
);
|
||||
}
|
||||
|
||||
// 初始导入表单
|
||||
return (
|
||||
<FormProvider form={form}>
|
||||
<FormLayout layout="vertical">
|
||||
@@ -295,9 +289,7 @@ ImportActionModel.registerFlow({
|
||||
placement: 'center',
|
||||
width: 800,
|
||||
title: ctx.t('Import Data', { ns: `${NAMESPACE}` }),
|
||||
content: (popover) => {
|
||||
return <ImportDialogContent popover={popover} />;
|
||||
},
|
||||
content: (popover) => <ImportDialogContent popover={popover} />,
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -349,7 +341,6 @@ ImportActionModel.registerFlow({
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': Cascader,
|
||||
required: true,
|
||||
|
||||
'x-component-props': {
|
||||
fieldNames: {
|
||||
label: 'title',
|
||||
@@ -417,7 +408,7 @@ ImportActionModel.registerFlow({
|
||||
const columns = importColumns
|
||||
?.filter((fieldItem) => fieldItem?.dataIndex?.length)
|
||||
.map((item) => ({
|
||||
dataIndex: item.dataIndex.map((di) => di.name ?? di),
|
||||
dataIndex: item.dataIndex.map((dataIndex) => dataIndex.name ?? dataIndex),
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
}));
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const EXCLUDE_INTERFACES = ['createdAt', 'createdBy', 'updatedAt', 'updatedBy'];
|
||||
const RELATION_TYPES = ['hasOne', 'belongsTo', 'hasMany', 'belongsToMany', 'belongsToArray'];
|
||||
|
||||
const isRelationField = (field) => field?.target && RELATION_TYPES.includes(field.type);
|
||||
|
||||
const shouldExcludeField = (field) => !field?.interface || EXCLUDE_INTERFACES.includes(field.interface);
|
||||
|
||||
const createOption = (field, getTitle, disabled = false) => ({
|
||||
name: field.name,
|
||||
title: getTitle(field),
|
||||
schema: field?.uiSchema,
|
||||
disabled,
|
||||
});
|
||||
|
||||
const buildFieldOption = (field, getTitle, getTargetFields, relationDepth = 0) => {
|
||||
if (shouldExcludeField(field)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isRelationField(field)) {
|
||||
return createOption(field, getTitle);
|
||||
}
|
||||
|
||||
const targetFields = getTargetFields(field) || [];
|
||||
const children = targetFields
|
||||
.map((targetField) => {
|
||||
if (shouldExcludeField(targetField)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isRelationField(targetField)) {
|
||||
return buildFieldOption(targetField, getTitle, getTargetFields);
|
||||
}
|
||||
|
||||
if (relationDepth >= 1) {
|
||||
return createOption(targetField, getTitle, true);
|
||||
}
|
||||
|
||||
return buildFieldOption(targetField, getTitle, getTargetFields, relationDepth + 1);
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
if (!children.length) {
|
||||
return createOption(field, getTitle, true);
|
||||
}
|
||||
|
||||
return {
|
||||
...createOption(field, getTitle),
|
||||
children,
|
||||
};
|
||||
};
|
||||
|
||||
export const buildImportFieldOptions = (fields, getTitle, getTargetFields) => {
|
||||
return (fields || [])
|
||||
.map((field) => buildFieldOption(field, getTitle, getTargetFields, isRelationField(field) ? 1 : 0))
|
||||
.filter(Boolean);
|
||||
};
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { buildImportFieldOptions } from '../buildImportFieldOptions';
|
||||
import { buildImportFieldOptions } from './buildImportFieldOptions';
|
||||
|
||||
export const getOptionFields = (fields, t) => {
|
||||
return buildImportFieldOptions(
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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 { Alert } from 'antd';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { NAMESPACE } from './locale';
|
||||
|
||||
export const initImportSettings = (fields) => {
|
||||
const importColumns = fields
|
||||
?.filter((field) => !field.children && !field.disabled)
|
||||
.map((field) => ({ dataIndex: [field.name] }));
|
||||
return { importColumns, explain: '' };
|
||||
};
|
||||
|
||||
export const ImportWarning = () => {
|
||||
const { t } = useTranslation([NAMESPACE, 'client'], { nsMode: 'fallback' });
|
||||
return <Alert type="warning" style={{ marginBottom: '10px' }} message={t('Import warnings', { limit: 2000 })} />;
|
||||
};
|
||||
|
||||
export const DownloadTips = () => {
|
||||
const { t } = useTranslation([NAMESPACE, 'client'], { nsMode: 'fallback' });
|
||||
return <Alert type="info" style={{ marginBottom: '10px', whiteSpace: 'pre-line' }} message={t('Download tips')} />;
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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 { Plugin } from '@nocobase/client-v2';
|
||||
import { ImportActionModel } from './ImportActionModel';
|
||||
|
||||
export class PluginActionImportClient extends Plugin {
|
||||
async load() {
|
||||
this.app.flowEngine.registerModels({ ImportActionModel });
|
||||
}
|
||||
}
|
||||
|
||||
export { buildImportFieldOptions } from './buildImportFieldOptions';
|
||||
export { ImportActionModel } from './ImportActionModel';
|
||||
export default PluginActionImportClient;
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const NAMESPACE = 'action-import';
|
||||
+2
-17
@@ -17,10 +17,9 @@ import {
|
||||
useSchemaInitializerItem,
|
||||
} from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { useImportTranslation } from './locale';
|
||||
import { useFields } from './useFields';
|
||||
import { Alert } from 'antd';
|
||||
import { lodash } from '@nocobase/utils/client';
|
||||
import { DownloadTips, ImportWarning, initImportSettings } from '../client-v2/importSupport';
|
||||
|
||||
const findSchema = (schema: Schema, key: string, action: string) => {
|
||||
return schema.reduceProperties((buf, s) => {
|
||||
@@ -35,11 +34,6 @@ const findSchema = (schema: Schema, key: string, action: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const initImportSettings = (fields) => {
|
||||
const importColumns = fields?.filter((f) => !f.children && !f.disabled).map((f) => ({ dataIndex: [f.name] }));
|
||||
return { importColumns, explain: '' };
|
||||
};
|
||||
|
||||
export const ImportActionInitializer = () => {
|
||||
const itemConfig = useSchemaInitializerItem();
|
||||
const { insert } = useSchemaInitializer();
|
||||
@@ -67,13 +61,4 @@ export const ImportActionInitializer = () => {
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ImportWarning = () => {
|
||||
const { t } = useImportTranslation();
|
||||
return <Alert type="warning" style={{ marginBottom: '10px' }} message={t('Import warnings', { limit: 2000 })} />;
|
||||
};
|
||||
|
||||
export const DownloadTips = () => {
|
||||
const { t } = useImportTranslation();
|
||||
return <Alert type="info" style={{ marginBottom: '10px', whiteSpace: 'pre-line' }} message={t('Download tips')} />;
|
||||
};
|
||||
export { DownloadTips, ImportWarning, initImportSettings };
|
||||
|
||||
+1
-57
@@ -7,60 +7,4 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
const EXCLUDE_INTERFACES = ['createdAt', 'createdBy', 'updatedAt', 'updatedBy'];
|
||||
const RELATION_TYPES = ['hasOne', 'belongsTo', 'hasMany', 'belongsToMany', 'belongsToArray'];
|
||||
|
||||
const isRelationField = (field) => field?.target && RELATION_TYPES.includes(field.type);
|
||||
|
||||
const shouldExcludeField = (field) => !field?.interface || EXCLUDE_INTERFACES.includes(field.interface);
|
||||
|
||||
const createOption = (field, getTitle, disabled = false) => ({
|
||||
name: field.name,
|
||||
title: getTitle(field),
|
||||
schema: field?.uiSchema,
|
||||
disabled,
|
||||
});
|
||||
|
||||
const buildFieldOption = (field, getTitle, getTargetFields, relationDepth = 0) => {
|
||||
if (shouldExcludeField(field)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isRelationField(field)) {
|
||||
return createOption(field, getTitle);
|
||||
}
|
||||
|
||||
const targetFields = getTargetFields(field) || [];
|
||||
const children = targetFields
|
||||
.map((targetField) => {
|
||||
if (shouldExcludeField(targetField)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isRelationField(targetField)) {
|
||||
return buildFieldOption(targetField, getTitle, getTargetFields);
|
||||
}
|
||||
|
||||
if (relationDepth >= 1) {
|
||||
return createOption(targetField, getTitle, true);
|
||||
}
|
||||
|
||||
return buildFieldOption(targetField, getTitle, getTargetFields, relationDepth + 1);
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
if (!children.length) {
|
||||
return createOption(field, getTitle, true);
|
||||
}
|
||||
|
||||
return {
|
||||
...createOption(field, getTitle),
|
||||
children,
|
||||
};
|
||||
};
|
||||
|
||||
export const buildImportFieldOptions = (fields, getTitle, getTargetFields) => {
|
||||
return (fields || [])
|
||||
.map((field) => buildFieldOption(field, getTitle, getTargetFields, isRelationField(field) ? 1 : 0))
|
||||
.filter(Boolean);
|
||||
};
|
||||
export { buildImportFieldOptions } from '../client-v2/buildImportFieldOptions';
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
export const NAMESPACE = 'action-import';
|
||||
export { NAMESPACE } from '../client-v2/locale';
|
||||
|
||||
@@ -16,11 +16,11 @@ export * from './ImportPluginProvider';
|
||||
export * from './useImportAction';
|
||||
|
||||
import { Plugin, useActionAvailable } from '@nocobase/client';
|
||||
import { ImportActionModel } from '../client-v2/ImportActionModel';
|
||||
import { ImportPluginProvider } from './ImportPluginProvider';
|
||||
import { importActionSchemaSettings } from './schemaSettings';
|
||||
import { ImportActionModel } from './models/ImportActionModel';
|
||||
|
||||
export { buildImportFieldOptions } from './buildImportFieldOptions';
|
||||
export { buildImportFieldOptions } from '../client-v2/buildImportFieldOptions';
|
||||
|
||||
export class PluginActionImportClient extends Plugin {
|
||||
async load() {
|
||||
@@ -49,4 +49,5 @@ export class PluginActionImportClient extends Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
export { ImportActionModel } from '../client-v2/ImportActionModel';
|
||||
export default PluginActionImportClient;
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
*/
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { NAMESPACE } from '../../client-v2/locale';
|
||||
|
||||
export const NAMESPACE = 'action-import';
|
||||
export { NAMESPACE };
|
||||
|
||||
export function useImportTranslation() {
|
||||
return useTranslation([NAMESPACE, 'client'], { nsMode: 'fallback' });
|
||||
|
||||
@@ -26,9 +26,6 @@ import { NAMESPACE } from './constants';
|
||||
import { useImportContext } from './context';
|
||||
import { ImportStatus } from './ImportModal';
|
||||
import { useEffect } from 'react';
|
||||
import { useFields } from './useFields';
|
||||
import { initImportSettings } from './ImportActionInitializer';
|
||||
import { useImportActionContext } from './ImportActionContext';
|
||||
|
||||
const useImportSchema = () => {
|
||||
const { fieldSchema: actionSchema } = useActionContext();
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './dist/client-v2';
|
||||
export { default } from './dist/client-v2';
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('./dist/client-v2/index.js');
|
||||
@@ -10,6 +10,7 @@
|
||||
"description.zh-CN": "支持 URL 格式的附件。",
|
||||
"peerDependencies": {
|
||||
"@nocobase/client": "2.x",
|
||||
"@nocobase/client-v2": "2.x",
|
||||
"@nocobase/plugin-file-manager": "2.x",
|
||||
"@nocobase/server": "2.x",
|
||||
"@nocobase/test": "2.x"
|
||||
|
||||
+17
-12
@@ -7,28 +7,29 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { UploadFieldModel } from '@nocobase/plugin-file-manager/client';
|
||||
import { Upload } from '@formily/antd-v5';
|
||||
import { css } from '@emotion/css';
|
||||
import { UploadOutlined } from '@ant-design/icons';
|
||||
import { DisplayItemModel, EditableItemModel, tExpr } from '@nocobase/flow-engine';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { FieldContext } from '@formily/react';
|
||||
import { css } from '@emotion/css';
|
||||
import { Upload } from '@formily/antd-v5';
|
||||
import { FieldContext, useField } from '@formily/react';
|
||||
import { DisplayItemModel, EditableItemModel } from '@nocobase/flow-engine';
|
||||
import { UploadFieldModel } from '@nocobase/plugin-file-manager/client-v2';
|
||||
import { castArray } from 'lodash';
|
||||
import { useField } from '@formily/react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { tExpr } from './locale';
|
||||
|
||||
const CardUpload = (props) => {
|
||||
const { showFileName, value, onChange } = props;
|
||||
const outerField: any = useField();
|
||||
const [fileList, setFileList] = useState(
|
||||
castArray(value || []).map((v) => {
|
||||
return { url: v };
|
||||
castArray(value || []).map((item) => {
|
||||
return { url: item };
|
||||
}),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setFileList(
|
||||
castArray(value || []).map((v) => {
|
||||
return { url: v };
|
||||
castArray(value || []).map((item) => {
|
||||
return { url: item };
|
||||
}),
|
||||
);
|
||||
}, [value]);
|
||||
@@ -47,6 +48,7 @@ const CardUpload = (props) => {
|
||||
onChange?.(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
@@ -101,13 +103,16 @@ const CardUpload = (props) => {
|
||||
</FieldContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export class AttachmentURLFieldModel extends UploadFieldModel {
|
||||
declare props: Record<string, any>;
|
||||
|
||||
render() {
|
||||
return <CardUpload {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
AttachmentURLFieldModel.define({
|
||||
(AttachmentURLFieldModel as any).define({
|
||||
label: tExpr('AttachmentURL'),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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 { Application, Plugin } from '@nocobase/client-v2';
|
||||
import { AttachmentURLFieldInterface } from './interfaces/attachment-url';
|
||||
|
||||
export class PluginFieldAttachmentUrlClient extends Plugin<any, Application> {
|
||||
async load() {
|
||||
this.app.addFieldInterfaces([AttachmentURLFieldInterface]);
|
||||
this.flowEngine.registerModelLoaders({
|
||||
AttachmentURLFieldModel: {
|
||||
loader: () => import('./AttachmentURLFieldModel'),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { AttachmentURLFieldInterface } from './interfaces/attachment-url';
|
||||
export { AttachmentURLFieldModel } from './AttachmentURLFieldModel';
|
||||
export default PluginFieldAttachmentUrlClient;
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* 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 type { ISchema } from '@formily/react';
|
||||
import { useField } from '@formily/react';
|
||||
import { CollectionFieldInterface } from '@nocobase/client-v2';
|
||||
|
||||
const attachmentUrlOperators = [
|
||||
{
|
||||
label: '{{t("contains")}}',
|
||||
value: '$includes',
|
||||
selected: true,
|
||||
schema: {
|
||||
type: 'string',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '{{t("does not contain")}}',
|
||||
value: '$notIncludes',
|
||||
schema: {
|
||||
type: 'string',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '{{t("is")}}',
|
||||
value: '$eq',
|
||||
schema: {
|
||||
type: 'string',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '{{t("is not")}}',
|
||||
value: '$ne',
|
||||
schema: {
|
||||
type: 'string',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '{{t("is empty")}}',
|
||||
value: '$empty',
|
||||
noValue: true,
|
||||
schema: {
|
||||
type: 'string',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '{{t("is not empty")}}',
|
||||
value: '$notEmpty',
|
||||
noValue: true,
|
||||
schema: {
|
||||
type: 'string',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const useAttachmentTargetProps = () => {
|
||||
const field = useField();
|
||||
return {
|
||||
service: {
|
||||
resource: 'collections:listFileCollectionsWithPublicStorage',
|
||||
params: {
|
||||
paginate: false,
|
||||
},
|
||||
},
|
||||
manual: false,
|
||||
fieldNames: {
|
||||
label: 'title',
|
||||
value: 'name',
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
field.data = field.data || {};
|
||||
field.data.options = data?.data;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export class AttachmentURLFieldInterface extends CollectionFieldInterface {
|
||||
name = 'attachmentURL';
|
||||
type = 'object';
|
||||
group = 'media';
|
||||
title = '{{t("Attachment (URL)")}}';
|
||||
default = {
|
||||
interface: 'attachmentURL',
|
||||
type: 'string',
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
};
|
||||
availableTypes = ['string', 'text'];
|
||||
validationType = 'string';
|
||||
availableValidationOptions = ['min', 'max', 'length', 'pattern'];
|
||||
properties = {
|
||||
target: {
|
||||
required: true,
|
||||
type: 'string',
|
||||
title: '{{t("Which file collection should it be uploaded to")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'RemoteSelect',
|
||||
'x-use-component-props': useAttachmentTargetProps,
|
||||
'x-reactions': (field) => {
|
||||
const options = field.data?.options || [];
|
||||
const hasAttachments = options.some((option) => option?.name === 'attachments');
|
||||
if (hasAttachments && !field.initialValue) {
|
||||
field.setInitialValue('attachments');
|
||||
}
|
||||
},
|
||||
},
|
||||
targetKey: {
|
||||
type: 'string',
|
||||
default: 'id',
|
||||
'x-hidden': true,
|
||||
},
|
||||
};
|
||||
filterable = {
|
||||
nested: true,
|
||||
operators: attachmentUrlOperators,
|
||||
};
|
||||
titleUsable = true;
|
||||
|
||||
schemaInitialize(schema: ISchema, { block }) {
|
||||
schema['x-component-props'] = schema['x-component-props'] || {};
|
||||
schema['x-component-props'].mode = 'AttachmentUrl';
|
||||
if (['Table', 'Kanban'].includes(block)) {
|
||||
schema['x-component-props'].ellipsis = true;
|
||||
schema['x-component-props'].size = 'small';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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 { tExpr as _tExpr, useFlowEngine } from '@nocobase/flow-engine';
|
||||
// @ts-ignore
|
||||
import pkg from '../../package.json';
|
||||
|
||||
export function useT() {
|
||||
const engine = useFlowEngine();
|
||||
return (str: string) => engine.context.t(str, { ns: [pkg.name, 'client'] });
|
||||
}
|
||||
|
||||
export function tExpr(key: string) {
|
||||
return _tExpr(key, { ns: [pkg.name, 'client'] });
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
import { Plugin, lazy } from '@nocobase/client';
|
||||
import { AttachmentURLFieldInterface } from './interfaces/attachment-url';
|
||||
import { useAttachmentUrlFieldProps } from './hook';
|
||||
import { AttachmentURLFieldModel } from './fieldModel/AttachmentURLFieldModel';
|
||||
import { AttachmentURLFieldModel } from '../client-v2/AttachmentURLFieldModel';
|
||||
const { AttachmentUrl } = lazy(() => import('./component/AttachmentUrl'), 'AttachmentUrl');
|
||||
|
||||
import { attachmentUrlComponentFieldSettings } from './settings';
|
||||
@@ -27,9 +27,10 @@ export class PluginFieldAttachmentUrlClient extends Plugin {
|
||||
this.app.addComponents({ AttachmentUrl });
|
||||
this.app.schemaSettingsManager.add(attachmentUrlComponentFieldSettings);
|
||||
this.flowEngine.registerModels({
|
||||
AttachmentURLFieldModel,
|
||||
AttachmentURLFieldModel: AttachmentURLFieldModel as any,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { AttachmentURLFieldModel } from '../client-v2/AttachmentURLFieldModel';
|
||||
export default PluginFieldAttachmentUrlClient;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './dist/client-v2';
|
||||
export { default } from './dist/client-v2';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user